From 4eba16525059668491b37234574843e430284c25 Mon Sep 17 00:00:00 2001 From: sysops Date: Sun, 5 Jul 2026 21:11:39 +0200 Subject: [PATCH] docs(PROJ-66): Backup & Restore Runbook schreiben docs/BACKUP_RESTORE_RUNBOOK.md deckt beide Sicherungsebenen ab: App-eigenes CLI-Backup (archivmail backup/restore, PROJ-66) und Infrastruktur-Ebene (PBS + Sync auf Zweitserver, pct restore). Enthaelt Bitwarden-Keyfile- Escrow-Anleitung, Pflicht-Verifikationsschritte (reconcile/reindex/ Stichprobe), Rotation-vs-DSGVO-Hinweis und den offenen Punkt "noch kein Restore-Test durchgefuehrt" als groesste verbleibende Luecke. AC 8 aus PROJ-66 damit erfuellt; AC 6 (Restore-Test) und AC 7 (Alerting) bleiben offen. --- docs/BACKUP_RESTORE_RUNBOOK.md | 170 +++++++++++++++++++++++++++ features/PROJ-66-backup-strategie.md | 75 +++++++++++- 2 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 docs/BACKUP_RESTORE_RUNBOOK.md diff --git a/docs/BACKUP_RESTORE_RUNBOOK.md b/docs/BACKUP_RESTORE_RUNBOOK.md new file mode 100644 index 0000000..849fb20 --- /dev/null +++ b/docs/BACKUP_RESTORE_RUNBOOK.md @@ -0,0 +1,170 @@ +# Backup & Restore Runbook – archivmail + +Bezug: `features/PROJ-66-backup-strategie.md` (Spec + Entscheidungsverlauf), +`docs/GOBD_DSGVO_CHECKLIST.md` Punkt 7 (Löschsperre) und 15 (Mandantentrennung). +Ziel: belegen können, dass ein Totalausfall von Store, Keyfile oder +PostgreSQL nicht zu unwiederbringlichem Mail-Verlust führt — und dass ein +Restore im Ernstfall tatsächlich funktioniert, nicht nur theoretisch existiert. + +Gilt für: `archivmail backup`/`archivmail restore` (PROJ-66, App-eigenes +CLI-Backup) und ergänzend die Proxmox Backup Server (PBS) + Sync-auf- +Zweitserver-Sicherung auf Infrastruktur-Ebene. + +## Grundsatz + +Store, Keyfile und PostgreSQL-Metadaten sind **nur gemeinsam** nutzbar: +- Store ohne DB: Dateien vorhanden, aber keine Zuordnung zu Mail/Tenant. +- Keyfile ohne Store: irrelevant, nichts zu entschlüsseln. +- DB ohne Keyfile: Metadaten da, Mail-Inhalte nicht mehr lesbar (GoBD-Verstoß). + +Ein Backup, das nur eine dieser drei Komponenten sichert, ist wertlos. Ein +Backup, das nie zurückgespielt wurde, ist nicht verifiziert — siehe +"Regelmäßiger Restore-Test" unten. + +## Zwei unabhängige Sicherungsebenen + +1. **Infrastruktur-Ebene (PBS + Sync auf Zweitserver)** — läuft vom + Proxmox-Host aus, sichert den kompletten Container (Store+Keyfile+ + Config+PostgreSQL-Datendir+Audit-Log in einem Snapshot). Von innerhalb + des Containers nicht sichtbar/steuerbar. Siehe Memory + `project_backup_infra_pbs` für CLI-Befehle (`vzdump`, `pct restore`). +2. **App-eigenes Backup (`archivmail backup`/`restore`)** — ergänzt Ebene 1 + um einen anwendungskonsistenten `pg_dump` (statt reinem Crash-Consistency- + Snapshot) und einen selektiven, von PBS unabhängigen Restore-Weg. + +Keine der beiden Ebenen ersetzt die andere. Ein vollständiger Restore-Test +sollte mindestens einmal **beide** Wege durchlaufen haben (siehe unten). + +## Backup erstellen + +```bash +archivmail backup --config /etc/archivmail/config.yml --dest --keep 14 +``` + +- `` MUSS auf einem vom Produktivsystem physisch getrennten Ziel + liegen (zweiter Host, NAS, o.ä.) — siehe PROJ-66-Spec, Abschnitt + "Backup-Ziel: On-Site vs. Off-Site". Ein Backup auf derselben Platte + schützt nicht vor Hardware-Ausfall. +- Schreibt `//`: `postgres.dump` (`pg_dump -Fc`), `store/` + (Hardlink-erhaltende Kopie, PROJ-65-Tenant-Ordner bleiben ohne + Speicherplatz-Verdopplung erhalten), `keyfile`, `config.yml`, `audit.log` + (best-effort). +- `--keep N` behält die letzten N Backup-Verzeichnisse, löscht ältere — + **nur nach erfolgreichem Lauf**. Ein fehlgeschlagener Lauf lässt den + partiellen Ordner stehen und rotiert nichts weg (Diagnose-Möglichkeit, + kein Verlust eines guten alten Backups). +- Cron-Aktivierung: `deploy/cron.d/archivmail` enthält eine auskommentierte + Beispielzeile. Erst aktivieren, wenn ein konkretes `--dest`-Ziel feststeht. + +## Keyfile-Escrow (Bitwarden) + +Zusätzlich zum Backup: das Keyfile als unabhängige Kopie in Bitwarden +hinterlegen (Secure Note, eigene Collection mit eingeschränktem Zugriff — +das Keyfile entschlüsselt das **gesamte Archiv aller Mandanten**). + +```bash +cat /etc/archivmail/keyfile +# → Wert 1:1 in eine neue Bitwarden Secure Note kopieren, Metadaten +# ergänzen (Server/Installation, Datum, wer hinterlegt hat). +``` + +Zweck: unabhängiger Wiederherstellungsweg, falls PBS/Snapshot-Infrastruktur +selbst nicht verfügbar ist. Ersetzt nicht das App-Backup — beide parallel +pflegen. Bei künftiger Keyfile-Rotation (aktuell nicht vorgesehen) den +Bitwarden-Eintrag im selben Schritt aktualisieren. + +## Restore durchführen + +**Vorbereitung:** +1. Zielsystem: frischer Server/Container oder vorhandenes System im + Wartungsmodus. `archivmail`/`archivmail-web` müssen gestoppt sein — + `archivmail restore` stoppt/startet die Dienste NICHT selbst. +2. Backup-Verzeichnis identifizieren: `//`. + +**Durchführung:** + +```bash +systemctl stop archivmail archivmail-web + +archivmail restore --config /etc/archivmail/config.yml \ + --source / [--force] +``` + +- Ohne `--force`: bricht ab, wenn `store_path`/Keyfile bereits Inhalt haben + — Schutz gegen versehentliches Überschreiben eines laufenden Systems. + `--force` nur auf einem bewusst freigeräumten/frischen Zielsystem nutzen. +- `--skip-db`: überspringt `pg_restore`, falls die Datenbank bereits separat + wiederhergestellt wurde (z.B. via PBS-Snapshot der DB-Ebene). +- Stellt Store (Hardlinks erhalten), Keyfile, Config und PostgreSQL + (`pg_restore --clean --if-exists`) wieder her. Gibt am Ende die + Pflicht-Verifikationsschritte aus (siehe unten). + +**Verifikation (Pflicht, nicht optional):** + +```bash +systemctl start archivmail archivmail-web +# Health-Check wie im Standard-Deploy-Workflow (Backend ✓, Frontend ✓) + +archivmail reconcile --days 7 # PROJ-52: Zählvergleich Store vs. DB muss aufgehen +archivmail reindex # Manticore aus Store+DB neu aufbauen +# Stichprobe: 3-5 zufällige Mails über die UI öffnen und lesbar entschlüsseln +# lassen — beweist, dass Keyfile+Store+DB zueinander passen. +``` + +Restore-Ergebnis (Mail-Zahl, Zeitstempel, Dauer, welche Verifikationsschritte +liefen) dokumentieren — Prinzip "Zählen, Prüfen, Belegen" wie im +Migrations-Runbook. + +## Restore aus einem PBS-Snapshot (Infrastruktur-Ebene) + +Auf dem Proxmox-**Host** (nicht im Container): + +```bash +pct restore :backup/ct// --storage +``` + +Restored als neuer Container mit `` — der Quell-Container bleibt +unangetastet. Danach dieselbe Verifikation wie oben (`reconcile`, `reindex`, +Stichprobe) im neuen Container durchführen. PostgreSQL kam dabei aus einem +Crash-Consistency-Snapshot, nicht aus einem `pg_dump` — WAL-Replay beim +Start ist der Normalfall, sollte aber beim Test explizit beobachtet werden +(`journalctl -u postgresql` auf Recovery-Meldungen prüfen). + +## Regelmäßiger Restore-Test + +Ein Backup, das nie zurückgespielt wurde, ist nicht verifiziert. Empfehlung: +mindestens vierteljährlich einen vollständigen Restore auf einen isolierten +Test-Container durchführen (**nicht auf 131/132!**) — einmal über +`archivmail restore` (App-Backup) und mindestens einmal über `pct restore` +(PBS-Snapshot), jeweils mit vollständiger Verifikation. Bislang (Stand +2026-07-05) wurde noch kein Restore-Test durchgeführt — das ist die +kritischste offene Lücke in der Backup-Strategie, nicht das Fehlen eines +Backup-Ziels. + +## Rotation vs. DSGVO-Löschung + +Backup-Rotation (`--keep N`, bzw. PBS-Retention) ist unabhängig von der +GoBD-Aufbewahrungsfrist der Mails selbst (PROJ-51). Eine in Produktion +bereits DSGVO-gelöschte Mail (PROJ-50) kann in einem älteren Backup noch +enthalten sein. Rotation muss kurz genug sein, dass gelöschte Daten nach +spätestens N Zyklen auch aus Backups verschwinden — Zeitraum mit +Datenschutzbeauftragtem/Nutzer abstimmen, kein rein technisches Thema. + +## Monitoring / Alerting (noch offen) + +Größtes Risiko bei Cron-Backups: der Job schlägt still fehl, niemand merkt +es bis zum Ernstfall (Lehre aus PROJ-58: Cron-Zeilen fehlten wochenlang +trotz aktivem Code). Aktuell **nicht gebaut**: ein sichtbarer Alarm, wenn +das letzte erfolgreiche Backup älter als ein Schwellenwert ist. Bis dahin: +Log-Datei des Backup-Cron-Jobs (`/var/log/archivmail/backup.log`, sobald der +Cron aktiviert ist) manuell im Rahmen der regulären Betriebs-Checks prüfen. + +## Verweis + +- `features/PROJ-66-backup-strategie.md` — vollständige Spec, Optionsabwägung, + QA-Ergebnisse. +- `docs/MIGRATION_RUNBOOK.md` — Vorbild für den "Zählen, Prüfen, Belegen"-Stil. +- Memory `project_backup_infra_pbs` — PBS/Sync-Infrastruktur-Details, + `vzdump`/`pct restore`-Befehle. +- Memory `project_132_teilproduktiv` — 132 ist kein reiner Testserver, + Backup-Sorgfalt gilt dort genauso wie auf 131. diff --git a/features/PROJ-66-backup-strategie.md b/features/PROJ-66-backup-strategie.md index c4becc4..bc5c59f 100644 --- a/features/PROJ-66-backup-strategie.md +++ b/features/PROJ-66-backup-strategie.md @@ -1,6 +1,6 @@ # PROJ-66: Backup-Strategie für archivmail (Produktiv + Teilproduktiv) -**Status:** In Review (BUG-1/BUG-2 aus QA-Runde 1 gefixt, Re-Test steht aus) +**Status:** Deployed (nur CLI-Kommandos `backup`/`restore` auf 192.168.1.131 — Cron-Aktivierung, Alerting, Bitwarden-Escrow und PBS-Restore-Verifikation bleiben offen, siehe "## Deployment") **Erstellt:** 2026-07-04 ## Problem / Ausgangslage @@ -392,6 +392,10 @@ Anforderungen: 8. Ein Backup-Runbook (analog `docs/MIGRATION_RUNBOOK.md`) existiert unter `docs/BACKUP_RESTORE_RUNBOOK.md` mit Schritt-für-Schritt-Anleitung für Backup, Restore und Verifikation. + **✓ Erfüllt (2026-07-05)** — `docs/BACKUP_RESTORE_RUNBOOK.md` angelegt, + deckt App-Backup (`archivmail backup`/`restore`) und Infrastruktur-Ebene + (PBS/`pct restore`) ab. AC 6 (Restore-Test) und AC 7 (Alerting) bleiben + davon unberührt weiterhin offen. 9. `update.sh`-Vollständigkeits-Check: Falls das Backup-Skript/der Cron-Job als Datei unter `deploy/` im Repo gepflegt wird, synct `update.sh` diese Datei nachweislich mit aus (Lehre aus PROJ-58, siehe Deploy-Vollständigkeits- @@ -620,3 +624,72 @@ der Fehler tritt nur bei der zweiten+ Inode-Referenz auf. Re-Test von Punkt 1 (Build) und Punkt 5b (`-force` gegen befüllten Store) steht aus. + +## QA Re-Test Runde 2 (2026-07-04, Testserver 132) + +**Getestet gegen Commit 55131de (aktueller HEAD, `fix(PROJ-66): Build-Fehler +in printHelp() + -force überschreibt keine Hardlinks`).** Nur die beiden zuvor +fehlgeschlagenen Punkte erneut geprüft (übrige Punkte waren in Runde 1 bereits +PASS und wurden nicht wiederholt). Isolierter Build via `git archive HEAD` nach +`/tmp/qa66-build` auf 132, Funktionstest in `/tmp/qa66-test` (synthetischer +Store mit PROJ-65-Tenant-Hardlink, `ab/abcdef123.bin` ↔ `tenant_1/abcdef123.bin`, +Inode-geteilt, link count 2). Deploy-Repo `/opt/archivmail/_build` NICHT +verändert, echte Produktions-/Store-Daten und echte DB nicht berührt. + +| # | Testpunkt | Runde 1 | Runde 2 | +|---|-----------|---------|---------| +| 1 | `CGO_ENABLED=0 go build -buildvcs=false ./cmd/archivmail/` | FAIL (BUG-1) | **PASS** — Build exit 0, Binary 21 MB erzeugt; `cmd_import.go:310` nutzt jetzt einfache Anführungszeichen statt Backticks | +| 5b | `restore -force` gegen befüllten Store mit Hardlinks (Normalfall) | FAIL (BUG-2) | **PASS** — Repro (2× Restore auf dasselbe Ziel, 2. Lauf mit `-force`) läuft jetzt exit 0 durch; Hardlink erhalten (Ziel-Inode identisch für `ab/` und `tenant_1/`, link count 2), Inhalt intakt | + +**BUG-1** verifiziert gefixt: `printHelp()`-Raw-String schließt nicht mehr +vorzeitig, gesamtes Backend kompiliert fehlerfrei. + +**BUG-2** verifiziert gefixt: `copyTreePreservingHardlinks()` entfernt vor +`os.Link()` eine bereits vorhandene Zieldatei (`os.Remove`, `os.IsNotExist` +ignoriert). Vorheriger `os.Link: file exists` tritt nicht mehr auf; `-force` +überschreibt einen mit Tenant-Hardlinks befüllten Store verlustfrei. + +**Gesamtergebnis Re-Test: BESTANDEN.** Beide zuvor blockierenden Bugs sind +grün. CLI-Kommandos `backup`/`restore` funktional freigegeben. Nicht Teil +dieses Re-Tests (unverändert offen laut Implementation Notes): Cron/Timer +(AC 1), physische Trennung (AC 3), Backup-Verschlüsselung (AC 4), +Restore-Verifikation via echtem `reconcile` gegen realen Bestand (AC 6), +Alerting (AC 7), Runbook (AC 8), Bitwarden-Escrow (AC 10), PBS-Restore-Test +(AC 11). Empfehlung an devops-deploy: 55131de deployen und Status auf +Deployed setzen; die noch offenen ACs bleiben als Folgeschritte bestehen. + +Testartefakte (`/tmp/qa66-build`, `/tmp/qa66-test` auf 132) nach Testende +entfernt und Entfernung verifiziert (`ls -d /tmp/qa66*` → NONE). + +## Deployment + +**2026-07-04, devops-deploy, Produktivserver 192.168.1.131** + +Deploy via `bash /opt/archivmail/update.sh` auf 131 (Commit 55131de, bereits +auf origin/main). `update.sh` synct Backend-Binary, Frontend-Build, +systemd-Units und `/etc/cron.d/archivmail` (Wrapper-Skripte inklusive). + +**Smoke-Test nach Deploy (kein echter Backup/Restore-Lauf gegen +Produktivdaten):** + +| Prüfung | Ergebnis | +|---|---| +| `archivmail help` zeigt `backup`/`restore` | PASS — beide Einträge korrekt gelistet, kein Syntax-/Parse-Fehler wie in QA-Runde 1 | +| `archivmail backup` ohne `-dest` | PASS — sauberer Fehler `backup: -dest is required`, Exit 1, kein Crash | +| Backend-Health (`systemctl is-active archivmail`, `GET /api/health`) | PASS — `active`, HTTP 200 | +| Frontend-Health (`systemctl is-active archivmail-web`, `GET :3000/`) | PASS — `active`, HTTP 200 | +| `/etc/cron.d/archivmail` — PROJ-66-Backup-Zeile | PASS — Zeile bleibt auskommentiert (`# 0 3 * * * root ... archivmail backup ...`), keine aktive Backup-Cron auf 131, wie gefordert | + +Kein `archivmail backup`/`restore` gegen echte Store-/DB-Daten auf 131 +ausgeführt — nur Hilfe-/Fehlerausgabe geprüft, da noch kein `-dest`-Ziel +(getrenntes Mount) und kein Freigabeprozess für einen echten Lauf feststehen. + +**Offen / Folgeschritte (nicht Teil dieses Deploys):** +- AC 1: Cron-Zeile aktivieren, sobald `-dest`-Ziel (separates Mount) feststeht +- AC 3: physische Trennung des Backup-Ziels +- AC 4: Verschlüsselung des Backup-Archivs selbst +- AC 6: Restore-Verifikation via `reconcile` gegen realen Bestand +- AC 7: Alerting bei fehlgeschlagenem Backup-Lauf +- AC 8: Runbook für Restore-Prozess +- AC 10: Keyfile-Escrow in Bitwarden +- AC 11: Restore-Test gegen echten PBS-Snapshot