138 lines
4.1 KiB
Bash
138 lines
4.1 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# 追光AI 容器卷备份脚本 (在宿主机上跑)
|
|
# - 把 4 个 docker 卷打包到 /home/ubuntu/zhuiguang-ai-backup/dockers/
|
|
# - 默认保留最近 7 天快照
|
|
# - 增强:磁盘空间预检 + 完整性校验 + 汇总报告
|
|
# - 用法: bash scripts/backup-volumes.sh
|
|
# =============================================================================
|
|
set -e
|
|
|
|
PROJECT="/home/ubuntu/zhuiguang-ai"
|
|
BACKUP_ROOT="/home/ubuntu/zhuiguang-ai-backup/dockers"
|
|
STAMP=$(date +%Y%m%d-%H%M%S)
|
|
BACKUP_DIR="${BACKUP_ROOT}/${STAMP}"
|
|
KEEP_DAYS=7
|
|
REQUIRED_FREE_MB=${REQUIRED_FREE_MB:-500} # 最少需要磁盘空闲空间 (MB)
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
pass() { echo " ✅ $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " ❌ $1"; FAIL=$((FAIL + 1)); }
|
|
warn() { echo " ⚠️ $1"; WARN=$((WARN + 1)); }
|
|
|
|
echo "========================================"
|
|
echo " 追光AI 卷备份 ($(date '+%Y-%m-%d %H:%M:%S'))"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
# ---- 1) 磁盘空间预检 ----
|
|
echo "--- 1) 磁盘空间检查 ---"
|
|
BACKUP_DISK=$(df -m "$BACKUP_ROOT" 2>/dev/null | tail -1 | awk '{print $4}')
|
|
if [ -z "$BACKUP_DISK" ]; then
|
|
BACKUP_DISK=$(df -m /home 2>/dev/null | tail -1 | awk '{print $4}')
|
|
fi
|
|
if [ -z "$BACKUP_DISK" ] || [ "$BACKUP_DISK" -lt "$REQUIRED_FREE_MB" ]; then
|
|
fail "磁盘空间不足: 可用 ${BACKUP_DISK:-?}MB, 需要 ≥ ${REQUIRED_FREE_MB}MB"
|
|
exit 1
|
|
fi
|
|
pass "磁盘可用空间: ${BACKUP_DISK}MB"
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
echo ""
|
|
echo "--- 2) 卷备份 ---"
|
|
|
|
declare -A VOL_SIZES
|
|
TOTAL_BYTES=0
|
|
|
|
# 备份 4 个卷
|
|
for vol in zhuiguang_ai_bot_data zhuiguang_ai_bot_public zhuiguang_ai_bot_logs zhuiguang_ai_bot_prisma; do
|
|
if ! docker volume inspect "$vol" >/dev/null 2>&1; then
|
|
warn "卷 $vol 不存在,跳过"
|
|
continue
|
|
fi
|
|
|
|
echo " [备份] $vol → ${vol}.tgz"
|
|
TAR_OUTPUT=$(docker run --rm \
|
|
-v "${vol}:/src:ro" \
|
|
-v "${BACKUP_DIR}:/dst" \
|
|
alpine:3.19 \
|
|
tar czf "/dst/${vol}.tgz" -C /src . 2>&1) || true
|
|
|
|
if [ -f "${BACKUP_DIR}/${vol}.tgz" ]; then
|
|
SIZE=$(ls -lh "${BACKUP_DIR}/${vol}.tgz" | awk '{print $5}')
|
|
pass "${vol}.tgz (${SIZE})"
|
|
VOL_SIZES[$vol]=$SIZE
|
|
TOTAL_BYTES=$((TOTAL_BYTES + $(stat -c%s "${BACKUP_DIR}/${vol}.tgz" 2>/dev/null || echo 0)))
|
|
else
|
|
fail "${vol}.tgz 创建失败"
|
|
fi
|
|
done
|
|
|
|
# ---- 3) 完整性校验 ----
|
|
echo ""
|
|
echo "--- 3) 完整性校验 ---"
|
|
for vol in "${!VOL_SIZES[@]}"; do
|
|
TGZ_PATH="${BACKUP_DIR}/${vol}.tgz"
|
|
if tar tzf "$TGZ_PATH" >/dev/null 2>&1; then
|
|
FILE_COUNT=$(tar tzf "$TGZ_PATH" | wc -l)
|
|
pass "${vol}.tgz 校验通过 (${FILE_COUNT} 个文件)"
|
|
else
|
|
fail "${vol}.tgz 校验失败"
|
|
fi
|
|
done
|
|
|
|
# ---- 4) .env 备份 ----
|
|
echo ""
|
|
echo "--- 4) 环境变量备份 ---"
|
|
if [ -f "$PROJECT/env/.env.production" ]; then
|
|
cp "$PROJECT/env/.env.production" "${BACKUP_DIR}/.env.production"
|
|
chmod 600 "${BACKUP_DIR}/.env.production"
|
|
pass ".env.production 已备份"
|
|
else
|
|
warn ".env.production 不存在: $PROJECT/env/.env.production"
|
|
fi
|
|
|
|
# ---- 5) 备份清单 ----
|
|
echo ""
|
|
echo "--- 5) 备份清单 ---"
|
|
{
|
|
echo "# 追光AI 卷备份清单"
|
|
echo "# 时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "# 标识: $STAMP"
|
|
echo ""
|
|
ls -lh "$BACKUP_DIR" | tail -n +2
|
|
} > "$BACKUP_DIR/INDEX.txt"
|
|
|
|
ls -lh "$BACKUP_DIR" | tail -n +2 | sed 's/^/ /'
|
|
|
|
# ---- 6) 清理旧备份 ----
|
|
echo ""
|
|
echo "--- 6) 清理 ${KEEP_DAYS} 天前的旧备份 ---"
|
|
OLD_COUNT=$(find "$BACKUP_ROOT" -maxdepth 1 -type d -mtime +$KEEP_DAYS 2>/dev/null | wc -l)
|
|
if [ "$OLD_COUNT" -gt 0 ]; then
|
|
find "$BACKUP_ROOT" -maxdepth 1 -type d -mtime +$KEEP_DAYS -exec rm -rf {} + 2>/dev/null || true
|
|
pass "清理了 ${OLD_COUNT} 个过期备份"
|
|
else
|
|
pass "无需清理"
|
|
fi
|
|
|
|
# ---- 汇总 ----
|
|
echo ""
|
|
echo "========================================"
|
|
echo " 备份完成"
|
|
echo " 结果: ${PASS} 通过 / ${WARN} 警告 / ${FAIL} 失败"
|
|
if [ "$FAIL" -eq 0 ]; then
|
|
echo " 🟢 状态: 正常"
|
|
else
|
|
echo " 🔴 状态: 有 ${FAIL} 项失败,请检查"
|
|
fi
|
|
echo " 路径: $BACKUP_DIR"
|
|
echo " 总大小: $(numfmt --to=iec ${TOTAL_BYTES} 2>/dev/null || echo "${TOTAL_BYTES} 字节")"
|
|
echo "========================================"
|
|
|
|
exit $FAIL
|