Files
homelab-infra/ops/restore-tests/authelia-restore-test.sh
T

279 lines
9.4 KiB
Bash

#!/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"
RESTORED_CONFIG_DIR="$RESTORE_ROOT/config"
TEST_CONFIG_DIR="$RESTORE_ROOT/test-config"
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 <<EOF
Authelia restore test
Mode: WhatIf
RestoreRoot: $RESTORE_ROOT
ReportRoot: $REPORT_ROOT
Expected Borg source paths:
- local/appdata/authelia/config
- local/borg-dumps/latest/postgresql17-authelia.dump (optional - wird uebersprungen wenn nicht vorhanden)
Planned isolation:
- Test-Postgres: postgres:18.4 mit Wegwerf-Credentials
- Test-Authelia: authelia/authelia:4.39.20 (Image-Pin wie Produktion)
- Wegwerf-Secrets ausschliesslich im Test-Compose
- test-config/configuration.yml wird im Restore-Lab erzeugt:
* storage -> 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 test-config/configuration.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 "$RESTORED_CONFIG_DIR" "$TEST_CONFIG_DIR" "$RESTORE_ROOT/postgres" "$RESTORE_ROOT/dumps/latest"
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/." "$RESTORED_CONFIG_DIR/"
# 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.
ORIGINAL_CONFIG_FILE="$RESTORED_CONFIG_DIR/configuration.yml"
TEST_CONFIG_FILE="$TEST_CONFIG_DIR/configuration.yml"
if [ ! -f "$ORIGINAL_CONFIG_FILE" ]; then
echo "configuration.yml missing in restored config dir" >&2
exit 1
fi
# Kopiere alle Begleitdateien (z. B. users_database.yml) in einen separaten
# Runtime-Mount. Die produktive configuration.yml selbst wird dort durch die
# sanitizte Testkonfiguration ersetzt, damit Authelia keine Default-Config mit
# produktivem notifier.smtp nachladen kann.
cp -a "$RESTORED_CONFIG_DIR/." "$TEST_CONFIG_DIR/"
cp "$ORIGINAL_CONFIG_FILE" "$RESTORED_CONFIG_DIR/configuration.yml.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 }
' "$ORIGINAL_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 "$TEST_CONFIG_DIR/notifier"
chmod -R a+rwX "$TEST_CONFIG_DIR/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_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 \
-v "$TEST_CONFIG_DIR:/config" \
authelia/authelia:4.39.20@sha256:1b363e9279e742397966333f364e0876ae02bf5c876de73e83af6d48c57ff51b \
authelia config validate --config /config/configuration.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 test-config als
# /config-Mount 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" <<EOF
# Authelia Restore Test Report - $(date +%F)
- Service: \`authelia\`
- Source repo: \`$repo\`
- Archive: \`$archive\`
- Restore root: \`$RESTORE_ROOT\`
- Test containers:
- \`restoretest-authelia\`
- \`restoretest-authelia-postgres\`
- Test endpoint: \`http://127.0.0.1:19091/api/health\`
- Result: \`SUCCESS\`
## Checks
- Borg extract of config: \`ok\`
- Borg extract of dump: \`$dump_status\`
- configuration.yml present: \`ok\`
- test runtime configuration.yml written: \`ok\`
- \`authelia config validate\`: \`$validate_status\`
- HTTP /api/health status: \`$http_status\`
## Notes
- Test ran without Traefik and without the productive domain \`auth.kaleschke.info\`.
- Productive Authelia secrets under \`/mnt/user/appdata/secrets/authelia_*.txt\` were NOT mounted.
- Notifier was forced to filesystem (\`/config/notifier/notifications.txt\`); no SMTP call to GMX.
- Storage forced to isolated test postgres; productive shared PostgreSQL 18 was NOT touched.
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
- Restore-Quelle Dump: \`local/borg-dumps/latest/postgresql17-authelia.dump\` (optional, wenn im Archiv).
EOF
RESTORE_SUCCESS=1
echo "Authelia restore test ok -> $REPORT_FILE"