Dokumente mitsichern - eine Akte ohne ihre Dateien ist wertlos

Der Sicherungsdienst machte taeglich ein pg_dump und sonst nichts. Nach
einem Plattenschaden waere die Akte vollstaendig wiederhergestellt worden
- mit allen Eintraegen, Verweisen und Pruefsummen, aber ohne eine einzige
Datei dahinter. Die Pruefsumme in der Akte haette dann ein Dokument
belegt, das es nicht mehr gibt. Gegenueber dem Betreuungsgericht ist das
der schlechteste denkbare Zustand.

Je Lauf entstehen jetzt zwei verschluesselte Dateien mit demselben
Zeitstempel: der Datenbankstand und ein Archiv des Ablageverzeichnisses.
Beide mit Pruefsumme, beide in der Aufbewahrung, beide im Monatsstand,
beide in der Selbstpruefung.

Schlaegt der Dokumententeil fehl, bricht der Lauf ab und raeumt den
angefangenen Datenbankstand wieder weg. Eine Sicherung, die nur die halbe
Wahrheit enthaelt, aber vollstaendig aussieht, ist schlimmer als gar
keine: Man verlaesst sich darauf und merkt es erst im Ernstfall.

## Abweichung von der urspruenglichen Idee

Im Issue stand "mc mirror statt rohem Verzeichnis". Beim Umsetzen zeigte
sich, dass der MinIO-Client in diesem Behaelter fehlt und bei jedem Lauf
aus dem Netz geholt werden muesste. Eine Internetabhaengigkeit im
Sicherungspfad ist genau das, was man dort nicht will.

Stattdessen wird das Verzeichnis direkt eingepackt. Das ist vertretbar,
weil MinIO ein Objekt erst vollstaendig unter .minio.sys/tmp schreibt und
dann an seinen Platz umbenennt: Ein laufender Upload liegt nie halbfertig
im Archiv, er fehlt hoechstens. Derselbe Weg deckt den Dateitreiber mit
ab.

## Nur lesend

Der Sicherungsdienst bekommt das Ablageverzeichnis mit ":ro" eingehaengt.
Er laeuft unbeaufsichtigt per cron; ein Fehler im Skript oder ein
kompromittierter Behaelter wuerde sonst Sicherung und Bestand auf einmal
treffen - genau der Fall, gegen den eine Sicherung da ist.

Deshalb schreibt die Wiederherstellung die Dateien auch nicht selbst an
ihren Platz, sondern packt sie nach ./backups aus und gibt den letzten
Handgriff als Einzeiler aus. In ein laufendes MinIO hineinzuschreiben
wuerde dessen Datenbestand ohnehin beschaedigen.

## Geprueft

Die Kernlogik einmal von Hand durchgespielt: packen, verschluesseln,
Selbstpruefung, zurueckholen - der Inhalt kam bitgleich zurueck, der
Klartext war im Archiv nicht auffindbar, und die Selbstpruefung schlug
sowohl bei einem beschaedigten Archiv als auch bei falschem Passwort an.

Dazu elf Pruefungen in tests/skripte.test.ts gegen vier Mutationen:
Dokumententeil entfernt, Abbruch entfernt, Aufraeumen halbiert,
Schreibrecht erteilt - jede wird rot.

Refs #41
This commit is contained in:
Florian Wilde
2026-09-01 16:03:26 +02:00
parent 6e6f022234
commit 4d826ef55f
5 changed files with 282 additions and 30 deletions
+39 -8
View File
@@ -731,13 +731,19 @@ Erwartete Ausgabe, in dieser Reihenfolge:
```
Sicherung beginnt: /backups/cockpit_2026-08-22_1430.dump.enc
Sicherung fertig (1.2M)
Selbstpruefung bestanden.
Datenbank gesichert (1.2M)
Dokumente gesichert (18M)
Selbstpruefung bestanden - Datenbank und Dokumente.
```
Die **Selbstprüfung** ist der eigentliche Punkt: Das Skript entschlüsselt
die eben geschriebene Datei sofort wieder. Schlägt das fehl, endet der
Lauf mit einem Fehler statt mit einem stillen Scheinerfolg.
beide Dateien sofort wieder und liest sie einmal durch. Schlägt das fehl,
endet der Lauf mit einem Fehler statt mit einem stillen Scheinerfolg.
Fehlt die Zeile `Dokumente gesichert`, ist das Ablageverzeichnis nicht in
den Sicherungsdienst eingehängt. Das Skript bricht dann ab und räumt den
angefangenen Datenbankstand wieder weg — eine Sicherung, die nur die halbe
Wahrheit enthält, aber vollständig aussieht, wäre schlimmer als gar keine.
### Und jetzt einmal zurückspielen
@@ -760,6 +766,25 @@ docker compose exec db psql -U cockpit probe \
docker compose exec db dropdb -U cockpit probe
```
Beim Zurückspielen packt das Skript auch das Dokumentenarchiv aus — nach
`backups/dokumente-wiederhergestellt_<datum>/`. An ihren Platz schreibt es
die Dateien **nicht**: Der Sicherungsdienst hat auf das Ablageverzeichnis
bewusst nur Lesezugriff, und in ein laufendes MinIO hineinzuschreiben würde
dessen Datenbestand beschädigen. Wie der letzte Handgriff aussieht, gibt
das Skript am Ende selbst aus.
Für die Übung genügt ein Blick in das ausgepackte Verzeichnis:
```bash
find backups/dokumente-wiederhergestellt_*/ -type f | head
rm -rf backups/dokumente-wiederhergestellt_*
```
> **Im Ernstfall gehört beides zusammen.** Eine Datenbank ohne die
> zugehörigen Dateien ergibt eine Akte, deren Prüfsummen Dokumente
> belegen, die es nicht mehr gibt. Spiel immer das Paar mit demselben
> Zeitstempel zurück.
### Kontrolle
```bash
@@ -767,12 +792,18 @@ ls -lh backups/
# cockpit_....dump.enc und cockpit_....dump.enc.sha256
```
> **Was das Backup nicht enthält:** den Schlüssel `ENC_KEY_V1` und die
> Dateien im Objektspeicher. Für die Dateien:
> **Was das Backup enthält:** die Datenbank *und* die Dokumente. Je Lauf
> entstehen zwei verschlüsselte Dateien mit demselben Zeitstempel:
>
> ```bash
> docker compose exec minio mc mirror lokal/betreuungs-cockpit /backups/dokumente
> ```
> cockpit_2026-09-01_0315.dump.enc <- Akte, Fristen, Buchungen
> dokumente_2026-09-01_0315.tar.gz.enc <- Bescheide, Schreiben, Belege
> ```
>
> **Was es nicht enthält:** den Schlüssel `ENC_KEY_V1`. Ohne ihn bleiben
> auch aus einer vollständig zurückgespielten Sicherung die Diagnosen,
> IBANs, Notizen und Dokumente unlesbar. Er gehört an die zwei Orte aus
> Schritt 11.
>
> Und kopier die Sicherungen regelmäßig **weg vom Server** — ein Backup,
> das neben dem Original liegt, hilft bei einem Serverausfall nicht.