feat: 完善数据库备份恢复流程
This commit is contained in:
@@ -122,13 +122,24 @@ alembic upgrade head
|
|||||||
|
|
||||||
## 备份
|
## 备份
|
||||||
|
|
||||||
可使用脚本:
|
备份脚本会生成压缩 SQL 和 SHA-256 校验文件,默认保留 14 天。密码只通过环境变量传入,不写入命令参数:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ai_knowledge_base_v2
|
cd ai_knowledge_base_v2
|
||||||
BACKUP_DIR=./backups ./scripts/mysql_backup.sh
|
MYSQL_PASSWORD='数据库密码' BACKUP_DIR=./backups ./scripts/mysql_backup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
恢复前应停止用户流量并再次备份当前数据库。恢复脚本要求显式确认,并会先验证校验和与压缩文件完整性:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MYSQL_PASSWORD='数据库密码' \
|
||||||
|
BACKUP_FILE='./backups/ai_knowledge_base_v2_YYYYMMDD_HHMMSS.sql.gz' \
|
||||||
|
RESTORE_CONFIRM=YES \
|
||||||
|
./scripts/mysql_restore.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
恢复后必须检查迁移版本、`/api/ready`、管理员登录、用户登录和一次真实问答。备份文件与 `CONFIG_ENCRYPTION_KEY` 必须分别保存;只有数据库备份而没有对应加密密钥时,敏感配置无法解密。
|
||||||
|
|
||||||
## 回滚原则
|
## 回滚原则
|
||||||
|
|
||||||
1. 先停止新版本服务。
|
1. 先停止新版本服务。
|
||||||
|
|||||||
@@ -5,20 +5,42 @@ BACKUP_DIR="${BACKUP_DIR:-./backups}"
|
|||||||
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
|
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
|
||||||
MYSQL_PORT="${MYSQL_PORT:-3307}"
|
MYSQL_PORT="${MYSQL_PORT:-3307}"
|
||||||
MYSQL_USER="${MYSQL_USER:-ai_kb}"
|
MYSQL_USER="${MYSQL_USER:-ai_kb}"
|
||||||
MYSQL_PASSWORD="${MYSQL_PASSWORD:-ai_kb}"
|
|
||||||
MYSQL_DATABASE="${MYSQL_DATABASE:-ai_knowledge_base_v2}"
|
MYSQL_DATABASE="${MYSQL_DATABASE:-ai_knowledge_base_v2}"
|
||||||
|
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-14}"
|
||||||
|
: "${MYSQL_PASSWORD:?必须通过 MYSQL_PASSWORD 配置数据库密码}"
|
||||||
|
|
||||||
|
command -v mysqldump >/dev/null || { echo "未找到 mysqldump" >&2; exit 1; }
|
||||||
|
command -v gzip >/dev/null || { echo "未找到 gzip" >&2; exit 1; }
|
||||||
|
command -v shasum >/dev/null || { echo "未找到 shasum" >&2; exit 1; }
|
||||||
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
mkdir -p "$BACKUP_DIR"
|
||||||
OUTPUT_FILE="$BACKUP_DIR/${MYSQL_DATABASE}_$(date +%Y%m%d_%H%M%S).sql"
|
timestamp="$(date +%Y%m%d_%H%M%S)"
|
||||||
|
output_file="$BACKUP_DIR/${MYSQL_DATABASE}_${timestamp}.sql.gz"
|
||||||
|
temporary_file="${output_file}.tmp"
|
||||||
|
trap 'rm -f "$temporary_file"' EXIT
|
||||||
|
|
||||||
mysqldump \
|
MYSQL_PWD="$MYSQL_PASSWORD" mysqldump \
|
||||||
-h "$MYSQL_HOST" \
|
--host="$MYSQL_HOST" \
|
||||||
-P "$MYSQL_PORT" \
|
--port="$MYSQL_PORT" \
|
||||||
-u "$MYSQL_USER" \
|
--user="$MYSQL_USER" \
|
||||||
-p"$MYSQL_PASSWORD" \
|
|
||||||
--single-transaction \
|
--single-transaction \
|
||||||
|
--quick \
|
||||||
--routines \
|
--routines \
|
||||||
--triggers \
|
--triggers \
|
||||||
"$MYSQL_DATABASE" > "$OUTPUT_FILE"
|
--events \
|
||||||
|
--set-gtid-purged=OFF \
|
||||||
|
--default-character-set=utf8mb4 \
|
||||||
|
"$MYSQL_DATABASE" | gzip -9 > "$temporary_file"
|
||||||
|
|
||||||
echo "备份完成:$OUTPUT_FILE"
|
gzip -t "$temporary_file"
|
||||||
|
test -s "$temporary_file"
|
||||||
|
mv "$temporary_file" "$output_file"
|
||||||
|
(cd "$(dirname "$output_file")" && shasum -a 256 "$(basename "$output_file")" > "$(basename "$output_file").sha256")
|
||||||
|
|
||||||
|
if [[ "$BACKUP_RETENTION_DAYS" =~ ^[0-9]+$ ]] && (( BACKUP_RETENTION_DAYS > 0 )); then
|
||||||
|
find "$BACKUP_DIR" -type f \( -name "${MYSQL_DATABASE}_*.sql.gz" -o -name "${MYSQL_DATABASE}_*.sql.gz.sha256" \) \
|
||||||
|
-mtime "+$BACKUP_RETENTION_DAYS" -delete
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "备份完成:$output_file"
|
||||||
|
echo "校验文件:${output_file}.sha256"
|
||||||
|
|||||||
38
ai_knowledge_base_v2/scripts/mysql_restore.sh
Executable file
38
ai_knowledge_base_v2/scripts/mysql_restore.sh
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
|
||||||
|
MYSQL_PORT="${MYSQL_PORT:-3307}"
|
||||||
|
MYSQL_USER="${MYSQL_USER:-ai_kb}"
|
||||||
|
MYSQL_DATABASE="${MYSQL_DATABASE:-ai_knowledge_base_v2}"
|
||||||
|
: "${MYSQL_PASSWORD:?必须通过 MYSQL_PASSWORD 配置数据库密码}"
|
||||||
|
: "${BACKUP_FILE:?必须通过 BACKUP_FILE 指定 .sql.gz 备份文件}"
|
||||||
|
|
||||||
|
if [[ "${RESTORE_CONFIRM:-}" != "YES" ]]; then
|
||||||
|
echo "恢复会覆盖目标数据库数据,确认后请设置 RESTORE_CONFIRM=YES" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v mysql >/dev/null || { echo "未找到 mysql 客户端" >&2; exit 1; }
|
||||||
|
command -v gzip >/dev/null || { echo "未找到 gzip" >&2; exit 1; }
|
||||||
|
command -v shasum >/dev/null || { echo "未找到 shasum" >&2; exit 1; }
|
||||||
|
test -f "$BACKUP_FILE" || { echo "备份文件不存在:$BACKUP_FILE" >&2; exit 1; }
|
||||||
|
test -f "${BACKUP_FILE}.sha256" || { echo "缺少校验文件:${BACKUP_FILE}.sha256" >&2; exit 1; }
|
||||||
|
|
||||||
|
(cd "$(dirname "$BACKUP_FILE")" && shasum -a 256 -c "$(basename "$BACKUP_FILE").sha256")
|
||||||
|
gzip -t "$BACKUP_FILE"
|
||||||
|
|
||||||
|
MYSQL_PWD="$MYSQL_PASSWORD" gzip -dc "$BACKUP_FILE" | MYSQL_PWD="$MYSQL_PASSWORD" mysql \
|
||||||
|
--host="$MYSQL_HOST" \
|
||||||
|
--port="$MYSQL_PORT" \
|
||||||
|
--user="$MYSQL_USER" \
|
||||||
|
--default-character-set=utf8mb4 \
|
||||||
|
"$MYSQL_DATABASE"
|
||||||
|
|
||||||
|
MYSQL_PWD="$MYSQL_PASSWORD" mysql \
|
||||||
|
--host="$MYSQL_HOST" \
|
||||||
|
--port="$MYSQL_PORT" \
|
||||||
|
--user="$MYSQL_USER" \
|
||||||
|
--database="$MYSQL_DATABASE" \
|
||||||
|
--batch --skip-column-names \
|
||||||
|
--execute="SELECT CONCAT('恢复完成,迁移版本:', COALESCE((SELECT version_num FROM alembic_version LIMIT 1), '未知'));"
|
||||||
@@ -46,4 +46,8 @@ BOOTSTRAP_ADMIN_USERNAME=admin \
|
|||||||
BOOTSTRAP_ADMIN_PASSWORD='Strong-Test-Password-2026' \
|
BOOTSTRAP_ADMIN_PASSWORD='Strong-Test-Password-2026' \
|
||||||
docker compose -f docker-compose.prod.yml config --quiet
|
docker compose -f docker-compose.prod.yml config --quiet
|
||||||
|
|
||||||
|
echo "== 备份恢复脚本语法检查 =="
|
||||||
|
bash -n scripts/mysql_backup.sh
|
||||||
|
bash -n scripts/mysql_restore.sh
|
||||||
|
|
||||||
echo "阶段六自动验证完成"
|
echo "阶段六自动验证完成"
|
||||||
|
|||||||
Reference in New Issue
Block a user