Prepare Immich restore smoke test

This commit is contained in:
2026-05-26 21:33:01 +02:00
parent 48099fb48d
commit c5d231a0db
14 changed files with 616 additions and 8 deletions
+7 -1
View File
@@ -32,6 +32,11 @@ Ziel:
- `paperless-restore-test.sh`: hosttauglicher Paperless-Restore-Job
- `paperless-plan.md`: konkreter Paperless-Testplan
- `paperless-compose.test.yml`: isolierte Testinstanz fuer Paperless inkl. Test-Postgres und Test-Redis
- `immich-restore-test.ps1`: Immich-Mini-Restore-Ablauf als Plan-/Windows-Scaffold
- `immich-restore-test.sh`: hosttauglicher Immich-Restore-Job, erster echter Lauf noch offen
- `immich-plan.md`: konkreter Immich-Testplan
- `immich-runbook.md`: Operator-Runbook fuer den ersten Immich-Lauf
- `immich-compose.test.yml`: isolierte Testinstanz fuer Immich inkl. pgvecto-rs Test-Postgres und Test-Redis
- `check-restore-freshness.ps1`: woechentlicher Frische-Check fuer Dumps und Reports
- `run-restore-checks.ps1`: einfacher Dispatcher fuer Restore-Jobs
- `check-restore-freshness.sh`: hosttauglicher Frische-Check
@@ -76,9 +81,10 @@ 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
- Immich-Restore-Test am 2026-05-26 vorbereitet; erster echter Lauf mit Report steht noch aus
- Bash-Dispatcher und Bash-Restore-Jobs am 2026-05-07 erfolgreich hostseitig verifiziert
- Restore-Lab und Report-Pfade auf dem Host angelegt
- V1-Ablauf weiter ohne `ntfy`, mit Bereinigung nach Erfolg
- naechster grosser Kandidat ist ein weiterer datenbankgestuetzter Dienst oder die Automatisierung
- naechster grosser Kandidat ist der erste echte Immich-Lauf mit Zeitmessung; erst danach in die Rotation aufnehmen
Vor dem ersten echten Testlauf muessen Zielpfade, Quellpfade und Bereinigungsschritte bewusst freigegeben werden.
+67
View File
@@ -0,0 +1,67 @@
services:
restoretest-immich-postgres:
# gleiches Image wie Produktion, damit pgvecto-rs / pgvector-Extensions
# beim Restore aus immich.dump verfuegbar sind
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
container_name: restoretest-immich-postgres
restart: "no"
environment:
TZ: Europe/Berlin
POSTGRES_USER: immich
POSTGRES_DB: immich
POSTGRES_PASSWORD: restoretest-immich-db
PGDATA: /var/lib/postgresql/data
volumes:
- /mnt/user/backups/restore-lab/immich/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U immich -d immich"]
interval: 10s
timeout: 5s
retries: 12
security_opt:
- no-new-privileges:true
restoretest-immich-redis:
image: redis:7.4-alpine
container_name: restoretest-immich-redis
restart: "no"
command:
- redis-server
- --save
- ""
- --appendonly
- "no"
security_opt:
- no-new-privileges:true
restoretest-immich-server:
# gleiches Image wie Produktion; ML-Container bleibt bewusst weg,
# weil der Smoke-Test nur Login-Page, DB-Restore und Asset-Count prueft.
image: ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5
container_name: restoretest-immich-server
restart: "no"
depends_on:
restoretest-immich-postgres:
condition: service_healthy
restoretest-immich-redis:
condition: service_started
environment:
DB_HOSTNAME: restoretest-immich-postgres
DB_USERNAME: immich
DB_PASSWORD: restoretest-immich-db
DB_DATABASE_NAME: immich
REDIS_HOSTNAME: restoretest-immich-redis
# ML bewusst deaktiviert: Endpoint zeigt auf eine lokale, nicht
# erreichbare URL. Immich-Server startet, ML-Features bleiben aus.
IMMICH_MACHINE_LEARNING_URL: http://restoretest-immich-ml-disabled:9999
TZ: Europe/Berlin
ports:
# nur 127.0.0.1 - keine Public-Route, keine Traefik-Labels
- "127.0.0.1:12283:2283"
volumes:
# Test-Upload-Verzeichnis ist leer und liegt im Restore-Lab.
# Produktive Assets unter /mnt/user/photos/immich werden NICHT eingebunden,
# damit der Smoke-Test keine produktiven Daten anfasst.
- /mnt/user/backups/restore-lab/immich/upload:/usr/src/app/upload
security_opt:
- no-new-privileges:true
+89
View File
@@ -0,0 +1,89 @@
# Immich Restore Test Plan
## Ziel
Nachweisen, dass `immich.dump` aus dem produktiven Borg-Archiv in einer isolierten Testumgebung wieder einspielbar ist und Immich-Server damit anlaufen, einloggen und Asset-Metadaten anzeigen kann.
Bewusst **nicht** Teil dieses Tests:
- Wiederherstellung produktiver Foto-Dateien aus `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive`. Der Smoke-Test bleibt DB-/UI-zentriert.
- Machine-Learning-Container. Spart Image-Pull-Zeit und Resource-Last; ML-Features sind im Smoke-Test nicht erforderlich.
- Echte Browser-Login-Sequenz. Smoke-Test prueft nur, dass die Login-Seite ausgeliefert wird und die DB-Tabellen `assets` und `users` lesbar sind.
## Quelle
- Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical` oder lokales Mirror)
- fachlich relevanter Dump im Archiv:
- `local/borg-dumps/latest/immich.dump`
- Erzeuger: `ops/borg-ui/scripts/pre-backup-dumps.sh`, Funktion `dump_pg_db immich_postgres ... immich immich` mit `pg_dump -Fc`
- produktive Foto-Pfade werden im Smoke-Test bewusst **nicht** angefasst
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/immich`
- Testdatenpfade:
- `/mnt/user/backups/restore-lab/immich/postgres` (Test-Postgres-Datadir)
- `/mnt/user/backups/restore-lab/immich/upload` (leeres Upload-Volume, Immich-Server braucht den Pfad nur als Mountpoint)
- `/mnt/user/backups/restore-lab/immich/dumps/latest/immich.dump` (extrahierter Dump)
- Testcontainer:
- `restoretest-immich-server`
- `restoretest-immich-postgres` (`tensorchord/pgvecto-rs:pg14-v0.2.0` - identisch zur Produktion, weil der Dump pgvecto-rs Extension referenziert)
- `restoretest-immich-redis` (`redis:7.4-alpine`, rebuildbar)
- Testport Web: `127.0.0.1:12283:2283`
- Report-Ziel: `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
## Schutzregeln
- produktive Pfade `/mnt/user/photos/immich` und `/mnt/user/photos/family_archive` werden **nicht** in den Test-Container gemountet
- produktive Domain `immich.kaleschke.info` wird **nicht** uebernommen
- keine Traefik-Labels fuer die Testinstanz
- keine produktive `immich_postgres`-/`immich_redis`-Instanz fuer den Test verwenden
- ML-Container bleibt weg
- Testcontainer publishen nur auf `127.0.0.1`, nicht auf LAN- oder Tailscale-Interface
- Borg-Passphrase wird aus `/mnt/user/appdata/secrets/borg_repo_passphrase.txt` gelesen und niemals in Logs, Reports oder Doku geschrieben
## Geplanter Ablauf
1. Restore-Ziel unter `/mnt/user/backups/restore-lab/immich` vorbereiten (postgres, upload, dumps/latest)
2. `local/borg-dumps/latest/immich.dump` aus dem aktuellsten Borg-Archiv extrahieren
3. Test-Postgres (`pgvecto-rs`) und Test-Redis mit `ops/restore-tests/immich-compose.test.yml` starten
4. `immich.dump` in Test-Postgres importieren (`pg_restore -Fc --clean --if-exists --no-owner --no-privileges`)
5. Testinstanz `restoretest-immich-server` starten
6. lokalen Smoke-Test gegen `http://127.0.0.1:12283` ausfuehren und Asset/User-Count aus DB lesen
7. Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md` schreiben
8. Testcontainer stoppen und Restore-Lab bereinigen
## Smoke-Test
Minimal erfolgreich:
- Test-Postgres startet `healthy`
- `pg_restore -Fc` laeuft ohne Fehler durch
- Immich-Server liefert HTTP `200`, `302` oder `303` auf `/`
- Response enthaelt mindestens einen der Marker `Immich`, `Login`, `Signin`
- `select count(*) from assets;` und `select count(*) from users;` sind lesbar
Optional spaeter:
- Echte Login-Form via API ansprechen
- pgvecto-rs Extension explizit per `\dx` pruefen
- Test mit gemountetem **read-only** Foto-Sample-Pfad und Thumbnail-Rendering
- Test inkl. ML-Container, sobald genug Test-Ressourcen verfuegbar
## Bekannte Komplikationen
| Risiko | Beschreibung | Mitigation |
|---|---|---|
| Dump-Groesse unbekannt | `pg_dump -Fc` der Immich-DB kann je nach Asset-/Face-Tabellen mehrere GB sein | Erster Lauf bewusst mit `--what-if`, anschliessend Operator-Test mit Zeitmessung |
| `pg_restore`-Dauer unbekannt | Index-/Constraint-Aufbau und pgvecto-rs-Index-Build koennen lange dauern | Test-Postgres mit Health-Polling startet; Lauf nicht abbrechen ohne `pg_restore`-Exit |
| pgvecto-rs Extension-Mismatch | Wenn das pgvecto-rs-Image im Test nicht exakt dieselbe Version wie Produktion ist, kann `CREATE EXTENSION vectors` im Restore fehlschlagen | Compose pinnt denselben Digest wie `apps/immich/docker-compose.yml` |
| Immich-Server-Migrations beim Start | Immich fuehrt beim ersten Start DB-Migrations aus; das kann nach Restore noch laufen, bevor Web-UI antwortet | Smoke-Test pollt HTTP bis zu 120 s, bevor er als Fehler markiert |
| Asset-Files fehlen | Der Test mountet kein Foto-Volume; Immich zeigt "missing" auf Asset-Detail-Seiten | Smoke-Test prueft nur Login-Page und DB-Counts, nicht Asset-Rendering |
| ML-Endpoint unreachable | Immich-Server kann ML-Endpoint nicht erreichen | `IMMICH_MACHINE_LEARNING_URL` zeigt bewusst auf einen nicht erreichbaren Hostnamen; Login bleibt funktional, ML-Features bleiben deaktiviert |
## Noch offen vor dem ersten echten Lauf
- Dump-Groesse `immich.dump` auf dem Host bestimmen (`ls -lh /mnt/user/backups/borg/dumps/latest/immich.dump`)
- Erwartete Restore-Dauer durch ersten Lauf mit `--keep-data` messen
- Pruefen, ob die Immich-Tabellen `assets`/`users` im aktuellen Schema noch existieren (Schema-Drift bei Major-Update wuerde die Asset-Count-Query brechen, das Skript faengt das tolerant ab)
- Schedule-Eintrag in `ops/restore-tests/schedule.md`: aktuell ist Immich nur als "spaeter, eigener Sprint" gefuehrt. Erst nach erstem erfolgreichen Lauf in Schedule aufnehmen, z. B. quartalsweise.
+44
View File
@@ -0,0 +1,44 @@
param(
[string]$BackupSource = "/mnt/user/backups/borg",
[string]$DumpSource = "/mnt/user/backups/borg/dumps/latest/immich.dump",
[string]$RestoreRoot = "/mnt/user/backups/restore-lab/immich",
[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 "Immich 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/borg-dumps/latest/immich.dump"
Write-Output ""
Write-Output "Planned isolation:"
Write-Output " - Test Postgres: tensorchord/pgvecto-rs:pg14-v0.2.0 (same as production)"
Write-Output " - Test Redis: redis:7.4-alpine (rebuildable, no restore needed)"
Write-Output " - Test Server: ghcr.io/immich-app/immich-server:release (pinned digest like production)"
Write-Output " - ML container: deliberately omitted"
Write-Output " - Test endpoint: 127.0.0.1:12283 (no Traefik, no public domain)"
Write-Output " - Productive photo paths under /mnt/user/photos/* will NOT be mounted"
Write-Output "Mode: $Mode"
Write-Output ""
Write-Output "Planned steps:"
Write-Output "1. Prepare restore-lab target under /mnt/user/backups/restore-lab/immich"
Write-Output "2. Extract immich.dump from current Borg archive into test path"
Write-Output ' Template: borg extract "$BORG_REPO" "::ARCHIVE_NAME" local/borg-dumps/latest/immich.dump'
Write-Output ' Passphrase source: $(cat /mnt/user/appdata/secrets/borg_repo_passphrase.txt)'
Write-Output "3. Start isolated test Postgres (pgvecto-rs) and test Redis"
Write-Output "4. Import immich.dump into test Postgres with pg_restore -Fc --clean --if-exists --no-owner --no-privileges"
Write-Output "5. Start restoretest-immich-server against isolated DB/Redis (ML omitted)"
Write-Output "6. Run smoke checks against http://127.0.0.1:12283 and DB asset count"
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."
Write-Output "Actual run happens on the Unraid host via ops/restore-tests/immich-restore-test.sh"
+172
View File
@@ -0,0 +1,172 @@
#!/bin/bash
set -euo pipefail
# Immich Restore Smoke Test
#
# Nicht-destruktiver Restore-Smoke-Test fuer Immich.
# - liest immich.dump aus dem produktiven Borg-Archiv
# - importiert in eine isolierte Test-Postgres-Instanz mit gleichem Image
# wie Produktion (tensorchord/pgvecto-rs)
# - startet einen isolierten Immich-Server-Container ohne Traefik und
# ohne ML-Container
# - prueft Login-Page und Asset-Anzahl aus DB
# - bereinigt anschliessend
#
# Produktiver Immich-Stack wird NICHT angefasst.
# Produktive Foto-Pfade unter /mnt/user/photos/* werden NICHT gemountet.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
WHATIF=0
KEEP_DATA=0
for arg in "$@"; do
case "$arg" in
--what-if) WHATIF=1 ;;
--keep-data) KEEP_DATA=1 ;;
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
esac
done
RESTORE_ROOT="/mnt/user/backups/restore-lab/immich"
REPORT_ROOT="/mnt/user/backups/restore-reports"
EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/immich-extract"
COMPOSE_FILE="$SCRIPT_DIR/immich-compose.test.yml"
REPORT_FILE="$REPORT_ROOT/immich-$(date +%F).md"
if [ "$WHATIF" -eq 1 ]; then
cat <<EOF
Immich restore test
Mode: WhatIf
RestoreRoot: $RESTORE_ROOT
ReportRoot: $REPORT_ROOT
Expected Borg source paths:
- local/borg-dumps/latest/immich.dump
Planned isolation:
- Test-Postgres: tensorchord/pgvecto-rs:pg14-v0.2.0
- Test-Redis: redis:7.4-alpine (rebuildbar, kein Restore)
- Test-Server: ghcr.io/immich-app/immich-server:release (Image-Pin wie Produktion)
- ML-Container bewusst weggelassen
- Test-Upload: leer, unter $RESTORE_ROOT/upload
- Productive photo paths NOT mounted: /mnt/user/photos/immich, /mnt/user/photos/family_archive
- Test endpoint: 127.0.0.1:12283 (no Traefik, no public domain)
Smoke-Test:
- Test-Postgres healthy
- pg_restore -Fc -> immich.dump
- HTTP 200/302/3xx von 127.0.0.1:12283
- Asset-Count aus DB
EOF
exit 0
fi
require_cmd docker
require_cmd curl
require_path "$BORG_PASSPHRASE_FILE_DEFAULT"
require_path "$COMPOSE_FILE"
cleanup() {
cleanup_compose "$COMPOSE_FILE"
if [ "$KEEP_DATA" -ne 1 ]; then
rm -rf "$RESTORE_ROOT"
fi
rm -rf "$EXTRACT_DIR"
}
trap cleanup EXIT
rm -rf "$EXTRACT_DIR" "$RESTORE_ROOT"
mkdir -p "$RESTORE_ROOT/postgres" "$RESTORE_ROOT/upload" "$RESTORE_ROOT/dumps/latest"
archive="$(latest_archive_name)"
repo="$(borg_repo_url)"
borg_extract "/restore/immich-extract" \
"local/borg-dumps/latest/immich.dump"
mv "$EXTRACT_DIR/local/borg-dumps/latest/immich.dump" "$RESTORE_ROOT/dumps/latest/immich.dump"
# Stufe 1: Test-Postgres und Test-Redis starten
docker compose -f "$COMPOSE_FILE" up -d \
restoretest-immich-postgres restoretest-immich-redis >/dev/null
# Warten auf Postgres ready
until docker exec restoretest-immich-postgres pg_isready -U immich -d immich >/dev/null 2>&1; do
sleep 2
done
# Stufe 2: Dump in Test-Postgres importieren
# Hinweis: pg_restore mit --clean --if-exists, damit die Operation idempotent ist.
# --no-owner / --no-privileges, weil im Test-Postgres kein produktiver User existiert.
docker exec -i restoretest-immich-postgres \
pg_restore -U immich -d immich --clean --if-exists --no-owner --no-privileges \
< "$RESTORE_ROOT/dumps/latest/immich.dump"
# Stufe 3: Immich-Server starten (ohne ML)
docker compose -f "$COMPOSE_FILE" up -d restoretest-immich-server >/dev/null
# Immich-Server braucht beim ersten Start einige Sekunden fuer DB-Migrations-Checks.
# Wir geben ihm bis zu 120s und pollen den HTTP-Endpunkt.
http_status=""
for _ in $(seq 1 60); do
http_status="$(curl -s -o /tmp/immich-body.html -w '%{http_code}' -L http://127.0.0.1:12283 || true)"
if [ "$http_status" = "200" ] || [ "$http_status" = "302" ] || [ "$http_status" = "303" ]; then
break
fi
sleep 2
done
# Body-Check: Immich-UI hat typische Marker. Wir matchen tolerant.
body_check="ok"
if ! grep -qiE "immich|login|signin" /tmp/immich-body.html 2>/dev/null; then
body_check="missing-marker"
fi
# Asset-Count aus DB. Wenn die Spalte nicht existiert (Schema-Drift),
# wird das im Report sichtbar gemacht statt das Skript zu killen.
asset_count="$(docker exec restoretest-immich-postgres \
psql -U immich -d immich -tAc "select count(*) from assets;" 2>/dev/null \
| tr -d '[:space:]' || true)"
if [ -z "$asset_count" ]; then
asset_count="n/a"
fi
# User-Count als zusaetzlicher DB-Sanity-Check
user_count="$(docker exec restoretest-immich-postgres \
psql -U immich -d immich -tAc "select count(*) from users;" 2>/dev/null \
| tr -d '[:space:]' || true)"
if [ -z "$user_count" ]; then
user_count="n/a"
fi
write_report "$REPORT_FILE" <<EOF
# Immich Restore Test Report - $(date +%F)
- Service: \`immich\`
- Source repo: \`$repo\`
- Archive: \`$archive\`
- Restore root: \`$RESTORE_ROOT\`
- Test containers:
- \`restoretest-immich-server\`
- \`restoretest-immich-postgres\` (tensorchord/pgvecto-rs:pg14-v0.2.0)
- \`restoretest-immich-redis\`
- Test endpoint: \`http://127.0.0.1:12283\`
- ML container: deliberately omitted
- Result: \`SUCCESS\`
## Checks
- Borg extract of \`immich.dump\`: \`ok\`
- Dump import into isolated Postgres: \`ok\`
- HTTP status after redirect: \`$http_status\`
- Login page marker: \`$body_check\`
- Asset count in test DB: \`$asset_count\`
- User count in test DB: \`$user_count\`
## Notes
- Test ran without Traefik and without the productive domain.
- Productive photo paths under /mnt/user/photos/* were NOT mounted.
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
- Restore-Quelle Dump: \`local/borg-dumps/latest/immich.dump\` aus aktuellem Borg-Archiv.
EOF
echo "Immich restore test ok -> $REPORT_FILE"
+128
View File
@@ -0,0 +1,128 @@
# Immich Restore Runbook
## Status
Skript und Test-Compose sind vorbereitet. **Erster echter Lauf steht noch aus.**
Vor dem ersten Lauf muss Operator entscheiden:
- ist genug freier Platz unter `/mnt/user/backups/restore-lab/immich` vorhanden (Dump + Test-Postgres-Datadir + Upload-Dummy)?
- ist genug freier RAM/CPU verfuegbar, um Immich-Server + Test-Postgres parallel zur produktiven Last laufen zu lassen?
- soll der Lauf zuerst mit `--what-if` ausgefuehrt werden, dann mit `--keep-data` zur Zeitmessung?
## Vorbedingungen
- Borg-Quelle ist verfuegbar
- Borg-UI laeuft (`docker ps | grep borg-ui`)
- Borg-Passphrase-Datei vorhanden: `/mnt/user/appdata/secrets/borg_repo_passphrase.txt`
- aktueller Dump `immich.dump` ist Teil des letzten Borg-Archivs (siehe `pre-backup-dumps.sh`)
- Testpfade unter `/mnt/user/backups/restore-lab/` und `/mnt/user/backups/restore-reports/` sind freigegeben
- produktiver Immich-Stack laeuft (oder ist bewusst aus); der Test ist davon unabhaengig
## Bestaetigter Host-Stand (Soll)
- produktiver Immich-Server: `immich_server` Container mit Image `ghcr.io/immich-app/immich-server:release@sha256:c15bff75068effb03f4355997d03dc7e0fc58720c2b54ad6f7f10d1bc57efaa5`
- produktive Postgres: `immich_postgres` mit `tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52`
- produktive Foto-Pfade: `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive`
- aktueller Dump-Pfad: `/mnt/user/backups/borg/dumps/latest/immich.dump`
- Secret: `/mnt/user/appdata/secrets/immich_postgres_password.txt` (wird vom Test **nicht** gebraucht; Test nutzt eigenes Test-Passwort)
## Bestaetigter Teststand
- noch kein echter Mini-Restore gelaufen
- Skript-Set vorbereitet:
- `ops/restore-tests/immich-compose.test.yml`
- `ops/restore-tests/immich-restore-test.sh`
- `ops/restore-tests/immich-restore-test.ps1` (Scaffold, kein Live-Run)
- `ops/restore-tests/immich-plan.md`
- `ops/restore-tests/immich-runbook.md`
## Erster Lauf - trockene Variante
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/immich-restore-test.sh --what-if
```
Erwartete Ausgabe: nur Plan-Output, kein Docker-Start, kein Borg-Extract.
## Erster Lauf - echter Test (Operator-freigegeben)
```bash
# Optional: laufende Borg-Jobs pruefen, damit Borg-UI nicht parallel ausgelastet ist
docker exec borg-ui sqlite3 /data/borg.db \
"select status, archive_name, datetime(updated_at,'unixepoch') from backup_jobs order by id desc limit 5;"
# Lauf mit Datenerhalt fuer Zeitmessung
bash /mnt/user/services/homelab-infra/ops/restore-tests/immich-restore-test.sh --keep-data
```
Bei erfolgreichem Lauf:
- Report unter `/mnt/user/backups/restore-reports/immich-YYYY-MM-DD.md`
- Test-Container `restoretest-immich-*` sind nach Lauf bereits `down`
- Restore-Lab-Daten bleiben mit `--keep-data` erhalten; ohne Flag werden sie geloescht
## Smoke-Test-Pruefungen
Minimal erwartet im Report:
- `HTTP status after redirect: 200|302|303`
- `Login page marker: ok`
- `Asset count in test DB`: Zahl, oder `n/a` bei Schema-Drift
- `User count in test DB`: Zahl, oder `n/a` bei Schema-Drift
- Pre-Dump-Hook-Kette (`pg_dump -Fc`) und Restore-Kette (`pg_restore -Fc`) sind kompatibel
- pgvecto-rs Extension ist im Restore-DB sichtbar
Manuelle Folgepruefung (optional):
Das Skript stoppt die Test-Container auch bei `--keep-data`; dieses Flag erhaelt nur die Restore-Lab-Daten. Fuer eine manuelle Folgepruefung nach einem erfolgreichen `--keep-data`-Lauf die Testinstanz kurz wieder hochfahren und danach wieder stoppen:
```bash
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/immich-compose.test.yml up -d \
restoretest-immich-postgres restoretest-immich-redis restoretest-immich-server
docker exec restoretest-immich-postgres psql -U immich -d immich -c "\dx"
docker exec restoretest-immich-postgres psql -U immich -d immich -tAc "select count(*) from assets;"
docker logs --tail 100 restoretest-immich-server
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/immich-compose.test.yml down
```
## Cleanup nach Lauf ohne `--keep-data`
Das Skript bereinigt:
- Test-Container via `docker compose down`
- Restore-Lab unter `/mnt/user/backups/restore-lab/immich`
- Extract-Cache unter `/mnt/user/appdata/borg-ui/restore/immich-extract`
**Vorsicht:** `rm -rf` arbeitet ausschliesslich auf dem festen Restore-Lab-Pfad. Produktive Immich-Pfade unter `/mnt/user/photos/*` werden vom Skript niemals beschrieben.
## Fehlerfaelle
| Symptom | Ursache | Massnahme |
|---|---|---|
| `pg_restore: error: could not find extension ... vectors` | Test-Postgres-Image nicht pgvecto-rs | Compose-Pin im Test-Compose pruefen |
| HTTP-Timeout nach 120 s | Immich-Migrations laufen noch | Wartezeit im Skript erhoehen oder Logs pruefen |
| `pg_isready` nie healthy | Test-Postgres bricht beim Start ab (Datadir-Konflikt) | Restore-Lab vor Lauf vollstaendig leer; `docker logs restoretest-immich-postgres` |
| Body matcht keine Marker | Immich UI hat sich versioniert; Marker-Liste anpassen | Marker im Skript erweitern (`grep -qiE`) |
| Disk-Space-Mangel | Dump + Postgres-Datadir + Extract-Cache | mehr Platz freigeben oder Lauf abbrechen |
## Schedule-Eintrag (geplant, noch nicht aktiv)
Aktuell in `ops/restore-tests/schedule.md` nur als "spaeter, eigener Sprint" gelistet.
Nach erstem erfolgreichen Lauf vorschlagen:
- quartalsweise (`0 9 1 1,4,7,10 *` o. ae.)
- ohne `--keep-data`
- Report wird automatisch von `monthly-random-restore.sh` mit eingelesen, sobald Immich dort eintragbar ist
## Festgelegte Entscheidungen
- Immich-Restore-Test nutzt isoliertes Test-Postgres mit gleichem Image wie Produktion.
- ML-Container wird im Smoke-Test **nicht** mitgestartet.
- Produktive Foto-Pfade werden **nicht** in den Test gemountet.
- Test-Daten werden nach erfolgreichem Lauf geloescht (`--keep-data` ueberschreibt das).
- Borg-Passphrase wird aus Host-Secret-Datei gelesen und nirgendwo geloggt.
- `ntfy` wird im ersten echten Lauf nicht eingebunden.
+9 -1
View File
@@ -1,5 +1,5 @@
param(
[ValidateSet("freshness","vaultwarden","gitea","paperless")]
[ValidateSet("freshness","vaultwarden","gitea","paperless","immich")]
[string]$Mode,
[switch]$WhatIf
)
@@ -35,4 +35,12 @@ switch ($Mode) {
}
exit $LASTEXITCODE
}
"immich" {
if ($WhatIf) {
& (Join-Path $base "immich-restore-test.ps1") -WhatIf
} else {
& (Join-Path $base "immich-restore-test.ps1")
}
exit $LASTEXITCODE
}
}
+7 -1
View File
@@ -28,8 +28,14 @@ case "$MODE" in
fi
exec "$SCRIPT_DIR/paperless-restore-test.sh"
;;
immich)
if [ "$WHATIF" = "--what-if" ]; then
exec "$SCRIPT_DIR/immich-restore-test.sh" --what-if
fi
exec "$SCRIPT_DIR/immich-restore-test.sh"
;;
*)
echo "Usage: $0 {freshness|vaultwarden|gitea|paperless} [--what-if]" >&2
echo "Usage: $0 {freshness|vaultwarden|gitea|paperless|immich} [--what-if]" >&2
exit 1
;;
esac
@@ -7,7 +7,7 @@ SUCCESS_TOPIC="${2:-${RESTORE_SUCCESS_TOPIC:-homelab-info}}"
FAILURE_TOPIC="${RESTORE_FAILURE_TOPIC:-homelab-alerts}"
if [ -z "$MODE" ]; then
echo "Usage: $0 <freshness|vaultwarden|gitea|paperless> [success_topic]" >&2
echo "Usage: $0 <freshness|vaultwarden|gitea|paperless|immich> [success_topic]" >&2
exit 1
fi
+1 -1
View File
@@ -35,7 +35,7 @@ Quartalsweise:
Spaeter:
- `immich` als eigener Sprint
- `immich` Restore-Smoke-Test ist vorbereitet, aber noch nicht in der Rotation. Erst manuell mit `--what-if`, dann mit `--keep-data` ausfuehren; nach erfolgreichem Report quartalsweise einplanen.
## Konkreter Kalender