Files
management/docs/wiki/deployment/restore.md
T
Thore CimbalandClaude Opus 4.8 30e1962280 docs(wiki): add restore procedure for the Matrix platform (#0030 step 2)
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 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00

7.4 KiB

type, area, sources, related
type area sources related
wiki-page deployment
docs/issues/0030-der-restore-ist-nie-geprobt-sicherungen-sind.md
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:

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-blake2die 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 -Fcpg_restore) und media/media_store/….

Zugriff von einer beliebigen Maschine mit Borg:

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):
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>
  1. 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.

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.