# 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 ```bash # 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 # 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.