syncova-backup/docs/disaster-recovery.md
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
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>
2026-08-17 09:10:54 +02:00

9.7 KiB

Disaster Recovery (Phase 18)

Vier Szenarien, alle vier real durchgespielt — der Plan (§20) sagt „Implement and test", und ein Notfallverfahren, das nie ausgeführt wurde, ist keines.

Die eine Entscheidung, die alles trägt

Eine Konfigurationssicherung, die nur auf dem Control-Server liegt, ist beim Verlust des Control-Servers wertlos.

Genau das ist Szenario A. Wer die Konfiguration in ein Verzeichnis neben der Datenbank schreibt, hat sie im Ernstfall nicht mehr — Server und Datenbank gehen typischerweise gemeinsam verloren, weil sie auf derselben Maschine stehen.

Der einzige Ort, der den Verlust überlebt, ist das Repository: Es liegt anderswo, es ist selbstbeschreibend, und ohne es wäre ohnehin alles verloren. Deshalb schreibt syncova-dr export die Control-Plane-Konfiguration nach metadata/control-plane/ — neben die Backups.

Was der Sicherungssatz enthält — und was nicht

Enthalten: Repositories, Aufträge samt Quellen, Aufbewahrungsregeln, Wartungsfenster, Konten mit ihren Rollen, Systemeinstellungen.

Nicht enthalten, und zwar mit Absicht:

Ausgelassen Warum
Passwörter und Passwort-Hashes Ein Argon2id-Hash ist kein Klartext, aber offline angreifbar — und ein Repository liegt naturgemäß außerhalb der Anlage
Zweite Faktoren (TOTP) Dasselbe
Zugangsdaten und Konfiguration der Benachrichtigungswege In der Konfiguration steht die Webhook-Adresse, und die trägt bei vielen Diensten das Token im Pfad
Sitzungen und Agenten-Tokens Sie sollen nach einem Ausfall ablaufen
Der Verschlüsselungsschlüssel der Anlage Er gehört in die Umgebung des Dienstes, nicht in eine Datei, die verreist

Bei den Benachrichtigungswegen wurde die härtere Wahl getroffen: Ein einzelnes Feld zu schwärzen hieße, bei jedem neuen Kanaltyp erneut daran zu denken — und einmal denkt niemand daran. Was bleibt, ist ein Merkzettel: Es gab einen Kanal dieses Namens, dieser Art, mit dieser Schwelle. Weniger, als man sich wünscht, und mehr als nichts.

Die Liste der Auslassungen steht im Sicherungssatz selbst, nicht nur hier. Wer nach einem Totalverlust eine Anlage wiederherstellt, hat die Betriebsanleitung nicht dabei; die Datei muss die Anleitung sein.

Nichts läuft von selbst wieder an

Nach dem Einspielen gilt:

  • Aufträge kommen angehalten zurück. Nach einem Totalverlust weiß niemand, ob die Quellen noch existieren und ob die wiederhergestellten Daten die richtigen sind. Ein Zeitplan, der um zwei Uhr nachts von selbst anläuft, könnte auf ein halb wiederhergestelltes System schreiben — und der Betreiber erführe davon am nächsten Morgen.
  • Repositories kommen als „nicht erreichbar" zurück. Ob die Ablage am alten Pfad noch da ist, steht nicht fest. Eines, das als active zurückkäme und fehlte, ließe den nächsten Lauf ins Leere greifen — und die Übersicht meldete alles in Ordnung.
  • Konten kommen deaktiviert zurück, Benachrichtigungswege abgeschaltet.
  • last_verified_at und last_restore_test_at bleiben leer. Dass ein Backup vor dem Ausfall geprüft wurde, sagt nichts über seinen heutigen Zustand.

