managarten/docs/BACKUP_STRATEGY.md
Till JS 670036d56d
Some checks are pending
CD Mac Mini / Detect Changes (push) Waiting to run
CD Mac Mini / Deploy (push) Blocked by required conditions
CI / Detect Changes (push) Waiting to run
CI / Validate (push) Waiting to run
CI / Build mana-search (push) Blocked by required conditions
CI / Build mana-sync (push) Blocked by required conditions
CI / Build mana-api-gateway (push) Blocked by required conditions
CI / Build mana-crawler (push) Blocked by required conditions
Mirror to Forgejo / Push to Forgejo (push) Waiting to run
docs: BACKUP_STRATEGY.md — current local-only setup + off-site plan
Dokumentiert:
- Was läuft (com.mana.backup-databases LaunchD, täglich 3 AM, ~45 GB)
- Was gebackupt wird (6 postgres-Container, 12 DBs)
- Was NICHT gebackupt wird (MinIO, Volumes-as-files, cloudflared-creds, ~/secrets/)
- Off-Site-Plan (R2/Hetzner/restic-Optionen, rclone-Skizze)
- Recovery-Drill (Quartals-Test mit pg_restore gegen Throwaway-Container)
- Pre-Live-Gate-Checkliste für Endurance-User-Daten

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:55:47 +02:00

129 lines
4.7 KiB
Markdown

# BACKUP_STRATEGY — Postgres + Volumes
**Stand 2026-05-13.** Wiederhergestellt und erweitert, nachdem der
LaunchD-Job 3 Monate stillgestanden hat (alter Pfad zeigte auf
`mana-monorepo/`, das nicht mehr existiert).
## Heute: lokales Backup, kein Off-Site
### Was läuft
- **`com.mana.backup-databases`** LaunchD-Job auf mana-server
- **Skript:** `~/projects/managarten/scripts/mac-mini/backup-databases.sh`
- **Schedule:** täglich 03:00 (StartCalendarInterval)
- **Ziel:** `/Volumes/ManaData/backups/postgres/{daily,weekly}/`
- **Retention:** daily 7 Tage, weekly (Sonntag) 4 Wochen
### Was wird gebackupt
Alle Postgres-Container, die `*postgres*` matchen, ausgenommen
`*exporter*` und `mana-infra-postgres-backup`. Stand 2026-05-13:
| Container | User | DB(s) |
|---|---|---|
| `mana-infra-postgres` | `postgres` | mana_platform, mana_sync, mana_admin, memoro (956 MB), forgejo, glitchtip, umami |
| `cards-postgres` | `cards` | cards |
| `manaspur-postgres` | `manaspur` | manaspur |
| `nutriphi-postgres` | `nutriphi` | nutriphi |
| `zitare-postgres` | `zitare` | zitare |
| `chorportal-prod-postgres` | `chorportal` | chorportal |
Dump-Pattern: `${container}_${db}_${date}.sql.gz`.
Total nach erstem Run 2026-05-13: **~45 GB** in `/Volumes/ManaData/backups/postgres`.
### Was NICHT gebackupt wird (heute)
- **MinIO-Objekte** (Cards-Media, mana-media, …) — getrennte Volumes
- **`/Volumes/ManaData/{cards,manaspur,…}/postgres`** auf File-Level —
pg_dump reicht für DBs, aber Disk-Bitrot würde damit nicht erfasst
- **Cloudflared-Tunnel-Credentials** unter `~/.cloudflared/` (kritisch!)
- **`~/secrets/`** (App-Service-Keys, Master-Keys-Klartexte)
## Off-Site — heute noch nicht aktiv
**Problem:** Mac Mini ist Single-Point-of-Failure. Defekte Disk,
Diebstahl, Brand → alle Backups weg, weil sie auf der **gleichen**
Disk liegen.
**Aufgaben für die Off-Site-Strategie:**
1. **Endpoint wählen:**
- **Cloudflare R2** (S3-kompatibel, Vereins-tauglich Preis, mana
hat schon Cloudflare-Account) ← Empfehlung
- **Hetzner Storage Box** (günstig, EU-Hoster, Verein-Datenschutz-
konform)
- **Wasabi / Backblaze B2** (günstig, US-Anbieter, DSGVO-fragwürdig)
- **eigener mana-server-2** (privater Off-Site-Mini, max Kontrolle)
2. **Tool wählen:**
- **rclone** (Multi-Provider, läuft auf macOS) ← Empfehlung
- **restic** (Encryption + Dedup eingebaut, S3-fähig)
- **borg** (klassisch, Repo-orientiert)
3. **Encryption:** Daten verlassen den Mac Mini — ohne Encryption-at-
transit-AND-at-rest verletzen wir die Vereins-Werte (Memoro/Cards/
Manaspur enthalten User-Inhalte). Empfehlung: **rclone crypt** als
Wrapper, Key in `~/secrets/` und Off-Site-Recovery-Code im
`secret_offsite_backup_key`-Memory.
### Vorgeschlagener Aufbau (zu implementieren)
```bash
# rclone-Config mit verschlüsseltem Remote:
rclone config create r2-raw s3 provider Cloudflare \
access_key_id <KEY> secret_access_key <SECRET> \
endpoint <ACCOUNT>.r2.cloudflarestorage.com
rclone config create r2-encrypted crypt remote r2-raw:mana-backups \
password <RANDOM> password2 <RANDOM2>
```
LaunchD-Job nach `backup-databases.sh`:
```bash
# scripts/mac-mini/backup-sync-offsite.sh
rclone sync /Volumes/ManaData/backups/postgres r2-encrypted:postgres \
--transfers 4 --checkers 8 --log-file /tmp/mana-backup-offsite.log
```
Cron alle 6h, separate plist `com.mana.backup-offsite.plist`.
### Pre-Live-Gate für mana-Plattform
Bevor manaspur-Endurance-User-Daten landen (siehe `manaspur-native/docs/ENDURANCE_TEST.md`):
- [x] Local-Backup-Job wieder aktiv (2026-05-13)
- [ ] Off-Site-Endpoint provisioniert (R2-Bucket o. ä.)
- [ ] rclone + Encryption-Setup
- [ ] LaunchD-Job für Off-Site-Sync alle 6h
- [ ] Recovery-Probe: zufällige Daily-Backup-Datei herunterladen +
entschlüsseln + pg_restore-Trockenlauf gegen Test-DB
## Recovery-Drill (Test, dass Backups wiederherstellbar sind)
Pro Quartal:
```bash
# Beispiel: cards-postgres aus Backup wiederherstellen
ssh mana-server
docker run --rm -d --name cards-postgres-restore-test \
-e POSTGRES_PASSWORD=test -e POSTGRES_USER=cards -e POSTGRES_DB=cards \
postgres:16-alpine
gunzip -c /Volumes/ManaData/backups/postgres/daily/cards-postgres_cards_2026-05-13.sql.gz \
| docker exec -i cards-postgres-restore-test psql -U cards -d cards
# Probe-Query
docker exec cards-postgres-restore-test psql -U cards -d cards \
-c "SELECT count(*) FROM cards.decks;"
docker stop cards-postgres-restore-test
```
## Cross-Refs
- `secret_offsite_backup_key.md` (Memory, kommt mit Off-Site-Setup)
- `mana/docs/PLAN.md` § Backup-Strategy
- `scripts/mac-mini/backup-databases.sh` — der eigentliche Code
- `~/Library/LaunchAgents/com.mana.backup-databases.plist` — LaunchD-Job