website-scripts/backup/backup-sites.sh

210 lines
8.6 KiB
Bash
Executable File
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.

#!/bin/bash
# 站点独立备份脚本
# 每个站点单独一个备份文件共享资源nginx/ssl/network单独备份
# 用法bash backup-sites.sh [站点名]
# 无参数:备份所有站点
# 指定站点:仅备份指定站点
set -e
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOCAL_DIR="/Users/niuniu/programs/docker/db-backups"
REMOTE_BASE="/root/docker"
CLOUD_DIR="/root/docker/db-backups"
log() { echo "[$(date '+%H:%M:%S')] $1"; }
warn() { echo "[$(date '+%H:%M:%S')] [警告] $1"; }
# 自动发现含数据库的站点(按目录约定:*-ai/docker-compose.yml 含 .db 或 DATABASE_PATH
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
ALL_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
ALL_SITES="$ALL_SITES $site"
done
# ============================================================
# 备份单个站点
# ============================================================
backup_site() {
local site="$1"
local backup_name="${site}-${TIMESTAMP}.tar.gz"
log "备份 ${site}..."
# 创建临时目录
ssh txjp "rm -rf /tmp/backup-${site} && mkdir -p /tmp/backup-${site}/data"
case "$site" in
assets)
# WAL checkpoint
ssh txjp "docker exec assets-ai node -e \"const D=require('better-sqlite3');const db=new D('/app/data/assets.db');db.pragma('wal_checkpoint(TRUNCATE)');db.close();\"" 2>/dev/null || true
# 数据库
ssh txjp "docker cp assets-ai:/app/data/assets.db /tmp/backup-${site}/data/"
# 上传文件
ssh txjp "cp -a /root/docker/assets-ai/data/uploads /tmp/backup-${site}/data/ 2>/dev/null || true"
# 配置文件
ssh txjp "cp /root/docker/assets-ai/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/assets-ai/.env /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/assets-ai/Dockerfile /tmp/backup-${site}/ 2>/dev/null || true"
;;
issue)
# WAL checkpoint
ssh txjp "docker exec issue-ai node -e \"const D=require('better-sqlite3');const db=new D('/app/data/issue.db');db.pragma('wal_checkpoint(TRUNCATE)');db.close();\"" 2>/dev/null || true
# 数据库
ssh txjp "docker cp issue-ai:/app/data/issue.db /tmp/backup-${site}/data/"
# 上传文件和报告
ssh txjp "cp -a /root/docker/issue-ai/data/uploads /tmp/backup-${site}/data/ 2>/dev/null || true"
ssh txjp "cp -a /root/docker/issue-ai/data/reports /tmp/backup-${site}/data/ 2>/dev/null || true"
# 配置文件
ssh txjp "cp /root/docker/issue-ai/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/issue-ai/.env /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/issue-ai/Dockerfile /tmp/backup-${site}/ 2>/dev/null || true"
;;
ldap)
# WAL checkpoint
ssh txjp "docker exec lldap sqlite3 /data/users.db 'PRAGMA wal_checkpoint(TRUNCATE);'" 2>/dev/null || true
# 数据库
ssh txjp "docker cp lldap:/data/users.db /tmp/backup-${site}/data/"
# 配置文件
ssh txjp "cp /root/docker/ldap-ai/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/ldap-ai/.env /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/ldap-ai/Dockerfile /tmp/backup-${site}/ 2>/dev/null || true"
;;
gitea)
# 数据库gitea 不允许以 root 运行 dump直接复制数据库文件
ssh txjp "docker cp gitea-ai:/data/gitea.db /tmp/backup-${site}/data/"
# 代码仓库
ssh txjp "cp -a /root/docker/gitea-ai/data/git /tmp/backup-${site}/ 2>/dev/null || true"
# 配置
ssh txjp "cp -a /root/docker/gitea-ai/data/gitea/conf /tmp/backup-${site}/gitea-conf 2>/dev/null || true"
# docker-compose
ssh txjp "cp /root/docker/gitea-ai/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
;;
oa)
# 配置文件
ssh txjp "cp /root/docker/oa-ai/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/oa-ai/.env /tmp/backup-${site}/ 2>/dev/null || true"
# OA 没有独立数据库,数据存在 assets/issue 中
;;
monitor)
ssh txjp "docker exec monitor-ai sqlite3 /app/data/monitor.db 'PRAGMA wal_checkpoint(TRUNCATE);'" 2>/dev/null || true
ssh txjp "docker cp monitor-ai:/app/data/monitor.db /tmp/backup-${site}/data/"
ssh txjp "cp /root/docker/monitor-ai/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/monitor-ai/.env /tmp/backup-${site}/ 2>/dev/null || true"
;;
*)
# 未知站点:仅备份配置文件(保守策略,确保新增站点不会因缺 case 而崩溃)
warn "${site}: 无专用备份逻辑,仅备份配置文件"
ssh txjp "cp /root/docker/${site}/docker-compose.yml /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/${site}/.env /tmp/backup-${site}/ 2>/dev/null || true"
ssh txjp "cp /root/docker/${site}/Dockerfile /tmp/backup-${site}/ 2>/dev/null || true"
;;
esac
# 打包
ssh txjp "cd /tmp && tar czf ${backup_name} backup-${site}/"
# 复制到本地
mkdir -p "${LOCAL_DIR}"
scp "txjp:/tmp/${backup_name}" "${LOCAL_DIR}/"
# 云端保留
ssh txjp "mkdir -p ${CLOUD_DIR} && cp /tmp/${backup_name} ${CLOUD_DIR}/"
# 清理临时文件
ssh txjp "rm -rf /tmp/backup-${site} /tmp/${backup_name}"
log "${site} 备份完成: ${backup_name}"
}
# ============================================================
# 备份共享资源nginx + ssl + docker网络
# ============================================================
backup_shared() {
local backup_name="shared-${TIMESTAMP}.tar.gz"
log "备份共享资源..."
ssh txjp "rm -rf /tmp/backup-shared && mkdir -p /tmp/backup-shared"
# Nginx 配置
ssh txjp "mkdir -p /tmp/backup-shared/nginx && cp -a /root/docker/nginx-proxy-ai/conf.d/* /tmp/backup-shared/nginx/ 2>/dev/null || true"
# SSL 证书
ssh txjp "mkdir -p /tmp/backup-shared/ssl && \
cp -a /etc/letsencrypt/live /tmp/backup-shared/ssl/ 2>/dev/null || true && \
cp -a /etc/letsencrypt/archive /tmp/backup-shared/ssl/ 2>/dev/null || true && \
cp -a /etc/letsencrypt/renewal /tmp/backup-shared/ssl/ 2>/dev/null || true"
# Docker 网络配置
ssh txjp "cat > /tmp/backup-shared/docker-network.sh << 'NETEOF'
#!/bin/bash
# 恢复 docker 网络
if ! docker network inspect webnet >/dev/null 2>&1; then
docker network create --driver bridge --subnet 172.18.0.0/16 webnet
echo 'Docker 网络 webnet 已创建'
else
echo 'Docker 网络 webnet 已存在'
fi
NETEOF"
# Nginx docker-compose用于恢复 nginx 容器)
ssh txjp "cp /root/docker/nginx-proxy-ai/docker-compose.yml /tmp/backup-shared/ 2>/dev/null || true"
# 打包
ssh txjp "cd /tmp && tar czf ${backup_name} backup-shared/"
# 复制到本地
mkdir -p "${LOCAL_DIR}"
scp "txjp:/tmp/${backup_name}" "${LOCAL_DIR}/"
# 云端保留
ssh txjp "mkdir -p ${CLOUD_DIR} && cp /tmp/${backup_name} ${CLOUD_DIR}/"
# 清理
ssh txjp "rm -rf /tmp/backup-shared /tmp/${backup_name}"
log "共享资源备份完成: ${backup_name}"
}
# ============================================================
# 主流程
# ============================================================
log "开始站点独立备份..."
# 解析参数
TARGET_SITES="${1:-all}"
if [[ "$TARGET_SITES" == "all" ]]; then
# 备份所有站点
for site in $ALL_SITES; do
backup_site "$site"
done
backup_shared
else
# 备份指定站点
for site in $TARGET_SITES; do
if echo "$ALL_SITES" | grep -qw "$site"; then
backup_site "$site"
else
warn "未知站点: $site"
fi
done
fi
# ============================================================
# 显示备份结果
# ============================================================
echo ""
log "备份完成!"
echo ""
echo "本地备份文件:"
ls -lh ${LOCAL_DIR}/*-${TIMESTAMP}.tar.gz 2>/dev/null
echo ""
echo "云端备份文件:"
ssh txjp "ls -lh ${CLOUD_DIR}/*-${TIMESTAMP}.tar.gz 2>/dev/null"