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>
246 lines
11 KiB
Markdown
246 lines
11 KiB
Markdown
# Prüfung und Recovery Assurance
|
|
|
|
Phase 10. Der Teil der Anlage, der die Frage beantwortet, die das ganze Produkt
|
|
trägt: **Kann ich mich auf dieses Backup verlassen?**
|
|
|
|
Ein Backup, dessen Wiederherstellbarkeit nie geprüft wurde, ist eine Vermutung.
|
|
Die Prüfung macht daraus eine Aussage — und wo sie fehlt, sagt die Anlage das,
|
|
statt die Lücke mit einer freundlichen Zahl zu füllen.
|
|
|
|
## Die fünf Prüfarten
|
|
|
|
Sie unterscheiden sich in Aussagekraft und Kosten erheblich. Das ist kein
|
|
Nachteil, sondern der Zweck: Eine Anlage, die nur die teuerste Prüfung kennt,
|
|
wird selten geprüft.
|
|
|
|
| Art | Was sie liest | Was sie beweist |
|
|
| --- | --- | --- |
|
|
| `manifest` | nur das Manifest | Das Verzeichnis ist stimmig. Über die Daten sagt sie nichts. |
|
|
| `chunk_presence` | Verzeichniseinträge der Blöcke | Alle Blöcke sind da. Ihr Inhalt wurde nicht gelesen. |
|
|
| `chunk_integrity` | jeden Block vollständig | Alle Blöcke sind da und unbeschädigt. |
|
|
| `chain` | die Manifeste der ganzen Kette | Kein Glied zwischen Voll- und Zuwachssicherung fehlt. |
|
|
| `restore_test` | alles, und schreibt es zurück | Das Backup **ist** wiederherstellbar. |
|
|
|
|
Nur die letzte Zeile ist ein Nachweis. Die übrigen sind Indizien — gute, aber
|
|
Indizien. Genau deshalb wiegt der Wiederherstellungstest in der Bewertung am
|
|
schwersten und hängt an einem eigenen Recht.
|
|
|
|
Ohne Angabe der Art wird `chunk_integrity` gewählt: die schwächste Prüfung, die
|
|
überhaupt etwas über die Daten aussagt. `manifest` als Vorgabe wäre bequem und
|
|
wertlos.
|
|
|
|
### Die Blockprüfung braucht keinen Schlüssel
|
|
|
|
Sie vergleicht die **gespeicherte** Form gegen `ChunkReference.StoredDigest`,
|
|
nicht gegen die Chunk-Kennung. Die Kennung beschreibt bei einem verschlüsselten
|
|
Repository den Klartext; eine Prüfung gegen sie meldete **jeden** Block als
|
|
beschädigt. Diesen Fehler hatte der Integritätsscan der Phase 2 (siehe
|
|
`docs/repository.md`), und die Prüfmaschine hätte ihn wiederholt.
|
|
|
|
Der Nebeneffekt ist wichtiger als die Fehlerbehebung: Eine Integritätsprüfung
|
|
läuft dadurch auch dort, wo der Datenschlüssel nicht vorliegt.
|
|
|
|
### Der Wiederherstellungstest
|
|
|
|
Er legt ein Wegwerfziel über `os.MkdirTemp` an, schreibt das Backup dorthin
|
|
zurück, vergleicht jede Datei gegen den `ContentHash` im Manifest und räumt
|
|
anschließend restlos auf (`defer os.RemoveAll`). Er braucht den Datenschlüssel —
|
|
liegt er nicht vor, wird die Prüfung abgelehnt statt scheinbar ausgeführt. Ein
|
|
Vergleich von Geheimtext gegen Klartext-Prüfsummen schlüge fehl und meldete ein
|
|
gesundes Backup als beschädigt.
|
|
|
|
Ein auf einen Teilbaum beschränkter Test hebt die Einstufung **nicht** auf
|
|
`recoverable`: Er hat nicht das Backup geprüft, sondern einen Teil davon.
|
|
|
|
## Einstufung
|
|
|
|
Fünf Stufen, vier davon auf einer Leiter:
|
|
|
|
```text
|
|
failed → successful → verified → recoverable
|
|
corrupted (außerhalb)
|
|
```
|
|
|
|
| Stufe | Bedeutung |
|
|
| --- | --- |
|
|
| `failed` | Der Sicherungslauf ist gescheitert. |
|
|
| `corrupted` | Eine Prüfung fand Beschädigungen oder fehlende Blöcke. |
|
|
| `successful` | Der Lauf ist abgeschlossen. Ob sich die Daten zurückholen lassen, wurde nicht geprüft. |
|
|
| `verified` | Die Blöcke sind vorhanden und unbeschädigt. |
|
|
| `recoverable` | Das Backup wurde zurückgeschrieben und verglichen. Nachweislich wiederherstellbar. |
|
|
|
|
`corrupted` steht bewusst außerhalb der Leiter: Es ist keine Zwischenstufe,
|
|
sondern ein Ausschlussgrund.
|
|
|
|
Die Einstufung wird von zwei Datenbank-Constraints gestützt, nicht nur von der
|
|
Anwendungslogik:
|
|
|
|
```sql
|
|
CHECK (classification <> 'recoverable' OR last_restore_test_at IS NOT NULL)
|
|
CHECK (classification <> 'verified' OR last_verified_at IS NOT NULL)
|
|
```
|
|
|
|
Die stärkste Aussage der Anlage lässt sich damit nicht ohne ihren Nachweis
|
|
vergeben — auch nicht durch einen Fehler in einer künftigen Codeänderung.
|
|
|
|
### Rangfolge beim Fortschreiben
|
|
|
|
Eine bestandene Blockprüfung hebt auf `verified`, stuft aber **nie** von
|
|
`recoverable` herab: Der Wiederherstellungstest bleibt die stärkere Aussage.
|
|
|
|
### Ein Befund löscht die bisherigen Nachweise
|
|
|
|
Findet eine Prüfung Beschädigungen, werden `last_verified_at`,
|
|
`last_restore_test_at` und die gemessene Wiederherstellungsdauer auf NULL
|
|
gesetzt. Der Grund stammt aus dem Nachweis dieser Phase: Ohne diesen Schritt
|
|
stand das Backup nach Behebung des Schadens sofort wieder als `recoverable` da —
|
|
auf Grundlage eines Tests, der **vor** dem Schaden lief. Wer den Nachweis will,
|
|
muss ihn wiederholen.
|
|
|
|
### Ein Fehlschlag ist kein Befund
|
|
|
|
Konnte die Prüfung nicht laufen — Repository nicht erreichbar, Control-Server
|
|
abgestürzt —, wird der Auftrag als `failed` vermerkt und die Einstufung des
|
|
Backups bleibt unberührt. Dass die Prüfung nicht stattfand, sagt nichts über die
|
|
Daten. Alles andere wäre ein Fehlalarm, und ein Prüfwerkzeug, das grundlos Alarm
|
|
schlägt, wird bald nicht mehr ernst genommen.
|
|
|
|
## Bewertung (Recovery Assurance Score)
|
|
|
|
Zehn Eingangsgrößen nach `SYNCOVA_IMPLEMENTATION_PLAN.md` §12:
|
|
|
|
| Größe | Gewicht |
|
|
| --- | --- |
|
|
| Wiederherstellungstest | 25 |
|
|
| Integritätsprüfung | 20 |
|
|
| Aktualität | 15 |
|
|
| Wiederherstellungspunkt (RPO) | 10 |
|
|
| Wiederherstellungsdauer (RTO) | 10 |
|
|
| Unveränderlichkeit | 5 |
|
|
| Zweiter Standort | 5 |
|
|
| Verschlüsselung | 5 |
|
|
| Repository-Zustand | 3 |
|
|
| Auffälligkeiten | 2 |
|
|
|
|
Die Gewichte folgen einer Überzeugung: Der Wiederherstellungstest wiegt am
|
|
schwersten, weil er als einziger etwas nachweist. Verschlüsselung und
|
|
Unveränderlichkeit schützen die Daten, sagen aber nichts darüber, ob sie noch da
|
|
sind.
|
|
|
|
### Unbekannt zählt niemals als gut
|
|
|
|
Jede Größe trägt ein `is_known`. Eine nie durchgeführte Messung bekommt null
|
|
Punkte **und** wird als ungemessen geführt. Bei mehr als drei unbekannten Größen
|
|
meldet `is_trustworthy` false und die Zusammenfassung sagt ausdrücklich, dass die
|
|
Prozentzahl eine Vermutung ist. `missing_measurements` nennt die fehlenden
|
|
Messungen — das ist die eigentliche Handlungsanweisung.
|
|
|
|
### Beschädigt heißt null Prozent
|
|
|
|
Ein Backup mit Befund und ein gescheiterter Lauf bekommen 0 %, unabhängig davon,
|
|
wie gut die übrigen Größen aussehen.
|
|
|
|
Der Fall stammt aus dem Nachweis dieser Phase: Ein Backup mit **einem einzigen**
|
|
beschädigten Block kam auf 70 %, weil Aktualität, Verschlüsselung und ein
|
|
früherer Wiederherstellungstest weiterhin zählten. Diese Zahl liest sich wie
|
|
„weitgehend in Ordnung". Ein Backup, aus dem sich ein Block nicht mehr lesen
|
|
lässt, ist aber nicht zu 70 % wiederherstellbar, sondern nicht wiederherstellbar.
|
|
|
|
Ebenso zählt ein früherer Wiederherstellungstest nach einem Befund nicht mehr: Er
|
|
bezieht sich auf einen Zustand des Repositorys, den es nachweislich nicht mehr
|
|
gibt.
|
|
|
|
### Berechnet, nicht gespeichert
|
|
|
|
`GET /backups/{id}/assurance` berechnet die Bewertung bei jedem Aufruf neu. Sie
|
|
hängt am Alter der Messungen und veraltet damit von selbst — ein gespeicherter
|
|
Wert würde mit jedem Tag falscher, ohne dass sich etwas ändert. Der Wert in
|
|
`backups.assurance_score` ist nur ein Abbild für Übersichten.
|
|
|
|
## Ausführung
|
|
|
|
Die Prüfungen laufen in einer eigenen Schleife neben Sicherung und
|
|
Wiederherstellung. Sie mit den Sicherungen zu vermengen wäre falsch: Eine Prüfung
|
|
wird nie wiederholt, weil sie fehlschlug, und sie darf eine laufende Sicherung
|
|
nicht verdrängen — beide lesen denselben Datenträger. Standardmäßig läuft **eine**
|
|
Prüfung gleichzeitig.
|
|
|
|
- **Übernahme** über `FOR UPDATE SKIP LOCKED`: Mehrere Control-Server greifen
|
|
nicht nach demselben Auftrag.
|
|
- **Ein Teilindex** verhindert zwei gleichzeitige Prüfungen desselben Backups.
|
|
Sie läsen dieselben Daten doppelt, ohne mehr festzustellen. Der zweite Versuch
|
|
bekommt 409.
|
|
- **Lebendmeldung** alle 30 Sekunden; nach 30 Minuten ohne Meldung gilt die
|
|
Prüfung als verwaist und wird beim nächsten Start freigegeben. Ohne diesen
|
|
Schritt sperrte der Teilindex das Backup dauerhaft gegen jede weitere Prüfung.
|
|
- Das Repository wird **schreibgeschützt** geöffnet: Eine Prüfung darf nichts
|
|
anfassen und soll neben einer laufenden Sicherung stattfinden können.
|
|
|
|
## Endpunkte
|
|
|
|
| Methode | Pfad | Recht |
|
|
| --- | --- | --- |
|
|
| GET | `/api/v1/verification` | `verification.read` |
|
|
| POST | `/api/v1/verification` | `verification.write` |
|
|
| GET | `/api/v1/verification/{id}` | `verification.read` |
|
|
| GET | `/api/v1/verification/{id}/results` | `verification.read` |
|
|
| POST | `/api/v1/verification/{id}/cancel` | `verification.write` |
|
|
| GET | `/api/v1/backups/{id}/assurance` | `backups.read` |
|
|
|
|
Ein `restore_test` verlangt zusätzlich `verification.restore_test`. Er schreibt
|
|
zwar nur an einen Wegwerfort, liest aber das gesamte Backup und belastet damit
|
|
Datenträger und Leitung.
|
|
|
|
Der vollständige Bericht steht unter `/results` und nicht am Auftrag: Bei einem
|
|
großen Backup trägt er viele Befunde, und eine Liste von Aufträgen bliebe damit
|
|
nicht mehr überschaubar. Liegt kein Bericht vor, antwortet der Endpunkt mit 404
|
|
statt mit einem leeren Ergebnis — ein leeres Ergebnis sähe aus wie ein sauberes.
|
|
|
|
### Beispiel
|
|
|
|
```bash
|
|
# Integritätsprüfung anstoßen
|
|
curl -X POST http://127.0.0.1:8080/api/v1/verification \
|
|
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
|
-d '{"backup_id":"<uuid>","verification_type":"chunk_integrity"}'
|
|
|
|
# Ergebnis abholen
|
|
curl http://127.0.0.1:8080/api/v1/verification/<id>/results -H "Authorization: Bearer $TOKEN"
|
|
|
|
# Bewertung des Backups
|
|
curl http://127.0.0.1:8080/api/v1/backups/<uuid>/assurance -H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
## Nachgewiesener Lebenszyklus
|
|
|
|
Gegen den laufenden Dienst mit einem echten Repository durchgespielt (420 KB,
|
|
drei Objekte, verschlüsselt):
|
|
|
|
| Schritt | Einstufung | Bewertung |
|
|
| --- | --- | --- |
|
|
| Backup, ungeprüft | `successful` | 35 % — drei Größen ungemessen |
|
|
| nach `chunk_integrity` | `verified` | 55 % |
|
|
| nach `restore_test` | `recoverable` | 90 % |
|
|
| ein Byte in einem Block gekippt, `chunk_integrity` | `corrupted` | 0 % |
|
|
| `restore_test` auf dem beschädigten Backup | `corrupted` | 0 % |
|
|
| Block zurückgespielt, `chunk_integrity` | `verified` | 55 % |
|
|
| `restore_test` wiederholt | `recoverable` | 90 % |
|
|
|
|
Der Befund nannte die betroffene Datei (`rechnung.txt`), nicht nur die Zahl der
|
|
Blöcke. Die verbleibenden 10 % fehlen für Unveränderlichkeit und eine Kopie an
|
|
einem zweiten Ort — beides ist noch nicht umgesetzt und wird deshalb mit null
|
|
Punkten geführt, nicht wohlwollend geschätzt.
|
|
|
|
## Bekannte Grenzen
|
|
|
|
- **Keine Prüfrichtlinien.** `verification_policies` aus `SYNCOVA_DATABASE.md`
|
|
existiert noch nicht; Prüfungen werden von Hand oder über die API angestoßen,
|
|
nicht nach Zeitplan. Der Endpunkt `/verification-policies` fehlt entsprechend.
|
|
- **Kein zweiter Standort.** `HasOffsiteCopy` ist fest `false`. Die Größe hier
|
|
wohlwollend anzunehmen wäre die bequeme und falsche Entscheidung.
|
|
- **Auffälligkeiten** werden aus der Zahl übergangener Objekte des Laufs
|
|
abgeleitet, nicht aus einer Verlaufsanalyse.
|
|
- **Der Wiederherstellungstest schreibt vollständig zurück.** Bei einem 10-TB-
|
|
Backup braucht das entsprechend Platz und Zeit; `RestoreTestWorkingDirectory`
|
|
legt den Ort fest.
|