syncova-backup/docs/recovery-runbook.md
Jerrit Fritzsche b78a6fb51c
Some checks failed
CI / Backend (Go) (push) Failing after 32s
CI / Frontend (React/TypeScript) (push) Successful in 46s
CI / Sicherheitsprüfungen (push) Successful in 28s
Dokumentation und Aenderungsliste fuer rc8
Der Abschnitt "Wohin darf zurueckgeschrieben werden?" im Runbook ist der
wichtigste Zusatz: Dass ein Ziel an ProtectSystem=strict scheitert und nicht an
den Rechten des Verzeichnisses, sieht man dem Fehler nicht an. Die Tabelle nennt
die vier Faelle samt Grund.

Die Beispiel-Einheit in der Installationsanleitung fuehrte in denselben Fehler —
sie nannte nur das Repository in ReadWritePaths. Eine Anleitung, deren
Ergebnis keine Wiederherstellung zulaesst, ist schlimmer als keine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:55:52 +02:00

343 lines
12 KiB
Markdown

# Wiederherstellung — Handbuch für den Ernstfall
Dieses Dokument ist für den Moment geschrieben, in dem etwas weg ist. Es
beschreibt **was zu tun ist**, nicht wie es gebaut wurde — das steht in
[recovery.md](recovery.md) und [disaster-recovery.md](disaster-recovery.md).
Vier Lagen, von der harmlosen zur schwersten. Suchen Sie die Ihre und
überspringen Sie den Rest.
| Lage | Abschnitt |
| --- | --- |
| Einzelne Dateien oder ein Ordner sind weg | [1](#1-dateien-und-ordner) |
| Ein Proxmox-Gast ist weg | [2](#2-ein-proxmox-gast) |
| Der Control-Server samt Datenbank ist weg, das Repository steht | [3](#3-control-server-und-datenbank) |
| Das Repository ist beschädigt | [4](#4-beschädigtes-repository) |
---
## 0. Zuerst: nichts überstürzen
Drei Fragen, jede kostet eine Minute und kann Stunden sparen.
**Gibt es einen brauchbaren Wiederherstellungspunkt?**
```bash
curl -s -H "Authorization: Bearer <token>" \
'https://<server>/api/v1/backups?classification=recoverable' | jq '.data[0]'
```
`recoverable` bedeutet: Dieses Backup wurde zurückgeschrieben und gegen das
Original verglichen. `verified` bedeutet nur, dass die Blöcke stimmen —
ein starkes Indiz, aber kein Nachweis. `unverified` heißt: ungeprüft.
**Lässt sich der Punkt überhaupt zurückspielen?**
```bash
curl -X POST https://<server>/api/v1/restores/validate \
-H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
-d '{"backup_id":"<id>","target_type":"filesystem","target_path":"/pfad/zum/ziel"}'
```
Die Vorabprüfung schreibt nichts. Sie prüft, ob **jeder benötigte Block noch
da ist** — ein Manifest allein belegt nur, dass jemand einmal etwas gesichert
hat.
**Wohin soll es?** Schreiben Sie nach Möglichkeit **neben** das Original, nicht
darüber. Ein Vergleich ist danach immer noch möglich; ein überschriebenes
Original nicht mehr.
---
## 1. Dateien und Ordner
### Wohin darf zurückgeschrieben werden?
**Die wichtigste Frage, und sie ist nicht offensichtlich.** Der Dienst läuft mit
`ProtectSystem=strict`: Außerhalb weniger Pfade ist das Dateisystem für ihn
schreibgeschützt, unabhängig von den Rechten des Verzeichnisses. Ein Ziel
außerhalb endet mit `mkdir: permission denied` — und zwar erst **nach** der
Vorabprüfung.
| Ort | Ergebnis |
| --- | --- |
| `/srv/syncova-restore` | ✓ von `setup.sh` angelegt und eingetragen |
| weitere aus `--wiederherstellungsziel` | ✓ |
| `/tmp/…` | ✗ landet im privaten `/tmp` des Dienstes und ist von außen unsichtbar |
| `/etc`, `/usr`, `/var/lib`, `/root` … | ✗ vom Zielschutz gesperrt |
| alles andere | ✗ schreibgeschützt durch `ProtectSystem=strict` |
Der Ordnerbaum in der Oberfläche beantwortet das direkt: Er meldet je
Verzeichnis, ob der Dienst dort schreiben darf — **gemessen** durch eine
Probedatei, nicht aus den Rechtebits abgeleitet.
Ein weiteres Ziel nachträglich freigeben:
```bash
sudo systemctl edit syncova-api # ReadWritePaths= ergänzen
sudo systemctl restart syncova-api
```
### Über die Oberfläche
Wiederherstellungspunkte → Punkt wählen → *Wiederherstellen*.
1. **Ziel** über den Ordnerbaum wählen. Beschreibbare Orte stehen oben als
Vorschlag; ein Unterverzeichnis lässt sich anlegen.
2. **Umfang** über den Backup-Browser wählen — das gesamte Backup, ein Ordner
oder eine einzelne Datei.
3. **Vorabprüfung** — sie schreibt nichts und stellt fest, ob jeder benötigte
Block noch da ist.
4. **Ausführen.**
### Über die API
```bash
curl -X POST https://<server>/api/v1/restores \
-H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" -d '{
"backup_id": "<id>",
"target_type": "filesystem",
"target_path": "/srv/wiederhergestellt",
"path_prefix": "daten/projekte"
}'
```
`path_prefix` trifft **einen Ordner oder eine einzelne Datei**: Der Server
vergleicht auf Gleichheit oder Präfix mit Verzeichnisgrenze — `dokumente`
trifft dabei nicht `dokumentation`. Ohne ihn kommt alles zurück.
Den Inhalt eines Backups durchsehen, ohne etwas zurückzuschreiben:
```bash
curl -s -H "Authorization: Bearer <token>" \
"https://<server>/api/v1/backups/<id>/contents?path=berichte" | jq '.data.entries'
```
Fortschritt:
```bash
curl -s -H "Authorization: Bearer <token>" \
https://<server>/api/v1/restores/<id> | jq '.data | {status, files_restored, bytes_restored}'
```
### Überschreiben
Drei Hürden, absichtlich:
1. `"overwrite_existing": true`
2. die Berechtigung `restores.overwrite` — sie steckt **nicht** in
`restores.execute`
3. `"confirm_overwrite": "/srv/daten"` — der Zielpfad **wörtlich wiederholt**
Ein versehentlich gesetztes Kennzeichen in einem Skript reicht damit nicht aus.
Läuft der Schalter ins Leere, weil am Ziel nichts liegt, entfällt die
Bestätigung — ein Ritual ohne Anlass gewöhnt das Wegklicken an.
### Wenn eine Wiederherstellung abbricht
Sie wird **nicht** selbsttätig wiederholt. Ein zweiter Lauf in ein halb
gefülltes Ziel kann Daten beschädigen, die der erste bereits am Platz hatte.
Stattdessen fortsetzen:
```bash
curl -X POST https://<server>/api/v1/restores/<id>/resume -H "Authorization: Bearer <token>"
```
Die Fortsetzung räumt zuerst die beim Abbruch angefangene Datei weg und
beginnt beim Prüfpunkt. Der Prüfpunkt entsteht erst, **nachdem** eine Datei
vollständig und umbenannt am Platz liegt.
### Ohne Control-Server
Geht der Server nicht, aber das Repository steht:
```bash
./bin/syncova-repo list --path /srv/syncova-repository
./bin/syncova-agent restore \
--repository /srv/syncova-repository \
--backup <backup-id> \
--target /srv/wiederhergestellt
```
Dafür wird `SYNCOVA_ENCRYPTION_KEYS` gebraucht — bei einem verschlüsselten
Repository liegt der Datenschlüssel unter `metadata/data-key.json` und ist mit
diesem Schlüssel versiegelt.
---
## 2. Ein Proxmox-Gast
> **Ungeprüft auf echter Hardware.** Der Weg läuft gegen einen Nachbau der API
> vollständig durch, aber **ob eine wiederhergestellte Maschine startet, ist
> nicht nachgewiesen** (siehe [proxmox.md](proxmox.md)). Rechnen Sie im
> Ernstfall mit Nacharbeit.
```bash
./bin/syncova-proxmox restore-guest \
--cluster <verbund-id> \
--repository /srv/syncova-repository \
--backup <backup-id> \
--target-guest qemu/900 \
--node pve-01
```
**`--target-guest` setzen.** Ohne ihn wird der Ursprungsgast überschrieben —
und wenn der noch läuft und nur Daten fehlen, vernichtet das genau den Stand,
den man retten wollte.
Der Ablauf: Archiv aus dem Repository lesen (entschlüsseln, entpacken, jeder
Block gegen seine Kennung geprüft) → über den Zugriffsweg auf einen
Proxmox-Speicher schreiben → `qmrestore` anstoßen → Archiv wieder abräumen.
Danach:
- **Der Gast startet nicht von selbst.** `--start` ist der ausdrückliche Weg.
Eine wiederhergestellte Maschine, die sich mit derselben Adresse ins Netz
meldet wie das noch laufende Original, richtet mehr Schaden an als der
Ausfall.
- **Die Plattenzuordnung wird nicht aus der gesicherten Konfiguration gesetzt.**
Sie verwiese auf den alten Ort. Das erscheint als Warnung und ist von Hand
zu prüfen.
- **Ausgenommene Platten** (`backup=0`) fehlen. Die Ausgabe nennt sie.
---
## 3. Control-Server und Datenbank
Der Fall, für den `syncova-dr` gebaut ist. Voraussetzung: Das Repository steht,
und Sie haben den **Verschlüsselungsschlüssel**.
> Ohne `SYNCOVA_ENCRYPTION_KEYS` sind die abgelegten Geheimnisse verloren. Die
> Backups selbst bleiben lesbar, sofern der Datenschlüssel des Repositorys mit
> demselben Schlüssel versiegelt war — ist er das nicht, sind auch sie weg.
> Deshalb gehört dieser Schlüssel außerhalb der Anlage aufbewahrt.
### 3.1 Ansehen, was da ist
```bash
./bin/syncova-dr inspect --repo /srv/syncova-repository
```
Braucht **keine Datenbank**. Nach einem Totalverlust will man zuerst sehen, was
vorhanden ist.
### 3.2 Schema anlegen
```bash
./bin/syncova-migrate up
```
### 3.3 Konfiguration einspielen
```bash
./bin/syncova-dr restore --repo /srv/syncova-repository --catalog
```
`--catalog` bringt zusätzlich die Wiederherstellungspunkte zurück.
**Das Einspielen ist eine Transaktion.** Alles oder nichts — eine halb
wiederhergestellte Anlage sieht arbeitsfähig aus und scheitert beim ersten
Lauf.
### 3.4 Was danach von Hand zu tun ist
Nichts läuft von selbst wieder an. Das ist Absicht: Ein Zeitplan, der nachts
anspringt, könnte auf eine halb wiederhergestellte Anlage schreiben.
```text
Aufträge angehalten → nach Prüfung einschalten
Repositories nicht erreichbar → nach Prüfung auf aktiv setzen
Konten deaktiviert → Passwörter neu vergeben
Benachrichtigungen abgeschaltet → neu einrichten
last_verified_at leer → Prüfung anstoßen
```
Im Sicherungssatz sind **nicht** enthalten: Passwörter, TOTP-Geheimnisse,
Zugangsdaten **und Konfiguration** der Benachrichtigungswege (dort steht die
Webhook-Adresse, und die trägt oft ein Token im Pfad), Betriebstokens der
Agenten und der Verschlüsselungsschlüssel selbst.
```bash
./bin/syncova-admin create-admin --username admin
```
### 3.5 Vor dem ersten neuen Lauf
**Eine Prüfung anstoßen.** Die Wiederherstellbarkeit der übernommenen Punkte
ist nach dem Einspielen nicht belegt — sie wurde nicht neu nachgewiesen, und
`last_verified_at` ist deshalb leer.
---
## 4. Beschädigtes Repository
### 4.1 Feststellen, was betroffen ist
```bash
./bin/syncova-repo scan --path /srv/syncova-repository --deep
```
Der vollständige Lauf liest jeden Block und prüft ihn gegen seine Prüfsumme.
Er braucht **keinen** Schlüssel: Geprüft wird gegen die Prüfsumme der
abgelegten Form.
Der Bericht nennt betroffene Backups namentlich. Ein Backup mit einem einzigen
beschädigten Block ist **nicht zu 70 % wiederherstellbar, sondern gar nicht** —
die Einstufung geht deshalb auf 0 %.
### 4.2 Katalog verloren
```bash
./bin/syncova-repo rebuild --path /srv/syncova-repository
```
Der Katalog ist nur ein Beschleuniger; verbindlich sind die Manifeste. Er wird
bei Verlust, Beschädigung oder fremder Repository-Kennung ohnehin still neu
gebaut.
### 4.3 Einzelnes Manifest beschädigt
Betroffen ist genau dieses eine Backup. Die Prüfung meldet es, die
Wiederherstellung bricht ab (**0 Dateien im Ziel**, kein halbes Ergebnis), und
der Katalog-Neuaufbau schließt es aus.
Dass der Katalog es zunächst weiter anzeigt, ist richtig — er ist ein
Beschleuniger, nicht die Wahrheit.
### 4.4 Blöcke fehlen
Fehlende Blöcke lassen sich nicht wiederherstellen; sie sind weg. Was bleibt:
1. Ein **anderer** Wiederherstellungspunkt derselben Kette. Da Syncova-Manifeste
vollständig sind, ist jeder Punkt für sich wiederherstellbar — eine
zerrissene Kette gibt es nicht.
2. Ein exportierter Container (`syncova-repo export`), sofern vorhanden.
### 4.5 Verweigerte Löschung im gehärteten Modus
Kein Fehler, sondern der Schutz. Zum Entfernen eines geschützten Backups:
```bash
curl -X DELETE https://<server>/api/v1/backups/<id> -H "Authorization: Bearer <token>"
```
Der Server prüft Aufbewahrungsfrist und Legal Hold. Eine abgewehrte Löschung
wird protokolliert wie eine erfolgreiche — wer wiederholt gegen den Schutz
läuft, tut entweder etwas Falsches oder etwas Böses.
Eine rekursive Aufhebungsfunktion für den gesamten Schutz gibt es
**ausdrücklich nicht**.
---
## Wie oft man das üben sollte
Einmal im Quartal, mit einem echten Ziel und einer Stoppuhr. Die gemessene
Dauer ist die einzige RTO-Angabe, die in einem Bericht etwas wert ist — alles
andere ist eine Hochrechnung aus Datenmenge und Durchsatz, auf die sich im
Ernstfall niemand verlassen kann.
Solange nicht gemessen wurde, steht in der RTO-Spalte der Berichte „nicht
gemessen". Das ist die richtige Angabe, aber keine gute.