完善数据库备份恢复链路
This commit is contained in:
Executable
+175
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# 数据库备份:postgres 容器内 pg_dump(custom 格式,自带压缩,MVCC 快照无需停机)
|
||||
# → 本地暂存 → 上传阿里云 OSS;同时打包 apps/backend/data 下的平台 JSON 配置(排除日志与缓存)。
|
||||
#
|
||||
# 用法:
|
||||
# bash deploy/backup-db.sh # 手动或 cron 调用
|
||||
# bash deploy/backup-db.sh --keep 14 # 本地保留最近 14 份(默认读 .env 的 DB_BACKUP_KEEP,再默认 7)
|
||||
#
|
||||
# 说明:
|
||||
# - 上传由 backend 镜像内的 dist/db-backup.js 完成;OSS 凭证优先读 .env 的 DB_BACKUP_OSS_*,
|
||||
# 未配置时复用 STORAGE_OSS_*。备份 key 形如 backups/postgres/order_site-<时间戳>.dump。
|
||||
# - OSS 端的过期删除请配置桶生命周期规则,见 docs/数据库备份与恢复.md。
|
||||
# - ubuntu-deploy.sh 在执行数据库迁移前会自动调用本脚本(--skip-backup 可跳过)。
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${PROJECT_ROOT}/.env"
|
||||
BACKUP_ROOT="${PROJECT_ROOT}/backups"
|
||||
DB_DIR="${BACKUP_ROOT}/postgres"
|
||||
CONFIG_DIR="${BACKUP_ROOT}/configs"
|
||||
KEEP=""
|
||||
BACKUP_PREFIX=""
|
||||
|
||||
log() { printf '\033[1;34m[backup]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[backup]\033[0m %s\n' "$*"; }
|
||||
fail() { printf '\033[1;31m[backup]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
parse_args() {
|
||||
while (($# > 0)); do
|
||||
case "$1" in
|
||||
--keep)
|
||||
[[ $# -ge 2 ]] || fail "--keep 需要一个正整数参数。"
|
||||
KEEP="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '4,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
fail "未知参数:$1。执行 bash deploy/backup-db.sh --help 查看用法。"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${KEEP}" ]]; then
|
||||
KEEP="${DB_BACKUP_KEEP:-$(read_env_value DB_BACKUP_KEEP || true)}"
|
||||
fi
|
||||
KEEP="${KEEP:-7}"
|
||||
[[ "${KEEP}" =~ ^[1-9][0-9]*$ ]] || fail "本地保留份数必须是正整数,当前值:${KEEP}"
|
||||
|
||||
BACKUP_PREFIX="${DB_BACKUP_OSS_PREFIX:-$(read_env_value DB_BACKUP_OSS_PREFIX || true)}"
|
||||
BACKUP_PREFIX="${BACKUP_PREFIX%/}"
|
||||
BACKUP_PREFIX="${BACKUP_PREFIX:-backups/postgres}"
|
||||
[[ "${BACKUP_PREFIX}" != /* && "${BACKUP_PREFIX}" != *'..'* ]] || fail "DB_BACKUP_OSS_PREFIX 不能是绝对路径或包含 ..。"
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
[[ -f "${ENV_FILE}" ]] || return 0
|
||||
awk -F= -v key="${key}" '
|
||||
$0 !~ /^[[:space:]]*#/ && $1 == key {
|
||||
value = substr($0, index($0, "=") + 1)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
gsub(/^"|"$/, "", value)
|
||||
gsub(/^'\''|'\''$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "${ENV_FILE}"
|
||||
}
|
||||
|
||||
run_backup_tool() {
|
||||
# 用一次性容器而不是 exec,部署过程中 backend 未运行时也能上传。
|
||||
docker compose run --rm --no-deps -T backend node dist/db-backup.js "$@"
|
||||
}
|
||||
|
||||
run_pg_dump() {
|
||||
local database_url
|
||||
database_url="$(read_env_value DATABASE_URL)"
|
||||
if [[ -z "${database_url}" || "${database_url}" == *"@postgres:"* ]]; then
|
||||
docker compose exec -T postgres sh -c 'pg_dump -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-order_site}" -Fc'
|
||||
else
|
||||
log "DATABASE_URL 指向外部数据库,从 postgres 容器经网络直连导出"
|
||||
docker compose exec -T -e DB_DUMP_URL="${database_url}" postgres sh -c 'pg_dump "$DB_DUMP_URL" -Fc'
|
||||
fi
|
||||
}
|
||||
|
||||
backup_database() {
|
||||
local stamp dump_file
|
||||
stamp="$(date +%Y%m%d-%H%M%S)"
|
||||
dump_file="${DB_DIR}/order_site-${stamp}.dump"
|
||||
|
||||
log "导出数据库(pg_dump custom 格式,无需停机)"
|
||||
if ! run_pg_dump > "${dump_file}"; then
|
||||
rm -f "${dump_file}"
|
||||
fail "pg_dump 失败。可先用 docker compose logs postgres 排查数据库状态。"
|
||||
fi
|
||||
[[ -s "${dump_file}" ]] || fail "pg_dump 输出为空:${dump_file}"
|
||||
|
||||
log "上传数据库备份到 OSS:${BACKUP_PREFIX}/$(basename "${dump_file}")"
|
||||
local dump_size
|
||||
dump_size="$(wc -c < "${dump_file}" | tr -d ' ')"
|
||||
if ! run_backup_tool upload --key "${BACKUP_PREFIX}/$(basename "${dump_file}")" --size "${dump_size}" < "${dump_file}"; then
|
||||
fail "备份上传 OSS 失败。数据库未受影响,本地已保留:${dump_file}"
|
||||
fi
|
||||
log "数据库备份完成:${dump_size} 字节"
|
||||
}
|
||||
|
||||
backup_configs() {
|
||||
local stamp tar_file sample
|
||||
sample="$(
|
||||
find "${PROJECT_ROOT}/apps/backend/data" -type f \
|
||||
! -path '*/logs/*' ! -path '*/cache/*' \
|
||||
! -path '*/browser-sessions/*' ! -path '*/redeem-screenshots/*' 2>/dev/null | head -n 1
|
||||
)"
|
||||
if [[ -z "${sample}" ]]; then
|
||||
warn "apps/backend/data 下没有需要备份的配置文件,跳过配置打包"
|
||||
return
|
||||
fi
|
||||
|
||||
stamp="$(date +%Y%m%d-%H%M%S)"
|
||||
tar_file="${CONFIG_DIR}/order_site-${stamp}.tar.gz"
|
||||
|
||||
if ! tar -czf "${tar_file}" \
|
||||
--exclude='./logs' --exclude='./cache' \
|
||||
--exclude='./browser-sessions' --exclude='./redeem-screenshots' \
|
||||
-C "${PROJECT_ROOT}/apps/backend/data" .; then
|
||||
rm -f "${tar_file}"
|
||||
fail "打包 apps/backend/data 失败。"
|
||||
fi
|
||||
|
||||
local config_prefix
|
||||
config_prefix="${BACKUP_PREFIX%/postgres}/configs"
|
||||
log "上传配置备份到 OSS:${config_prefix}/$(basename "${tar_file}")"
|
||||
local config_size
|
||||
config_size="$(wc -c < "${tar_file}" | tr -d ' ')"
|
||||
if ! run_backup_tool upload --key "${config_prefix}/$(basename "${tar_file}")" --kind app-configs --size "${config_size}" < "${tar_file}"; then
|
||||
fail "配置备份上传 OSS 失败。数据库备份已上传,本地配置备份已保留:${tar_file}"
|
||||
fi
|
||||
log "配置备份完成:${config_size} 字节"
|
||||
}
|
||||
|
||||
prune_local() {
|
||||
local dir="$1" removed
|
||||
removed="$(cd "${dir}" && ls -1t order_site-*.dump order_site-*.tar.gz 2>/dev/null | tail -n +$((KEEP + 1)) || true)"
|
||||
if [[ -z "${removed}" ]]; then
|
||||
return
|
||||
fi
|
||||
while IFS= read -r file; do
|
||||
rm -f -- "${file}"
|
||||
log "清理本地旧备份:${file}(本地保留 ${KEEP} 份,OSS 仍由生命周期规则管理)"
|
||||
done <<<"${removed}"
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
command -v docker >/dev/null 2>&1 || fail "未找到 docker。"
|
||||
docker compose version >/dev/null 2>&1 || fail "未找到 docker compose 插件。"
|
||||
if [[ -z "$(docker compose images backend -q 2>/dev/null)" ]]; then
|
||||
fail "backend 镜像不存在,请先完成部署:bash deploy/ubuntu-deploy.sh"
|
||||
fi
|
||||
|
||||
mkdir -p "${DB_DIR}" "${CONFIG_DIR}"
|
||||
backup_database
|
||||
backup_configs
|
||||
prune_local "${DB_DIR}"
|
||||
prune_local "${CONFIG_DIR}"
|
||||
log "备份完成"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user