#!/usr/bin/env bash # 在线热备:不停服备份 SQLite(backup API 保证一致性)+ files/ 原始文件目录。 # 用法:./backup.sh [备份根目录](默认 ../backups) # 保留策略:日备保留 30 天,每月 1 号的首份备份额外保留 12 个月(monthly/)。 # 建议宿主机 cron:15 2 * * * /path/to/deploy/backup.sh >> /path/to/deploy/backup.log 2>&1 # 官方环境建议至少将 monthly/ 同步一份到异机/异盘。 set -euo pipefail ROOT="$(cd "$(dirname "$0")" && pwd)" DEST="${1:-$ROOT/../backups}" CONTAINER="${CONTAINER:-caiwuzongzhang-app}" STAMP="$(date +%Y%m%d-%H%M%S)" DAY_DIR="$DEST/daily" MONTH_DIR="$DEST/monthly" mkdir -p "$DAY_DIR" "$MONTH_DIR" # 1) SQLite 在线热备(容器内 python sqlite3 backup API,主库可继续写入) docker exec "$CONTAINER" python - <<'PY' import os, sqlite3 src = sqlite3.connect(os.environ.get("APP_DB_PATH", "/app/data/app.db")) dst = sqlite3.connect("/tmp/app.db.backup") src.backup(dst) dst.close(); src.close() print("hot-backup ok") PY docker cp "$CONTAINER:/tmp/app.db.backup" "$DAY_DIR/app.db.$STAMP" docker exec "$CONTAINER" rm -f /tmp/app.db.backup # 2) 原始文件目录打包(银行原始证据,只读复制,绝不改动) tar -C "$ROOT/.." -czf "$DAY_DIR/files.$STAMP.tar.gz" data/files # 3) 完整性自检:PRAGMA integrity_check + 关键计数 docker run --rm -v "$DAY_DIR/app.db.$STAMP:/check/app.db:ro" python:3.12-slim-bookworm \ python -c " import sqlite3 c = sqlite3.connect('/check/app.db') print('integrity:', c.execute('PRAGMA integrity_check').fetchone()[0]) for t in ('source_rows', 'transfer_match_decisions', 'period_close_runs', 'audit_log'): try: print(t, c.execute(f'SELECT COUNT(*) FROM {t}').fetchone()[0]) except sqlite3.OperationalError: print(t, 'n/a') " # 4) 保留策略:日备 >30 天删除;每月 1 号留档 monthly/ find "$DAY_DIR" -name 'app.db.*' -mtime +30 -delete find "$DAY_DIR" -name 'files.*.tar.gz' -mtime +30 -delete if [ "$(date +%d)" = "01" ]; then cp "$DAY_DIR/app.db.$STAMP" "$MONTH_DIR/app.db.$STAMP" cp "$DAY_DIR/files.$STAMP.tar.gz" "$MONTH_DIR/files.$STAMP.tar.gz" find "$MONTH_DIR" -mtime +365 -delete fi echo "backup complete: $DAY_DIR/app.db.$STAMP"