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>
14 KiB
Release Candidate (Phase 22)
Phase 22 tut zwei Dinge: Sie friert die Verträge ein und fährt die Prüfungen, die man vor einer Auslieferung genau einmal ernsthaft macht.
Was „eingefroren" hier bedeutet
Nicht: „bitte nicht mehr ändern." Sondern: Eine Änderung muss eine bewusste Handlung sein. Vier Verträge stehen deshalb in Dateien, die man anfassen muss, und in Tests, die bei jeder Abweichung anschlagen.
| Vertrag | Wo er steht | Was der Test fängt |
|---|---|---|
| API | apps/api/internal/httpapi/contract_routes.txt (102 Endpunkte) |
neuer, entfallener oder anders berechtigter Endpunkt |
| Migrationen | migrations/checksums.txt |
nachträglich geänderte oder entfernte Migration, fehlende Gegenrichtung, Lücke in der Nummerierung |
| Backup-Format | packages/backupformat/testdata/container-v1.syncova |
Format-, Abschnitts- und Kennzeichenwerte; ein Container von gestern, der heute nicht mehr lesbar ist |
| Repository | packages/repository/testdata/frozen-repository/ |
Verzeichnis- und Dateinamen, Manifestversion, Lesbarkeit, Katalogaufbau ohne Datenbank |
Warum der API-Vertrag den Quelltext auswertet
http.ServeMux gibt seine Routen nicht heraus. Ein Test, der die bekannten Adressen anfragt, bemerkt eine hinzugefügte nicht — und eine neue Route ist die häufigste Vertragsänderung überhaupt. Der Test liest deshalb router.go mit go/parser und zieht Methode, Pfad und geforderte Berechtigung heraus.
Die Berechtigung gehört ausdrücklich dazu. Eine stillschweigend gelockerte Prüfung ist die gefährlichste Änderung, die es gibt: Der Endpunkt funktioniert weiter, nur dürfen ihn plötzlich mehr Leute aufrufen. Beides — Lockerung und Wegfall — ist mit einer Mutation nachgewiesen.
Warum eine ausgelieferte Migration sich nie ändern darf
Datenbanken, die eine Migration angewandt haben, führen sie nicht erneut aus; golang-migrate merkt sich nur die Versionsnummer. Eine nachträgliche Änderung wirkt deshalb ausschließlich auf neue Installationen. Das Ergebnis sind zwei Schemata mit derselben Versionsnummer, und der Unterschied fällt erst auf, wenn eine Abfrage auf einer der beiden Anlagen scheitert.
Wer etwas ändern will, schreibt eine neue Migration. Immer.
Warum Fixture-Dateien und kein Rundlauftest
Ein Rundlauftest schreibt und liest mit demselben Code. Er bleibt grün, wenn sich beide Seiten gemeinsam ändern — also genau in dem Fall, den man fürchtet. Die eingefrorenen Dateien sind mit Formatversion 1 geschrieben und liegen im Quellbestand; sie werden vom Code von heute gelesen.
Neu erzeugen lassen sie sich nur mit ausdrücklichem Schalter:
SYNCOVA_WRITE_FIXTURE=ja go test ./packages/backupformat -run TestWriteFrozenContainerFixture
SYNCOVA_WRITE_FIXTURE=ja go test ./packages/repository -run TestWriteFrozenRepositoryFixture
Sie versehentlich mitlaufen zu lassen hieße, den Vertrag jedes Mal auf den aktuellen Stand zu heben — und damit genau die Prüfung abzuschalten, um die es geht.
Der Upgrade-Test
Der einzige Test dieser Anlage, den es bis Phase 22 nie gab. Bisher wurde jedes Schema von Grund auf angelegt; damit prüft man ausschließlich die Neuinstallation. Ein Kunde macht etwas anderes: Er hat Daten und spielt eine neue Version darüber.
Der Unterschied ist nicht theoretisch:
- Eine Migration, die eine Spalte mit
NOT NULLohne Vorgabewert ergänzt, läuft auf einer leeren Tabelle einwandfrei durch und scheitert auf einer gefüllten. - Eine nachträgliche
CHECK-Regel prüft auch die Zeilen, die schon da sind.
Der Ablauf ist der einer echten Aktualisierung: Schemastand 4 herstellen, Benutzer, Repositories, Aufträge, Quellen mit Ausschlussmustern, Läufe, Kette und ein abgeschlossenes Backup anlegen — dann die neuen Migrationen darüber.
Geprüft wird danach die Zeilenzahl und der Inhalt. Eine Migration, die jede Zeile behält und dabei ein Feld leert, bliebe sonst unbemerkt.
Eine Absicherung im Test selbst weist einen leeren Ausgangsbestand zurück. Ohne sie wäre der ganze Test ein Placebo: Ein Vergleich „vorher gleich nachher" ist auf leeren Tabellen immer erfüllt.
Beide Fehlerarten sind mit einer Mutation nachgewiesen — eine löschende Migration und eine NOT NULL-Spalte ohne Vorgabewert.
Der Rollback-Test
Ein Rollback ist die unangenehmste Zusage einer Datenbank. Geprüft ist:
- Genau ein Schritt. Ein versehentlicher Rücklauf auf Version 0 löschte die gesamte Control Plane; deshalb geht
downimmer nur einen Schritt und verlangt in der ProduktionSYNCOVA_MIGRATE_CONFIRM_DOWN=yes. - Daten älterer Stände bleiben unberührt. Ein Rückschritt, der nebenbei Aufträge oder Backups mitnimmt, wäre ein Datenverlust ohne Ansage.
- Der Weg nach vorn bleibt offen. Eine Anlage, die sich zurücknehmen, aber nicht wieder aufbauen lässt, ist verloren.
- Alle Rückrichtungen laufen auf einer gefüllten Datenbank — bis Version 0 und wieder hoch. Eine down-Migration wird typischerweise nie ausgeführt, bis zu dem Tag, an dem sie gebraucht wird; dann steht sie unter Zeitdruck und mit Daten davor.
Was ein Rollback nicht kann
Daten in Tabellen, die es vorher nicht gab, sind danach weg. Der Test hält das ausdrücklich fest: Ein eingerichteter Virtualisierungsverbund überlebt den Rückschritt seiner Migration nicht, und nach dem erneuten Vorlauf steht die Tabelle leer da. Die Zugangsdaten sind nicht wiederherstellbar.
Ein Rollback ersetzt keine Sicherung der Datenbank. Wer eine Aktualisierung fahren will, sichert vorher — der Rückschritt ist die zweite Verteidigungslinie, nicht die erste.
Der vollständige Durchlauf
Gefahren gegen den laufenden Dienst, 30 MiB inkompressible Daten in 61 Dateien mit Unterordnern und einem symbolischen Verweis:
| Schritt | Ergebnis |
|---|---|
| Repository eintragen | über POST /repositories, Kennung aus dem Descriptor gelesen |
| Auftrag anlegen und starten | 202, Lauf succeeded, 30,0 MiB gelesen, 68 Objekte |
| Integritätslauf | 69 Blöcke geprüft, 0 fehlend, 0 beschädigt, 0 verwaist |
| Prüfung mit Wiederherstellungstest | completed / clean, Einstufung recoverable, 70 % |
| Quelle gelöscht | 61 Dateien vernichtet |
| Vorabprüfung | durchführbar, 68 Objekte, 69 Blöcke, keine Befunde |
| Wiederherstellung | succeeded, 68 Objekte, 0 übergangen, 31 457 320 Byte in 0,41 s |
| Vergleich | 61 von 61 Dateien bitgenau, Symlink und Rechte erhalten |
Zwei Funde des Durchlaufs
Ein Repository ließ sich über die API gar nicht anlegen. SYNCOVA_API.md §8 verlangt POST, PATCH, GET /{id}, test, health-check, integrity-scan und rebuild-catalog — vorhanden war ausschließlich die Liste. Repositories kamen bis dahin über SQL in die Control Plane. Aufgefallen ist das erst hier, weil jeder frühere Nachweis die Zeile selbst eingetragen hat. Die Anlage war über ihre eigene API nicht in Betrieb zu nehmen.
Die sieben Endpunkte sind ergänzt. POST /repositories legt dabei nichts an, sondern übernimmt: Es öffnet das vorhandene Repository, liest dessen Kennung aus dem Descriptor und trägt es ein. Ein Datenbankeintrag ohne Repository dahinter wäre ein Ziel, das erst um zwei Uhr nachts als nicht vorhanden auffällt — real geprüft, die Antwort verweist auf syncova-repo create.
Der Ort eines Repositorys war nicht eindeutig. Zwei Einträge konnten auf dasselbe Verzeichnis zeigen. Das ist kein Schönheitsfehler: Zwei Aufträge hielten sie für verschiedene Ziele, rissen sich um dieselbe Schreibsperre, und die Kennzahlen zählten denselben Speicher doppelt. Migration 000013 setzt die Eindeutigkeit; bestehende Doppelungen lassen die Aktualisierung scheitern, weil nur ein Betreiber weiß, welcher Eintrag der richtige ist.
Disaster Recovery
Auf einer leeren Datenbank mit dem neuen Schemastand 13, allein aus dem Repository:
11 Repositories, 12 Aufträge, 6 Konten übernommen
0 aktive Aufträge, 0 aktive Konten
Die beiden Nullen sind das Wesentliche. Nichts läuft von selbst wieder an: Ein Zeitplan, der nachts anspringt, könnte auf eine halb wiederhergestellte Anlage schreiben.
Security-Review
Gegen die Entwicklungsdatenbank: 29 von 88 Punkten (33 %), Einstufung unzureichend, 8 kritische Befunde. Das ist das richtige Ergebnis für diese Umgebung — kein gehärtetes Repository, keine gemessene Durchsetzungsstufe, Konten mit Löschrecht ohne zweiten Faktor, unverschlüsselte Wiederherstellungspunkte aus früheren Nachweisen.
Der Satz daneben ist der Kern: „Solange sie bestehen, ist die Anlage nicht sicher zu betreiben — die Prozentzahl daneben ändert daran nichts." Ein kritischer Befund deckelt die Einstufung, unabhängig von der Zahl.
Dazu die Prüfungen aus make check: Abhängigkeiten (govulncheck: keine Schwachstelle), Statik (go vet, staticcheck), Geheimnissuche im gesamten Quellbestand.
Performance-Review
Apple M1, 8 Kerne, lokale SSD, inkompressible Daten, ohne Verschlüsselung — dieselben Bedingungen wie in Phase 20, damit die Zahlen vergleichbar sind:
| Szenario | Phase 20 | Phase 22 |
|---|---|---|
| Eine große Quelle (512 MiB) | 151,9 MiB/s | 179,5 MiB/s |
| Zweiter Lauf über unveränderte Daten | 662,8 MiB/s | 636,7 MiB/s |
| Viele kleine Dateien | 107 Dateien/s | 100 Dateien/s |
Keine Regression. Der zweite Lauf bleibt rund viermal schneller — die unabhängige Bestätigung der Aussage aus Phase 6: Der Gewinn einer Zusatzsicherung ist Zeit, nicht Speicher.
Die Streaming-Pipeline hält weiterhin: 256 MiB Quelle bei 256 MiB Speichergrenze, Höchststand 182,5 MiB.
Akzeptanzmatrix (§26)
Abgehakt wird nur, was tatsächlich gefahren wurde. Eine Zeile, die man abhakt, weil der Code vorhanden ist, ist die teuerste Zeile des ganzen Dokuments.
Backup
| Zeile | Nachweis | |
|---|---|---|
| ✅ | Linux file backup | Phase 6, Sonderdateien und Rechte; Phase 22 E2E |
| ⬜ | Windows file backup | Der Agent übersetzt für Windows und wurde nie dort ausgeführt. Kommandozeilenweg und Auftragsausführung sind plattformunabhängig nachgewiesen, der Dienstwrapper nicht |
| ⚠️ | Proxmox VM backup | vollständig gegen einen API-Nachbau, nie gegen einen echten Verbund |
| ✅ | incremental | Phase 6 und 8; zweiter Lauf 0,0 MiB statt 38,1 MiB |
| ✅ | deduplication | Phase 4; zweiter Lauf 636,7 MiB/s gegen 179,5 |
| ✅ | compression | Phase 4, zstd mit Marker für nicht verkleinerbare Blöcke |
| ✅ | encryption | Phase 4, AES-256-GCM mit abgeleiteter Nonce |
| ✅ | retry | Phase 8, nur bei transient/network/repository/source |
| ✅ | resume | Phase 9 und 18, Fortsetzung nach SIGKILL bei 496 von 1500 Dateien |
| ✅ | bandwidth control | Phase 8, gemessen: 19,1 MiB in 0,37 s ohne Grenze gegen 18,17 s bei 1 MiB/s |
Recovery
| Zeile | Nachweis | |
|---|---|---|
| ✅ | file restore | Phase 9; Phase 22 E2E bitgenau |
| ✅ | folder restore | Phase 22 E2E, 61 Dateien in Unterordnern |
| ⚠️ | Proxmox restore | Archiv bitgenau auf den Knoten zurückgeschrieben und qmrestore angestoßen — ob die Maschine bootet, ist ungeprüft |
| ⚠️ | alternate-host restore | im Provider umgesetzt (--node), nur gegen den Nachbau gefahren |
| ✅ | restore validation | Phase 9, Vorabprüfung mit Blockprüfung; im E2E gefahren |
| ✅ | interrupted restore recovery | Phase 18, Prüfpunkt und Fortsetzung nach SIGKILL |
Repository
| Zeile | Nachweis | |
|---|---|---|
| ✅ | local repository | durchgehend |
| ✅ | hardened repository | Phase 11, rm -rf → 15 verweigerte Löschungen |
| ✅ | integrity scan | Phase 2; Phase 22 über die API |
| ✅ | corruption detection | Phase 10 und 21; Phase 22 Fixture mit gekipptem Byte |
| ✅ | catalog rebuild | Phase 2, 18 und 22 (Fixture ohne indexes/) |
| ✅ | immutable retention | Phase 11, Verlängern ja, Verkürzen nie |
Security
| Zeile | Nachweis | |
|---|---|---|
| ✅ | RBAC | Phase 19, sechs schreibende Zugriffe als Viewer → 403; Phase 7 erneut |
| ✅ | MFA | Phase 1, gegen alle zehn RFC-6238-Testvektoren, mit Replay-Schutz |
| ✅ | audit | Phase 1, append-only per Datenbank-Trigger |
| ✅ | TLS | Phase 19, mit TLS 1.3 nachgewiesen; halbe Konfiguration wird beim Start abgelehnt |
| ✅ | secret protection | Phase 19 Geheimnissuche; Phase 7 real geprüft, dass kein Token im Klartext in der Zeile steht |
| ✅ | privilege boundaries | Phase 5, Agent-Token gegen /users → 401; Phase 11, backups.delete getrennt von immutability.manage |
Monitoring
| Zeile | Nachweis | |
|---|---|---|
| ✅ | dashboard | Phase 12, 9 von 10 Kennzahlen mit Datengrundlage |
| ✅ | metrics | Phase 13 |
| ✅ | graphs | Phase 13, zwölf Diagramme über sieben Zeiträume |
| ✅ | alerts | Phase 14, zehn Regeln, Zustellung gegen echten SMTP- und HTTP-Server |
| ⬜ | capacity forecast | nicht umgesetzt. Die Kennzahl erscheint mit Begründung statt mit einer Null |
| ✅ | health | Phase 0, Liveness ohne Abhängigkeiten, Readiness mit |
Reliability
| Zeile | Nachweis | |
|---|---|---|
| ✅ | control server restart | Phase 8, SIGTERM bricht laufende Vorgänge ab und schreibt ihr Ergebnis |
| ✅ | database restart | Phase 21, /health/live bleibt 200, Dienst 2 s nach Rückkehr wieder bereit |
| ✅ | repository restart | Phase 21, volle Platte → REPOSITORY_FULL, null Manifeste |
| ⚠️ | agent restart | Betriebsschleife und Wiederaufnahme nachgewiesen; der Windows-Dienst wurde nie geladen |
| ⚠️ | network interruption | Rückzugsverhalten des Agenten geprüft, aber nur lokale Repositories — nicht neu ausgelöst |
| ✅ | corrupted data | Phase 21, beschädigtes Manifest: Wiederherstellung bricht ab, 0 Dateien im Ziel |
Zusammenfassung
32 von 38 Zeilen erbracht, 2 nicht umgesetzt, 4 nur gegen Nachbauten.
Die vier Zeilen mit ⚠️ und die Windows-Zeile hängen alle an derselben Sache: fehlender Hardware. Sie lassen sich hier nicht erbringen, und sie zu behaupten wäre die eine Sorte Fehler, die dieses Produkt nicht machen darf.
Was vor der Auslieferung bleibt
- Windows: Dienst laden, Start, Stopp, Neustart und die Rechte des Dienstkontos prüfen.
- Linux:
deployment/syncova-agent.serviceladen undsystemd-analyze verifyfahren. - Proxmox: den verpflichtenden Meilenstein zu Ende gehen — Test-VM löschen, wiederherstellen, booten, validieren.
- Netzunterbrechung gegen ein entferntes Repository auslösen.
- Capacity Forecast umsetzen oder als bewusste Auslassung in die Freigabe schreiben.
Punkte 1 bis 4 sind Ausführung, kein Bau. Punkt 5 ist eine Entscheidung.