diff --git a/ops/restore-tests/redis-compose.test.yml b/ops/restore-tests/redis-compose.test.yml new file mode 100644 index 0000000..5b97b8b --- /dev/null +++ b/ops/restore-tests/redis-compose.test.yml @@ -0,0 +1,16 @@ +services: + restoretest-redis: + image: redis:8.8.0-alpine@sha256:09160599abd229764c0fb44cb6be640294e1d360a54b19985ab4843dcf2d90f1 + container_name: restoretest-redis + restart: "no" + command: + - sh + - -c + - exec redis-server --appendonly yes --requirepass "$$(cat /run/secrets/redis_password)" + ports: + - "127.0.0.1:16379:6379/tcp" + volumes: + - /mnt/user/backups/restore-lab/redis/data:/data + - /mnt/user/backups/restore-lab/redis/secrets/redis_password.txt:/run/secrets/redis_password:ro + security_opt: + - no-new-privileges:true diff --git a/ops/restore-tests/redis-restore-test.sh b/ops/restore-tests/redis-restore-test.sh new file mode 100755 index 0000000..bd1ea6b --- /dev/null +++ b/ops/restore-tests/redis-restore-test.sh @@ -0,0 +1,165 @@ +#!/bin/bash +set -euo pipefail + +# Redis 8 Restore Smoke Test +# +# Scope: +# - Borg-Extract von /local/appdata/redis und redis_password.txt +# - Start einer isolierten Redis-8-Testinstanz auf localhost:16379 +# - PING, INFO server und DBSIZE ohne Ausgabe des Passworts + +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/redis" +REPORT_ROOT="/mnt/user/backups/restore-reports" +EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/redis-extract" +COMPOSE_FILE="$SCRIPT_DIR/redis-compose.test.yml" +REPORT_FILE="$REPORT_ROOT/redis-$(date +%F).md" + +if [ "$WHATIF" -eq 1 ]; then + cat <&2 + exit 1 +fi + +borg_extract "/restore/redis-extract" \ + "local/appdata/redis" \ + "local/appdata/secrets/redis_password.txt" + +if [ ! -d "$EXTRACT_DIR/local/appdata/redis" ]; then + echo "Redis data path missing in Borg archive" >&2 + exit 1 +fi + +if [ ! -s "$EXTRACT_DIR/local/appdata/secrets/redis_password.txt" ]; then + echo "Redis password file missing in Borg archive" >&2 + exit 1 +fi + +cp -a "$EXTRACT_DIR/local/appdata/redis/." "$RESTORE_ROOT/data/" +cp "$EXTRACT_DIR/local/appdata/secrets/redis_password.txt" "$RESTORE_ROOT/secrets/redis_password.txt" +chmod -R a+rwX "$RESTORE_ROOT/data" +chmod a+r "$RESTORE_ROOT/secrets/redis_password.txt" + +data_files="$(find "$RESTORE_ROOT/data" -type f | wc -l | tr -d ' ')" +data_bytes="$(du -sb "$RESTORE_ROOT/data" | awk '{print $1}')" + +docker compose -f "$COMPOSE_FILE" up -d restoretest-redis >/dev/null + +ping_result="" +for _ in $(seq 1 60); do + ping_result="$(docker exec restoretest-redis sh -lc \ + 'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning PING' 2>/dev/null || true)" + if [ "$ping_result" = "PONG" ]; then + break + fi + sleep 1 +done + +if [ "$ping_result" != "PONG" ]; then + echo "Redis PING smoke failed: $ping_result" >&2 + docker logs --tail 80 restoretest-redis >&2 || true + exit 1 +fi + +redis_version="$(docker exec restoretest-redis sh -lc \ + 'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning INFO server | awk -F: "/^redis_version:/ {gsub(/\r/, \"\", \$2); print \$2}"')" +dbsize="$(docker exec restoretest-redis sh -lc \ + 'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning DBSIZE' | tr -d '\r')" +aof_enabled="$(docker exec restoretest-redis sh -lc \ + 'p=$(cat /run/secrets/redis_password); redis-cli -a "$p" --no-auth-warning INFO persistence | awk -F: "/^aof_enabled:/ {gsub(/\r/, \"\", \$2); print \$2}"')" + +case "$redis_version" in + 8.*) ;; + *) + echo "Unexpected Redis version: $redis_version" >&2 + exit 1 + ;; +esac + +if [ "${dbsize:-0}" -lt 1 ]; then + echo "Unexpected Redis DBSIZE: $dbsize" >&2 + exit 1 +fi + +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 5c336cb..ef6d84e 100755 --- a/ops/restore-tests/run-restore-checks.sh +++ b/ops/restore-tests/run-restore-checks.sh @@ -46,6 +46,12 @@ case "$MODE" in fi exec "$SCRIPT_DIR/adguard-restore-test.sh" ;; + redis) + if [ "$WHATIF" = "--what-if" ]; then + exec "$SCRIPT_DIR/redis-restore-test.sh" --what-if + fi + exec "$SCRIPT_DIR/redis-restore-test.sh" + ;; nextcloud) if [ "$WHATIF" = "--what-if" ]; then exec "$SCRIPT_DIR/nextcloud-restore-test.sh" --what-if @@ -89,7 +95,7 @@ case "$MODE" in exec "$SCRIPT_DIR/shared-pg-cluster-restore-test.sh" ;; *) - echo "Usage: $0 {freshness|vaultwarden|gitea|paperless|immich|authelia|adguard|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2 + echo "Usage: $0 {freshness|vaultwarden|gitea|paperless|immich|authelia|adguard|redis|nextcloud|komodo-bootstrap|komodo-mongo-restore|traefik|mailarchiver|mealie|shared-pg-cluster} [--what-if]" >&2 exit 1 ;; esac