syncova-backup/docs/architecture.md
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
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>
2026-08-17 09:10:54 +02:00

5.8 KiB

Aufbau des Codes

Dieses Dokument beschreibt den aktuell implementierten Stand. Die angestrebte Gesamtarchitektur steht in SYNCOVA_ARCHITECTURE.md.

Schichtung

apps/web        Weboberfläche (React + TypeScript)
     |  HTTP (gleicher Ursprung, im Entwicklungsbetrieb über Proxy)
     v
apps/api        Control-Plane-API (Go)
     |
     v
packages/platform   Querschnittsdienste: config, logging, database, health
     |
     v
PostgreSQL      ausschließlich Control Plane, niemals Backup-Nutzdaten

Die Abhängigkeitsrichtung ist strikt: packages/ kennt apps/ nicht. Damit können Agent und Worker später dieselben Querschnittsdienste nutzen, ohne die API einzubinden.

packages/platform

Paket Verantwortung
config Konfiguration aus Umgebungsvariablen, vollständige Validierung
logging strukturiertes Logging, Correlation IDs, Secret-Redaction
database PostgreSQL-Pool, Health-Check, Migrationsverwaltung
health Health Engine über alle Komponenten

config

Alle Werte stammen aus SYNCOVA_*-Umgebungsvariablen. Zwei Entscheidungen prägen das Paket:

  • Secrets besitzen keinen Standardwert. Das Datenbankpasswort ist als nicht exportiertes Feld abgelegt, damit es nicht versehentlich serialisiert wird. Zugriff besteht nur über ConnectionString() (mit Passwort) und RedactedConnectionString() (ohne).
  • Validierungsfehler werden gesammelt. Load() meldet alle Probleme gemeinsam, statt beim ersten abzubrechen.

Sicherheitsrelevante Voreinstellungen: Listener nur auf 127.0.0.1, CORS leer, Body-Limit 1 MiB, Wildcard-Herkunft grundsätzlich unzulässig, unverschlüsselte Herkunft in der Produktion abgelehnt.

logging

slog mit JSON-Ausgabe. Die Redaction hängt an ReplaceAttr und greift damit für jedes Attribut — der einzige Ort, an dem sie nicht vergessen werden kann. Erkannt werden Feldnamen, die auf ein Geheimnis hindeuten (password, token, secret, connection_string und weitere), unabhängig von Schreibweise und Präfix.

health

Jede Komponente meldet einen expliziten Zustand; der Gesamtzustand ist der schlechteste Einzelzustand. Drei Eigenschaften sind bewusst so gebaut:

  • Eine Prüfung ohne gemeldeten Zustand gilt als kritisch, nicht als gesund.
  • Eine Zeitüberschreitung gilt als kritisch, statt den Endpunkt zu blockieren.
  • Ein Panic in einer Prüfung wird abgefangen und als kritisch gemeldet.

Nur als kritisch registrierte Komponenten beeinflussen die Readiness. Eine ausgefallene Nebenkomponente nimmt den Dienst also nicht aus dem Verkehr, erscheint aber weiterhin ehrlich im Bericht.

apps/api

Middleware-Reihenfolge

Recovery → Correlation → SecurityHeaders → CORS → BodyLimit → AccessLog → Router

Recovery liegt außen, damit auch Fehler innerer Schichten erfasst werden. Correlation folgt unmittelbar, damit jede weitere Schicht bereits eine ID zum Loggen hat.

Request-ID und Correlation ID

Die Request-ID wird immer serverseitig als UUID vergeben. Die Correlation ID darf der Aufrufer über X-Correlation-ID vorgeben, um eine Operation über Systemgrenzen zu verfolgen — aber nur, wenn der Wert eine gültige UUID ist. Andernfalls wird er verworfen, damit keine fremden Zeichenketten in die Logs gelangen.

Antworthülle

{ "data": {}, "meta": { "request_id": "uuid" } }
{ "error": { "code": "...", "message": "...", "request_id": "uuid" } }

APIError trennt die ausgelieferte Darstellung von der internen Ursache: Letztere wird über WithCause mitgeführt, erscheint im Log und nie in der Antwort.

Maßgeblich für den Client ist die Hülle, nicht der HTTP-Status. Beide tragen unterschiedliche Aussagen: der Status beschreibt den Betriebszustand, die Hülle den Inhalt. GET /api/v1/health nutzt genau das und meldet einen kritischen Systemzustand mit 503, liefert dabei aber den vollständigen Bericht als Nutzlast — das externe Monitoring schlägt an, und die Oberfläche kann trotzdem anzeigen, was kaputt ist.

Health-Endpunkte

Endpunkt Prüft Zweck
GET /health/live nur den Prozess Neustartentscheidung
GET /health/ready alle kritischen Komponenten Verkehrszuteilung
GET /api/v1/health alle Komponenten einzeln Diagnose

Liveness prüft bewusst keine Abhängigkeiten: sonst würde eine kurzzeitig nicht erreichbare Datenbank einen Neustart des Dienstes auslösen und das Problem verschlimmern.

Datenbank

PostgreSQL ist die Control-Plane-Datenbank und enthält niemals Backup-Nutzdaten. Beide Zugriffswege — Verbindungspool und Migrationsläufe — nutzen denselben pgx-Treiber. Das ist eine bewusste Festlegung: golang-migrate würde sonst seinen eingebauten lib/pq-Treiber verwenden, der andere SSL-Modi kennt, womit dieselbe Konfiguration je nach Kommando funktionieren oder scheitern würde.

Migrationen sind über embed ins Binary eingebettet und laufen ausschließlich über syncova-migrate. Der API-Dienst prüft beim Start nur, ob der Schemastand zur Programmversion passt.

apps/web

Aufbau nach PROMPT.md §104:

src/
  api/         HTTP-Client mit Fehlerbehandlung
  components/  wiederverwendbare Bausteine des Designsystems
  features/    fachliche Bereiche (aktuell: health)
  styles/      Design-Token
  types/       Typen des API-Vertrags

Der Client requestApi kapselt die Antworthülle und liefert Fehler stets als ApiError. Eine Antwort außerhalb des Vertrags führt zu einem klaren Fehler statt zu einem stillschweigend leeren Ergebnis.

Die Statusfarben (grün/gelb/orange/rot/blau) sind ausschließlich Zuständen vorbehalten. Ein Zustand wird nie allein über Farbe vermittelt, sondern immer zusätzlich als Text — Farbe allein wäre für farbfehlsichtige Anwender unzugänglich.