Enterprise-Backup-, Recovery-, Verification-, Security- und Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme. Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes geht in keine Bewertung als "gut" ein. Umfang (Phasen 0-23): - Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll, Katalogaufbau allein aus den Manifesten — ohne Datenbank - Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck - Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell) - Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive - Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit - Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center, Ransomware-Heuristik (meldet, handelt nie) - Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing - Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository - Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64 Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs. Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md und docs/release-candidate.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
166 lines
8.1 KiB
SQL
166 lines
8.1 KiB
SQL
-- Wiederherstellung (Phase 9).
|
|
--
|
|
-- Der Grundsatz aus PROMPT.md: Ein Backup gilt erst als vertrauenswuerdig, wenn
|
|
-- die Wiederherstellbarkeit nachgewiesen wurde. Daraus folgen zwei Dinge, die
|
|
-- dieses Schema traegt:
|
|
--
|
|
-- 1. Eine Wiederherstellung wird **vorab geprueft**, ohne etwas zu schreiben.
|
|
-- Das Ergebnis der Pruefung gehoert zum Auftrag, damit spaeter belegbar
|
|
-- ist, was man vor dem Start wusste.
|
|
-- 2. Eine Wiederherstellung hat eine **Sitzung mit Pruefpunkt**. Ein Abbruch
|
|
-- bei 90 Prozent darf nicht bedeuten, dass alles von vorn beginnt —
|
|
-- bei mehreren Terabyte waere das der Unterschied zwischen Stunden und
|
|
-- Tagen, und zwar in einer Lage, in der ohnehin schon etwas schiefging.
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Wiederherstellungsauftraege
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
CREATE TABLE restore_jobs (
|
|
-- id ist der oeffentliche Bezeichner.
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
-- backup_id ist das wiederherzustellende Backup.
|
|
--
|
|
-- ON DELETE RESTRICT: Ein Backup, aus dem gerade wiederhergestellt wird,
|
|
-- darf nicht verschwinden.
|
|
backup_id UUID NOT NULL REFERENCES backups(id) ON DELETE RESTRICT,
|
|
-- source_type benennt die Art der urspruenglichen Quelle.
|
|
source_type TEXT NOT NULL,
|
|
-- target_type benennt die Art des Ziels.
|
|
target_type TEXT NOT NULL,
|
|
-- target_ref ist der Zielpfad oder die Zielkennung.
|
|
target_ref TEXT NOT NULL,
|
|
-- path_prefix beschraenkt die Wiederherstellung auf einen Teilbaum.
|
|
--
|
|
-- Der haeufigste Fall im Betrieb ist nicht die vollstaendige
|
|
-- Wiederherstellung, sondern eine einzelne versehentlich geloeschte Datei.
|
|
path_prefix TEXT,
|
|
-- status ist der Zustand des Auftrags.
|
|
status TEXT NOT NULL DEFAULT 'queued',
|
|
-- overwrite_existing erlaubt das Ueberschreiben vorhandener Daten.
|
|
--
|
|
-- Der gefaehrlichste Schalter der ganzen Anlage: Er richtet sich gegen
|
|
-- Daten, die es noch gibt. Er wird niemals vorbelegt und immer auditiert
|
|
-- (PROMPT.md §140: destruktive Aktionen niemals still).
|
|
overwrite_existing BOOLEAN NOT NULL DEFAULT false,
|
|
-- restore_permissions setzt die urspruenglichen Rechte.
|
|
restore_permissions BOOLEAN NOT NULL DEFAULT true,
|
|
-- verify_content prueft jede Datei gegen ihre Pruefsumme.
|
|
verify_content BOOLEAN NOT NULL DEFAULT true,
|
|
-- validation_result haelt das Ergebnis der Vorabpruefung als JSON.
|
|
--
|
|
-- Es gehoert zum Auftrag, nicht in ein Protokoll: Spaeter muss belegbar
|
|
-- sein, was vor dem Start bekannt war — etwa dass ein Block fehlte und man
|
|
-- trotzdem begonnen hat.
|
|
validation_result JSONB,
|
|
-- started_at ist der Beginn in UTC.
|
|
started_at TIMESTAMPTZ,
|
|
-- completed_at ist das Ende in UTC.
|
|
completed_at TIMESTAMPTZ,
|
|
-- bytes_restored ist die zurueckgeschriebene Datenmenge.
|
|
bytes_restored BIGINT NOT NULL DEFAULT 0,
|
|
-- files_restored ist die Zahl zurueckgeschriebener Objekte.
|
|
files_restored BIGINT NOT NULL DEFAULT 0,
|
|
-- files_skipped ist die Zahl uebergangener Objekte.
|
|
--
|
|
-- Groesser als 0 bedeutet: Die Wiederherstellung ist unvollstaendig. Sie
|
|
-- darf dann nicht als Erfolg dastehen (PROMPT.md §138).
|
|
files_skipped BIGINT NOT NULL DEFAULT 0,
|
|
-- error_code ist die Fehlerkennung in SCREAMING_SNAKE_CASE.
|
|
error_code TEXT,
|
|
-- error_message ist die verstaendliche Fehlermeldung.
|
|
error_message TEXT,
|
|
-- correlation_id verbindet den Auftrag mit seinen Protokollzeilen.
|
|
correlation_id UUID NOT NULL,
|
|
-- scheduler_instance benennt den ausfuehrenden Control-Server.
|
|
scheduler_instance TEXT,
|
|
-- heartbeat_at ist die letzte Lebendmeldung in UTC.
|
|
heartbeat_at TIMESTAMPTZ,
|
|
-- created_by benennt den Anfordernden.
|
|
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
-- created_at ist der Anlagezeitpunkt in UTC.
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
CONSTRAINT restore_jobs_status_valid
|
|
CHECK (status IN ('queued', 'running', 'succeeded', 'partial_failure', 'failed', 'cancelled')),
|
|
CONSTRAINT restore_jobs_target_type_valid
|
|
CHECK (target_type IN ('filesystem', 'original_location', 'proxmox_vm', 'new_proxmox_vm')),
|
|
-- Ein abgeschlossener Auftrag ohne Endzeitpunkt waere in jeder Auswertung
|
|
-- eine Luecke.
|
|
CONSTRAINT restore_jobs_completion_consistent CHECK (
|
|
status IN ('queued', 'running') OR completed_at IS NOT NULL
|
|
),
|
|
-- Dieselbe Regel wie bei Sicherungen: Uebergangene Objekte sind kein Erfolg.
|
|
CONSTRAINT restore_jobs_skipped_is_not_success CHECK (
|
|
files_skipped = 0 OR status <> 'succeeded'
|
|
)
|
|
);
|
|
|
|
CREATE INDEX restore_jobs_backup_idx ON restore_jobs (backup_id, created_at DESC);
|
|
CREATE INDEX restore_jobs_status_idx ON restore_jobs (status) WHERE status IN ('queued', 'running');
|
|
CREATE INDEX restore_jobs_created_idx ON restore_jobs (created_at DESC);
|
|
|
|
-- Zwei gleichzeitige Wiederherstellungen in dasselbe Ziel schrieben sich
|
|
-- gegenseitig zu — und zwar ohne dass es auffiele, weil beide erfolgreich
|
|
-- endeten. Der Teilindex verhindert das in der Datenbank, nicht in der
|
|
-- Anwendung: Zwei Control-Server pruefen sonst beide erfolgreich.
|
|
CREATE UNIQUE INDEX restore_jobs_single_active_target_idx
|
|
ON restore_jobs (target_ref)
|
|
WHERE status IN ('queued', 'running');
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Sitzungen mit Pruefpunkt
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
CREATE TABLE restore_sessions (
|
|
-- id ist der oeffentliche Bezeichner.
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
-- restore_job_id ist der zugehoerige Auftrag.
|
|
restore_job_id UUID NOT NULL REFERENCES restore_jobs(id) ON DELETE CASCADE,
|
|
-- state ist der Zustand der Sitzung.
|
|
state TEXT NOT NULL DEFAULT 'active',
|
|
-- checkpoint haelt den Fortschritt als JSON.
|
|
--
|
|
-- Darin steht der zuletzt vollstaendig zurueckgeschriebene Pfad. Beim
|
|
-- Fortsetzen wird alles davor uebersprungen — die Reihenfolge der Objekte
|
|
-- im Manifest ist fest sortiert, deshalb ist ein einzelner Pfad als Marke
|
|
-- ausreichend und braucht keine Liste.
|
|
checkpoint JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
-- attempt_number zaehlt die Fortsetzungen, beginnend bei 1.
|
|
attempt_number INTEGER NOT NULL DEFAULT 1,
|
|
-- created_at ist der Anlagezeitpunkt in UTC.
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
-- updated_at ist der Zeitpunkt der letzten Aenderung in UTC.
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
CONSTRAINT restore_sessions_state_valid
|
|
CHECK (state IN ('active', 'suspended', 'completed', 'abandoned')),
|
|
CONSTRAINT restore_sessions_attempt_positive CHECK (attempt_number >= 1)
|
|
);
|
|
|
|
CREATE INDEX restore_sessions_job_idx ON restore_sessions (restore_job_id, created_at DESC);
|
|
|
|
-- Je Auftrag darf nur eine Sitzung aktiv sein. Zwei aktive Sitzungen schrieben
|
|
-- mit verschiedenen Pruefpunkten in dasselbe Ziel.
|
|
CREATE UNIQUE INDEX restore_sessions_single_active_idx
|
|
ON restore_sessions (restore_job_id)
|
|
WHERE state = 'active';
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Berechtigungen
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- restores.read und restores.execute stammen bereits aus 000002_identity.
|
|
-- Neu ist allein das Ueberschreiben: Es richtet sich gegen Daten, die es noch
|
|
-- gibt, und ist damit etwas anderes als eine Wiederherstellung an einen leeren
|
|
-- Ort. Wer wiederherstellen darf, darf deshalb nicht automatisch ueberschreiben.
|
|
INSERT INTO permissions (name, description) VALUES
|
|
('restores.overwrite', 'Wiederherstellungen ausfuehren, die vorhandene Daten ueberschreiben');
|
|
|
|
-- Das Ueberschreiben produktiver Daten bleibt den Administratoren und dem
|
|
-- Restore Operator vorbehalten — den Rollen, die die Folgen verantworten.
|
|
INSERT INTO role_permissions (role_id, permission_id)
|
|
SELECT r.id, p.id FROM roles r, permissions p
|
|
WHERE r.name IN ('super_administrator', 'infrastructure_administrator', 'restore_operator')
|
|
AND p.name = 'restores.overwrite';
|