docs(issues): #0030 inventory done, #0010 scope reduced — age key is the real risk

Cluster-side backups are healthier than assumed: three nightly Borg jobs to a
Hetzner Storage Box, all completing with plausible volumes and working prune
(synapse 199MB/247 files, authentik ~150MB, wikijs 223kB DB-only). No silent
failures.

Critical finding for #0030: the Borg passphrase and SSH key needed to READ those
backups are SOPS-encrypted under a single age key that exists only in the cluster
being backed up and on one laptop — no documented cold copy. Losing both makes all
three repos permanently unreadable. Cold escrow must precede any restore drill.

For #0010 this shrinks the work: the Storage Box + Borg pattern already exists and
is proven, so Gitea needs only its own repo there. The disabled cron (no backups
since 2026-07-30) remains separately urgent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thore Cimbal
2026-08-14 12:00:00 +00:00
co-authored by Claude Opus 4.8
parent c0a428f43d
commit 74f4fa91f0
3 changed files with 84 additions and 4 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ Verteilung: M1 8 · M2 19 · M4 2 · M5 4
| [0004](docs/issues/0004-overmind-02-e1000e-nic-hang-beobachtung-nach.md) | waiting | M1 | low | OVERMIND-02: e1000e-NIC-Hang — Beobachtung nach EEE-Fix + Firmware-Update | | [0004](docs/issues/0004-overmind-02-e1000e-nic-hang-beobachtung-nach.md) | waiting | M1 | low | OVERMIND-02: e1000e-NIC-Hang — Beobachtung nach EEE-Fix + Firmware-Update |
| [0008](docs/issues/0008-cfgmon-03-prometheus-remote-write-und-loki.md) | waiting | M1 | medium | CFGMON-03: Prometheus-Remote-Write und Loki öffentlich ohne Auth — Weg A, nachgelagerte Prüfung | | [0008](docs/issues/0008-cfgmon-03-prometheus-remote-write-und-loki.md) | waiting | M1 | medium | CFGMON-03: Prometheus-Remote-Write und Loki öffentlich ohne Auth — Weg A, nachgelagerte Prüfung |
| [0009](docs/issues/0009-cfgmon-04-grafana-admin-credentials-aus-env.md) | open | M2 | low | CFGMON-04: Grafana-Admin-Credentials aus .env gelten nicht für die HTTP-API | | [0009](docs/issues/0009-cfgmon-04-grafana-admin-credentials-aus-env.md) | open | M2 | low | CFGMON-04: Grafana-Admin-Credentials aus .env gelten nicht für die HTTP-API |
| [0010](docs/issues/0010-cfgmon-09-gitea-backups-off-host-borg-storage.md) | open | M1 | medium | CFGMON-09: Gitea-Backups off-host (Borg/Storage Box) — Backup-Cron ist DEAKTIVIERT | | [0010](docs/issues/0010-cfgmon-09-gitea-backups-off-host-borg-storage.md) | in-progress | M1 | medium | CFGMON-09: Gitea-Backups off-host (Borg/Storage Box) — Backup-Cron ist DEAKTIVIERT |
| [0014](docs/issues/0014-cfgmon-14-root-zugang-ueber-die-docker-gruppe.md) | waiting | M2 | low | CFGMON-14: Root-Zugang über die docker-Gruppe umgeht sudo und hinterlässt keine Spur | | [0014](docs/issues/0014-cfgmon-14-root-zugang-ueber-die-docker-gruppe.md) | waiting | M2 | low | CFGMON-14: Root-Zugang über die docker-Gruppe umgeht sudo und hinterlässt keine Spur |
| [0015](docs/issues/0015-cfgmon-15-token-hygiene-einmal-tokens-der.md) | next | M2 | medium | CFGMON-15: Token-Hygiene — Einmal-Tokens der LABNET-02-Nacht widerrufen | | [0015](docs/issues/0015-cfgmon-15-token-hygiene-einmal-tokens-der.md) | next | M2 | medium | CFGMON-15: Token-Hygiene — Einmal-Tokens der LABNET-02-Nacht widerrufen |
| [0018](docs/issues/0018-doc-01-wiki-rollout-abschliessen-ci-freigaben.md) | open | M2 | low | DOC-01: Wiki-Rollout abschließen — CI-Freigaben, Zeitplan, Dokploy-Stack, wiki.lab | | [0018](docs/issues/0018-doc-01-wiki-rollout-abschliessen-ci-freigaben.md) | open | M2 | low | DOC-01: Wiki-Rollout abschließen — CI-Freigaben, Zeitplan, Dokploy-Stack, wiki.lab |
@@ -23,7 +23,7 @@ Verteilung: M1 8 · M2 19 · M4 2 · M5 4
| [0027](docs/issues/0027-audit-01-acht-widersprueche-aus-dem-labnet-02.md) | waiting | M2 | medium | AUDIT-01: Acht Widersprüche aus dem LABNET-02-Nachlauf (Selbst-Audit CFGMON-Session) | | [0027](docs/issues/0027-audit-01-acht-widersprueche-aus-dem-labnet-02.md) | waiting | M2 | medium | AUDIT-01: Acht Widersprüche aus dem LABNET-02-Nachlauf (Selbst-Audit CFGMON-Session) |
| [0028](docs/issues/0028-mirror-01-ein-ausfall-der-push-mirrors-bleibt.md) | open | M2 | low | MIRROR-01: Ein Ausfall der Push-Mirrors bleibt unbemerkt — Produktion friert still ein | | [0028](docs/issues/0028-mirror-01-ein-ausfall-der-push-mirrors-bleibt.md) | open | M2 | low | MIRROR-01: Ein Ausfall der Push-Mirrors bleibt unbemerkt — Produktion friert still ein |
| [0029](docs/issues/0029-ui-harmonisieren-gleiche-farben-und-formen.md) | open | M4 | medium | UI harmonisieren: gleiche Farben und Formen über alle Oberflächen | | [0029](docs/issues/0029-ui-harmonisieren-gleiche-farben-und-formen.md) | open | M4 | medium | UI harmonisieren: gleiche Farben und Formen über alle Oberflächen |
| [0030](docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md) | open | M1 | medium | Der Restore ist nie geprobt — Sicherungen sind bisher eine Vermutung | | [0030](docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md) | in-progress | M1 | medium | Der Restore ist nie geprobt — Sicherungen sind bisher eine Vermutung |
| [0031](docs/issues/0031-stillstandspruefung-gitea-token-und-authentik.md) | open | M1 | low | Stillstandsprüfung: GITEA_TOKEN und Authentik-Teil nachziehen | | [0031](docs/issues/0031-stillstandspruefung-gitea-token-und-authentik.md) | open | M1 | low | Stillstandsprüfung: GITEA_TOKEN und Authentik-Teil nachziehen |
| [0032](docs/issues/0032-gameserver-hat-keinen-push-mirror-und-auf-gitea.md) | open | M2 | medium | gameserver hat keinen Push-Mirror — und auf Gitea liegt ein anderer Stand | | [0032](docs/issues/0032-gameserver-hat-keinen-push-mirror-und-auf-gitea.md) | open | M2 | medium | gameserver hat keinen Push-Mirror — und auf Gitea liegt ein anderer Stand |
| [0033](docs/issues/0033-overmind-01-element-desktop-build-lab-registry.md) | open | M2 | low | OVERMIND-01 — element-desktop-build von rohana in die Lab-Registry umziehen | | [0033](docs/issues/0033-overmind-01-element-desktop-build-lab-registry.md) | open | M2 | low | OVERMIND-01 — element-desktop-build von rohana in die Lab-Registry umziehen |
@@ -1,7 +1,7 @@
--- ---
type: issue type: issue
id: "0010" id: "0010"
status: open status: in-progress
created: 2026-08-01 created: 2026-08-01
milestone: M1 milestone: M1
priority: medium priority: medium
@@ -32,3 +32,33 @@ Quelle: [hosts/cfgmon.md](https://git.lab/axion1337.chat/management/-/blob/main/
--- ---
*Übernommen aus dem Backlogs-Markdown beim Framework-Umbau 2026-08-01 (voller Wortlaut: Git-Historie der Datei).* *Übernommen aus dem Backlogs-Markdown beim Framework-Umbau 2026-08-01 (voller Wortlaut: Git-Historie der Datei).*
## In Arbeit 2026-08-14 — Muster existiert bereits (Aufwand deutlich kleiner als geplant)
Bei der Bestandsaufnahme für #0030 zeigte sich: **Borg auf Hetzner Storage Box läuft im
Matrix-Cluster bereits produktiv** — der für dieses Issue geplante Aufbau muss also nicht
erfunden, sondern nur auf Gitea übertragen werden.
**Vorhandenes Muster (erprobt, nächtlich, verifiziert):**
- Storage Box `u641795@u641795.your-storagebox.de:23`, drei getrennte Repos
(`/./synapse-backup`, `/./authentik-backup`, `/./wikijs-backup`).
- Image `rohana.axion1337.de/sorb/axion-backup:v2`; Retention `borg prune` 7d/4w/6m.
- Credentials (Borg-Passphrase + SSH-Key) als SOPS-Secret, per Env in den Job.
**Konsequenz für Gitea:** kein neuer Storage-Box-Vertrag nötig — ein zusätzliches Repo
`/./gitea-backup` auf derselben Box (oder ein Sub-Account) genügt. Der Host braucht weiterhin
einen eigenen SSH-Key (CFGMON ist **nicht** der Cluster), dessen Public Key in der Storage Box
hinterlegt wird. Wie im Issue vorgesehen: Dump **unkomprimiert** an Borg geben (`gzip` aus
`backup/gitea-backup.sh` entfernen), sonst greift die Deduplizierung nicht.
⚠️ **Unverändert akut:** Der nächtliche Cron auf CFGMON ist seit 2026-07-30 auskommentiert —
seit über zwei Wochen läuft **kein** Gitea-Backup, der letzte Stand ist
`/opt/backup/gitea-dump-2026-07-30.tar.gz`. Das ist unabhängig von der Borg-Umstellung sofort
behebbar (Cron wieder einkommentieren) und sollte **nicht** auf das Off-host-Projekt warten.
⚠️ **Vorrang laut #0030:** Bevor Backups erweitert werden, muss die kalte Kopie des
age-Schlüssels stehen (dort dokumentierte Zirkelabhängigkeit) — sonst wächst nur die Menge
potenziell unlesbarer Sicherungen.
**Offen (nur mit Host-Zugang, kein SSH zu CFGMON von hier):** Cron reaktivieren; SSH-Key auf
CFGMON erzeugen + in der Storage Box hinterlegen; `gitea-backup.sh` auf Borg umstellen.
@@ -1,7 +1,7 @@
--- ---
type: issue type: issue
id: "0030" id: "0030"
status: open status: in-progress
created: 2026-08-06 created: 2026-08-06
milestone: M1 milestone: M1
priority: medium priority: medium
@@ -42,3 +42,53 @@ Der letzte Punkt ist hier besonders relevant: **Der SOPS-age-Schlüssel entschl
Ich kenne den Sicherungsaufbau nur aus deiner Beschreibung und einem Screenshot, nicht aus eigener Anschauung. Der erste Schritt ist deshalb Bestandsaufnahme, nicht Bewertung. Ich kenne den Sicherungsaufbau nur aus deiner Beschreibung und einem Screenshot, nicht aus eigener Anschauung. Der erste Schritt ist deshalb Bestandsaufnahme, nicht Bewertung.
*Aufgenommen am 2026-08-06 bei einer Bestandsaufnahme der Sicherheitslage.* *Aufgenommen am 2026-08-06 bei einer Bestandsaufnahme der Sicherheitslage.*
## Schritt 1 erledigt 2026-08-14 — Bestandsaufnahme (Cluster-Seite)
Wie im Issue gefordert zuerst „das Billigste": in die vorhandenen Sicherungen hineingeschaut,
nichts erweitert. Geprüft wurde die **Matrix-Cluster-Seite** (Hetzner/K3s); die Homelab-Seite
(GitLab auf Overmind → MinIO/DSM) ist von hier nicht erreichbar und weiterhin ungeprüft.
**Befund: die Sicherungen selbst sind besser als vermutet.** Drei nächtliche CronJobs, alle
`Complete`, keiner suspendiert, alle **off-host** per Borg auf eine Hetzner Storage Box
(`u641795@…your-storagebox.de:23`) — also genau das Muster, das #0010 für Gitea erst plant:
| Job | Zeit | Ziel-Repo | Volumen (letzter Lauf) | Bewertung |
|---|---|---|---|---|
| `synapse-backup` (matrix) | 03:00 | `/./synapse-backup` | 199,5 MB, 247 Dateien, Dedup-Delta 3,7 MB | plausibel ✅ |
| `authentik-backup` (authentik) | 03:15 | `/./authentik-backup` | ~150 MB gesamt, Delta ~15 MB | plausibel ✅ |
| `wikijs-backup` (matrix) | 03:30 | `/./wikijs-backup` | 223 kB | plausibel ✅ (nur Postgres; die Wiki-**Inhalte** liegen per git-storage in Gitea/git.lab) |
Retention greift nachweislich (`borg prune`, 7 daily / 4 weekly / 6 monthly, „Deleted data"
in den Logs). Kein stiller Ausfall, keine leeren Archive — die Sicherung ist **keine bloße
Vermutung mehr**, zumindest was Existenz und Inhalt angeht.
### ⚠️ Kritischer Befund: Zirkelabhängigkeit beim age-Schlüssel
Genau das vom Issue vorhergesagte Muster („der Dump ist da, aber der Schlüssel lag nur auf dem
Host, der weg ist") liegt real vor:
1. Zum **Lesen** der Borg-Repos braucht man Borg-Passphrase **und** SSH-Key.
2. Beide liegen in `synapse-backup-secret.yaml` / `authentik-backup-secret.yaml`**SOPS-verschlüsselt**.
3. Entschlüsselbar ist das nur mit **einem einzigen** age-Schlüssel
(Empfänger `age14l0hw…`, siehe `.sops.yaml`).
4. Dieser private Schlüssel existiert an **genau zwei Orten**, und beide sind „heiß":
- `sops-age`-Secret in `flux-system`**im Cluster, den das Backup schützen soll**
- `~/.age/keys.txt` auf sorbs Mac — **ein einzelnes Gerät**
**Folge:** Gehen Cluster und Mac zusammen verloren (Totalschaden Hetzner + Laptop weg/defekt),
sind **alle drei Borg-Repos dauerhaft unlesbar**. Die Sicherungen wären technisch einwandfrei
und trotzdem wertlos. Eine Suche über `docs/` und `hosts/` findet **keine** dokumentierte
Auslagerung (Escrow, Offline-Kopie, Passwort-Manager) des Schlüssels.
**Billigste wirksame Gegenmaßnahme (vor jedem Restore-Test):** eine **kalte Kopie** des
age-Schlüssels außerhalb von Cluster und Mac anlegen — Passwort-Manager und/oder Ausdruck an
sicherem Ort — und die Fundstelle in `hosts/` dokumentieren (nur *wo*, nie der Wert). Erst danach
lohnt der eigentliche Restore-Durchlauf, sonst probt man einen Ablauf, dessen Voraussetzung
selbst ungesichert ist.
### Nächste Schritte (unverändert nach Issue-Plan)
2. Restore-Verfahren schreiben (Reihenfolge, Herkunft von age-Key und kubeconfig).
3. Einmal gegen eine Wegwerf-Umgebung durchspielen.
4. Ergebnis nach `verfahren/` und Wiederholungsrhythmus festlegen.