Merge #41: Dokumente mitsichern

This commit was merged in pull request #48.
This commit is contained in:
2026-09-01 14:04:03 +00:00
5 changed files with 282 additions and 30 deletions
+11
View File
@@ -155,9 +155,20 @@ services:
PGDATABASE: ${POSTGRES_DB:-betreuungs_cockpit} PGDATABASE: ${POSTGRES_DB:-betreuungs_cockpit}
SICHERUNG_PASSWORT: ${SICHERUNG_PASSWORT:?SICHERUNG_PASSWORT fehlt in der .env} SICHERUNG_PASSWORT: ${SICHERUNG_PASSWORT:?SICHERUNG_PASSWORT fehlt in der .env}
SICHERUNG_AUFBEWAHRUNG_TAGE: ${SICHERUNG_AUFBEWAHRUNG_TAGE:-30} SICHERUNG_AUFBEWAHRUNG_TAGE: ${SICHERUNG_AUFBEWAHRUNG_TAGE:-30}
SICHERUNG_DOKUMENTE_QUELLE: /dokumente
TZ: Europe/Berlin TZ: Europe/Berlin
volumes: volumes:
- ./backups:/backups - ./backups:/backups
# Die Dokumente, ausdruecklich NUR LESEND.
#
# Der Sicherungsdienst laeuft unbeaufsichtigt per cron. Ein Dienst,
# der die Daten zerstoeren kann, die er sichern soll, ist ein Risiko
# ohne Gegenwert - ein Fehler im Skript oder ein kompromittierter
# Behaelter wuerde beides auf einmal treffen.
#
# Zurueckspielen geschieht deshalb nicht hierher, sondern in ein
# Verzeichnis unter ./backups; siehe scripts/wiederherstellung.sh.
- minio-daten:/dokumente:ro
- ./scripts/sicherung.sh:/sicherung.sh:ro - ./scripts/sicherung.sh:/sicherung.sh:ro
- ./scripts/wiederherstellung.sh:/wiederherstellung.sh:ro - ./scripts/wiederherstellung.sh:/wiederherstellung.sh:ro
entrypoint: ['/bin/sh', '-c'] entrypoint: ['/bin/sh', '-c']
+39 -8
View File
@@ -731,13 +731,19 @@ Erwartete Ausgabe, in dieser Reihenfolge:
``` ```
Sicherung beginnt: /backups/cockpit_2026-08-22_1430.dump.enc Sicherung beginnt: /backups/cockpit_2026-08-22_1430.dump.enc
Sicherung fertig (1.2M) Datenbank gesichert (1.2M)
Selbstpruefung bestanden. Dokumente gesichert (18M)
Selbstpruefung bestanden - Datenbank und Dokumente.
``` ```
Die **Selbstprüfung** ist der eigentliche Punkt: Das Skript entschlüsselt Die **Selbstprüfung** ist der eigentliche Punkt: Das Skript entschlüsselt
die eben geschriebene Datei sofort wieder. Schlägt das fehl, endet der beide Dateien sofort wieder und liest sie einmal durch. Schlägt das fehl,
Lauf mit einem Fehler statt mit einem stillen Scheinerfolg. 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 ### 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 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 ### Kontrolle
```bash ```bash
@@ -767,12 +792,18 @@ ls -lh backups/
# cockpit_....dump.enc und cockpit_....dump.enc.sha256 # cockpit_....dump.enc und cockpit_....dump.enc.sha256
``` ```
> **Was das Backup nicht enthält:** den Schlüssel `ENC_KEY_V1` und die > **Was das Backup enthält:** die Datenbank *und* die Dokumente. Je Lauf
> Dateien im Objektspeicher. Für die Dateien: > 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, > Und kopier die Sicherungen regelmäßig **weg vom Server** — ein Backup,
> das neben dem Original liegt, hilft bei einem Serverausfall nicht. > das neben dem Original liegt, hilft bei einem Serverausfall nicht.
+81 -18
View File
@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# ============================================================================= # =============================================================================
# Taegliche Sicherung der Datenbank # Taegliche Sicherung: Datenbank UND Dokumente
# ============================================================================= # =============================================================================
# #
# Laeuft im Behaelter "sicherung" per cron um 03:15 Uhr. Von Hand: # Laeuft im Behaelter "sicherung" per cron um 03:15 Uhr. Von Hand:
@@ -10,17 +10,44 @@
# Was passiert: # Was passiert:
# 1. pg_dump im eigenen Format (-Fc), damit einzelne Tabellen # 1. pg_dump im eigenen Format (-Fc), damit einzelne Tabellen
# wiederherstellbar bleiben. # wiederherstellbar bleiben.
# 2. Verschluesselung mit AES-256 und PBKDF2 aus SICHERUNG_PASSWORT. # 2. Die abgelegten Dokumente als Archiv.
# 3. Beides verschluesselt mit AES-256 und PBKDF2 aus SICHERUNG_PASSWORT.
# Das Backup liegt oft auf fremdem Speicher - unverschluesselt waere # Das Backup liegt oft auf fremdem Speicher - unverschluesselt waere
# es ein Datenleck mit Ansage. # es ein Datenleck mit Ansage.
# 3. SHA-256-Pruefsumme daneben. # 4. SHA-256-Pruefsumme je Datei.
# 4. Staende, die aelter sind als SICHERUNG_AUFBEWAHRUNG_TAGE, entfernen. # 5. Staende, die aelter sind als SICHERUNG_AUFBEWAHRUNG_TAGE, entfernen.
# Der jeweils erste Stand eines Monats bleibt stehen. # Der jeweils erste Stand eines Monats bleibt stehen.
# #
# WICHTIG: Dieses Backup enthaelt die verschluesselten Felder, aber NICHT # ## Warum die Dokumente dazugehoeren
# den Schluessel ENC_KEY_V1. Ohne ihn sind Diagnosen, IBANs und Notizen #
# auch aus einer wiederhergestellten Datenbank nicht lesbar. Den Schluessel # Bis v1.11.0 sicherte dieses Skript nur die Datenbank. Nach einem
# getrennt sichern - Passwortmanager oder ausgedruckt im Ordner. # 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.
#
# ## Warum das Ablageverzeichnis roh eingepackt wird
#
# Der naheliegende Weg waere "mc mirror" aus dem MinIO-Client. Der ist in
# diesem Behaelter aber nicht vorhanden und muesste bei jedem Lauf aus dem
# Netz geholt werden - eine Internetabhaengigkeit im Sicherungspfad ist
# genau das, was man dort nicht will.
#
# Stattdessen wird das Datenverzeichnis 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
# also nie halbfertig im Archiv, er fehlt hoechstens. Bei einem Lauf um
# 03:15 Uhr auf einem Einzelplatzsystem ist auch das unwahrscheinlich.
#
# Derselbe Weg deckt den Dateitreiber mit ab - dort liegen die Dateien
# ebenfalls einfach im Verzeichnis.
#
# WICHTIG: Dieses Backup enthaelt die verschluesselten Felder und die
# verschluesselten Dateien, aber NICHT den Schluessel ENC_KEY_V1. Ohne ihn
# sind Diagnosen, IBANs, Notizen und Dokumente auch aus einer
# wiederhergestellten Sicherung nicht lesbar. Den Schluessel getrennt
# sichern - Passwortmanager oder ausgedruckt im Ordner.
# ============================================================================= # =============================================================================
set -eu set -eu
@@ -30,6 +57,11 @@ AUFBEWAHRUNG="${SICHERUNG_AUFBEWAHRUNG_TAGE:-30}"
ZEITSTEMPEL="$(date +%Y-%m-%d_%H%M)" ZEITSTEMPEL="$(date +%Y-%m-%d_%H%M)"
TAG_IM_MONAT="$(date +%d)" TAG_IM_MONAT="$(date +%d)"
DATEI="$ZIEL/cockpit_${ZEITSTEMPEL}.dump.enc" DATEI="$ZIEL/cockpit_${ZEITSTEMPEL}.dump.enc"
DOKUMENTE_DATEI="$ZIEL/dokumente_${ZEITSTEMPEL}.tar.gz.enc"
# Wo die Dokumente liegen. Der Behaelter bekommt das Ablageverzeichnis
# eingehaengt - siehe docker-compose.yml, Dienst "sicherung".
DOKUMENTE_QUELLE="${SICHERUNG_DOKUMENTE_QUELLE:-/dokumente}"
if [ -z "${SICHERUNG_PASSWORT:-}" ]; then if [ -z "${SICHERUNG_PASSWORT:-}" ]; then
echo "$(date -Iseconds) FEHLER: SICHERUNG_PASSWORT ist nicht gesetzt. Abbruch." >&2 echo "$(date -Iseconds) FEHLER: SICHERUNG_PASSWORT ist nicht gesetzt. Abbruch." >&2
@@ -50,17 +82,39 @@ pg_dump --format=custom --compress=6 --no-owner --no-privileges \
GROESSE="$(du -h "$DATEI" | cut -f1)" GROESSE="$(du -h "$DATEI" | cut -f1)"
sha256sum "$DATEI" > "$DATEI.sha256" sha256sum "$DATEI" > "$DATEI.sha256"
# Der jeweils erste Stand eines Monats bekommt eine Markierung und bleibt echo "$(date -Iseconds) Datenbank gesichert ($GROESSE)"
# beim Aufraeumen verschont.
if [ "$TAG_IM_MONAT" = "01" ]; then # --- Dokumente ---------------------------------------------------------------
touch "$DATEI.monatsstand" # Schlaegt dieser Teil fehl, bricht der ganze Lauf ab (set -e). Das ist
# Absicht: 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.
if [ ! -d "$DOKUMENTE_QUELLE" ]; then
echo "$(date -Iseconds) FEHLER: Das Ablageverzeichnis $DOKUMENTE_QUELLE ist nicht eingehaengt." >&2
echo " Ohne die Dokumente ist die Sicherung unvollstaendig." >&2
echo " Siehe docker-compose.yml, Dienst \"sicherung\"." >&2
rm -f "$DATEI" "$DATEI.sha256"
exit 1
fi fi
echo "$(date -Iseconds) Sicherung fertig ($GROESSE)" # "tar" ohne Leitung an einen Verbraucher, der frueh schliesst - sonst
# SIGPIPE, siehe die Anmerkung zur Selbstpruefung weiter unten.
tar -czf - -C "$DOKUMENTE_QUELLE" . | openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -salt -pass env:SICHERUNG_PASSWORT -out "$DOKUMENTE_DATEI"
DOK_GROESSE="$(du -h "$DOKUMENTE_DATEI" | cut -f1)"
sha256sum "$DOKUMENTE_DATEI" > "$DOKUMENTE_DATEI.sha256"
echo "$(date -Iseconds) Dokumente gesichert ($DOK_GROESSE)"
# Der jeweils erste Stand eines Monats bekommt eine Markierung und bleibt
# beim Aufraeumen verschont. Beide Teile, sonst faellt die Haelfte weg.
if [ "$TAG_IM_MONAT" = "01" ]; then
touch "$DATEI.monatsstand" "$DOKUMENTE_DATEI.monatsstand"
fi
# --- Aufraeumen -------------------------------------------------------------- # --- Aufraeumen --------------------------------------------------------------
ENTFERNT=0 ENTFERNT=0
for alt in "$ZIEL"/cockpit_*.dump.enc; do for alt in "$ZIEL"/cockpit_*.dump.enc "$ZIEL"/dokumente_*.tar.gz.enc; do
[ -e "$alt" ] || continue [ -e "$alt" ] || continue
[ -e "$alt.monatsstand" ] && continue [ -e "$alt.monatsstand" ] && continue
@@ -87,9 +141,18 @@ KOPF="$(openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
-pass env:SICHERUNG_PASSWORT -in "$DATEI" 2>/dev/null \ -pass env:SICHERUNG_PASSWORT -in "$DATEI" 2>/dev/null \
| dd bs=1 count=5 2>/dev/null || true)" | dd bs=1 count=5 2>/dev/null || true)"
if [ "$KOPF" = "PGDMP" ]; then if [ "$KOPF" != "PGDMP" ]; then
echo "$(date -Iseconds) Selbstpruefung bestanden." echo "$(date -Iseconds) FEHLER: Der Datenbankstand laesst sich nicht entschluesseln!" >&2
else
echo "$(date -Iseconds) FEHLER: Die Sicherung laesst sich nicht entschluesseln!" >&2
exit 1 exit 1
fi fi
# Dasselbe fuer die Dokumente: entschluesseln und das Archiv auflisten
# lassen. "tar -tzf" liest die ganze Datei und meldet jeden Schaden - das
# ist bei den Groessenordnungen hier bezahlbar und sagt mehr aus als ein
# Blick auf die ersten Bytes.
if ! openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -pass env:SICHERUNG_PASSWORT -in "$DOKUMENTE_DATEI" 2>/dev/null | tar -tzf - >/dev/null 2>&1; then
echo "$(date -Iseconds) FEHLER: Das Dokumentenarchiv laesst sich nicht lesen!" >&2
exit 1
fi
echo "$(date -Iseconds) Selbstpruefung bestanden - Datenbank und Dokumente."
+64 -4
View File
@@ -9,7 +9,7 @@
# ausdruecklich nach, weil dabei der aktuelle Datenbestand ueberschrieben # ausdruecklich nach, weil dabei der aktuelle Datenbestand ueberschrieben
# wird. # wird.
# #
# ZWEI DINGE, DIE MAN VORHER WISSEN MUSS: # DREI DINGE, DIE MAN VORHER WISSEN MUSS:
# #
# 1. Die Sicherung enthaelt die anwendungsseitig verschluesselten Felder, # 1. Die Sicherung enthaelt die anwendungsseitig verschluesselten Felder,
# aber NICHT den Schluessel. Ohne den passenden ENC_KEY_Vn aus der .env # aber NICHT den Schluessel. Ohne den passenden ENC_KEY_Vn aus der .env
@@ -20,6 +20,14 @@
# das hier einmal im Jahr auf einer Testdatenbank aus, bevor du es im # das hier einmal im Jahr auf einer Testdatenbank aus, bevor du es im
# Ernstfall zum ersten Mal brauchst. Siehe README, Abschnitt # Ernstfall zum ersten Mal brauchst. Siehe README, Abschnitt
# "Wiederherstellung ueben". # "Wiederherstellung ueben".
#
# 3. Die Dokumente werden NICHT selbsttaetig an ihren Platz geschrieben.
# Dieses Skript packt sie nach ./backups aus und sagt, wie es
# weitergeht. Grund: Der Sicherungsdienst hat auf das Ablageverzeichnis
# bewusst nur Lesezugriff (siehe docker-compose.yml), und in ein
# laufendes MinIO hineinzuschreiben wuerde dessen Datenbestand
# beschaedigen. Der letzte Handgriff bleibt beim Menschen - er ist ein
# Einzeiler und im Klartext beschrieben.
# ============================================================================= # =============================================================================
set -eu set -eu
@@ -87,7 +95,44 @@ openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
| pg_restore --clean --if-exists --no-owner --no-privileges \ | pg_restore --clean --if-exists --no-owner --no-privileges \
--dbname="${PGDATABASE:-postgres}" --exit-on-error --dbname="${PGDATABASE:-postgres}" --exit-on-error
echo "$(date -Iseconds) Wiederherstellung abgeschlossen." echo "$(date -Iseconds) Datenbank wiederhergestellt."
# --- Dokumente ---------------------------------------------------------------
# Zu "cockpit_2026-09-01_0315.dump.enc" gehoert
# "dokumente_2026-09-01_0315.tar.gz.enc" - gleicher Zeitstempel, anderer
# Name. Fehlt sie, stammt die Sicherung aus einer Fassung vor v1.11.1.
ZEITSTEMPEL="$(basename "$DATEI" | sed -n 's/^cockpit_\(.*\)\.dump\.enc$/\1/p')"
DOKUMENTE_DATEI="$(dirname "$DATEI")/dokumente_${ZEITSTEMPEL}.tar.gz.enc"
AUSPACKZIEL="$(dirname "$DATEI")/dokumente-wiederhergestellt_${ZEITSTEMPEL}"
if [ ! -f "$DOKUMENTE_DATEI" ]; then
echo ""
echo "WARNUNG: Zu dieser Sicherung gibt es kein Dokumentenarchiv."
echo " Erwartet wurde: $DOKUMENTE_DATEI"
echo " Sicherungen aus Fassungen vor v1.11.1 enthalten nur die"
echo " Datenbank. Die Akte ist dann vollstaendig, die Dateien"
echo " dahinter fehlen."
else
if [ -f "$DOKUMENTE_DATEI.sha256" ]; then
echo "Pruefsumme des Dokumentenarchivs wird geprueft ..."
if sha256sum -c "$DOKUMENTE_DATEI.sha256" >/dev/null 2>&1; then
echo " in Ordnung."
else
echo "FEHLER: Die Pruefsumme des Dokumentenarchivs stimmt nicht." >&2
exit 1
fi
fi
mkdir -p "$AUSPACKZIEL"
openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
-pass env:SICHERUNG_PASSWORT -in "$DOKUMENTE_DATEI" \
| tar -xzf - -C "$AUSPACKZIEL"
ANZAHL="$(find "$AUSPACKZIEL" -type f | wc -l)"
echo "$(date -Iseconds) Dokumente ausgepackt: $ANZAHL Dateien nach"
echo " $AUSPACKZIEL"
fi
echo "" echo ""
echo "Naechste Schritte:" echo "Naechste Schritte:"
echo " 1. Die SQL-Skripte erneut ausfuehren (Volltextsuche und Audit-Schutz):" echo " 1. Die SQL-Skripte erneut ausfuehren (Volltextsuche und Audit-Schutz):"
@@ -95,6 +140,21 @@ echo " docker compose exec app npm run db:sql"
echo " 2. Pruefen, ob ENC_KEY_V1 in der .env zu diesem Bestand passt -" echo " 2. Pruefen, ob ENC_KEY_V1 in der .env zu diesem Bestand passt -"
echo " ohne den richtigen Schluessel bleiben die verschluesselten" echo " ohne den richtigen Schluessel bleiben die verschluesselten"
echo " Felder unlesbar." echo " Felder unlesbar."
echo " 3. In der Anwendung eine betreute Person oeffnen und nachsehen, ob" echo " 3. Die Dokumente an ihren Platz bringen. Dazu MinIO anhalten, das"
echo " die Diagnosen im Klartext erscheinen. Erst dann ist die" echo " Datenverzeichnis ersetzen und wieder starten - auf dem Host,"
echo " nicht in diesem Behaelter:"
echo ""
echo " docker compose stop minio app"
echo " docker run --rm \\"
echo " -v betreuungs-cockpit_minio-daten:/ziel \\"
echo " -v \"\$PWD/backups:/quelle:ro\" alpine \\"
echo " sh -c 'rm -rf /ziel/* /ziel/..?* && cp -a /quelle/dokumente-wiederhergestellt_*/. /ziel/'"
echo " docker compose up -d"
echo ""
echo " Steht mehr als ein ausgepacktes Verzeichnis unter ./backups,"
echo " vorher das richtige auswaehlen statt des Sternchens."
echo ""
echo " 4. In der Anwendung eine betreute Person oeffnen und nachsehen, ob"
echo " die Diagnosen im Klartext erscheinen. Dann ein Dokument"
echo " herunterladen. Erst wenn beides klappt, ist die"
echo " Wiederherstellung wirklich gelungen." echo " Wiederherstellung wirklich gelungen."
+87
View File
@@ -132,3 +132,90 @@ describe('Skripte mit pipefail', () => {
).toEqual([]) ).toEqual([])
}) })
}) })
/**
* Die Sicherung muss die Dokumente enthalten - nicht nur die Datenbank.
*
* Bis v1.11.0 sicherte scripts/sicherung.sh ausschliesslich die Datenbank.
* 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.
*
* Aufgefallen ist das nicht durch einen Test, sondern beim Nachsehen, wo
* die Daten eigentlich liegen. Die folgenden Pruefungen lesen deshalb den
* Skripttext: Sie koennen die Sicherung nicht ausfuehren - dafuer braeuchte
* es Postgres, MinIO und einen Behaelter -, aber sie schlagen an, sobald
* einer der Bausteine wieder herausfaellt.
*/
describe('Sicherung umfasst die Dokumente', () => {
const sicherung = readFileSync('scripts/sicherung.sh', 'utf8')
const wiederherstellung = readFileSync('scripts/wiederherstellung.sh', 'utf8')
it('packt das Ablageverzeichnis ein', () => {
expect(sicherung).toMatch(/tar -czf - -C "\$DOKUMENTE_QUELLE"/)
})
it('verschlüsselt das Archiv mit demselben Verfahren wie den Datenbankstand', () => {
// Zwei Aufrufe: einer fuer den Dump, einer fuer die Dokumente.
const verschluesselt = sicherung.match(/openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -salt/g)
expect(verschluesselt).toHaveLength(2)
})
it('legt zu beiden Teilen eine Prüfsumme', () => {
expect(sicherung).toMatch(/sha256sum "\$DATEI"/)
expect(sicherung).toMatch(/sha256sum "\$DOKUMENTE_DATEI"/)
})
it('bricht ab, wenn das Ablageverzeichnis nicht eingehängt ist', () => {
// Eine Sicherung, die nur die halbe Wahrheit enthaelt, aber
// vollstaendig aussieht, ist schlimmer als gar keine.
expect(sicherung).toMatch(/if \[ ! -d "\$DOKUMENTE_QUELLE" \]/)
expect(sicherung).toMatch(/rm -f "\$DATEI" "\$DATEI\.sha256"/)
})
it('prüft am Ende beide Teile, nicht nur den Datenbankstand', () => {
expect(sicherung).toMatch(/tar -tzf -/)
expect(sicherung).toMatch(/Datenbank und Dokumente/)
})
it('räumt alte Dokumentenarchive genauso auf wie alte Datenbankstände', () => {
expect(sicherung).toMatch(/dokumente_\*\.tar\.gz\.enc/)
})
it('verschont beim Monatsstand beide Teile', () => {
expect(sicherung).toMatch(/touch "\$DATEI\.monatsstand" "\$DOKUMENTE_DATEI\.monatsstand"/)
})
it('holt die Dokumente beim Zurückspielen mit zurück', () => {
expect(wiederherstellung).toMatch(/DOKUMENTE_DATEI=/)
expect(wiederherstellung).toMatch(/tar -xzf - -C "\$AUSPACKZIEL"/)
})
it('sagt beim Zurückspielen deutlich, wenn kein Dokumentenarchiv dabei ist', () => {
// Sonst haelt man eine halbe Wiederherstellung fuer eine ganze.
expect(wiederherstellung).toMatch(/kein Dokumentenarchiv/)
})
it('schreibt die Dokumente nicht selbst an ihren Platz', () => {
// Der Sicherungsdienst hat auf das Ablageverzeichnis nur Lesezugriff.
// In ein laufendes MinIO hineinzuschreiben wuerde dessen Datenbestand
// beschaedigen - der letzte Handgriff bleibt beim Menschen.
expect(wiederherstellung).toMatch(/docker compose stop minio app/)
})
})
/**
* Der Sicherungsdienst darf die Daten, die er sichert, nicht zerstoeren
* koennen.
*
* 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.
*/
describe('Sicherungsdienst greift nur lesend auf die Dokumente zu', () => {
it('hängt das Ablageverzeichnis mit :ro ein', () => {
const compose = readFileSync('docker-compose.yml', 'utf8')
expect(compose).toMatch(/minio-daten:\/dokumente:ro/)
})
})