diff --git a/docs/BACKUP_AND_RECOVERY_PLAN.md b/docs/BACKUP_AND_RECOVERY_PLAN.md index b0c98bc..eb77529 100644 --- a/docs/BACKUP_AND_RECOVERY_PLAN.md +++ b/docs/BACKUP_AND_RECOVERY_PLAN.md @@ -70,7 +70,6 @@ ## 6. 当前缺口 - 尚无自动化备份脚本 -- 尚无恢复演练脚本 - 尚无定时任务 - 尚无备份完整性校验 @@ -80,3 +79,9 @@ 2. 至少在临时目录恢复一次可读数据库 3. 明确密钥存放位置和负责人 4. 不得再使用“Git 备份即可恢复系统”作为对外或内部结论 + +## 8. 当前已落地产物 + +- `scripts/backup_verify.sh` + - 可复制当前 SQLite 数据库与示例报告到临时目录 + - 可验证复制后的数据库可读、表可枚举、报告文件可见 diff --git a/scripts/backup_verify.sh b/scripts/backup_verify.sh new file mode 100644 index 0000000..4d8f42d --- /dev/null +++ b/scripts/backup_verify.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VERIFY_DIR="${1:-$(mktemp -d /tmp/gaokao-backup-verify-XXXXXX)}" +ADMIN_DB="${GAOKAO_DB_PATH:-${ROOT_DIR}/data/orders/admin.db}" +ORDERS_DB="${GAOKAO_ORDERS_DB_PATH:-${ROOT_DIR}/data/orders.db}" +SHARE_DB="${GAOKAO_SHARE_DB_PATH:-${ROOT_DIR}/data/share/short_links.db}" +SHARE_REPORT_DIR="${GAOKAO_SHARE_REPORT_DIR:-${ROOT_DIR}/data/share/reports}" +EXAMPLE_REPORT_DIR="${ROOT_DIR}/data/examples" + +log() { + printf '[backup-verify] %s\n' "$1" +} + +copy_if_exists() { + local src="$1" + local dest_dir="$2" + if [[ -f "$src" ]]; then + mkdir -p "$dest_dir" + cp "$src" "$dest_dir/" + log "copied file: $src" + else + log "skip missing file: $src" + fi +} + +copy_dir_if_exists() { + local src="$1" + local dest_dir="$2" + if [[ -d "$src" ]]; then + mkdir -p "$dest_dir" + cp -R "$src" "$dest_dir/" + log "copied directory: $src" + else + log "skip missing directory: $src" + fi +} + +verify_sqlite_file() { + local db_path="$1" + if [[ ! -f "$db_path" ]]; then + return 0 + fi + python3 - <<'PY' "$db_path" +import sqlite3 +import sys +from pathlib import Path +p = Path(sys.argv[1]) +conn = sqlite3.connect(p) +try: + cur = conn.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") + tables = [row[0] for row in cur.fetchall()] + print(f"sqlite_ok path={p} tables={','.join(tables) if tables else 'none'}") +finally: + conn.close() +PY +} + +main() { + mkdir -p "$VERIFY_DIR" + log "verify dir: $VERIFY_DIR" + + copy_if_exists "$ADMIN_DB" "$VERIFY_DIR/db" + copy_if_exists "$ORDERS_DB" "$VERIFY_DIR/db" + copy_if_exists "$SHARE_DB" "$VERIFY_DIR/db" + copy_dir_if_exists "$SHARE_REPORT_DIR" "$VERIFY_DIR/files" + copy_dir_if_exists "$EXAMPLE_REPORT_DIR" "$VERIFY_DIR/files" + + log "verifying copied sqlite files" + if [[ -d "$VERIFY_DIR/db" ]]; then + while IFS= read -r -d '' db_file; do + verify_sqlite_file "$db_file" + done < <(find "$VERIFY_DIR/db" -type f -name '*.db' -print0 | sort -z) + fi + + log "verifying copied report artifacts" + if [[ -d "$VERIFY_DIR/files" ]]; then + find "$VERIFY_DIR/files" -type f | sort | sed 's#^#[backup-verify] artifact: #' + fi + + log "backup verification finished" +} + +main "$@"