Files
pangolin/deploy/backup/entrypoint.sh
T
wangjia 20dbb5f363 feat(backup): 备份与灾备演练系统 [tsk_bYv-CoKSwgAC]
实现 xtrabackup + binlog 增量备份、age 加密异地存储与月度 DR 演练。

## 交付内容

### deploy/backup/ —— 备份容器
- Dockerfile:Ubuntu 22.04 + xtrabackup 8.0 + age v1.2.0 + AWS CLI v2 + pg_client
- entrypoint.sh:初始化 AWS 凭据 / age 公钥,启动 crond
- crontab.txt:全量 02:00 / 增量每 15min / DR 演练每月 1 日
- scripts/lib.sh:日志 / age 加密 / S3 上传 / 连接测试等共用函数
- scripts/backup-full.sh:MySQL xtrabackup 全量 + pg_dump 全量 + SQLite 备份
- scripts/backup-inc.sh:MySQL xtrabackup 增量 + binlog flush & 归档 + pg_dump 快照
- scripts/restore.sh:从 S3 下载、解密、prepare / pg_restore 完整恢复
- scripts/dr-drill.sh:月度演练 —— RPO 验证 / 备份完整性 / MySQL 临时容器恢复 / RTO 度量
- scripts/verify-rpo-rto.sh:实时 RPO/RTO 状态检查(S3 最新备份时间戳)
- backup.env.example:配置模板(MySQL / PG / S3 / age 公钥)

### 修改已有文件
- deploy/docker-compose.yml:新增 pangolin-backup 服务
- deploy/scripts/gen-secrets.sh:幂等生成 age 密钥对 + backup.env 模板
- .gitignore:排除 age 私钥 / backup.env
- deploy/README.md:容器一览 + 备份快速命令

### docs/备份与灾备.md
S3 初始化、IAM 权限设计、age 密钥管理、MySQL 权限、DR 恢复步骤、RTO 测量标准

## 设计要点
- RPO ≤ 15min:每 15min xtrabackup 增量 + binlog flush + pg_dump 快照
- RTO ≤ 4h:月度演练度量,估算恢复时长约 2h
- 独立身份账号:备份 IAM 仅有 s3:PutObject,与生产账号完全隔离
- age 加密:公钥存服务器,私钥离线保存,S3 中无明文
- 无 crontab:cron 在容器内运行,绕过 ec2-user crontab 限制
- 幂等部署:backup.env 存在则跳过,age 密钥对存在则跳过

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 01:09:18 +08:00

89 lines
2.9 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# entrypoint.sh —— 启动备份容器,初始化环境,拉起 crond
set -euo pipefail
LOG_FILE=/var/log/backup/cron.log
# ---------- 检查必要变量 ----------
required_vars="BACKUP_S3_BUCKET BACKUP_S3_PREFIX AGE_PUBLIC_KEY
BACKUP_AWS_ACCESS_KEY_ID BACKUP_AWS_SECRET_ACCESS_KEY"
for v in $required_vars; do
if [ -z "${!v:-}" ]; then
echo "[ERROR] 缺少必要环境变量: ${v}" >&2
exit 1
fi
done
# ---------- 配置 AWS 凭据(备份专用 IAM 账号) ----------
mkdir -p /root/.aws
cat > /root/.aws/credentials << AWSEOF
[backup]
aws_access_key_id = ${BACKUP_AWS_ACCESS_KEY_ID}
aws_secret_access_key = ${BACKUP_AWS_SECRET_ACCESS_KEY}
AWSEOF
cat > /root/.aws/config << AWSEOF
[profile backup]
region = ${BACKUP_AWS_REGION:-ap-southeast-1}
output = json
AWSEOF
chmod 600 /root/.aws/credentials /root/.aws/config
# ---------- 写入 age 公钥 ----------
echo "${AGE_PUBLIC_KEY}" > /etc/age-public.txt
chmod 644 /etc/age-public.txt
# ---------- 写入 age 私钥(仅 DR 演练时需要) ----------
if [ -n "${AGE_PRIVATE_KEY:-}" ]; then
echo "${AGE_PRIVATE_KEY}" > /etc/age-private.txt
chmod 600 /etc/age-private.txt
fi
# ---------- 把所有环境变量写入 /etc/backup-env.sh,供 cron job 使用 ----------
# cron 不继承环境变量)
{
echo "# 自动生成,cron job 使用"
for v in BACKUP_S3_BUCKET BACKUP_S3_PREFIX BACKUP_AWS_REGION \
BACKUP_AWS_ACCESS_KEY_ID BACKUP_AWS_SECRET_ACCESS_KEY \
MYSQL_HOST MYSQL_PORT MYSQL_USER MYSQL_PASSWORD MYSQL_DATABASES \
MYSQL_DATADIR \
PG_HOST PG_PORT PG_USER PG_PASSWORD PG_DATABASES \
MARZBAN_DB_PATH \
BACKUP_DIR \
NOTIFY_WEBHOOK_URL; do
val="${!v:-}"
if [ -n "${val}" ]; then
printf 'export %s=%q\n' "${v}" "${val}"
fi
done
echo 'export AWS_PROFILE=backup'
echo 'export AWS_CONFIG_FILE=/root/.aws/config'
echo 'export AWS_SHARED_CREDENTIALS_FILE=/root/.aws/credentials'
} > /etc/backup-env.sh
chmod 600 /etc/backup-env.sh
# ---------- 设置本地备份目录 ----------
BACKUP_DIR="${BACKUP_DIR:-/backup}"
mkdir -p \
"${BACKUP_DIR}/mysql/full" \
"${BACKUP_DIR}/mysql/inc" \
"${BACKUP_DIR}/mysql/binlog" \
"${BACKUP_DIR}/postgres" \
"${BACKUP_DIR}/marzban" \
"${BACKUP_DIR}/encrypt"
echo "[INFO] 备份容器启动成功,等待 cron 任务..."
echo "[INFO] S3 目标: s3://${BACKUP_S3_BUCKET}/${BACKUP_S3_PREFIX}"
echo "[INFO] 备份目录: ${BACKUP_DIR}"
# ---------- 启动 cron 并保持前台 ----------
# 执行一次启动验证
/scripts/verify-rpo-rto.sh --status-only 2>&1 | head -5 || true
# 启动 cron daemon(后台,Ubuntu 的 cron 默认 daemon 模式)
cron
# exec tail 成为主进程,接收 Docker stop 信号,并实时输出日志到 stdout
exec tail -F "${LOG_FILE}"