#!/usr/bin/env bash # 恢复:pg_restore 覆盖当前库 → 打开只读对账(automation.readOnlyReconcile=true)→ 映射清单行数与备份比对。 # 会删除并重建备份里的全部对象。先停掉 app 再做: # docker compose stop app # docker compose run --rm --no-deps app restore /data/backups/<时间戳> --yes # docker compose up -d app # 选项:--yes 跳过确认;--force 即使库里还有其它连接也继续(不建议)。 set -euo pipefail if [ "$(id -u)" = "0" ] && command -v gosu >/dev/null 2>&1; then exec gosu "${PUID:-10001}:${PGID:-10001}" "$0" "$@" fi : "${DATABASE_URL:?需要 DATABASE_URL}" DATA_DIR="${DATA_DIR:-/data}" BIN="${UMT_BIN:-/app/umt-server}" src="" yes=0 force=0 for a in "$@"; do case "$a" in --yes) yes=1 ;; --force) force=1 ;; -*) echo "restore: 未知选项 $a" >&2; exit 2 ;; *) src="$a" ;; esac done if [ -z "$src" ]; then echo "用法: restore <备份目录> [--yes] [--force] 可用备份:" >&2 ls -1 "$DATA_DIR/backups" 2>/dev/null >&2 || true exit 2 fi case "$src" in /*) ;; *) [ -d "$src" ] || src="$DATA_DIR/backups/$src" ;; esac for f in db.dump mappings.json; do [ -f "$src/$f" ] || { echo "restore: $src 缺少 $f" >&2; exit 2; } done if [ -f "$src/SHA256SUMS" ]; then (cd "$src" && sha256sum --quiet -c SHA256SUMS) || { echo "restore: 校验和不符,备份可能损坏" >&2; exit 2; } fi others="$(psql "$DATABASE_URL" -Atqc "select count(*) from pg_stat_activity where datname = current_database() and pid <> pg_backend_pid()")" if [ "$others" != "0" ] && [ "$force" -ne 1 ]; then echo "restore: 数据库还有 $others 个其它连接(app 是否仍在运行?)。先 docker compose stop app,或加 --force" >&2 exit 3 fi if [ "$yes" -ne 1 ]; then read -r -p "将用 $src 覆盖当前数据库,输入 yes 继续: " ans [ "$ans" = "yes" ] || { echo "已取消"; exit 1; } fi echo "restore: pg_restore ← $src/db.dump" # 经 psql 回放而不是 pg_restore 直连:镜像里是 17 版客户端,它会输出 16 版服务端不认识的 # SET transaction_timeout;过滤掉这一行即可兼容 16。单事务 + ON_ERROR_STOP:失败整体回滚。 pg_restore --clean --if-exists --no-owner --no-privileges --file - "$src/db.dump" \ | sed '/^SET transaction_timeout = /d' \ | psql -X -q -v ON_ERROR_STOP=1 --single-transaction -d "$DATABASE_URL" >/dev/null if [ -f "$src/agent-sessions.tar.gz" ]; then sessions="${AGENT_SESSION_DIR:-$DATA_DIR/agent-sessions}" mkdir -p "$sessions" echo "restore: 还原 Agent 会话 → $sessions" tar -C "$sessions" -xzf "$src/agent-sessions.tar.gz" fi "$BIN" --role read-only-on --reason "restore $(basename "$src")" >&2 check="$DATA_DIR/tmp/restore-check-$(date -u +%Y%m%dT%H%M%SZ).json" set +e "$BIN" --role export-mappings --out "$check" --compare "$src/mappings.json" >&2 cmp=$? set -e cat <