Add Komodo bootstrap dry-run scaffold (F-09 rest)

Mirror of the Immich restore-test pattern for the Komodo bootstrap
anchor. Brings up a throwaway komodo-mongo + komodo-core +
komodo-periphery under project restoretest-komodo, isolated from
production:

- same image digests as production (mongo:7.0.32, komodo-core:2,
  komodo-periphery:2) to prove compose-level bootstrap compatibility
- restore-lab paths under /mnt/user/backups/restore-lab/komodo
- 127.0.0.1:19120 only, no LAN bind, no Traefik, no Authelia
- test periphery runs WITHOUT docker.sock mount and WITHOUT
  /mnt/user/services mount; cannot manage productive containers
- KOMODO_* secrets are throwaway placeholders hardcoded in the test
  compose; productive secrets never enter this path

Smoke test: compose config valid, mongo healthy, mongo auth-ping
with test creds, komodo-core HTTP 200/302/303/401, periphery
container running. Report under restore-reports/komodo-bootstrap-*.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 15:25:41 +02:00
parent 1a4929f9ef
commit e4b0db2af6
4 changed files with 389 additions and 0 deletions
@@ -0,0 +1,77 @@
services:
# Wegwerf-Mongo fuer Komodo-Bootstrap-Trockenlauf.
# Schreibt in den Restore-Lab-Pfad, NICHT in das produktive
# /mnt/user/appdata/komodo/mongo-Volume.
restoretest-komodo-mongo:
image: mongo:7.0.32@sha256:32979a1189dfdc44da3f5ed40d910495f5ad8f6f7f77556646f890a30b2d3f56
container_name: restoretest-komodo-mongo
restart: "no"
command: --quiet
environment:
MONGO_INITDB_ROOT_USERNAME: komodo
MONGO_INITDB_ROOT_PASSWORD: restoretest-komodo-mongo-pwd
volumes:
- /mnt/user/backups/restore-lab/komodo/mongo:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
security_opt:
- no-new-privileges:true
restoretest-komodo-core:
# Selbes Image wie Produktion, damit Compose-Diff Bootstrap-Kompatibilitaet
# nachweist.
image: ghcr.io/moghtech/komodo-core:2@sha256:8a7dbba232e4e49797bb412be5f78207c89fcf22cc2727b38631ae30f7518a4c
container_name: restoretest-komodo-core
init: true
restart: "no"
depends_on:
restoretest-komodo-mongo:
condition: service_healthy
volumes:
- /mnt/user/backups/restore-lab/komodo/core:/repo-cache
- /mnt/user/backups/restore-lab/komodo/keys:/config/keys
environment:
TZ: Europe/Berlin
KOMODO_HOST: http://127.0.0.1:19120
KOMODO_TITLE: Restore-Test
# Wegwerf-Secrets, ausschliesslich fuer den lokalen Trockenlauf.
# Niemals produktive Komodo-Secrets in dieses Compose schreiben.
KOMODO_SECRET_KEY: restoretest-secret-key-placeholder-32
KOMODO_WEBHOOK_SECRET: restoretest-webhook-secret
KOMODO_PASSKEY: restoretest-periphery-passkey
KOMODO_DATABASE_ADDRESS: restoretest-komodo-mongo:27017
KOMODO_DATABASE_USERNAME: komodo
KOMODO_DATABASE_PASSWORD: restoretest-komodo-mongo-pwd
KOMODO_LOG_LEVEL: info
KOMODO_LOCAL_AUTH: "true"
KOMODO_JWT_SECRET: restoretest-jwt-secret-placeholder
KOMODO_DISABLE_WEBSOCKETS: "true"
ports:
- "127.0.0.1:19120:9120"
security_opt:
- no-new-privileges:true
restoretest-komodo-periphery:
image: ghcr.io/moghtech/komodo-periphery:2@sha256:8ac9f2ef9c1461b95c862d445da00253005e7094d1e30f5b7b04b8d60ca7a3d6
container_name: restoretest-komodo-periphery
init: true
restart: "no"
depends_on:
restoretest-komodo-core:
condition: service_started
volumes:
- /mnt/user/backups/restore-lab/komodo/keys:/config/keys
# bewusst KEIN docker.sock-Mount: dieser Test-Periphery darf nicht
# versehentlich produktive Container managen.
- /mnt/user/backups/restore-lab/komodo/periphery:/etc/komodo
environment:
PERIPHERY_ROOT_DIRECTORY: /tmp/restoretest-periphery
PERIPHERY_PASSKEYS: restoretest-periphery-passkey
PERIPHERY_SSL_ENABLED: "false"
TZ: Europe/Berlin
security_opt:
- no-new-privileges:true
@@ -0,0 +1,82 @@
# Komodo Bootstrap Trockenlauf - Plan
## Ziel
Nachweisen, dass `ops/komodo/docker-compose.yml` als Recovery-Anker fuer einen Komodo-Kaltstart tauglich ist, ohne den produktiven Komodo-Stack anzufassen.
Bewusst **nicht** Teil dieses Tests:
- Restore aus dem produktiven `komodo-mongo.archive.gz`-Dump (eigene Folgeaufgabe; dieser Test prueft nur das Compose-Bootstrap, nicht den Daten-Restore).
- docker.sock-Mount fuer die Test-Periphery (die Test-Periphery darf nie produktive Container managen).
- Traefik-Route oder Authelia-Anbindung (Test laeuft ausschliesslich auf `127.0.0.1:19120`).
## Quelle
- Bootstrap-Anker: `ops/komodo/docker-compose.yml` (Soll-Stand laut `docs/SERVICES_RECOVERY.md` Stufe A-F).
- Image-Digests: identisch zur Produktion fuer komodo-core und komodo-periphery; Mongo-Image identisch.
## Test-Ziel
- Restore-Lab: `/mnt/user/backups/restore-lab/komodo`
- Wegwerf-Pfade:
- `/mnt/user/backups/restore-lab/komodo/mongo` (Test-Mongo-Datadir)
- `/mnt/user/backups/restore-lab/komodo/core` (Repo-Cache)
- `/mnt/user/backups/restore-lab/komodo/keys` (gemeinsamer Keys-Pfad fuer Core+Periphery)
- `/mnt/user/backups/restore-lab/komodo/periphery` (Periphery-Etc)
- Testcontainer:
- `restoretest-komodo-mongo`
- `restoretest-komodo-core` (Test-Port `127.0.0.1:19120`)
- `restoretest-komodo-periphery` (ohne docker.sock)
- Compose-Project: `restoretest-komodo` (isoliert von Produktions-Project `komodo`)
- Report-Ziel: `/mnt/user/backups/restore-reports/komodo-bootstrap-YYYY-MM-DD.md`
## Schutzregeln
- produktive Datadirs `/mnt/user/appdata/komodo/{mongo,core,periphery}` werden **nicht** gemountet
- produktive Container `komodo-mongo`, `komodo-core`, `komodo-periphery` werden **nicht** gestoppt
- produktive `KOMODO_*`-Secrets werden **nicht** verwendet
- Test-Compose enthaelt nur Wegwerf-Werte fuer `KOMODO_SECRET_KEY`, `KOMODO_WEBHOOK_SECRET`, `KOMODO_JWT_SECRET`, `KOMODO_PASSKEY` und Mongo-Root-Password
- Test-Periphery laeuft ohne docker.sock-Mount und ohne `/mnt/user/services`-Mount
- Test-Port nur auf `127.0.0.1:19120`, keine LAN-/Tailscale-Bindung
## Geplanter Ablauf
1. Restore-Lab-Pfade leer anlegen
2. `docker compose config` auf dem Test-Compose validieren
3. Mongo und Core hochfahren, auf Mongo-`healthy` warten
4. HTTP-Smoke gegen `http://127.0.0.1:19120` (Login-Seite oder Auth-Redirect erwartet)
5. Periphery dazustarten, kurz beobachten
6. Mongo-`authenticated ping` mit Test-Credentials
7. Report schreiben
8. Cleanup `docker compose down -v` und Restore-Lab loeschen (ausser `--keep-data`)
## Smoke-Test
Minimal erfolgreich:
- `docker compose config` valid
- Test-Mongo erreicht `healthy`
- Mongo-Authentifizierung mit Test-Creds funktioniert (`db.adminCommand({ping:1}).ok = 1`)
- Komodo-Core HTTP `200`, `302`, `303` oder `401` (alles ist ein valider Lebenszeichen)
- Test-Periphery container state `running`
Optional spaeter:
- Periphery-Verbindung gegen Test-Core verifizieren (braucht Periphery-Konfig mit `core_url`)
- Echtes Restore aus `komodo-mongo.archive.gz`-Dump in die Test-Mongo
- Schreiben einer Wegwerf-Resource (Server/Stack) ueber die API
## Bekannte Komplikationen
| Risiko | Beschreibung | Mitigation |
|---|---|---|
| Image-Drift | Komodo-Images aktualisieren ihre Major-Tag-Digests | Compose pinnt denselben Digest wie Produktion; bei Image-Update auch Test-Compose nachziehen |
| Port-Konflikt | wenn 19120 anderweitig belegt ist | nur `127.0.0.1`-Bind; bei Konflikt Port im Compose anpassen |
| Volume-Reste | unterbrochener Lauf laesst Wegwerf-Datadir liegen | Skript loescht Restore-Lab vor jedem Lauf; `--keep-data` ueberschreibt das bewusst |
| Periphery-Erreichbarkeit | Core sucht Periphery initial nicht aktiv | Test prueft nur Periphery `State.Status=running`; voller Handshake ist optional |
## Noch offen vor dem ersten echten Lauf
- Erstlauf mit `--what-if` zur Plan-Verifikation
- Erstlauf mit `--keep-data` zur Zeitmessung
- Bei Erfolg `docs/RESTORE_DRILL_ROUTINE.md` Quartals-Belegung pruefen (Q2 ist bereits Immich; Komodo passt eher zu Q4 oder zum quartalsweisen DR-Sanity-Check)
@@ -0,0 +1,95 @@
# Komodo Bootstrap Trockenlauf - Runbook
## Status
Skript und Test-Compose sind vorbereitet. Erster echter Lauf steht noch aus.
## Vorbedingungen
- Docker auf dem Unraid-Host
- `borg-ui`-Container muss **nicht** laufen (im Gegensatz zum Immich-Test braucht der Komodo-Bootstrap kein Borg-Archiv)
- freier Speicher unter `/mnt/user/backups/restore-lab/komodo` (~500 MB reichen)
- Port `127.0.0.1:19120` ist frei
## Bestaetigter Host-Stand (Soll)
- produktiver Komodo-Stack: `komodo-mongo`, `komodo-core`, `komodo-periphery` unter Project `komodo`
- produktive Mongo-Datadir: `/mnt/user/appdata/komodo/mongo`
- produktive Secrets: `KOMODO_*` Stack-ENV-only (Restore-Reihenfolge siehe `docs/SECRETS_MAP.md`)
- Test isoliert das alles unter Project `restoretest-komodo` mit Restore-Lab-Datadir
## Erster Lauf - trockene Variante
```bash
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --what-if
```
Erwartete Ausgabe: nur Plan-Output, kein Docker-Start, kein Verzeichnis angelegt.
## Erster Lauf - echter Test
```bash
# optional: produktiven Komodo-Stack-Status pruefen, damit nichts kollidiert
docker ps --filter name=komodo --format "{{.Names}}\t{{.Status}}"
# Lauf mit Datenerhalt
bash /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-test.sh --keep-data
```
Bei Erfolg:
- Report unter `/mnt/user/backups/restore-reports/komodo-bootstrap-YYYY-MM-DD.md`
- Test-Container `restoretest-komodo-*` werden nach Lauf gestoppt und entfernt (auch bei `--keep-data`)
- Restore-Lab-Daten bleiben mit `--keep-data` erhalten
## Smoke-Test-Pruefungen
Minimal erwartet im Report:
- `docker compose config valid: ok`
- `Test-Mongo healthy: ok`
- `Mongo authenticated ping (Test-Creds): ok`
- `Komodo Core HTTP status: 200|302|303|401`
- `Test-Periphery container state: running`
Manuelle Folgepruefung (optional):
```bash
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-compose.test.yml \
-p restoretest-komodo up -d
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:19120
docker exec restoretest-komodo-mongo mongosh --quiet -u komodo \
-p restoretest-komodo-mongo-pwd --authenticationDatabase admin --eval 'db.adminCommand({ping:1})'
docker compose -f /mnt/user/services/homelab-infra/ops/restore-tests/komodo-bootstrap-compose.test.yml \
-p restoretest-komodo down -v
```
## Cleanup ohne `--keep-data`
Skript bereinigt:
- Test-Container und Test-Volumes ueber `docker compose down -v`
- Restore-Lab unter `/mnt/user/backups/restore-lab/komodo`
Produktive Komodo-Container, Mongo-Datadir und `KOMODO_*`-Secrets werden niemals beruehrt.
## Fehlerfaelle
| Symptom | Ursache | Massnahme |
|---|---|---|
| `Test-Mongo never reported healthy` | mongo-image konnte nicht starten | `docker logs restoretest-komodo-mongo` pruefen; Restore-Lab-Pfad leer? |
| HTTP-Timeout nach 120 s | Komodo-Core haengt in Mongo-Connect | `docker logs restoretest-komodo-core` pruefen; Mongo-Auth-Test wiederholen |
| `bind: address already in use` | Port 19120 belegt | Compose-Port-Mapping anpassen oder konfligierenden Prozess identifizieren |
| Periphery `restarting` | Periphery braucht zusaetzliche ENVs | Logs lesen; Periphery-Verbindung ist optional fuer den Smoke-Test |
## Schedule
Aktuell nicht im automatischen Schedule. Empfohlen als Teil des quartalsweisen DR-Sanity-Check (`docs/RESTORE_DRILL_ROUTINE.md`).
## Festgelegte Entscheidungen
- Test-Compose nutzt dieselben Image-Digests wie Produktion.
- Test-Periphery laeuft bewusst ohne docker.sock-Mount.
- Test-Secrets sind Wegwerf-Werte im Compose; niemals produktive Werte einsetzen.
- Test-Port nur auf `127.0.0.1`, keine LAN-Bindung.
- `restoretest-komodo` als Compose-Project-Name reserviert; Test-Container heissen `restoretest-komodo-*`.
+135
View File
@@ -0,0 +1,135 @@
#!/bin/bash
set -euo pipefail
# Komodo Bootstrap Trockenlauf
#
# Verifiziert, dass `ops/komodo/docker-compose.yml` als Recovery-Anker
# tauglich ist: Wegwerf-Mongo, Wegwerf-Core, Wegwerf-Periphery werden
# isoliert hochgefahren und auf Bootstrap-Faehigkeit geprueft.
#
# Produktive Komodo-Container, produktive Mongo-Datadir und produktive
# Komodo-Secrets werden NICHT angefasst.
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/komodo"
REPORT_ROOT="/mnt/user/backups/restore-reports"
COMPOSE_FILE="$SCRIPT_DIR/komodo-bootstrap-compose.test.yml"
PROJECT_NAME="restoretest-komodo"
REPORT_FILE="$REPORT_ROOT/komodo-bootstrap-$(date +%F).md"
if [ "$WHATIF" -eq 1 ]; then
cat <<EOF
Komodo bootstrap trockenlauf
Mode: WhatIf
RestoreRoot: $RESTORE_ROOT
ReportRoot: $REPORT_ROOT
Planned isolation:
- Test-Mongo: mongo:7.0.32 (gleicher Digest wie Produktion), Datadir $RESTORE_ROOT/mongo
- Test-Core: ghcr.io/moghtech/komodo-core:2 (gleicher Digest wie Produktion), Port 127.0.0.1:19120
- Test-Periphery: ghcr.io/moghtech/komodo-periphery:2, ohne docker.sock-Mount
- KOMODO_*-Secrets: Wegwerf-Werte ausschliesslich fuer Trockenlauf
- Compose-Project: $PROJECT_NAME (isoliert von "komodo")
Smoke-Test:
- compose config valid
- Mongo healthy
- Core HTTP 200/4xx auf 127.0.0.1:19120 (Login-Seite erwartet)
- Periphery container running
EOF
exit 0
fi
require_cmd docker
require_path "$COMPOSE_FILE"
cleanup() {
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" down -v >/dev/null 2>&1 || true
if [ "$KEEP_DATA" -ne 1 ]; then
rm -rf "$RESTORE_ROOT"
fi
}
trap cleanup EXIT
rm -rf "$RESTORE_ROOT"
mkdir -p "$RESTORE_ROOT/mongo" "$RESTORE_ROOT/core" "$RESTORE_ROOT/keys" "$RESTORE_ROOT/periphery"
# Stufe 1: Compose syntaktisch validieren
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" config >/dev/null
# Stufe 2: Mongo und Core hochfahren
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" up -d \
restoretest-komodo-mongo restoretest-komodo-core >/dev/null
# Stufe 3: Warten auf Mongo healthy
mongo_ok=0
for _ in $(seq 1 30); do
s="$(docker inspect restoretest-komodo-mongo --format '{{.State.Health.Status}}' 2>/dev/null || true)"
if [ "$s" = "healthy" ]; then mongo_ok=1; break; fi
sleep 2
done
if [ "$mongo_ok" -ne 1 ]; then
echo "Test-Mongo never reported healthy" >&2
docker logs --tail 80 restoretest-komodo-mongo >&2 || true
exit 1
fi
# Stufe 4: Warten bis Core HTTP antwortet
http_status=""
for _ in $(seq 1 60); do
http_status="$(curl -s -o /tmp/komodo-bootstrap-body.html -w '%{http_code}' -L http://127.0.0.1:19120 || true)"
if [ "$http_status" = "200" ] || [ "$http_status" = "302" ] || [ "$http_status" = "303" ] || [ "$http_status" = "401" ]; then
break
fi
sleep 2
done
# Stufe 5: Periphery dazustarten und Health pruefen
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" up -d \
restoretest-komodo-periphery >/dev/null
sleep 8
periphery_state="$(docker inspect restoretest-komodo-periphery --format '{{.State.Status}}' 2>/dev/null || echo missing)"
# Stufe 6: Mongo-Ping mit Test-Credentials als zusaetzlicher Sanity-Check
mongo_ping="n/a"
if docker exec restoretest-komodo-mongo mongosh --quiet -u komodo \
-p restoretest-komodo-mongo-pwd --authenticationDatabase admin \
--eval 'db.adminCommand({ping:1}).ok' 2>/dev/null | grep -q '^1$'; then
mongo_ping="ok"
fi
write_report "$REPORT_FILE" <<EOF
# Komodo Bootstrap Trockenlauf - $(date +%F)
- Compose: \`ops/restore-tests/komodo-bootstrap-compose.test.yml\`
- Project: \`$PROJECT_NAME\`
- Restore root: \`$RESTORE_ROOT\`
- Test endpoint: \`http://127.0.0.1:19120\`
- Result: \`SUCCESS\`
## Checks
- docker compose config valid: \`ok\`
- Test-Mongo healthy: \`ok\`
- Mongo authenticated ping (Test-Creds): \`$mongo_ping\`
- Komodo Core HTTP status: \`$http_status\`
- Test-Periphery container state: \`$periphery_state\`
## Notes
- Produktive Komodo-Container, Mongo-Datadir und Secrets wurden nicht beruehrt.
- Test-Periphery laeuft bewusst ohne docker.sock-Mount.
- Test-Daten wurden \`$([ "$KEEP_DATA" -eq 1 ] && echo behalten || echo bereinigt)\`.
EOF
echo "Komodo bootstrap trockenlauf ok -> $REPORT_FILE"