#!/bin/sh set -eu archive="${1:-}" if [ -z "$archive" ] || [ ! -f "$archive" ]; then echo "用法: CONFIRM_RESTORE=yes BACKUP_ENCRYPTION_PASSWORD=... sh scripts/restore.sh /绝对路径/备份.enc" >&2; exit 2; fi if [ "${CONFIRM_RESTORE:-}" != "yes" ]; then echo "恢复会覆盖当前数据库和上传素材,请设置 CONFIRM_RESTORE=yes" >&2; exit 2; fi backup_password="${BACKUP_ENCRYPTION_PASSWORD:-}" if [ "${#backup_password}" -lt 20 ]; then echo "BACKUP_ENCRYPTION_PASSWORD 至少需要 20 个字符" >&2; exit 2; fi archive_dir="$(cd "$(dirname "$archive")" && pwd)" archive_name="$(basename "$archive")" restore_id="$(date +%Y%m%d-%H%M%S)" echo "停止应用并保留恢复前备份…" BACKUP_ENCRYPTION_PASSWORD="$BACKUP_ENCRYPTION_PASSWORD" sh scripts/backup.sh daily docker compose stop web cleanup() { docker compose start web >/dev/null 2>&1 || true; } trap cleanup EXIT INT TERM docker compose run --rm --no-deps \ -v "$archive_dir:/restore-source:ro" \ -e RESTORE_ARCHIVE="/restore-source/$archive_name" \ -e RESTORE_ID="$restore_id" \ -e BACKUP_ENCRYPTION_PASSWORD="$BACKUP_ENCRYPTION_PASSWORD" \ web sh -eu -c ' restore_dir="/app/data/restore-$RESTORE_ID" mkdir -p "$restore_dir" openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \ -pass env:BACKUP_ENCRYPTION_PASSWORD -in "$RESTORE_ARCHIVE" | tar -xzf - -C "$restore_dir" test -f "$restore_dir/huiyu.sqlite" node -e "const Database=require(\"better-sqlite3\"); const db=new Database(process.argv[1],{readonly:true}); const result=db.pragma(\"integrity_check\",{simple:true}); db.close(); if(result!==\"ok\") process.exit(1)" "$restore_dir/huiyu.sqlite" rm -f /app/data/db/huiyu.sqlite /app/data/db/huiyu.sqlite-wal /app/data/db/huiyu.sqlite-shm cp "$restore_dir/huiyu.sqlite" /app/data/db/huiyu.sqlite rm -rf /app/data/uploads cp -R "$restore_dir/uploads" /app/data/uploads rm -rf "$restore_dir" ' docker compose start web trap - EXIT INT TERM echo "恢复完成。请执行: docker compose ps && curl -fsS http://localhost:3000/api/health"