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>
7.4 KiB
type, area, sources, related
| type | area | sources | related | |||
|---|---|---|---|---|---|---|
| wiki-page | deployment |
|
|
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-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:
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
- Host + K3s neu aufsetzen (siehe
gitops:docs/install.md, Schritt 1). Kubeconfig sichern. - 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>
- 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 (siehegitops: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) undclusters/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_restoredagegen 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→ allesReady=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.