#!/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"; } # 要备份的站点列表 ALL_SITES="assets issue ldap gitea oa" # ============================================================ # 备份单个站点 # ============================================================ 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 中 ;; 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"