From 30e1962280143965fd29670b64727c4539cdd2a9 Mon Sep 17 00:00:00 2001 From: Thore Cimbal Date: Fri, 14 Aug 2026 12:00:00 +0000 Subject: [PATCH] docs(wiki): add restore procedure for the Matrix platform (#0030 step 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Derived from the running system: prerequisites (age key from the vault first), the three Borg repos with their archive layout, bootstrap order (host/K3s, the two manual secrets, Flux, kustomization dependencies), data restore and verification. Lives in git rather than Wiki.js on purpose — the wiki runs on the cluster being restored. Two previously undocumented findings: Flux pulls from Gitea rather than git.lab, so a simultaneous loss of rohana requires repointing gotk-sync first; and consumers must be scaled down before pg_restore or their startup schema collides. Co-Authored-By: Claude Opus 4.8 --- ...estore-ist-nie-geprobt-sicherungen-sind.md | 27 +++ docs/wiki/deployment/restore.md | 158 ++++++++++++++++++ docs/wiki/index.md | 2 +- 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 docs/wiki/deployment/restore.md diff --git a/docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md b/docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md index b44e58b..bac4925 100644 --- a/docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md +++ b/docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md @@ -112,3 +112,30 @@ Schritt 1 (Bestandsaufnahme) ist erledigt und positiv ausgefallen; Schritt 2–4 Restore-Verfahren schreiben, einmal gegen eine Wegwerf-Umgebung durchspielen, Ergebnis nach `verfahren/` und Wiederholungsrhythmus. Die Homelab-Seite (GitLab auf Overmind → MinIO/DSM) ist weiterhin ungeprüft — von der Hetzner-Seite aus nicht erreichbar. + +## Schritt 2 erledigt 2026-08-14 — Restore-Verfahren geschrieben + +[`docs/wiki/deployment/restore.md`](../wiki/deployment/restore.md) — als Ablauf, nicht als Prosa: +Voraussetzungen (age-Key aus dem Vault zuerst), Fundort und Layout der drei Borg-Repos, +Bootstrap-Reihenfolge (Host/K3s → die zwei manuellen Secrets `sops-age` + `flux-system` → Flux → +`infra-apps` → production/authentik/monitoring), Daten-Rückspielung und Verifikation. + +**Abweichung vom Issue-Wortlaut:** abgelegt unter `docs/wiki/deployment/` statt `verfahren/` — +dort liegt bereits die Deploy-Übergabe, nur `docs/**` wird von `validate.py` geprüft, und die +Seite ist über den Wiki-Index auffindbar. `verfahren/` enthält bislang ausschließlich Skripte. + +**Zwei Erkenntnisse beim Schreiben, die vorher nicht dokumentiert waren:** + +1. **Flux zieht aus Gitea (rohana), nicht aus git.lab.** Ist rohana beim Ausfall ebenfalls weg, + hängt der Wiederanlauf an einem Host, der gar nicht Teil des Backup-Konzepts ist — dann muss + zuerst eine erreichbare Git-Quelle hergestellt und `gotk-sync.yaml` umgebogen werden. Im + Verfahren als Warnung vermerkt. +2. **Konsumenten müssen vor dem `pg_restore` heruntergefahren werden.** Synapse/MAS/Authentik/ + Wiki.js legen beim Start ein leeres Schema an, gegen das ein Restore kollidiert. + +Ebenfalls festgehalten: Borg nutzt `repokey-blake2`, die Passphrase allein genügt (keine separate +Schlüsseldatei), und Passphrase/SSH-Key sind **ohne Cluster** per `sops -d` aus einem lokalen +Clone lesbar — der age-Key aus dem Vault ist damit tatsächlich der einzige harte Startpunkt. + +**Offen: Schritt 3** (einmal gegen eine Wegwerf-Umgebung durchspielen) und **Schritt 4** +(Wiederholungsrhythmus). Das Verfahren ist bis dahin abgeleitet, aber unerprobt. diff --git a/docs/wiki/deployment/restore.md b/docs/wiki/deployment/restore.md new file mode 100644 index 0000000..0f10eef --- /dev/null +++ b/docs/wiki/deployment/restore.md @@ -0,0 +1,158 @@ +--- +type: wiki-page +area: deployment +sources: + - "docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md" +related: + - "docs/wiki/deployment/deploy-uebergabe.md" + - "docs/adr/0001-gitlab-kanonisch-push-mirror.md" +--- + +# Wiederherstellung: Matrix-Plattform (Hetzner/K3s) + +Ablauf für den Ernstfall — Totalverlust des Hetzner-Hosts. Kein Fließtext, sondern Reihenfolge: +**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. +> 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). +⚠️ **Noch nicht durchgespielt** — siehe „Offene Punkte" am Ende. + +## 0. Was du in der Hand haben musst, bevor du anfängst + +| Was | Woher | Ohne das geht | +|---|---|---| +| **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 | +| **gitops-Repo** | `git.lab/axion1337.chat/axion1337.chat-gitops` (kanonisch), Spiegel auf Gitea/rohana | kein Cluster-Zustand | +| **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: `-`. Layout im Archiv: +`scratch/dumps/.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 -p 23" +borg list "$BORG_REPO" # Archive auflisten +borg extract "$BORG_REPO::" # 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= + +# b) Git-Zugang für Flux (Keys heißen: username / password) +kubectl create secret generic flux-system -n flux-system \ + --from-literal=username= --from-literal=password= +``` + +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. diff --git a/docs/wiki/index.md b/docs/wiki/index.md index dc8773b..4aa78b4 100644 --- a/docs/wiki/index.md +++ b/docs/wiki/index.md @@ -16,7 +16,7 @@ mit Inhalt. Projektfassung des neckbeard-Index (Original: | Area | Enthält | Stand | |---|---|---| | `admin/` | Betrieb: [cfgmon](admin/cfgmon.md) · [game](admin/game.md) · [matrix](admin/matrix.md) · [overmind](admin/overmind.md) · [Refinement & Retro](admin/refinement.md) · [Stillstandsprüfung](admin/stillstandspruefung.md) · [Textbausteine](admin/textbloecke.md) | belegt | -| `deployment/` | [Deploy-Übergabe](deployment/deploy-uebergabe.md) (Definition of Done, Kanonisierungs-Verfahren) | belegt | +| `deployment/` | [Deploy-Übergabe](deployment/deploy-uebergabe.md) (Definition of Done, Kanonisierungs-Verfahren) · [Wiederherstellung](deployment/restore.md) (Restore-Ablauf Matrix-Plattform) | belegt | | `architecture/` | [Mirror-Topologie](architecture/mirror-topologie.md) · [Lab-Netz](architecture/lab-netzwerk.md) · [DNS-Zone](architecture/zone-axion1337.md) · [Branding](architecture/branding.md) | belegt | | `vision/` | Eine Datei je Linie: [axion1337.chat](vision/axion1337-chat.md) · [Homelab](vision/homelab.md) · [ThreadNet](vision/threadnet.md) | belegt | | `user-guide/` | Für Nicht-Owner | entfällt — Gate 0: Publikum ist Owner + Sessions |