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>
149 lines
9.0 KiB
Markdown
149 lines
9.0 KiB
Markdown
# Repository Engine
|
|
|
|
Dieses Dokument beschreibt den **aktuell implementierten** Stand nach Phase 2.
|
|
|
|
## Leitgedanke
|
|
|
|
> Ein Repository ist selbstbeschreibend. Es enthält alles, was zur Wiederherstellung nötig ist — PostgreSQL hält davon nur eine Kopie zur schnellen Abfrage.
|
|
|
|
Daraus folgt die Prüffrage für jede Designentscheidung: *Bliebe das Repository nutzbar, wenn der Control Server und seine Datenbank ersatzlos verschwinden?* Alles, was diese Frage mit Nein beantwortet, ist falsch — auch wenn es bequemer wäre.
|
|
|
|
## Aufbau
|
|
|
|
```text
|
|
repository/
|
|
format/ repository.json — Descriptor: Format, Verfahren, Betriebsart
|
|
manifests/ <backup-id>.manifest.json — die maßgebliche Beschreibung je Backup
|
|
chunks/ aa/bb/<sha256> — inhaltsadressierte Datenblöcke
|
|
indexes/ catalog.json — Übersicht (nur Beschleuniger, jederzeit neu erzeugbar)
|
|
journals/ <session>.journal.json — Fortschritt laufender Schreibvorgänge
|
|
verification/ scan-<zeit>.json — Ergebnisse der Integritätsläufe
|
|
metadata/ ergänzende Angaben
|
|
staging/ unfertige Daten bis zum Commit
|
|
repository.lock Schreibsperre
|
|
```
|
|
|
|
## Chunk-Ablage
|
|
|
|
Die Kennung eines Chunks **ist** sein SHA-256-Inhaltshash. Daraus folgt unmittelbar:
|
|
|
|
- **Deduplizierung ohne Index** — gleicher Inhalt ergibt dieselbe Kennung und damit denselben Ablageort. Ein bereits vorhandener Chunk wird schlicht nicht erneut geschrieben.
|
|
- **Integritätsprüfung ohne Zusatzdaten** — beim Lesen wird neu gehasht und mit der Kennung verglichen. Eine separate Prüfsummenliste, die selbst beschädigt werden könnte, gibt es nicht.
|
|
|
|
**Warum SHA-256:** kryptografisch sicher (PROMPT.md §9), Teil der Standardbibliothek, auf allen Zielplattformen hardwarebeschleunigt. Eine Fremdbibliothek brächte hier keinen Vorteil, aber eine zusätzliche Abhängigkeit im sicherheitskritischsten Pfad des Produkts.
|
|
|
|
**Fanout:** `chunks/aa/bb/<hash>` — zwei Ebenen à zwei Hex-Zeichen ergeben 65 536 Verzeichnisse. Ohne diese Aufteilung lägen Millionen Dateien in einem einzigen Verzeichnis, was jede Suche unbrauchbar langsam macht.
|
|
|
|
**Kennungen werden vor jeder Pfadbildung geprüft.** Sie stammen aus Manifesten, die auch aus einem fremden Repository kommen können. Ohne Prüfung liesse sich über einen Wert wie `../../etc/passwd` auf beliebige Pfade zugreifen.
|
|
|
|
## Crash-sicheres Schreiben
|
|
|
|
Jede Datei entsteht in vier Schritten:
|
|
|
|
1. In eine temporäre Datei **im Zielverzeichnis** schreiben (damit `rename` keine Dateisystemgrenze überschreitet und atomar bleibt).
|
|
2. `fsync` auf die Datei — erst danach liegen die Daten wirklich auf dem Datenträger.
|
|
3. `rename` auf den Zielnamen — unter POSIX atomar.
|
|
4. `fsync` auf das Verzeichnis — erst danach überlebt auch der Namenseintrag einen Stromausfall.
|
|
|
|
Ohne Schritt 2 und 4 könnte nach einem Absturz eine Datei existieren, deren Inhalt leer oder unvollständig ist — ein beschädigtes Backup, das sich als vollständig ausgibt.
|
|
|
|
## Commit-Protokoll
|
|
|
|
Sieben Schritte laut SYNCOVA_ARCHITECTURE.md §10:
|
|
|
|
| Schritt | Vorgang |
|
|
| --- | --- |
|
|
| 1 | Session öffnen, Journal anlegen |
|
|
| 2 | Chunks schreiben |
|
|
| 3 | Manifest erzeugen, Kennzahlen aus der Session übernehmen |
|
|
| 4 | Prüfsumme bilden und sofort gegenprüfen |
|
|
| 5 | Manifest atomar an seinen Platz bringen — **hier wird das Backup sichtbar** |
|
|
| 6 | Katalog fortschreiben |
|
|
| 7 | Erfolg melden |
|
|
|
|
**Vor Schritt 5 existiert das Backup für keinen Leser.** Bricht der Vorgang vorher ab, bleiben nur Chunks liegen — die ein späterer Lauf wiederverwendet. Es entsteht **nie** ein halbes, scheinbar gültiges Backup.
|
|
|
|
Schlägt Schritt 6 fehl, ist das Backup dennoch vollständig: der Katalog lässt sich jederzeit neu aufbauen. Das wird protokolliert, nicht als Fehler gemeldet.
|
|
|
|
**Die Kennzahlen im Manifest stammen immer aus der Session**, nie vom Aufrufer — erfundene Statistiken wären eine Falschaussage über gesicherte Daten.
|
|
|
|
## Abschlussvermerk
|
|
|
|
Ein Manifest gilt nur mit `complete: true` **und** gültigem `content_hash`. Der Hash deckt alle Felder außer diesen beiden ab.
|
|
|
|
Fehlt der Vermerk oder passt der Hash nicht, liefert `ReadManifest` einen Fehler und der Katalog-Wiederaufbau überspringt das Backup. Ein unvollständiges Backup als wiederherstellbar auszuweisen wäre der schwerste denkbare Fehler dieses Produkts.
|
|
|
|
## Katalog und Wiederaufbau
|
|
|
|
Der Katalog ist **ausschließlich ein Beschleuniger**. Er wird automatisch neu erzeugt, wenn er fehlt, unlesbar ist oder zu einem fremden Repository gehört (erkennbar an der Repository-Kennung).
|
|
|
|
Der Wiederaufbau liest ausschließlich die Manifeste — keine Datenbank, keine externe Quelle:
|
|
|
|
```bash
|
|
syncova-repo rebuild --path /backup/repository-01
|
|
```
|
|
|
|
Das ist der Kern der Repository-Wiederherstellung (PROMPT.md §46): Repository anhängen, Katalog aufbauen, wiederherstellen.
|
|
|
|
## Integritätsprüfung
|
|
|
|
```bash
|
|
syncova-repo scan --path <pfad> # nur Anwesenheit prüfen (schnell)
|
|
syncova-repo scan --path <pfad> --deep # jeden Chunk neu hashen (vollständig)
|
|
```
|
|
|
|
Geprüft wird **von den Manifesten aus**, nicht von der Chunk-Ablage: nur so zeigt sich, ob ein Backup tatsächlich wiederherstellbar ist. Ein von mehreren Backups genutzter Chunk wird dabei nur einmal gelesen.
|
|
|
|
Der Bericht benennt betroffene Backups namentlich und liefert einen Exit-Status ungleich 0, damit ein Überwachungssystem anschlägt. Die Zusammenfassung beschönigt nichts: „*n* von *m* Backups sind NICHT vollständig wiederherstellbar."
|
|
|
|
Verwaiste Chunks sind eine Warnung, kein Datenverlust — sie belegen nur Speicherplatz.
|
|
|
|
## Bereinigung
|
|
|
|
```bash
|
|
syncova-repo prune --path <pfad> # Simulation
|
|
syncova-repo prune --path <pfad> --apply # tatsächlich ausführen
|
|
```
|
|
|
|
Bewusst getrennt vom Scan, da potenziell datenzerstörend. Zwei Schutzmaßnahmen:
|
|
|
|
- **Ist ein Manifest unlesbar, bricht die Bereinigung ab.** Die Menge der benötigten Chunks wäre unbekannt; ein Löschen auf unvollständiger Grundlage könnte Daten vernichten.
|
|
- **Sie verlangt die Schreibsperre.** Liefe sie während eines Backups, hätte ein gerade abgelegter Chunk noch kein Manifest und würde als verwaist gelöscht.
|
|
|
|
## Sperren
|
|
|
|
Eine Sperrdatei (`O_CREATE|O_EXCL`, damit das Anlegen unteilbar ist) verhindert gleichzeitige Schreibzugriffe. Sie enthält PID, Hostname und Zeitpunkt, damit eine hängengebliebene Sperre einzuordnen ist.
|
|
|
|
**Lesender Zugriff ist parallel möglich** (`OpenOptions{ReadOnly: true}`) — eine Wiederherstellung muss auch während eines laufenden Backups funktionieren.
|
|
|
|
Nach einem Absturz bleibt die Sperre liegen. Sie wird **nicht** automatisch gelöst — das wäre gefährlich, falls der Vorgang doch noch läuft. Der Eingriff ist ausdrücklich manuell (`repository.BreakLock`).
|
|
|
|
## Abgebrochene Sessions
|
|
|
|
`FindStaleSessions` findet Journale, die ein früherer Lauf offen gelassen hat, und meldet, ob trotzdem ein Manifest vorliegt (dann war der Abschluss vollzogen und nur das Aufräumen unterbrochen). `CleanupStaleSession` räumt Journal und Arbeitsverzeichnis weg — ein bereits geschriebenes Manifest bleibt unangetastet.
|
|
|
|
Die Chunks eines abgebrochenen Laufs bleiben liegen und werden vom nächsten Versuch wiederverwendet. Das ist das Resume-Verhalten aus PROMPT.md §81: ein bei 70 % abgebrochenes Backup beginnt nicht bei null.
|
|
|
|
## Gehärteter Modus
|
|
|
|
`--hardened` setzt `immutable: true`. Wirkung:
|
|
|
|
- Jedes Backup erhält eine Aufbewahrungsfrist (Standard 30 Tage).
|
|
- `DeleteBackup` scheitert, solange die Frist läuft — unabhängig davon, wer löschen möchte.
|
|
- Manifeste werden schreibgeschützt abgelegt (`0400`).
|
|
|
|
**Grenze, die nicht verschwiegen wird:** Der Schreibschutz ist eine zusätzliche Hürde, keine Garantie. Wer Eigentümer der Datei ist, kann die Rechte ändern. Echte Unveränderlichkeit muss die Speicherebene durchsetzen — S3 Object Lock, WORM-Medien oder ein gehärtetes Linux-System mit getrenntem Konto. PROMPT.md §15 verlangt ausdrücklich, keine Unveränderlichkeit zu behaupten, die die Speicherebene nicht hergibt.
|
|
|
|
## Statistiken
|
|
|
|
`DeduplicationRatio()` liefert **zwei** Werte: das Verhältnis und ob es überhaupt existiert. Wurde jeder Block bereits vorgefunden, ist nichts abgelegt worden und eine Division nicht definiert. Ein stillschweigend geliefertes `0` würde den besten aller Fälle als den schlechtesten darstellen.
|
|
|
|
Für Anzeigen ist `SavingsPercentage()` die verlässlichere Angabe — sie ist immer bestimmbar; 100 % bedeutet vollständige Deduplizierung.
|
|
|
|
## Noch nicht umgesetzt
|
|
|
|
- **Kompression und Verschlüsselung der Chunks** (Phase 3/4). Die Felder `encryption_key_version` und `compression_algorithm` sind bereits Teil des Manifestformats — eine spätere Ergänzung wäre ein Formatbruch.
|
|
- **Variables Chunking** (Content-Defined Chunking). Derzeit bestimmt der Aufrufer die Blockgrenzen; feste Grenzen erkennen Einfügungen mitten in einer Datei nicht als Verschiebung.
|
|
- **S3-kompatibler Objektspeicher.** Die `Repository`-Schnittstelle ist dafür geschnitten, umgesetzt ist bislang nur das Dateisystem.
|
|
- **Synthetic Full und Forever Forward Incremental.** Kettenverwaltung und -prüfung stehen, das Zusammenführen fehlt.
|