feat(PROJ-66): CLI-Kommandos archivmail backup/restore

Sichert Store (Hardlinks erhalten, PROJ-65-tauglich ohne Abhängigkeit von
rsync -H), Keyfile, config.yml und PostgreSQL (pg_dump -Fc) konsistent in
ein Zielverzeichnis. Rotation läuft nur nach erfolgreichem Lauf, ein
fehlgeschlagener Backup rotiert nie ein gutes altes Backup weg.

restore ist bewusst konservativ: bricht bei nicht-leerem Store/Keyfile ohne
-force ab, stoppt/startet den Dienst nicht selbst, gibt am Ende die
Pflicht-Verifikationsschritte (reconcile, reindex, Stichprobe) aus.

Ergänzt die vorhandene PBS+Sync-Infrastruktur um einen App-eigenen,
selektiven Restore-Weg. Kein automatischer Cron-Eintrag aktiv (Zielpfad
noch offen), nur als Vorlage in deploy/cron.d/archivmail auskommentiert.
Kein lokaler go build möglich, QA folgt auf Testserver.
This commit is contained in:
sysops
2026-07-04 14:09:37 +02:00
parent ebab716006
commit f3a7dea3cc
7 changed files with 428 additions and 4 deletions
+1 -1
View File
@@ -81,7 +81,7 @@
| PROJ-63 | Defensive Tenant-Scope-Härtung der Tenant-Verwaltungs-Endpunkte (FUND-2) | Deployed | [PROJ-63](PROJ-63-harden-tenant-admin-scope.md) | 2026-06-25 |
| PROJ-64 | Session-Invalidation bei Passwort-Change + Datei-Permissions-Härtung (Security-Audit) | Deployed | [PROJ-64](PROJ-64-session-invalidation-file-permissions.md) | 2026-07-03 |
| PROJ-65 | Physische Tenant-Trennung im Storage-Layer | Deployed | [PROJ-65](PROJ-65-physische-tenant-trennung.md) | 2026-07-04 |
| PROJ-66 | Backup-Strategie für Store, Keyfile, PostgreSQL (Produktiv + Teilproduktiv) | Planned | [PROJ-66](PROJ-66-backup-strategie.md) | 2026-07-04 |
| PROJ-66 | Backup-Strategie für Store, Keyfile, PostgreSQL (Produktiv + Teilproduktiv) | In Review | [PROJ-66](PROJ-66-backup-strategie.md) | 2026-07-04 |
<!-- Add features above this line -->
+57 -3
View File
@@ -1,6 +1,6 @@
# PROJ-66: Backup-Strategie für archivmail (Produktiv + Teilproduktiv)
**Status:** Planned
**Status:** In Review
**Erstellt:** 2026-07-04
## Problem / Ausgangslage
@@ -451,9 +451,63 @@ Test-Container für den Restore-Test zur Verfügung (nicht 131/132 selbst)?
## Nicht Teil dieses Tickets
- Implementierung selbst (dieses Ticket liefert nur die Spec, Status
"Planned" — Implementierung erst nach Freigabe).
- Konfiguration/Änderung der PBS-Jobs selbst (liegt außerhalb des
archivmail-Deploy-Scopes, eigener Verantwortungsbereich).
- Proxmox-Host-seitige ZFS-Snapshot-Konfiguration (separates Thema, anderer
Verantwortungsbereich/Zugriff).
## Implementation Notes (2026-07-04) — App-eigenes `archivmail backup`/`restore`
Nutzer-Entscheidung: App-eigene Backup-CLI zusätzlich zur PBS-Sicherung
bauen (Phase 2 vorgezogen), statt nur auf Infra-Ebene zu verlassen — gibt
einen von PBS unabhängigen, selektiven Restore-Weg (einzelne Tabellen via
`pg_restore`, ohne ganzen Container zurückspielen zu müssen).
### Neue Dateien
- `cmd/archivmail/cmd_backup.go`: `archivmail backup -dest <dir> [-config ...] [-keep N]`.
Schreibt in `<dest>/<timestamp>/`: `postgres.dump` (`pg_dump -Fc`, shell-out),
`store/` (Hardlink-erhaltende Kopie, siehe unten), `keyfile`, `config.yml`,
`audit.log` (best-effort). Rotation (`-keep`, Default 7) läuft NUR nach
erfolgreichem Durchlauf — ein fehlgeschlagener Lauf lässt den partiellen
Ordner stehen und rotiert nichts weg (Lehre aus PROJ-58: ein Job darf beim
Scheitern nie gute alte Stände zerstören).
- `cmd/archivmail/cmd_restore.go`: `archivmail restore -source <dir> [-force] [-skip-db]`.
Bewusst konservativ: bricht ab, wenn `store_path`/Keyfile bereits Inhalt
haben, außer `-force` ist gesetzt — ein versehentlicher Restore gegen ein
laufendes System soll nicht kommentarlos Produktivdaten überschreiben.
Stoppt/startet den Dienst NICHT selbst (Restore ist für einen frischen oder
bewusst leergeräumten Zielserver gedacht, kein Live-Overlay). Gibt am Ende
die Pflicht-Verifikationsschritte aus dem Runbook aus (`reconcile`,
`reindex`, Stichproben-Entschlüsselung).
### Hardlink-Erhalt ohne externe Tools
`copyTreePreservingHardlinks()` (in `cmd_backup.go`, von `cmd_restore.go`
mitgenutzt) läuft den Store-Baum ab, merkt sich pro Datei die Inode-Nummer
(`syscall.Stat_t.Ino`, Linux) und legt beim zweiten Auftreten derselben Inode
einen Hardlink statt einer Kopie an. Das macht die Backup-CLI unabhängig von
`rsync -H` (kein zusätzliches Tool-Dependency) und funktioniert identisch für
Backup wie Restore — PROJ-65s Tenant-Hardlink-Struktur bleibt dadurch sowohl
im Backup-Ziel als auch nach einem Restore verlustfrei erhalten (kein
Speicherplatz-Mehrverbrauch).
### Bewusst nicht gebaut
- Kein automatischer Cron-Eintrag aktiv — `deploy/cron.d/archivmail` enthält
ihn nur auskommentiert als Vorlage, da `-dest` ein konkretes, vom Host
getrenntes Ziel braucht, das noch nicht feststeht (siehe offene Entscheidung
oben).
- Kein automatisches Stop/Start der Dienste im Restore-Kommando (siehe oben).
- Keine Backup-Verschlüsselung im Tool selbst (Abschnitt "Verschlüsselung des
Backups selbst" bleibt gültig — Transport/Ziel-Absicherung ist
Infrastruktur-Aufgabe, nicht Teil dieses CLI-Kommandos).
- Kein Alerting bei Backup-Alter/-Ausfall im Code selbst (AC 7 bleibt offen,
bräuchte einen zweiten Cron-Job/Health-Check, der die letzte
Backup-Verzeichnis-Zeit prüft — noch nicht gebaut).
### Offen / Handoff
- Kein lokaler `go build` möglich — QA auf Testserver 132 nötig, insbesondere:
Backup+Restore-Roundtrip (Backup ziehen, auf leeren Store restoren,
`reconcile`+Stichprobe), Hardlink-Erhalt verifizieren (Inode-Vergleich wie
bei PROJ-65-QA), Verhalten bei vollem `-dest`-Ziel, `-force`-Schutz wirklich
blockierend bei nicht-leerem Store.
- `-dest`-Ziel für einen produktiven Cron-Eintrag muss noch vom Nutzer
festgelegt werden, bevor die auskommentierte Cron-Zeile aktiviert wird.