feat(restore): traefik restore smoke test
Borg-Extract von dynamic/ und letsencrypt/, Traefik startet mit File-Provider gegen restaurierte Config, /ping Health antwortet. Bewusst kein docker.sock (wuerde produktive Container discovern), kein CF-Token (keine DNS-Challenge), keine produktiven Ports. acme.json-Existenz und -Groesse wird geprueft, TLS-Validitaet nicht. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,12 @@ case "$MODE" in
|
|||||||
fi
|
fi
|
||||||
exec "$SCRIPT_DIR/komodo-mongo-restore-test.sh"
|
exec "$SCRIPT_DIR/komodo-mongo-restore-test.sh"
|
||||||
;;
|
;;
|
||||||
|
traefik)
|
||||||
|
if [ "$WHATIF" = "--what-if" ]; then
|
||||||
|
exec "$SCRIPT_DIR/traefik-restore-test.sh" --what-if
|
||||||
|
fi
|
||||||
|
exec "$SCRIPT_DIR/traefik-restore-test.sh"
|
||||||
|
;;
|
||||||
mailarchiver)
|
mailarchiver)
|
||||||
if [ "$WHATIF" = "--what-if" ]; then
|
if [ "$WHATIF" = "--what-if" ]; then
|
||||||
exec "$SCRIPT_DIR/mailarchiver-restore-test.sh" --what-if
|
exec "$SCRIPT_DIR/mailarchiver-restore-test.sh" --what-if
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
restoretest-traefik:
|
||||||
|
image: traefik:v3.7@sha256:6b9cbca6fac42ab0075f5437d8dc1685cfd188626d8d515839ea94f8b6271c42
|
||||||
|
container_name: restoretest-traefik
|
||||||
|
restart: "no"
|
||||||
|
command:
|
||||||
|
# Minimale Config fuer den Smoke: kein Docker-Provider (kein docker.sock),
|
||||||
|
# kein ACME (kein CF-Token), kein TLS-Entrypoint. Nur:
|
||||||
|
# - File-Provider mit restauriertem dynamic/-Verzeichnis
|
||||||
|
# - HTTP-Entrypoint auf Test-Port
|
||||||
|
# - Ping-Endpoint fuer Health-Check
|
||||||
|
- --ping=true
|
||||||
|
- --ping.entrypoint=web
|
||||||
|
- --providers.file.directory=/dynamic
|
||||||
|
- --providers.file.watch=false
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --api.dashboard=false
|
||||||
|
- --log.level=INFO
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:18880:80"
|
||||||
|
volumes:
|
||||||
|
# Restauriertes dynamic/-Verzeichnis aus Borg (read-only)
|
||||||
|
- /mnt/user/backups/restore-lab/traefik/dynamic:/dynamic:ro
|
||||||
|
# Restauriertes letsencrypt/ fuer Existenz-Nachweis (read-only)
|
||||||
|
- /mnt/user/backups/restore-lab/traefik/letsencrypt:/letsencrypt:ro
|
||||||
|
# KEIN docker.sock — Test-Traefik darf keine produktiven Container discovern
|
||||||
|
# KEIN CF-Token — keine DNS-Challenge im Smoke
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Traefik Restore Smoke Test
|
||||||
|
#
|
||||||
|
# Beweist, dass die restaurierten Traefik-Dateien (dynamic/, letsencrypt/)
|
||||||
|
# aus dem Borg-Archiv einen funktionsfaehigen Traefik-Start ermoeglichen.
|
||||||
|
#
|
||||||
|
# Scope:
|
||||||
|
# - Borg-Extract von dynamic/ und letsencrypt/
|
||||||
|
# - Traefik startet mit File-Provider gegen restauriertes dynamic/
|
||||||
|
# - /ping Health-Endpoint antwortet
|
||||||
|
# - acme.json aus letsencrypt/ ist vorhanden und nicht leer
|
||||||
|
#
|
||||||
|
# Bewusst NICHT Teil des Smokes:
|
||||||
|
# - Docker-Provider (kein docker.sock im Test — wuerde produktive Container discovern)
|
||||||
|
# - ACME/DNS-Challenge (kein CF-Token im Test)
|
||||||
|
# - TLS-Terminierung (kein Cert-Test)
|
||||||
|
# - Produktive Ports 80/443
|
||||||
|
|
||||||
|
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/traefik"
|
||||||
|
REPORT_ROOT="/mnt/user/backups/restore-reports"
|
||||||
|
EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/traefik-extract"
|
||||||
|
COMPOSE_FILE="$SCRIPT_DIR/traefik-compose.test.yml"
|
||||||
|
REPORT_FILE="$REPORT_ROOT/traefik-$(date +%F).md"
|
||||||
|
|
||||||
|
if [ "$WHATIF" -eq 1 ]; then
|
||||||
|
cat <<EOF
|
||||||
|
Traefik restore test
|
||||||
|
Mode: WhatIf
|
||||||
|
RestoreRoot: $RESTORE_ROOT
|
||||||
|
Borg source: local/appdata/traefik
|
||||||
|
Test endpoint: 127.0.0.1:18880/ping
|
||||||
|
Scope: File-Provider + Ping, kein Docker-Provider, kein ACME, kein CF-Token
|
||||||
|
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 "traefik" "$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"
|
||||||
|
|
||||||
|
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: Traefik-Dateien aus Borg (dynamic/ + letsencrypt/)
|
||||||
|
# Secrets/ wird bewusst NICHT extrahiert (enthaelt CF-Token)
|
||||||
|
borg_extract "/restore/traefik-extract" \
|
||||||
|
"local/appdata/traefik/dynamic" \
|
||||||
|
"local/appdata/traefik/letsencrypt"
|
||||||
|
|
||||||
|
if [ ! -d "$EXTRACT_DIR/local/appdata/traefik/dynamic" ]; then
|
||||||
|
echo "Traefik dynamic/ path missing in Borg archive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -a "$EXTRACT_DIR/local/appdata/traefik/dynamic" "$RESTORE_ROOT/dynamic"
|
||||||
|
cp -a "$EXTRACT_DIR/local/appdata/traefik/letsencrypt" "$RESTORE_ROOT/letsencrypt"
|
||||||
|
chmod -R a+rX "$RESTORE_ROOT/dynamic" "$RESTORE_ROOT/letsencrypt"
|
||||||
|
|
||||||
|
# Stufe 2: Datei-Checks
|
||||||
|
dynamic_files="$(find "$RESTORE_ROOT/dynamic" -type f | wc -l)"
|
||||||
|
acme_size="$(stat -c %s "$RESTORE_ROOT/letsencrypt/acme.json" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
# Stufe 3: Traefik starten
|
||||||
|
docker compose -f "$COMPOSE_FILE" up -d restoretest-traefik >/dev/null
|
||||||
|
|
||||||
|
http_status=""
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
http_status="$(curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
http://127.0.0.1:18880/ping || true)"
|
||||||
|
if [ "$http_status" = "200" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$http_status" != "200" ]; then
|
||||||
|
echo "Traefik /ping smoke failed: status=$http_status" >&2
|
||||||
|
docker logs --tail 60 restoretest-traefik >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_report "$REPORT_FILE" <<EOF
|
||||||
|
# Traefik Restore Test Report - $(date +%F)
|
||||||
|
|
||||||
|
- Service: \`traefik\`
|
||||||
|
- Source repo: \`$repo\`
|
||||||
|
- Archive: \`$archive\`
|
||||||
|
- Restore root: \`$RESTORE_ROOT\`
|
||||||
|
- Test container: \`restoretest-traefik\`
|
||||||
|
- Test endpoint: \`http://127.0.0.1:18880/ping\`
|
||||||
|
- Result: \`SUCCESS\`
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- Borg extract of dynamic/: \`ok\` ($dynamic_files files)
|
||||||
|
- Borg extract of letsencrypt/: \`ok\` (acme.json ${acme_size} bytes)
|
||||||
|
- Traefik /ping health: \`$http_status\`
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Config-Restore + File-Provider-Boot + Ping-Health. Kein Docker-Provider
|
||||||
|
(docker.sock nicht gemountet), kein ACME/DNS-Challenge (CF-Token nicht
|
||||||
|
im Test), keine produktiven Ports.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Productive CF-Token under /mnt/user/appdata/traefik/secrets/ was NOT extracted or mounted.
|
||||||
|
- dynamic/ contains middlewares.yml and usersfile — Traefik loads them via File-Provider.
|
||||||
|
- acme.json is present and non-empty; TLS cert validity not tested in smoke.
|
||||||
|
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
|
||||||
|
EOF
|
||||||
|
|
||||||
|
RESTORE_SUCCESS=1
|
||||||
|
echo "Traefik restore test ok -> $REPORT_FILE"
|
||||||
Reference in New Issue
Block a user