diff --git a/ops/restore-tests/mailarchiver-compose.test.yml b/ops/restore-tests/mailarchiver-compose.test.yml new file mode 100644 index 0000000..2ea3cf7 --- /dev/null +++ b/ops/restore-tests/mailarchiver-compose.test.yml @@ -0,0 +1,41 @@ +services: + restoretest-mailarchiver-postgres: + image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39 + container_name: restoretest-mailarchiver-postgres + restart: "no" + environment: + TZ: Europe/Berlin + POSTGRES_USER: mailarchiver + POSTGRES_DB: mailarchiver + POSTGRES_PASSWORD: restoretest-mailarchiver-db + PGDATA: /var/lib/postgresql/18/docker + volumes: + - /mnt/user/backups/restore-lab/mailarchiver/postgres:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mailarchiver -d mailarchiver"] + interval: 10s + timeout: 5s + retries: 10 + security_opt: + - no-new-privileges:true + + restoretest-mailarchiver: + image: s1t5/mailarchiver@sha256:ea7fd8c2e3e0ef0941e8dd9e726e35a8de33296f5c7b9ed811df5168ae6a9714 + container_name: restoretest-mailarchiver + restart: "no" + depends_on: + restoretest-mailarchiver-postgres: + condition: service_healthy + environment: + TZ: Europe/Berlin + # Wegwerf-Connection-String fuer isolierten Test. + # Produktiver MAILARCHIVER_DB_CONNECTION ist Stack-ENV-only und wird + # hier bewusst NICHT verwendet. + ConnectionStrings__DefaultConnection: "Host=restoretest-mailarchiver-postgres;Database=mailarchiver;Username=mailarchiver;Password=restoretest-mailarchiver-db" + Authentication__Password: restoretest-mailarchiver-auth + ports: + - "127.0.0.1:15000:5000" + volumes: + - /mnt/user/backups/restore-lab/mailarchiver/data-protection-keys:/app/DataProtection-Keys + security_opt: + - no-new-privileges:true diff --git a/ops/restore-tests/mailarchiver-restore-test.sh b/ops/restore-tests/mailarchiver-restore-test.sh new file mode 100644 index 0000000..67dd735 --- /dev/null +++ b/ops/restore-tests/mailarchiver-restore-test.sh @@ -0,0 +1,172 @@ +#!/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" diff --git a/ops/restore-tests/run-restore-checks.sh b/ops/restore-tests/run-restore-checks.sh index be3e11a..ae6e239 100755 --- a/ops/restore-tests/run-restore-checks.sh +++ b/ops/restore-tests/run-restore-checks.sh @@ -58,6 +58,12 @@ case "$MODE" in fi exec "$SCRIPT_DIR/komodo-mongo-restore-test.sh" ;; + mailarchiver) + if [ "$WHATIF" = "--what-if" ]; then + exec "$SCRIPT_DIR/mailarchiver-restore-test.sh" --what-if + fi + exec "$SCRIPT_DIR/mailarchiver-restore-test.sh" + ;; mealie) if [ "$WHATIF" = "--what-if" ]; then exec "$SCRIPT_DIR/mealie-restore-test.sh" --what-if