# Immich Restore Runbook ## Status Skript und Test-Compose sind vorbereitet. **Erstlauf 2026-05-27 erfolgreich** (`SUCCESS`, HTTP `200`, `11977` Assets im Test-DB-Check). Report: `/mnt/user/backups/restore-reports/immich-2026-05-27.md`. Folgelaeufe je Quartal gemaess `docs/RESTORE_DRILL_ROUTINE.md` (Q2 = Immich). 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 `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23` - 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 - VectorChord-/pgvector-Extensions sind in der 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 asset;" 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 ... vector/vchord` | Test-Postgres-Image passt nicht zur Produktion | 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.