#!/bin/bash set -euo pipefail # Authelia Restore Smoke Test # # Nicht-destruktiver Restore-Smoke-Test fuer Authelia. # - extrahiert die Authelia-Config aus dem produktiven Borg-Archiv # - patcht in einer Restore-Lab-Kopie der configuration.yml die # externen Abhaengigkeiten (storage = lokales Test-Postgres, # notifier = Filesystem-Notifier, identity_validation auf Test-Werte) # - importiert optional den shared-Postgres-Dump fuer Authelia # - validiert die gepatchte Konfiguration mit `authelia config validate` # - startet einen isolierten Authelia-Container ohne Traefik # - prueft den HTTP-Health-Endpunkt # - bereinigt anschliessend # # Produktive Authelia-Container, produktive Postgres-DB, produktive Secrets # und produktiver SMTP-Versand 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/authelia" REPORT_ROOT="/mnt/user/backups/restore-reports" EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/authelia-extract" COMPOSE_FILE="$SCRIPT_DIR/authelia-compose.test.yml" REPORT_FILE="$REPORT_ROOT/authelia-$(date +%F).md" if [ "$WHATIF" -eq 1 ]; then cat < Test-Postgres (kein produktives Postgres erreicht) * notifier -> Filesystem (KEIN SMTP-Versand) * session -> lokaler Smoke ohne produktive Session-Secrets - Test endpoint: 127.0.0.1:19091/api/health (no Traefik, no public domain) Smoke-Test: - authelia config validate gegen configuration.restoretest.yml - HTTP 200 von /api/health EOF exit 0 fi require_cmd docker require_cmd curl require_path "$BORG_PASSPHRASE_FILE_DEFAULT" require_path "$COMPOSE_FILE" RESTORE_SUCCESS=0 cleanup() { cleanup_compose "$COMPOSE_FILE" if [ "$RESTORE_SUCCESS" -ne 1 ]; then preserve_on_failure "authelia" "$RESTORE_ROOT" rm -rf "$EXTRACT_DIR" return fi 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/config" "$RESTORE_ROOT/postgres" "$RESTORE_ROOT/dumps/latest" "$RESTORE_ROOT/notifier" archive="$(latest_archive_name)" repo="$(borg_repo_url)" if [ -z "$archive" ] || [ -z "$repo" ]; then echo "Could not resolve Borg repo/archive from borg-ui database" >&2 exit 1 fi # Stufe 1: Config aus Borg extrahieren borg_extract "/restore/authelia-extract" "local/appdata/authelia/config" if [ ! -d "$EXTRACT_DIR/local/appdata/authelia/config" ]; then echo "Authelia config path missing in Borg archive" >&2 exit 1 fi cp -a "$EXTRACT_DIR/local/appdata/authelia/config/." "$RESTORE_ROOT/config/" # Stufe 2: optionalen Postgres-Dump extrahieren und ggf. einspielen dump_available=0 if borg_extract "/restore/authelia-extract" "local/borg-dumps/latest/postgresql17-authelia.dump" 2>/dev/null; then if [ -f "$EXTRACT_DIR/local/borg-dumps/latest/postgresql17-authelia.dump" ]; then mv "$EXTRACT_DIR/local/borg-dumps/latest/postgresql17-authelia.dump" \ "$RESTORE_ROOT/dumps/latest/postgresql17-authelia.dump" dump_available=1 fi fi # Stufe 3: configuration.yml im Restore-Lab gezielt fuer den Test sanitizen. # Wir entfernen produktive Top-Level-Bloecke, die im Test andere Backends # brauchen, und haengen danach Test-Definitionen an. So verlassen wir uns # nicht darauf, dass ein Overlay alte Map-Keys wie notifier.smtp loescht. CONFIG_FILE="$RESTORE_ROOT/config/configuration.yml" TEST_CONFIG_FILE="$RESTORE_ROOT/config/configuration.restoretest.yml" if [ ! -f "$CONFIG_FILE" ]; then echo "configuration.yml missing in restored config dir" >&2 exit 1 fi # Sichere Originalkopie fuer Diff/Diagnose cp "$CONFIG_FILE" "$CONFIG_FILE.original" # Entferne produktive Blocks, die der Restore-Smoke bewusst ersetzt. awk ' /^[A-Za-z_][A-Za-z0-9_]*:/ { key = $0 sub(/:.*/, "", key) skip = (key == "storage" || key == "notifier" || key == "session" || key == "identity_validation" || key == "jwt_secret") } !skip { print } ' "$CONFIG_FILE" > "$TEST_CONFIG_FILE" cat >> "$TEST_CONFIG_FILE" <<'YAML' # Restore-Smoke-Test-Backends. Produktive externe Abhaengigkeiten sind oben # entfernt und werden hier mit isolierten Test-Werten ersetzt. storage: postgres: address: tcp://restoretest-authelia-postgres:5432 database: authelia username: authelia # Passwort kommt ueber AUTHELIA_STORAGE_POSTGRES_PASSWORD ENV notifier: disable_startup_check: true filesystem: filename: /config/notifier/notifications.txt session: cookies: - name: authelia_session_restoretest domain: kaleschke.info authelia_url: https://auth.kaleschke.info default_redirection_url: https://glance.kaleschke.info expiration: 1h inactivity: 5m identity_validation: reset_password: jwt_secret: restoretest-authelia-reset-password-jwt-secret-placeholder-64bytes jwt_lifespan: 5m jwt_algorithm: HS256 YAML mkdir -p "$RESTORE_ROOT/config/notifier" chmod -R a+rwX "$RESTORE_ROOT/config/notifier" # Stufe 4: Test-Postgres hochfahren docker compose -f "$COMPOSE_FILE" up -d restoretest-authelia-postgres >/dev/null until docker exec restoretest-authelia-postgres pg_isready -U authelia -d authelia >/dev/null 2>&1; do sleep 2 done # Stufe 5: optional Dump einspielen dump_status="skipped (no dump in archive)" if [ "$dump_available" -eq 1 ]; then restore_ok=0 for attempt in $(seq 1 12); do if docker exec -i restoretest-authelia-postgres \ pg_restore -U authelia -d authelia --clean --if-exists --no-owner --no-privileges \ < "$RESTORE_ROOT/dumps/latest/postgresql17-authelia.dump" 2>/tmp/authelia-pg-restore.err; then restore_ok=1 break fi if grep -qiE "starting up|shutting down|connection refused|database .* does not exist" /tmp/authelia-pg-restore.err; then sleep 5 continue fi cat /tmp/authelia-pg-restore.err >&2 exit 1 done if [ "$restore_ok" -ne 1 ]; then cat /tmp/authelia-pg-restore.err >&2 exit 1 fi dump_status="restored" fi # Stufe 6: config validate im Container-Kontext, gegen sanitizte Test-Config validate_status="ok" if ! docker run --rm \ -e AUTHELIA_JWT_SECRET=restoretest-authelia-jwt-secret-placeholder-32bytes \ -e AUTHELIA_SESSION_SECRET=restoretest-authelia-session-secret-placeholder-32 \ -e AUTHELIA_STORAGE_ENCRYPTION_KEY=restoretest-authelia-storage-enc-key-placeholder-32 \ -e AUTHELIA_STORAGE_POSTGRES_PASSWORD=restoretest-authelia-db \ -e AUTHELIA_NOTIFIER_SMTP_PASSWORD=restoretest-authelia-smtp-placeholder \ -v "$RESTORE_ROOT/config:/config" \ authelia/authelia:4.39.20@sha256:1b363e9279e742397966333f364e0876ae02bf5c876de73e83af6d48c57ff51b \ authelia config validate --config /config/configuration.restoretest.yml \ >/tmp/authelia-validate.log 2>&1; then validate_status="failed" cat /tmp/authelia-validate.log >&2 exit 1 fi # Stufe 7: Authelia-Container starten. Das Compose nutzt die sanitizte # configuration.restoretest.yml mit isolierten Test-Backends. docker compose -f "$COMPOSE_FILE" up -d restoretest-authelia >/dev/null http_status="" for _ in $(seq 1 60); do http_status="$(curl -s -o /tmp/authelia-body.html -w '%{http_code}' \ http://127.0.0.1:19091/api/health || true)" if [ "$http_status" = "200" ]; then break fi sleep 2 done if [ "$http_status" != "200" ]; then echo "Authelia HTTP health failed: status=$http_status" >&2 docker logs --tail 120 restoretest-authelia >&2 || true exit 1 fi write_report "$REPORT_FILE" < $REPORT_FILE"