189 lines
7.3 KiB
Bash
Executable File
189 lines
7.3 KiB
Bash
Executable File
#!/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"
|