From 2cc39c73f6632fd578d2d12355a3b7b3f8ea481a Mon Sep 17 00:00:00 2001 From: Micha Date: Thu, 7 May 2026 11:01:27 +0200 Subject: [PATCH] Add validated Paperless restore test pattern --- docs/DISASTER_RECOVERY.md | 2 +- docs/MIGRATION_LOG.md | 10 ++ docs/RESTORE_MATRIX.md | 9 +- ops/restore-tests/README.md | 9 +- ops/restore-tests/paperless-compose.test.yml | 61 ++++++++++ ops/restore-tests/paperless-plan.md | 72 ++++++++++++ ops/restore-tests/paperless-restore-test.ps1 | 38 ++++++ ops/restore-tests/paperless-runbook.md | 115 +++++++++++++++++++ 8 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 ops/restore-tests/paperless-compose.test.yml create mode 100644 ops/restore-tests/paperless-plan.md create mode 100644 ops/restore-tests/paperless-restore-test.ps1 create mode 100644 ops/restore-tests/paperless-runbook.md diff --git a/docs/DISASTER_RECOVERY.md b/docs/DISASTER_RECOVERY.md index 34547fc..4c40180 100644 --- a/docs/DISASTER_RECOVERY.md +++ b/docs/DISASTER_RECOVERY.md @@ -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 --- diff --git a/docs/MIGRATION_LOG.md b/docs/MIGRATION_LOG.md index dbd5281..33b5a24 100644 --- a/docs/MIGRATION_LOG.md +++ b/docs/MIGRATION_LOG.md @@ -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. diff --git a/docs/RESTORE_MATRIX.md b/docs/RESTORE_MATRIX.md index a5d769b..78e54de 100644 --- a/docs/RESTORE_MATRIX.md +++ b/docs/RESTORE_MATRIX.md @@ -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. diff --git a/ops/restore-tests/README.md b/ops/restore-tests/README.md index 3ee63e7..4f42fbb 100644 --- a/ops/restore-tests/README.md +++ b/ops/restore-tests/README.md @@ -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. diff --git a/ops/restore-tests/paperless-compose.test.yml b/ops/restore-tests/paperless-compose.test.yml new file mode 100644 index 0000000..00d65da --- /dev/null +++ b/ops/restore-tests/paperless-compose.test.yml @@ -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 diff --git a/ops/restore-tests/paperless-plan.md b/ops/restore-tests/paperless-plan.md new file mode 100644 index 0000000..fd31381 --- /dev/null +++ b/ops/restore-tests/paperless-plan.md @@ -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 diff --git a/ops/restore-tests/paperless-restore-test.ps1 b/ops/restore-tests/paperless-restore-test.ps1 new file mode 100644 index 0000000..afc4190 --- /dev/null +++ b/ops/restore-tests/paperless-restore-test.ps1 @@ -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." diff --git a/ops/restore-tests/paperless-runbook.md b/ops/restore-tests/paperless-runbook.md new file mode 100644 index 0000000..b69a72c --- /dev/null +++ b/ops/restore-tests/paperless-runbook.md @@ -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.