docs: restore drill passed; procedure moves to the notfallhandbuch repo

The databases are no longer an assumption: notfall.sh stage 3 restored all
three Borg repos into a throwaway postgres inside the pod (synapse 31908 rows,
MAS 16085, authentik 325149, wiki 251), isolated from production and repeatable.

Procedure and tool now live in git.lab/axion1337.chat/notfallhandbuch so an
emergency needs one clone; this repo keeps a pointer. Still open: phase A/B on
an empty host, Synapse media, a repeat cadence, and mirroring that new repo off
git.lab.

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 30e1962280
commit b3fa132f50
2 changed files with 56 additions and 144 deletions
@@ -139,3 +139,44 @@ Clone lesbar — der age-Key aus dem Vault ist damit tatsächlich der einzige ha
**Offen: Schritt 3** (einmal gegen eine Wegwerf-Umgebung durchspielen) und **Schritt 4** **Offen: Schritt 3** (einmal gegen eine Wegwerf-Umgebung durchspielen) und **Schritt 4**
(Wiederholungsrhythmus). Das Verfahren ist bis dahin abgeleitet, aber unerprobt. (Wiederholungsrhythmus). Das Verfahren ist bis dahin abgeleitet, aber unerprobt.
## Schritt 3 (teilweise) erledigt 2026-08-14 — Restore-Probe bestanden
Eigenes Repo **`git.lab/axion1337.chat/notfallhandbuch`** angelegt (Wunsch sorb): Einstiegs-
README, das Verfahren (`restore-matrix.md`) und ein menügeführtes Werkzeug (`notfall.sh`).
Das Verfahren wurde aus `docs/wiki/deployment/` dorthin verschoben — hier steht nur noch ein
Zeiger. Begründung: im Ernstfall will man **einen** Clone, nicht drei Repos absuchen.
**Der Beweis, den dieses Issue verlangt, ist für die Datenbanken erbracht.** `notfall.sh`
Stufe 3 spielt die Sicherungen in eine **Wegwerf-Postgres im Pod** zurück — isoliert, die
Produktion bleibt unberührt, beliebig wiederholbar:
| Repo | Datenbank | zurückgespielte Zeilen |
|---|---|---|
| synapse-backup | `synapse` | **31.908** |
| synapse-backup | `matrixauthenticationservice` | **16.085** |
| authentik-backup | `authentik` | **325.149** |
| wikijs-backup | `wiki` | **251** |
Die Sicherungen sind damit **keine Vermutung mehr** — sie wurden tatsächlich zurückgespielt.
**Entwurfsentscheidungen des Werkzeugs (bewusst, nicht beiläufig):**
- Arbeit läuft **im Cluster**, nicht auf dem Laptop: `axion-backup:v2` bringt borg + pg-Tools
mit, die Backup-Secrets verlassen den Cluster nicht, lokal genügt `kubectl`.
- `whiptail` wird genutzt **falls vorhanden**, sonst Textmenü — ein Notfallwerkzeug darf nicht
mit „installier erst ein Paket" beginnen (auf dem Mac fehlt whiptail).
- **Bestehenskriterium ist die Zeilenzahl, nicht der Exitcode.** Die erste Fassung meldete bei
fehlgeschlagenem `pg_restore` fälschlich „OK" (Pipe verschluckte den Code) — genau der
stille Ausfall, den dieses Issue beschreibt, nur im Prüfwerkzeug selbst. Behoben und
gegengetestet.
**Weiterhin offen (Rest von Schritt 3 + Schritt 4):**
- **Phase A/B nie durchgespielt:** Wiederanlauf auf leerem Host (K3s, die zwei Bootstrap-
Secrets, Flux) ist abgeleitet, nicht getestet — das braucht eine Wegwerf-Umgebung.
- **Synapse-Medien** (`media_store` → PVC) nicht erprobt; ohne sie sind Bilder in Räumen tote Links.
- **Wiederholungsrhythmus** festlegen (Stufe 3 ist gefahrlos → z.B. monatlich).
- Homelab-Seite (GitLab/Overmind → MinIO/DSM) weiterhin ungeprüft.
⚠️ **Empfehlung Spiegelung:** Das Notfallhandbuch liegt bisher nur auf git.lab — also im
Homelab, das im Ernstfall selbst nicht erreichbar sein kann. Ein Push-Mirror nach Gitea
(rohana) wie bei den übrigen Repos wäre hier besonders sinnvoll.
+15 -144
View File
@@ -5,154 +5,25 @@ sources:
- "docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md" - "docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md"
related: related:
- "docs/wiki/deployment/deploy-uebergabe.md" - "docs/wiki/deployment/deploy-uebergabe.md"
- "docs/adr/0001-gitlab-kanonisch-push-mirror.md"
--- ---
# Wiederherstellung: Matrix-Plattform (Hetzner/K3s) # Wiederherstellung — liegt im Notfallhandbuch
Ablauf für den Ernstfall — Totalverlust des Hetzner-Hosts. Kein Fließtext, sondern Reihenfolge: Das Verfahren und das Werkzeug leben in einem **eigenen Repo**:
**was zuerst, woher kommt was, wie prüft man es.**
> **Warum dieses Dokument in Git liegt und nicht im Wiki:** Wiki.js läuft *auf* dem Cluster. **`git.lab/axion1337.chat/notfallhandbuch`**
> Ist der Cluster weg, ist das Wiki weg. Dieses Verfahren muss aus einem Git-Clone (oder von
> git.lab/Gitea) lesbar sein, ohne dass irgendetwas von der Plattform läuft.
**Stand:** 2026-08-14, geschrieben aus dem laufenden System heraus (Cluster-Inspektion). | Datei | Inhalt |
⚠️ **Noch nicht durchgespielt** — siehe „Offene Punkte" am Ende. |---|---|
| `README.md` | Einstieg: erst Lage klären, dann erst Restore |
| `restore-matrix.md` | Wiederherstellung der Matrix-Plattform (Hetzner/K3s), Phase AD |
| `notfall.sh` | Menügeführtes Werkzeug: Lage prüfen · Backups prüfen · **Restore-Probe** · Ernstfall |
## 0. Was du in der Hand haben musst, bevor du anfängst **Warum ein eigenes Repo und nicht hier:** Im Ernstfall will man **einen** Clone, der alles
enthält, und kein Dokument, das tief in einem Management-Repo (oder gar im Wiki, das auf dem
betroffenen Cluster läuft) liegt. Das Notfallhandbuch ist bewusst klein und eigenständig.
| Was | Woher | Ohne das geht | **Erprobter Stand (2026-08-14):** Die Rückspielung der Datenbanken ist mit `notfall.sh`
|---|---|---| Stufe 3 nachgewiesen — alle drei Borg-Repos ließen sich in eine Wegwerf-Postgres
| **age-Schlüssel** (`age.agekey`) | Passwort-Vault (sorb). Zweitkopien: `~/.age/keys.txt` auf sorbs Mac, `sops-age`-Secret im alten Cluster | **gar nichts** — alle Secrets sind damit verschlüsselt | zurückspielen (synapse 31.908 Zeilen, MAS 16.085, authentik 325.149, wiki 251). Der
| **gitops-Repo** | `git.lab/axion1337.chat/axion1337.chat-gitops` (kanonisch), Spiegel auf Gitea/rohana | kein Cluster-Zustand | vollständige Wiederanlauf auf leerem Host steht weiterhin aus (#0030).
| **Zugang Hetzner** | Hetzner Cloud Console (Neuinstallation) | kein Host |
| **Gitea-Zugangsdaten** für Flux | im Repo SOPS-verschlüsselt bzw. Gitea-Token neu erzeugen | Flux kann nicht syncen |
| **Storage-Box-Zugang** | SSH-Key + Borg-Passphrase, beide in `apps/production/synapse-backup-secret.yaml` (SOPS) | keine Daten |
**Wichtig:** Borg-Passphrase und Storage-Box-SSH-Key holst du **ohne Cluster** aus einem lokalen
Clone — genau dafür ist der age-Schlüssel da:
```bash
export SOPS_AGE_KEY_FILE=~/.age/keys.txt # aus dem Vault wiederhergestellt
sops -d apps/production/synapse-backup-secret.yaml # Borg-Passphrase + SSH-Key
sops -d apps/authentik/authentik-backup-secret.yaml
```
## 1. Wo die Daten liegen
Hetzner **Storage Box** `u641795@u641795.your-storagebox.de`, **Port 23**, drei getrennte
Borg-Repos. Verschlüsselung `repokey-blake2`**die Passphrase allein genügt**, es gibt keine
separate Schlüsseldatei zu retten.
| Repo | Inhalt | Quelle im Betrieb |
|---|---|---|
| `/./synapse-backup` | DBs `synapse` + `matrixauthenticationservice`, **plus** Medien (`media_store`) | `matrix-stack-postgres` |
| `/./authentik-backup` | DB `authentik` | `authentik-postgresql` |
| `/./wikijs-backup` | DB `wiki` | `wikijs-postgres` |
Aufbewahrung: 7 täglich / 4 wöchentlich / 6 monatlich (`borg prune`, nachweislich aktiv).
Archivname: `<hostname>-<zeitstempel>`. Layout im Archiv:
`scratch/dumps/<db>.dump` (Format `pg_dump -Fc``pg_restore`) und `media/media_store/…`.
Zugriff von einer beliebigen Maschine mit Borg:
```bash
export BORG_REPO='ssh://u641795@u641795.your-storagebox.de:23/./synapse-backup'
export BORG_PASSPHRASE='…' # aus sops -d, s.o.
export BORG_RSH="ssh -i <storagebox-key> -p 23"
borg list "$BORG_REPO" # Archive auflisten
borg extract "$BORG_REPO::<archivname>" # ins aktuelle Verzeichnis
```
## 2. Reihenfolge der Wiederherstellung
### Phase A — Fundament
1. **Host + K3s** neu aufsetzen (siehe `gitops:docs/install.md`, Schritt 1). Kubeconfig sichern.
2. **Zwei Secrets von Hand anlegen** — ohne die kann Flux nichts tun (Henne-Ei, bewusst manuell):
```bash
kubectl create namespace flux-system
# a) age-Schlüssel (aus dem Vault) — entschlüsselt alle SOPS-Secrets
kubectl create secret generic sops-age -n flux-system --from-file=age.agekey=<pfad>
# b) Git-Zugang für Flux (Keys heißen: username / password)
kubectl create secret generic flux-system -n flux-system \
--from-literal=username=<user> --from-literal=password=<token>
```
3. **Flux installieren** und auf das Repo zeigen lassen (`clusters/matrix`).
> ⚠️ **Flux zieht aus Gitea (`rohana.axion1337.de`), nicht aus git.lab** — bewusst so, damit der
> Cluster ohne das Homelab baubar ist (siehe `gitops:CLAUDE.md`). **Ist rohana ebenfalls weg**,
> dann zuerst eine erreichbare Git-Quelle herstellen (Repo aus git.lab oder lokalem Clone auf ein
> neues Remote pushen) und `clusters/matrix/flux-system/gotk-sync.yaml` (`url:`) darauf zeigen
> lassen. Sonst hängt der Wiederanlauf an einem Host, der gar nicht Teil des Backups ist.
### Phase B — Dienste hochziehen (noch leer)
Flux arbeitet die Kustomizations in dieser Abhängigkeit ab:
```
flux-system
└── infra-apps (Namespaces, Cert-Manager, HelmRepositories) — keine Secrets nötig
├── production-apps (ESS/Synapse/MAS/Wiki.js, entschlüsselt via sops-age)
├── authentik-apps (entschlüsselt via sops-age)
└── monitoring-apps
```
Warten, bis die **Postgres-Instanzen** laufen (`matrix-stack-postgres`, `authentik-postgresql`,
`wikijs-postgres`). Die Anwendungen kommen dabei mit **leeren** Datenbanken hoch — das ist
erwartet.
### Phase C — Daten zurückspielen
> ⚠️ **Konsumenten vorher herunterfahren.** Synapse/MAS/Authentik/Wiki.js legen beim Start ein
> leeres Schema an; ein `pg_restore` dagegen kollidiert. Also erst herunterskalieren, dann
> zurückspielen, dann wieder hochfahren.
```bash
kubectl scale -n matrix deploy --replicas=0 -l app.kubernetes.io/name=synapse
kubectl scale -n matrix deploy --replicas=0 matrix-stack-matrix-authentication-service wikijs
kubectl scale -n authentik deploy --replicas=0 authentik-server authentik-worker
```
Pro Datenbank: Archiv extrahieren, Dump einspielen (`pg_restore` in die jeweils **leere** DB;
bei Bedarf `--clean --if-exists`), danach Konsumenten wieder hochskalieren.
Reihenfolge sinnvoll: **Authentik zuerst** (Anmeldung/OIDC hängt daran), dann Synapse+MAS,
dann Wiki.js.
**Synapse-Medien** nicht vergessen: `media/media_store/…` aus dem Archiv zurück in das
PVC `matrix-stack-synapse-media`. Ohne die Medien ist die Datenbank zwar konsistent, aber alle
Bilder/Dateien in Räumen sind tote Links.
### Phase D — Verifikation
- `flux get kustomizations -A` → alles `Ready=True`.
- Anmeldung über Authentik funktioniert (OIDC-Kette steht).
- Ein bestehender Raum zeigt **Verlauf und Medien**.
- Wiki erreichbar; Inhalte kommen ohnehin zusätzlich aus git-storage (Gitea/git.lab).
- Zertifikate: Traefik/Cert-Manager stellen neu aus (DNS-01 ist umgestellt, kein offener
Port 443 nötig — siehe #0007).
- Die drei Backup-CronJobs laufen wieder (`kubectl get cronjob -A`).
## 3. Was am Backup **nicht** hängt
- **Wiki-Inhalte**: liegen zusätzlich als Git-Repo (Wiki.js → Gitea → git.lab kanonisiert).
- **Gesamte Konfiguration**: im gitops-Repo, nicht im Backup (deshalb ist der Cluster
reproduzierbar und nicht nur wiederherstellbar).
- **Gitea/rohana selbst**: Push-Mirror, wird bewusst nicht gesichert (#0010) — Repos aus git.lab
neu befüllen. **Aber:** die Container-Registry dort ist kein Spiegel; die vier eigenen Images
(u.a. `threadnet-web`, `axion-backup`) müssen ggf. neu gebaut werden.
## 4. Offene Punkte
- **Nie durchgespielt.** Dieses Verfahren ist aus dem laufenden System abgeleitet, nicht erprobt
(#0030, Schritt 3). Ein Durchlauf gegen eine Wegwerf-Umgebung steht aus — erst danach ist es
belastbar.
- **Homelab-Seite ungeprüft**: GitLab auf Overmind → MinIO/DSM ist nicht Teil dieser Prüfung.
- Nach dem ersten echten Durchlauf: Zeitbedarf und Stolpersteine hier ergänzen und einen
Wiederholungsrhythmus festlegen.