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>
203 lines
9.7 KiB
Markdown
203 lines
9.7 KiB
Markdown
# 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 <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.
|