Das Einspielen läuft in einer Transaktion: alles oder nichts. Ein Abbruch auf halber Strecke hinterließe Aufträge ohne Repositories und Quellen ohne Aufträge — eine Anlage, die arbeitsfähig aussieht und beim ersten Lauf scheitert. Im Nachweis ist genau das eingetreten (siehe Funde), und die Datenbank blieb leer.

Der Weg nach einem Totalverlust

# 1. Ansehen, was da ist — OHNE Datenbank
syncova-dr inspect --repo /pfad/zum/repository

# 2. Schema anlegen
syncova-migrate up

# 3. Konfiguration und Wiederherstellungspunkte einspielen
syncova-dr restore --repo /pfad/zum/repository --catalog

# 4. Ersten Zugang anlegen
syncova-admin create-admin --username <name>

# 5. Repositorypfade prüfen, auf 'aktiv' setzen, Aufträge freigeben

Schritt 1 braucht keine Datenbank — das ist der Zweck. Wer nach einem Ausfall vor einem Repository steht, muss sehen können, was darin ist, bevor er entscheidet, wie er weitermacht.

--catalog baut die Wiederherstellungspunkte aus den Manifesten neu auf, nicht aus dem vorhandenen Katalog: Der könnte veraltet oder beschädigt sein, und darauf darf man sich im Ernstfall nicht verlassen. Verbindlich sind die Manifeste.

Die Kennungen entstehen deterministisch (UUIDv5 aus Repository-Kennung, Art und Backupkennung). Eine zweite Übernahme desselben Repositorys ergibt dieselben Kennungen; mit Zufallswerten entstünden bei jedem Lauf Dubletten, und niemand könnte sagen, welcher Eintrag der richtige ist.

Die vier Szenarien im Nachweis

A und B — Control-Server und Datenbank verloren

DROP DATABASE syncova — null Tabellen. Danach: Schema angelegt, Konfiguration und Katalog aus dem Repository eingespielt, Administrator neu angelegt, Repository auf „aktiv" gesetzt.

Dann die Probe: Quellverzeichnis gelöscht, Wiederherstellung über die API angestoßen.

Vorabprüfung:  durchführbar, 3 Dateien / 3.500.040 Byte, 0 fehlende Blöcke
Ergebnis:      succeeded, 5 Objekte, 3.500.040 Byte
Prüfsummen:    ✓ alle stimmen überein
Symlink:       erhalten (unterordner/verweis.txt -> ../rechnung.txt)

C — Repository-Katalog verloren

rm -rf indexes/ — der Katalog samt Verzeichnis. Danach syncova-repo list: Der Katalog wird stillschweigend aus den Manifesten neu gebaut und wieder abgelegt.

D — Netzunterbrechung

Kein simulierter Netzwerkfehler, sondern der härtere Fall: SIGKILL mitten in einer Wiederherstellung von 1500 Dateien.

Abbruch nach 8 s:   496 von 1500 Dateien geschrieben, Prüfpunkt bei 367
Freigabe:           SCHEDULER_LOST (die Anlage gibt den verwaisten Lauf selbst frei)
Fortsetzung:        succeeded, 1500 Dateien, 0 übersprungen
Prüfsummen:         ✓ alle 1500 stimmen — bitgenau nach Abbruch und Fortsetzung

Der Prüfpunkt hinkt bewusst hinterher (Schreibintervall 5 s): 496 Dateien lagen am Platz, der Prüfpunkt stand bei 367. Die Fortsetzung schreibt die Differenz erneut. Das ist gewollt — die Alternative wäre ein Datenbankschreibvorgang je Datei.

Ein verwaister Lauf wird nicht automatisch fortgesetzt: Ein zweiter Lauf in ein halb gefülltes Ziel kann Daten beschädigen, die der erste bereits am Platz hatte. Er wird als gescheitert vermerkt und wartet auf einen Blick.

Funde

Fünf, alle erst durch das reale Durchspielen sichtbar.

