#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" . "$ROOT_DIR/scripts/lib.sh" BACKUP_NAME="${1:-}" [ -n "$BACKUP_NAME" ] || fail_json "XUQM_PRIVATE_4010" "usage: restore.sh " "restore" BACKUP_DIR="$ROOT_DIR/data/backups" BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME" [ -d "$BACKUP_PATH" ] || fail_json "XUQM_PRIVATE_4011" "backup not found: $BACKUP_PATH" "restore" audit "restore" "STARTED" "backup=$BACKUP_NAME" progress "restore" "STARTED" "backup=$BACKUP_NAME" # Verify checksums if [ -f "$BACKUP_PATH/sha256sums.txt" ]; then (cd "$BACKUP_DIR" && sha256sum -c "$BACKUP_NAME/sha256sums.txt" --ignore-missing) || \ fail_json "XUQM_PRIVATE_4012" "checksum verification failed for $BACKUP_NAME" "restore" audit "restore" "CHECKSUM_OK" "$BACKUP_NAME" fi # Stop running services compose down 2>/dev/null || true audit "restore" "SERVICES_STOPPED" "" # Restore config (skip secrets.env if already exists) if [ -f "$BACKUP_PATH/config.tar.gz" ]; then tar -xzf "$BACKUP_PATH/config.tar.gz" -C "$ROOT_DIR" \ --exclude='config/secrets.env' audit "restore" "CONFIG_DONE" "config restored (secrets.env preserved)" fi load_env # Restore MySQL if [ -f "$BACKUP_PATH/mysql.sql" ] && grep -qv '^#' "$BACKUP_PATH/mysql.sql" 2>/dev/null; then if [ "${MYSQL_MODE:-external}" = "managed" ]; then COMPOSE_PROFILES=infra-mysql compose up -d mysql sleep 5 MYSQL_CTR="$(compose ps -q mysql | head -1)" docker exec -i "$MYSQL_CTR" \ mysql -u root -p"${MYSQL_ROOT_PASSWORD:-}" "${MYSQL_DATABASE:-xuqm_private}" \ < "$BACKUP_PATH/mysql.sql" audit "restore" "MYSQL_DONE" "restored to managed mysql" elif command -v mysql >/dev/null 2>&1; then mysql -h "${MYSQL_HOST:-127.0.0.1}" -P "${MYSQL_PORT:-3306}" \ -u "${MYSQL_USERNAME:-}" -p"${MYSQL_PASSWORD:-}" \ "${MYSQL_DATABASE:-xuqm_private}" < "$BACKUP_PATH/mysql.sql" audit "restore" "MYSQL_DONE" "restored to external mysql" else audit "restore" "MYSQL_SKIPPED" "mysql client not available; restore manually" printf 'WARNING: MySQL restore skipped. Restore manually with:\n' printf ' mysql -h %s -P %s -u %s -p %s < %s/mysql.sql\n' \ "${MYSQL_HOST:-}" "${MYSQL_PORT:-3306}" "${MYSQL_USERNAME:-}" \ "${MYSQL_DATABASE:-}" "$BACKUP_PATH" fi fi # Restore Redis if [ -f "$BACKUP_PATH/redis-dump.rdb" ]; then if [ "${REDIS_MODE:-external}" = "managed" ]; then COMPOSE_PROFILES=infra-redis compose up -d redis sleep 3 REDIS_CTR="$(compose ps -q redis | head -1)" compose pause redis 2>/dev/null || docker pause "$REDIS_CTR" 2>/dev/null || true docker cp "$BACKUP_PATH/redis-dump.rdb" "$REDIS_CTR:/data/dump.rdb" compose unpause redis 2>/dev/null || docker unpause "$REDIS_CTR" 2>/dev/null || true docker restart "$REDIS_CTR" audit "restore" "REDIS_DONE" "redis-dump.rdb restored to managed redis" else audit "restore" "REDIS_SKIPPED" "external Redis — restore dump.rdb manually" printf 'WARNING: Redis restore skipped for external mode. Copy %s/redis-dump.rdb to Redis data dir.\n' "$BACKUP_PATH" fi fi # Restart services "$ROOT_DIR/scripts/render-config.sh" COMPOSE_PROFILES="${COMPOSE_PROFILES:-base}" compose up -d "$ROOT_DIR/scripts/healthcheck.sh" audit "restore" "DONE" "backup=$BACKUP_NAME restored successfully" progress "restore" "DONE" "backup=$BACKUP_NAME" printf 'Restore complete from: %s\n' "$BACKUP_PATH"