#!/bin/bash set -euo pipefail # Mail-Archiver Restore Smoke Test # # Borg-Extract der Data-Protection-Keys + pg_restore des mailarchiver-Dumps # in isoliertes Test-Postgres + Container-Boot + HTTP-Smoke. # # In Produktion nutzt Mail-Archiver die Shared PostgreSQL 18 — im Test # bekommt er ein eigenes isoliertes Test-Postgres mit Wegwerf-Credentials. # Authelia-ForwardAuth wird im Smoke nicht geprueft (kein Traefik, kein # Auth-Middleware). 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/mailarchiver" REPORT_ROOT="/mnt/user/backups/restore-reports" EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/mailarchiver-extract" COMPOSE_FILE="$SCRIPT_DIR/mailarchiver-compose.test.yml" REPORT_FILE="$REPORT_ROOT/mailarchiver-$(date +%F).md" DUMP_HOST_PATH="/mnt/user/backups/borg/dumps/latest/postgresql17-mailarchiver.dump" if [ "$WHATIF" -eq 1 ]; then cat <&2 exit 1 fi # Stufe 1: Data-Protection-Keys aus Borg borg_extract "/restore/mailarchiver-extract" "local/appdata/mailarchiver/data-protection-keys" if [ ! -d "$EXTRACT_DIR/local/appdata/mailarchiver/data-protection-keys" ]; then echo "Mailarchiver data-protection-keys path missing in Borg archive" >&2 exit 1 fi cp -a "$EXTRACT_DIR/local/appdata/mailarchiver/data-protection-keys/." "$RESTORE_ROOT/data-protection-keys/" chmod -R a+rwX "$RESTORE_ROOT/data-protection-keys" # Stufe 2: Test-Postgres + Dump docker compose -f "$COMPOSE_FILE" up -d restoretest-mailarchiver-postgres >/dev/null until docker exec restoretest-mailarchiver-postgres pg_isready -U mailarchiver -d mailarchiver >/dev/null 2>&1; do sleep 2 done restore_ok=0 for attempt in $(seq 1 12); do if docker exec -i restoretest-mailarchiver-postgres \ pg_restore -U mailarchiver -d mailarchiver --clean --if-exists --no-owner --no-privileges \ < "$DUMP_HOST_PATH" 2>/tmp/mailarchiver-pg-restore.err; then restore_ok=1 break fi if grep -qiE "starting up|shutting down|connection refused" /tmp/mailarchiver-pg-restore.err; then sleep 5 continue fi if grep -qiE "FATAL|PANIC" /tmp/mailarchiver-pg-restore.err; then cat /tmp/mailarchiver-pg-restore.err >&2 exit 1 fi restore_ok=1 break done if [ "$restore_ok" -ne 1 ]; then cat /tmp/mailarchiver-pg-restore.err >&2 exit 1 fi # Stufe 3: Container starten docker compose -f "$COMPOSE_FILE" up -d restoretest-mailarchiver >/dev/null # Mailarchiver ist ein .NET-App, braucht ein paar Sekunden fuer DB-Migration. # Smoke gegen den Root-Endpunkt — bei Authelia-geschuetztem Dienst liefert # der Container selbst trotzdem einen HTTP-Response (302 oder 200). http_status="" for _ in $(seq 1 60); do http_status="$(curl -s -o /tmp/mailarchiver-body.html -w '%{http_code}' \ -L http://127.0.0.1:15000/ || true)" if [ "$http_status" = "200" ] || [ "$http_status" = "302" ] || [ "$http_status" = "401" ]; then break fi sleep 2 done if [ "$http_status" != "200" ] && [ "$http_status" != "302" ] && [ "$http_status" != "401" ]; then echo "Mailarchiver HTTP smoke failed: status=$http_status" >&2 docker logs --tail 80 restoretest-mailarchiver >&2 || true exit 1 fi # Tabellen-Count als Sanity table_count="$(docker exec restoretest-mailarchiver-postgres \ psql -U mailarchiver -d mailarchiver -tAc \ "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';" \ 2>/dev/null | tr -d '[:space:]' || echo "n/a")" write_report "$REPORT_FILE" < $REPORT_FILE"