Die Manifest-Statistik war strukturell null. Die Backup Engine schrieb ihre Blöcke über WriteTransformedChunk unmittelbar am Repository — und damit an der Zählung der Schreibsession vorbei. Jedes Manifest trug logical_bytes: 0. Alle Tests blieben grün, weil keiner das Manifest auf seine Kennzahlen ansah.

Aufgefallen ist es erst, als das Repository zum ersten Mal als alleinige Quelle diente: Nach dem Verlust der Datenbank meldete jedes Backup die Größe null. Ein Repository, das seine eigene Größe nicht kennt, ist nicht selbstbeschreibend — und das ist die zentrale Zusage des Produkts. Die Engine schreibt jetzt über die Session; deduplizierte Blöcke werden ebenfalls vermerkt, denn sonst bliebe die Statistik eines Laufs über unveränderte Daten bei null.

Der Katalog-Neuaufbau scheiterte, wenn das Verzeichnis fehlte. „Katalog verloren" heißt nicht immer „Datei gelöscht"; bei einem Teilausfall des Dateisystems fehlt das ganze Verzeichnis. Ausgerechnet der Neuaufbau, der den Verlust beheben soll, brach dann ab — mit einer Meldung über eine temporäre Datei, die nichts über das eigentliche Problem sagte. Die bestehenden Tests löschten stets nur die Katalogdatei.

Eine Fortsetzung meldete einen Teilfehler, obwohl das Ergebnis vollständig war. Die Dateien zwischen Prüfpunkt und tatsächlichem Fortschritt lagen bereits am Platz und wurden übergangen — 85 von 1500. Sie stammen aus dem eigenen abgebrochenen Lauf und werden jetzt ersetzt. Ein Teilfehler, der keiner ist, ist genau die Meldung, die beim nächsten Mal niemand mehr liest. (Der Schutz gegen fremde Daten bleibt: Die Lockerung gilt ausschließlich bei einer Fortsetzung.)

Die beim Absturz geschriebene Datei blieb liegen. Unter ihrem temporären Namen, dauerhaft. Im Nachweis lagen 1501 Dateien in einem Ziel, das 1500 enthalten sollte. Eine Fortsetzung räumt jetzt auf, bevor sie beginnt.

Ein leeres Musterfeld wurde zu NULL. Beim ersten Einspielversuch brach die Wiederherstellung an der häufigsten aller Quellen ab — der ohne Filter. Die Transaktion rollte sauber zurück; die Datenbank blieb leer, statt halb gefüllt zu sein. Danach folgte gleich der zweite Fehler: Die Spalten sind jsonb, nicht text[].

Bekannte Grenzen

  • Die Datenbank selbst wird nicht gesichert. Szenario B geht davon aus, dass sie verloren ist und aus dem Repository wiederaufgebaut wird. Ein pg_dump daneben ist trotzdem sinnvoll: Er bringt Läufe, Meldungen, Prüfungen und Kennzahlen zurück, die der Sicherungssatz bewusst nicht enthält.
  • Der Export läuft nicht selbsttätig. syncova-dr export ist ein Kommando; es gehört in den Zeitplan des Betriebssystems oder hinter jeden Sicherungslauf. Ein Sicherungssatz von vor drei Monaten kennt die Aufträge von heute nicht.
  • Konten kommen ohne Passwort zurück. Das ist gewollt, bedeutet aber: Bei vielen Konten ist der Wiederaufbau Handarbeit.
  • Ein Repository, das der Sicherungssatz nicht kennt, wird abgelehnt. Die Kennung im Repository muss zu der im Satz passen; sonst landeten die Wiederherstellungspunkte unter einer fremden Kennung, und ein späterer Restore suchte sie am falschen Ort.
  • Szenario D ist gegen einen Serverabsturz geprüft, nicht gegen eine echte Netzunterbrechung. Für ein lokales Repository sind beide Fälle gleich; bei einem entfernten Ziel käme das Verhalten des Netzwerkstapels hinzu.