From 86435d40915b73803e7cb769e13f00d3d33cef9c Mon Sep 17 00:00:00 2001 From: Micha Date: Wed, 3 Jun 2026 13:49:51 +0200 Subject: [PATCH] feat(restore): mealie restore test + freshness check negativ-test fix Mealie-Restore-Test: Borg-Extract der App-Daten + pg_restore in isoliertes Test-Postgres + Mealie-Boot + HTTP /api/app/about Smoke. Machbarkeit vorab verifiziert (kein shfs-chmod-Problem, Mealie laeuft als root und switcht intern auf PUID 99). Freshness-Check: pg_header_ok() Docker-Fallback lieferte bei korruptem Dump return 2 (unchecked) statt return 1 (invalid). Negativ-Test am 2026-06-03 bewiesen: korrupter mealie.dump wird jetzt als DUMP_HEADER_INVALID erkannt (Critical, Exit 1). Co-Authored-By: Claude Opus 4.7 --- ops/restore-tests/mealie-compose.test.yml | 45 ++++++ ops/restore-tests/mealie-restore-test.sh | 162 ++++++++++++++++++++++ ops/restore-tests/run-restore-checks.sh | 6 + 3 files changed, 213 insertions(+) create mode 100644 ops/restore-tests/mealie-compose.test.yml create mode 100644 ops/restore-tests/mealie-restore-test.sh diff --git a/ops/restore-tests/mealie-compose.test.yml b/ops/restore-tests/mealie-compose.test.yml new file mode 100644 index 0000000..20d803b --- /dev/null +++ b/ops/restore-tests/mealie-compose.test.yml @@ -0,0 +1,45 @@ +services: + restoretest-mealie-postgres: + image: postgres:18.4@sha256:8ff36f3c66371cba71d20ceedccfc3de9669a68737607888c4ef0af93abe8e39 + container_name: restoretest-mealie-postgres + restart: "no" + environment: + TZ: Europe/Berlin + POSTGRES_USER: mealie + POSTGRES_DB: mealie + POSTGRES_PASSWORD: restoretest-mealie-db + PGDATA: /var/lib/postgresql/18/docker + volumes: + - /mnt/user/backups/restore-lab/mealie/postgres:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mealie -d mealie"] + interval: 10s + timeout: 5s + retries: 10 + security_opt: + - no-new-privileges:true + + restoretest-mealie: + image: ghcr.io/mealie-recipes/mealie:v3.19.2@sha256:f68e959bf66f4f458893ea58facac71690fe6f2ac7a31466b5cecb41b4e99c02 + container_name: restoretest-mealie + restart: "no" + depends_on: + restoretest-mealie-postgres: + condition: service_healthy + environment: + TZ: Europe/Berlin + ALLOW_SIGNUP: "false" + PUID: "99" + PGID: "100" + DB_ENGINE: postgres + POSTGRES_SERVER: restoretest-mealie-postgres + POSTGRES_DB: mealie + POSTGRES_USER: mealie + POSTGRES_PASSWORD: restoretest-mealie-db + BASE_URL: http://127.0.0.1:19925 + ports: + - "127.0.0.1:19925:9000" + volumes: + - /mnt/user/backups/restore-lab/mealie/data:/app/data + security_opt: + - no-new-privileges:true diff --git a/ops/restore-tests/mealie-restore-test.sh b/ops/restore-tests/mealie-restore-test.sh new file mode 100644 index 0000000..31c8dbc --- /dev/null +++ b/ops/restore-tests/mealie-restore-test.sh @@ -0,0 +1,162 @@ +#!/bin/bash +set -euo pipefail + +# Mealie Restore Smoke Test +# +# Borg-Extract der App-Daten + pg_restore des mealie.dump in isoliertes +# Test-Postgres + Mealie-Boot + HTTP-Smoke. + +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/mealie" +REPORT_ROOT="/mnt/user/backups/restore-reports" +EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/mealie-extract" +COMPOSE_FILE="$SCRIPT_DIR/mealie-compose.test.yml" +REPORT_FILE="$REPORT_ROOT/mealie-$(date +%F).md" +DUMP_HOST_PATH="/mnt/user/backups/borg/dumps/latest/mealie.dump" + +if [ "$WHATIF" -eq 1 ]; then + cat <&2 + exit 1 +fi + +# Stufe 1: App-Daten aus Borg +borg_extract "/restore/mealie-extract" "local/appdata/mealie/data" +if [ ! -d "$EXTRACT_DIR/local/appdata/mealie/data" ]; then + echo "Mealie data path missing in Borg archive" >&2 + exit 1 +fi +cp -a "$EXTRACT_DIR/local/appdata/mealie/data/." "$RESTORE_ROOT/data/" +chmod -R a+rwX "$RESTORE_ROOT/data" + +# Stufe 2: Test-Postgres hochfahren + Dump einspielen +docker compose -f "$COMPOSE_FILE" up -d restoretest-mealie-postgres >/dev/null +until docker exec restoretest-mealie-postgres pg_isready -U mealie -d mealie >/dev/null 2>&1; do + sleep 2 +done + +restore_ok=0 +for attempt in $(seq 1 12); do + if docker exec -i restoretest-mealie-postgres \ + pg_restore -U mealie -d mealie --clean --if-exists --no-owner --no-privileges \ + < "$DUMP_HOST_PATH" 2>/tmp/mealie-pg-restore.err; then + restore_ok=1 + break + fi + if grep -qiE "starting up|shutting down|connection refused" /tmp/mealie-pg-restore.err; then + sleep 5 + continue + fi + if grep -qiE "FATAL|PANIC" /tmp/mealie-pg-restore.err; then + cat /tmp/mealie-pg-restore.err >&2 + exit 1 + fi + restore_ok=1 + break +done +if [ "$restore_ok" -ne 1 ]; then + cat /tmp/mealie-pg-restore.err >&2 + exit 1 +fi + +# Stufe 3: Mealie starten +docker compose -f "$COMPOSE_FILE" up -d restoretest-mealie >/dev/null + +http_status="" +for _ in $(seq 1 60); do + http_status="$(curl -s -o /tmp/mealie-body.html -w '%{http_code}' \ + -L http://127.0.0.1:19925/api/app/about || true)" + if [ "$http_status" = "200" ]; then + break + fi + sleep 2 +done + +if [ "$http_status" != "200" ]; then + echo "Mealie HTTP smoke failed: status=$http_status" >&2 + docker logs --tail 80 restoretest-mealie >&2 || true + exit 1 +fi + +# Rezept-Count als Sanity-Check +recipe_count="$(docker exec restoretest-mealie-postgres \ + psql -U mealie -d mealie -tAc \ + "SELECT count(*) FROM recipes;" 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 db0d702..be3e11a 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" ;; + mealie) + if [ "$WHATIF" = "--what-if" ]; then + exec "$SCRIPT_DIR/mealie-restore-test.sh" --what-if + fi + exec "$SCRIPT_DIR/mealie-restore-test.sh" + ;; shared-pg-cluster) if [ "$WHATIF" = "--what-if" ]; then exec "$SCRIPT_DIR/shared-pg-cluster-restore-test.sh" --what-if