Add validated Paperless restore test pattern

This commit is contained in:
2026-05-07 11:01:27 +02:00
parent d351b1cac8
commit 2cc39c73f6
8 changed files with 309 additions and 7 deletions
+1 -1
View File
@@ -393,7 +393,7 @@ Wenn weder externer Mirror noch lokaler Clone verfuegbar sind, ist `services/git
- Unraid-USB-/Flash-Backup pruefen
- Borg-Passphrase extern sicher hinterlegen
- Komodo Stack-ENV-Werte zentral ausserhalb von Komodo dokumentieren
- Restore-Smoke-Test fuer mindestens einen weiteren kritischen Dienst nach Vaultwarden und Gitea dokumentieren
- regelmaessige automatisierte Restore-Smoke-Tests fuer Vaultwarden, Gitea und Paperless etablieren
- `komodo-mongo`-Dump nach Major-Upgrades gezielt kontrollieren
---
+10
View File
@@ -36,6 +36,16 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab
- Report wurde unter `/mnt/user/backups/restore-reports/gitea-2026-05-07.md` geschrieben.
- Testdaten unter `/mnt/user/backups/restore-lab/gitea/data` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-07 - Paperless Restore-Test praktisch verifiziert
- Erster echter Paperless-Mini-Restore gegen das produktive Borg-Repo `hetzner_borg_appdata_critical` erfolgreich durchgefuehrt.
- Restore umfasste sowohl die Dateipfade als auch `postgresql17-paperless.dump` aus dem Borg-Archiv.
- Testinstanzen `restoretest-paperless`, `restoretest-paperless-postgres` und `restoretest-paperless-redis` liefen isoliert ohne Traefik.
- Login-Seite war lokal auf `127.0.0.1:18120` erreichbar.
- Der Dump-Import in Test-Postgres war erfolgreich; die Test-Datenbank enthielt `25` Dokumente.
- Report wurde unter `/mnt/user/backups/restore-reports/paperless-2026-05-07.md` geschrieben.
- Testdaten unter `/mnt/user/backups/restore-lab/paperless` wurden nach erfolgreichem Lauf wieder bereinigt.
### 2026-05-06 - Komodo Webhook Secret getrennt
- `KOMODO_WEBHOOK_SECRET` von `KOMODO_SECRET_KEY` getrennt und als eigene Stack-ENV-Variable dokumentiert.
+5 -4
View File
@@ -42,7 +42,7 @@ Sie ist die fachliche Ergaenzung zu `docs/DISASTER_RECOVERY.md`.
| Dienst | Fuehrende Quelle | Datei-Restore | Dump / DB | Secrets / ENV | Abhaengigkeiten | Smoke-Test |
|---|---|---|---|---|---|---|
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS` | PostgreSQL 17, Redis, Traefik | Web-UI startet, Dokumente vorhanden |
| Paperless-ngx | Borg + Dumps | `/mnt/user/appdata/paperless-ngx/data`, `/mnt/user/documents/paperless`, `/mnt/user/documents/paperless/export`, `/mnt/user/documents/scans_inbox` | `postgresql17-paperless.dump` | `PAPERLESS_DBPASS`, `PAPERLESS_REDIS`, `borg_repo_passphrase.txt` fuer Restore-Tests | PostgreSQL 17, Redis, Traefik | Web-UI startet, Dokumente vorhanden; Mini-Restore nach `/mnt/user/backups/restore-lab/paperless` am 2026-05-07 erfolgreich validiert |
| Mealie | Borg + Dump | `/mnt/user/appdata/mealie/data`, optional `/mnt/user/appdata/mealie/postgres` bei lokalem Share-Weiterbetrieb | `mealie.dump` | `mealie_postgres_password.txt` | `mealie-postgres`, Traefik | UI startet, Rezepte vorhanden |
| Immich | Borg + Dump | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | `immich.dump` | `IMMICH_DB_PASSWORD`, `immich_postgres_password.txt` | `immich_postgres`, `immich_redis`, Traefik | UI startet, Medienbibliothek sichtbar |
| Mail-Archiver | Borg + Shared Dump | `/mnt/user/appdata/mailarchiver/data-protection-keys` | `postgresql17-mailarchiver.dump` | `MAILARCHIVER_DB_CONNECTION`, `MAILARCHIVER_AUTH_PASSWORD` | PostgreSQL 17, Traefik, Authelia | Authelia-Weiterleitung greift; nach Login startet die Web-UI und das Archiv laesst sich oeffnen |
@@ -110,8 +110,9 @@ Die Dump-Erzeugung ist host-seitig ueber `ops/borg-ui/scripts/pre-backup-dumps.s
Wenn weitere Restore-Uebungen dokumentiert werden sollen, sind diese Dienste besonders geeignet:
1. `paperless-ngx`
2. `gitea`
3. `vaultwarden`
1. `mail-archiver`
2. `paperless-ngx`
3. `gitea`
4. `vaultwarden`
Sie liefern hohen Erkenntnisgewinn ohne den kompletten Homelab-Neuaufbau zu brauchen.
+7 -2
View File
@@ -26,7 +26,9 @@ Ziel:
- `gitea-restore-test.ps1`: Gitea-Mini-Restore-Ablauf
- `gitea-plan.md`: konkreter Gitea-Testplan
- `gitea-compose.test.yml`: isolierte Testinstanz fuer Gitea
- spaeter weiterer Test fuer `paperless`
- `paperless-restore-test.ps1`: Paperless-Mini-Restore-Ablauf
- `paperless-plan.md`: konkreter Paperless-Testplan
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis
## Automatisierungsmodell
@@ -50,14 +52,17 @@ Stand nach dem ersten echten Vaultwarden-Test:
Das ist das bevorzugte Muster fuer weitere dateibasierte Restore-Tests wie `gitea`.
Fuer datenbankgestuetzte Dienste wie `paperless` kommt zusaetzlich ein isolierter Dump-Restore in Test-Postgres dazu.
## Status
Aktuell ist das erste validierte Muster vorhanden.
- echter Vaultwarden-Restore am 2026-05-07 erfolgreich verifiziert
- echter Gitea-Restore am 2026-05-07 erfolgreich verifiziert
- echter Paperless-Restore am 2026-05-07 erfolgreich verifiziert
- Restore-Lab und Report-Pfade auf dem Host angelegt
- V1-Ablauf weiter ohne `ntfy`, mit Bereinigung nach Erfolg
- `paperless` ist der naechste Referenz-Restore
- naechster grosser Kandidat ist ein weiterer datenbankgestuetzter Dienst oder die Automatisierung
Vor dem ersten echten Testlauf muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden.
@@ -0,0 +1,61 @@
services:
restoretest-paperless-postgres:
image: postgres:17.9@sha256:5b96f1a16bd9768b060dd2ffe55cb6225c4d9ef4d214a8b21eb08134869a97e4
container_name: restoretest-paperless-postgres
restart: "no"
environment:
TZ: Europe/Berlin
POSTGRES_USER: paperless
POSTGRES_DB: paperless
POSTGRES_PASSWORD: restoretest-paperless-db
PGDATA: /var/lib/postgresql/data
volumes:
- /mnt/user/backups/restore-lab/paperless/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"]
interval: 10s
timeout: 5s
retries: 10
security_opt:
- no-new-privileges:true
restoretest-paperless-redis:
image: redis:7-alpine
container_name: restoretest-paperless-redis
restart: "no"
command:
- sh
- -c
- exec redis-server --appendonly yes --requirepass "restoretest-paperless-redis"
security_opt:
- no-new-privileges:true
restoretest-paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.10@sha256:07a0b4ba01ce377c82a0636e16c0c3d931fde5b7e9304de6601986cc42d9b6e6
container_name: restoretest-paperless
restart: "no"
depends_on:
restoretest-paperless-postgres:
condition: service_healthy
restoretest-paperless-redis:
condition: service_started
environment:
PAPERLESS_TIKA_ENABLED: "0"
PAPERLESS_DBENGINE: postgresql
PAPERLESS_DBHOST: restoretest-paperless-postgres
PAPERLESS_DBNAME: paperless
PAPERLESS_DBUSER: paperless
PAPERLESS_DBPASS: restoretest-paperless-db
PAPERLESS_REDIS: redis://:restoretest-paperless-redis@restoretest-paperless-redis:6379
PAPERLESS_TIME_ZONE: Europe/Berlin
PAPERLESS_OCR_LANGUAGE: deu+eng
PAPERLESS_URL: http://127.0.0.1:18120
ports:
- "127.0.0.1:18120:8000"
volumes:
- /mnt/user/backups/restore-lab/paperless/consume:/usr/src/paperless/consume
- /mnt/user/backups/restore-lab/paperless/data:/usr/src/paperless/data
- /mnt/user/backups/restore-lab/paperless/export:/usr/src/paperless/export
- /mnt/user/backups/restore-lab/paperless/media:/usr/src/paperless/media
security_opt:
- no-new-privileges:true
+72
View File
@@ -0,0 +1,72 @@
# Paperless Restore Test Plan
## Ziel
Nachweisen, dass ein Paperless-Backup in einer isolierten Testumgebung wieder startbar ist und sowohl Dokumentenpfade als auch PostgreSQL-Dump sauber zusammenlaufen.
## Quelle
- Backup-Quelle: Borg / Share-Backup
- fachlich relevante Dateipfade:
- `/mnt/user/appdata/paperless-ngx/data`
- `/mnt/user/documents/paperless`
- `/mnt/user/documents/paperless/export`
- `/mnt/user/documents/scans_inbox`
- fachlich relevanter Dump:
- `/mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump`
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/paperless`
- Testdatenpfade:
- `/mnt/user/backups/restore-lab/paperless/data`
- `/mnt/user/backups/restore-lab/paperless/media`
- `/mnt/user/backups/restore-lab/paperless/export`
- `/mnt/user/backups/restore-lab/paperless/consume`
- `/mnt/user/backups/restore-lab/paperless/postgres`
- Testcontainer:
- `restoretest-paperless`
- `restoretest-paperless-postgres`
- `restoretest-paperless-redis`
- Testport Web: `127.0.0.1:18120:8000`
- Report-Ziel: `/mnt/user/backups/restore-reports/paperless-YYYY-MM-DD.md`
## Schutzregeln
- produktive Pfade nie beschreiben
- produktive Domain `paperless.kaleschke.info` nicht fuer die Testinstanz uebernehmen
- keine Traefik-Labels fuer die Testinstanz
- keine produktive PostgreSQL- oder Redis-Instanz fuer den Test verwenden
- Testcontainer nur gegen Restore-Lab-Daten und isolierte Test-Backends starten
## Geplanter Ablauf
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/paperless` vorbereiten
2. Paperless-Dateipfade aus Borg in das Restore-Lab wiederherstellen
3. Test-Postgres und Test-Redis mit `ops/restore-tests/paperless-compose.test.yml` starten
4. `postgresql17-paperless.dump` in Test-Postgres importieren
5. Testinstanz `restoretest-paperless` starten
6. lokalen Smoke-Test gegen `http://127.0.0.1:18120` ausfuehren
7. Report unter `/mnt/user/backups/restore-reports/` schreiben
8. Testcontainer stoppen und Testumgebung bereinigen
## Smoke-Test
Minimal erfolgreich:
- Test-Postgres startet
- Dump-Import gelingt
- Paperless-Web-UI antwortet
- mindestens ein Dokument liegt im Restore-Lab-Medienpfad
Optional spaeter:
- Login-Seite gezielt pruefen
- Dokumentanzahl aus UI oder DB querpruefen
- OCR-/Task-Worker-Status verifizieren
## Noch offen vor dem ersten echten Lauf
- exakter Borg-Restore-Befehl fuer alle vier Dateipfade
- exakter `pg_restore`-Befehl im Test-Postgres
- wie stark wir `consume` im ersten Lauf ueberhaupt brauchen
@@ -0,0 +1,38 @@
param(
[string]$BackupSource = "/mnt/user/backups/borg",
[string]$DumpSource = "/mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump",
[string]$RestoreRoot = "/mnt/user/backups/restore-lab/paperless",
[string]$ReportRoot = "/mnt/user/backups/restore-reports",
[string]$BorgPassphraseFile = "/mnt/user/appdata/secrets/borg_repo_passphrase.txt",
[switch]$WhatIf
)
$Mode = if ($WhatIf) { "WhatIf" } else { "PlanOnly" }
Write-Output "Paperless restore test scaffold"
Write-Output "BackupSource: $BackupSource"
Write-Output "DumpSource: $DumpSource"
Write-Output "RestoreRoot: $RestoreRoot"
Write-Output "ReportRoot: $ReportRoot"
Write-Output "BorgPassphraseFile: $BorgPassphraseFile"
Write-Output "Expected Borg source paths inside archive:"
Write-Output " - local/appdata/paperless-ngx/data"
Write-Output " - local/paperless/media"
Write-Output " - local/paperless/export"
Write-Output " - local/paperless/consume"
Write-Output "Mode: $Mode"
Write-Output ""
Write-Output "Planned steps:"
Write-Output "1. Prepare restore-lab target under /mnt/user/backups/restore-lab/paperless"
Write-Output "2. Restore Paperless file data into isolated test paths"
Write-Output ' Template: borg extract "$BORG_REPO" "::ARCHIVE_NAME" local/appdata/paperless-ngx/data local/paperless/media local/paperless/export local/paperless/consume'
Write-Output ' Passphrase source: $(cat /mnt/user/appdata/secrets/borg_repo_passphrase.txt)'
Write-Output "3. Start isolated test Postgres and test Redis"
Write-Output "4. Import /mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump into test Postgres"
Write-Output "5. Start restoretest-paperless against restored files and isolated DB/Redis"
Write-Output "6. Run smoke checks against the local web endpoint"
Write-Output "7. Write markdown report under /mnt/user/backups/restore-reports"
Write-Output "8. Stop test containers and clean restore data after success"
Write-Output ""
Write-Output "This script is intentionally a scaffold only."
Write-Output "No restore, no dump import, no container start, no file write is executed yet."
+115
View File
@@ -0,0 +1,115 @@
# Paperless Restore Runbook
## Vorbedingungen
- Borg-Quelle ist verfuegbar
- Borg-Passphrase-Datei vorhanden: `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
- aktueller Dump vorhanden: `/mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump`
- Testpfade unter `/mnt/user/backups/restore-lab/` und `/mnt/user/backups/restore-reports/` sind freigegeben
## Bestaetigter Host-Stand
- produktive Paperless-Daten liegen unter `/mnt/user/appdata/paperless-ngx/data`
- produktive Medien liegen unter `/mnt/user/documents/paperless`
- produktiver Exportpfad liegt unter `/mnt/user/documents/paperless/export`
- produktiver Consume-Pfad liegt unter `/mnt/user/documents/scans_inbox`
- aktueller Dump `postgresql17-paperless.dump` ist vorhanden
## Bestaetigter Teststand
- echter Mini-Restore am `2026-05-07` erfolgreich gelaufen
- Datei-Restore und Dump kamen aus dem produktiven Borg-Archiv
- Testcontainer:
- `restoretest-paperless`
- `restoretest-paperless-postgres`
- `restoretest-paperless-redis`
- Login-Seite war lokal auf `127.0.0.1:18120` erreichbar
- Dump-Import in Test-Postgres war erfolgreich
- Test-Datenbank enthielt `25` Dokumente
- Report liegt unter `/mnt/user/backups/restore-reports/paperless-2026-05-07.md`
## Platzhalter
- `ARCHIVE_NAME`: Borg-Archiv fuer den Restore-Test
- `REPORT_DATE`: z. B. `2026-05-07`
- `BORG_REPO`: Host-Borg-Repo
- `BORG_PASSPHRASE_FILE`: `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
## Ablauf
1. Testpfade vorbereiten
```bash
mkdir -p /mnt/user/backups/restore-lab/paperless/{data,media,export,consume,postgres}
mkdir -p /mnt/user/backups/restore-reports
rm -rf /mnt/user/backups/restore-lab/paperless/data/*
rm -rf /mnt/user/backups/restore-lab/paperless/media/*
rm -rf /mnt/user/backups/restore-lab/paperless/export/*
rm -rf /mnt/user/backups/restore-lab/paperless/consume/*
rm -rf /mnt/user/backups/restore-lab/paperless/postgres/*
```
2. Paperless-Dateipfade aus Borg in das Restore-Lab extrahieren
```bash
export BORG_REPO='...'
export BORG_PASSPHRASE="$(cat /mnt/user/appdata/secrets/borg_repo_passphrase.txt)"
borg list "$BORG_REPO"
cd /mnt/user/backups/restore-lab/paperless
borg extract "$BORG_REPO" "::ARCHIVE_NAME" local/appdata/paperless-ngx/data local/paperless/media local/paperless/export local/paperless/consume
mv /mnt/user/backups/restore-lab/paperless/local/appdata/paperless-ngx/data /mnt/user/backups/restore-lab/paperless/data
mv /mnt/user/backups/restore-lab/paperless/local/paperless/media /mnt/user/backups/restore-lab/paperless/media
mv /mnt/user/backups/restore-lab/paperless/local/paperless/export /mnt/user/backups/restore-lab/paperless/export
mv /mnt/user/backups/restore-lab/paperless/local/paperless/consume /mnt/user/backups/restore-lab/paperless/consume
```
3. Test-Postgres und Test-Redis starten
```bash
docker compose -f /mnt/user/services/homelab/ops/restore-tests/paperless-compose.test.yml up -d restoretest-paperless-postgres restoretest-paperless-redis
```
4. Dump in Test-Postgres importieren
```bash
docker exec -i restoretest-paperless-postgres pg_restore -U paperless -d paperless < /mnt/user/backups/borg/dumps/latest/postgresql17-paperless.dump
```
5. Testinstanz starten
```bash
docker compose -f /mnt/user/services/homelab/ops/restore-tests/paperless-compose.test.yml up -d restoretest-paperless
```
6. Smoke-Test
```bash
curl -I http://127.0.0.1:18120
docker logs restoretest-paperless --tail 50
find /mnt/user/backups/restore-lab/paperless/media -type f | head -n 10
```
Minimal erfolgreich:
- Dump-Import gelingt
- HTTP-Antwort kommt
- Dokumentenpfad ist im Restore-Lab befuellt
7. Testcontainer wieder stoppen
```bash
docker compose -f /mnt/user/services/homelab/ops/restore-tests/paperless-compose.test.yml down
```
8. Testdaten nach erfolgreichem Lauf bereinigen
```bash
rm -rf /mnt/user/backups/restore-lab/paperless
```
## Festgelegte Entscheidungen
- Paperless nutzt fuer Restore-Tests immer isoliertes Test-Postgres und Test-Redis.
- Testdaten werden nach erfolgreichem Lauf geloescht.
- `ntfy` wird nicht im ersten echten Lauf eingebunden.
- Die Borg-Passphrase wird fuer Restore-Tests aus einer Host-Secret-Datei gelesen, nicht aus Borg-UI-Interna.