99 lines
2.9 KiB
Bash
Executable File
99 lines
2.9 KiB
Bash
Executable File
#!/bin/bash
|
||
# 云端数据库一键备份脚本
|
||
# 用法:bash backup-db.sh
|
||
|
||
set -e
|
||
|
||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||
LOCAL_DIR="/Users/niuniu/programs/docker/db-backups"
|
||
|
||
log() { echo "[$(date '+%H:%M:%S')] $1"; }
|
||
|
||
# 自动发现含数据库的站点
|
||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
DB_SITES=""
|
||
for dir in "$PROJECT_ROOT"/*-ai/; do
|
||
site=$(basename "$dir")
|
||
[[ "$site" == "nginx-proxy-ai" ]] && continue
|
||
[ -f "$dir/docker-compose.yml" ] || continue
|
||
grep -qE "\.db|DATABASE_PATH|sqlite" "$dir/docker-compose.yml" 2>/dev/null || continue
|
||
DB_SITES="$DB_SITES $site"
|
||
done
|
||
|
||
log "发现数据库站点:${DB_SITES}"
|
||
log "开始备份云端数据库..."
|
||
|
||
# 站点 → 容器名 + DB路径 映射(自动推导:<site> → <site>:/app/data/<site去掉-ai>.db)
|
||
# LLDAP/gitea 容器名特殊,显式声明
|
||
declare -A DB_CONTAINERS=([ldap]="lldap:/data/users.db" [gitea]="gitea-ai:/data/gitea.db" [oa]="")
|
||
|
||
# 并行 WAL checkpoint
|
||
for site in $DB_SITES; do
|
||
[[ "$site" == "oa" ]] && continue # OA 无独立 DB
|
||
if [[ -n "${DB_CONTAINERS[$site]}" ]]; then
|
||
cname="${DB_CONTAINERS[$site]%%:*}"
|
||
else
|
||
cname="${site}" # assets-ai / issue-ai / monitor-ai
|
||
fi
|
||
log "WAL checkpoint: ${site} (${cname})..."
|
||
ssh txjp "docker exec ${cname} sqlite3 /app/data/*.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" &
|
||
done
|
||
wait
|
||
|
||
# 云端打包
|
||
ssh txjp "mkdir -p /tmp/db-backup"
|
||
for site in $DB_SITES; do
|
||
[[ "$site" == "oa" ]] && continue
|
||
if [[ -n "${DB_CONTAINERS[$site]}" ]]; then
|
||
cname="${DB_CONTAINERS[$site]%%:*}"
|
||
dbpath="${DB_CONTAINERS[$site]##*:}"
|
||
else
|
||
cname="${site}"
|
||
short="${site%-ai}" # assets-ai → assets
|
||
dbpath="/app/data/${short}.db"
|
||
fi
|
||
log "备份 ${site} (${cname}:${dbpath})..."
|
||
ssh txjp "docker cp ${cname}:${dbpath} /tmp/db-backup/${site}-${TIMESTAMP}.db"
|
||
done
|
||
|
||
# 确保本地备份目录存在
|
||
mkdir -p "${LOCAL_DIR}"
|
||
|
||
# 云端留一份持久备份
|
||
CLOUD_DIR="/root/docker/db-backups"
|
||
ssh txjp "mkdir -p ${CLOUD_DIR}"
|
||
for site in $DB_SITES; do
|
||
[[ "$site" == "oa" ]] && continue
|
||
ssh txjp "cp /tmp/db-backup/${site}-${TIMESTAMP}.db ${CLOUD_DIR}/"
|
||
done
|
||
log "云端备份已保存至: ${CLOUD_DIR}"
|
||
|
||
# 复制到本地
|
||
log "复制到本地..."
|
||
mkdir -p "${LOCAL_DIR}"
|
||
for site in $DB_SITES; do
|
||
[[ "$site" == "oa" ]] && continue
|
||
scp "txjp:/tmp/db-backup/${site}-${TIMESTAMP}.db" "${LOCAL_DIR}/"
|
||
done
|
||
|
||
# 验证备份文件为有效 SQLite 数据库
|
||
log "验证备份完整性..."
|
||
for site in $DB_SITES; do
|
||
[[ "$site" == "oa" ]] && continue
|
||
f="${LOCAL_DIR}/${site}-${TIMESTAMP}.db"
|
||
if ! sqlite3 "$f" "SELECT count(*) FROM sqlite_master" > /dev/null 2>&1; then
|
||
echo "[错误] 备份文件无效或损坏: $f"
|
||
exit 1
|
||
fi
|
||
done
|
||
log "备份文件验证通过"
|
||
|
||
# 清理云端临时文件
|
||
ssh txjp "rm -rf /tmp/db-backup"
|
||
|
||
log "备份完成:${TIMESTAMP}"
|
||
echo " 云端: ${CLOUD_DIR}/"
|
||
ssh txjp "ls -lh ${CLOUD_DIR}/*-${TIMESTAMP}.db 2>/dev/null"
|
||
echo " 本地:"
|
||
ls -lh "${LOCAL_DIR}"/*-${TIMESTAMP}.db 2>/dev/null
|