From e4b0db2af6c2eb737c02a3f2605201726bcb1bc6 Mon Sep 17 00:00:00 2001 From: Micha Date: Fri, 29 May 2026 15:25:41 +0200 Subject: [PATCH] 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 --- .../komodo-bootstrap-compose.test.yml | 77 ++++++++++ ops/restore-tests/komodo-bootstrap-plan.md | 82 +++++++++++ ops/restore-tests/komodo-bootstrap-runbook.md | 95 ++++++++++++ ops/restore-tests/komodo-bootstrap-test.sh | 135 ++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 ops/restore-tests/komodo-bootstrap-compose.test.yml create mode 100644 ops/restore-tests/komodo-bootstrap-plan.md create mode 100644 ops/restore-tests/komodo-bootstrap-runbook.md create mode 100644 ops/restore-tests/komodo-bootstrap-test.sh diff --git a/ops/restore-tests/komodo-bootstrap-compose.test.yml b/ops/restore-tests/komodo-bootstrap-compose.test.yml new file mode 100644 index 0000000..182bebb --- /dev/null +++ b/ops/restore-tests/komodo-bootstrap-compose.test.yml @@ -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 diff --git a/ops/restore-tests/komodo-bootstrap-plan.md b/ops/restore-tests/komodo-bootstrap-plan.md new file mode 100644 index 0000000..305791b --- /dev/null +++ b/ops/restore-tests/komodo-bootstrap-plan.md @@ -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) diff --git a/ops/restore-tests/komodo-bootstrap-runbook.md b/ops/restore-tests/komodo-bootstrap-runbook.md new file mode 100644 index 0000000..6b5145b --- /dev/null +++ b/ops/restore-tests/komodo-bootstrap-runbook.md @@ -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-*`. diff --git a/ops/restore-tests/komodo-bootstrap-test.sh b/ops/restore-tests/komodo-bootstrap-test.sh new file mode 100644 index 0000000..fbccae8 --- /dev/null +++ b/ops/restore-tests/komodo-bootstrap-test.sh @@ -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 </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" < $REPORT_FILE"