fix: auto-discovery strip -ai suffix + container_for helper + delete old scripts
This commit is contained in:
parent
ad2305305b
commit
63e96164bf
|
|
@ -1,98 +0,0 @@
|
|||
#!/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
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
#!/bin/bash
|
||||
# 完整灾难恢复备份脚本
|
||||
# 包含:数据库、代码仓库、用户数据、配置文件、SSL证书
|
||||
# 用法:bash backup-full.sh
|
||||
|
||||
set -e
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
LOCAL_DIR="/Users/niuniu/programs/docker/db-backups"
|
||||
BACKUP_NAME="full-backup-${TIMESTAMP}"
|
||||
|
||||
log() { echo "[$(date '+%H:%M:%S')] $1"; }
|
||||
warn() { echo "[$(date '+%H:%M:%S')] [警告] $1"; }
|
||||
|
||||
log "开始完整灾难恢复备份..."
|
||||
|
||||
# ============================================================
|
||||
# 1. 数据库备份(WAL checkpoint + docker cp)
|
||||
# ============================================================
|
||||
# 自动发现数据库站点
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DB_SITES=""
|
||||
for dir in "$PROJECT_ROOT"/*-ai/; do
|
||||
s=$(basename "$dir"); [[ "$s" == "nginx-proxy-ai" ]] && continue
|
||||
[ -f "$dir/docker-compose.yml" ] || continue
|
||||
grep -qE "\.db|DATABASE_PATH|sqlite" "$dir/docker-compose.yml" 2>/dev/null || continue
|
||||
[[ "$s" == "oa" ]] && continue; DB_SITES="$DB_SITES $s"
|
||||
done
|
||||
|
||||
log "执行 WAL checkpoint..."
|
||||
for s in $DB_SITES; do
|
||||
case "$s" in
|
||||
ldap) ssh txjp "docker exec lldap sqlite3 /data/users.db 'PRAGMA wal_checkpoint(TRUNCATE);'" 2>/dev/null || warn "ldap checkpoint 失败" ;;
|
||||
*) ssh txjp "docker exec ${s} sqlite3 /app/data/*.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" & ;;
|
||||
esac
|
||||
done; wait
|
||||
|
||||
log "导出数据库..."
|
||||
ssh txjp "mkdir -p /tmp/full-backup/databases"
|
||||
for s in $DB_SITES; do
|
||||
case "$s" in
|
||||
ldap) ssh txjp "docker cp lldap:/data/users.db /tmp/full-backup/databases/ldap.db" ;;
|
||||
gitea) ssh txjp "docker cp gitea-ai:/data/gitea.db /tmp/full-backup/databases/gitea.db" ;;
|
||||
*) short="${s%-ai}"; ssh txjp "docker cp ${s}:/app/data/${short}.db /tmp/full-backup/databases/${short}.db" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# 2. Gitea 代码仓库 + 配置
|
||||
# ============================================================
|
||||
log "备份 Gitea 代码仓库..."
|
||||
ssh txjp "mkdir -p /tmp/full-backup/gitea && \
|
||||
cp -a /root/docker/gitea-ai/data/git /tmp/full-backup/gitea/ && \
|
||||
cp -a /root/docker/gitea-ai/data/gitea/conf /tmp/full-backup/gitea/ 2>/dev/null || true"
|
||||
|
||||
# ============================================================
|
||||
# 3. 用户上传文件
|
||||
# ============================================================
|
||||
log "备份用户上传文件..."
|
||||
ssh txjp "mkdir -p /tmp/full-backup/uploads && \
|
||||
cp -a /root/docker/assets-ai/data/uploads /tmp/full-backup/uploads/assets-uploads 2>/dev/null || true && \
|
||||
cp -a /root/docker/issue-ai/data/uploads /tmp/full-backup/uploads/issue-uploads 2>/dev/null || true && \
|
||||
cp -a /root/docker/issue-ai/data/reports /tmp/full-backup/uploads/issue-reports 2>/dev/null || true"
|
||||
|
||||
# ============================================================
|
||||
# 4. Nginx 配置
|
||||
# ============================================================
|
||||
log "备份 Nginx 配置..."
|
||||
ssh txjp "mkdir -p /tmp/full-backup/nginx && \
|
||||
cp -a /root/docker/nginx-proxy-ai/conf.d/* /tmp/full-backup/nginx/ 2>/dev/null || true"
|
||||
|
||||
# ============================================================
|
||||
# 5. 各站点 docker-compose.yml + .env
|
||||
# ============================================================
|
||||
log "备份站点配置文件..."
|
||||
ssh txjp "mkdir -p /tmp/full-backup/configs && \
|
||||
for site in assets-ai issue-ai oa-ai ldap-ai gitea-ai; do \
|
||||
mkdir -p /tmp/full-backup/configs/\$site && \
|
||||
cp /root/docker/\$site/docker-compose.yml /tmp/full-backup/configs/\$site/ 2>/dev/null || true && \
|
||||
cp /root/docker/\$site/.env /tmp/full-backup/configs/\$site/ 2>/dev/null || true && \
|
||||
cp /root/docker/\$site/Dockerfile /tmp/full-backup/configs/\$site/ 2>/dev/null || true; \
|
||||
done"
|
||||
|
||||
# ============================================================
|
||||
# 6. SSL 证书
|
||||
# ============================================================
|
||||
log "备份 SSL 证书..."
|
||||
ssh txjp "mkdir -p /tmp/full-backup/ssl && \
|
||||
cp -a /etc/letsencrypt/live /tmp/full-backup/ssl/ 2>/dev/null || true && \
|
||||
cp -a /etc/letsencrypt/archive /tmp/full-backup/ssl/ 2>/dev/null || true && \
|
||||
cp -a /etc/letsencrypt/renewal /tmp/full-backup/ssl/ 2>/dev/null || true"
|
||||
|
||||
# ============================================================
|
||||
# 7. 打包压缩
|
||||
# ============================================================
|
||||
log "打包压缩..."
|
||||
ssh txjp "cd /tmp && tar czf ${BACKUP_NAME}.tar.gz full-backup/ && rm -rf /tmp/full-backup"
|
||||
|
||||
# ============================================================
|
||||
# 8. 复制到本地
|
||||
# ============================================================
|
||||
log "复制到本地..."
|
||||
mkdir -p "${LOCAL_DIR}"
|
||||
scp "txjp:/tmp/${BACKUP_NAME}.tar.gz" "${LOCAL_DIR}/"
|
||||
|
||||
# ============================================================
|
||||
# 9. 云端保留一份
|
||||
# ============================================================
|
||||
CLOUD_DIR="/root/docker/db-backups"
|
||||
ssh txjp "mkdir -p ${CLOUD_DIR} && cp /tmp/${BACKUP_NAME}.tar.gz ${CLOUD_DIR}/"
|
||||
log "云端备份已保存至: ${CLOUD_DIR}"
|
||||
|
||||
# ============================================================
|
||||
# 10. 清理云端临时文件
|
||||
# ============================================================
|
||||
ssh txjp "rm -f /tmp/${BACKUP_NAME}.tar.gz"
|
||||
|
||||
# ============================================================
|
||||
# 11. 验证备份
|
||||
# ============================================================
|
||||
log "验证备份完整性..."
|
||||
cd "${LOCAL_DIR}"
|
||||
if tar tzf "${BACKUP_NAME}.tar.gz" > /dev/null 2>&1; then
|
||||
log "备份文件验证通过"
|
||||
else
|
||||
echo "[错误] 备份文件损坏"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 显示备份内容
|
||||
log "备份内容清单:"
|
||||
tar tzf "${BACKUP_NAME}.tar.gz" | head -30
|
||||
|
||||
echo ""
|
||||
log "备份完成:${TIMESTAMP}"
|
||||
echo " 文件: ${LOCAL_DIR}/${BACKUP_NAME}.tar.gz"
|
||||
echo " 大小: $(du -h ${LOCAL_DIR}/${BACKUP_NAME}.tar.gz | cut -f1)"
|
||||
echo ""
|
||||
echo " 云端: ${CLOUD_DIR}/${BACKUP_NAME}.tar.gz"
|
||||
ssh txjp "ls -lh ${CLOUD_DIR}/${BACKUP_NAME}.tar.gz 2>/dev/null"
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
#!/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"
|
||||
|
|
@ -22,10 +22,19 @@ for dir in "$PROJECT_ROOT"/*-ai/; do
|
|||
s=$(basename "$dir"); [[ "$s" == "nginx-proxy-ai" ]] && continue
|
||||
[ -f "$dir/docker-compose.yml" ] || continue
|
||||
grep -qE "\.db|DATABASE_PATH|sqlite" "$dir/docker-compose.yml" 2>/dev/null || continue
|
||||
s="${s%-ai}" # assets-ai → assets
|
||||
[[ "$s" == "oa" ]] && continue
|
||||
DB_SITES="$DB_SITES $s"
|
||||
done
|
||||
|
||||
# 站点 → 容器名映射
|
||||
container_for() {
|
||||
case "$1" in
|
||||
ldap) echo "lldap" ;;
|
||||
*) echo "${1}-ai" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# === 模式 1:站点完整备份(默认)===
|
||||
backup_site() {
|
||||
local site="$1"
|
||||
|
|
@ -107,25 +116,23 @@ backup_shared() {
|
|||
backup_db_only() {
|
||||
log "开始数据库备份..."
|
||||
|
||||
declare -A DB_CONTAINERS=([ldap]="lldap:/data/users.db" [gitea]="gitea-ai:/data/gitea.db")
|
||||
|
||||
for s in $DB_SITES; do
|
||||
if [[ -n "${DB_CONTAINERS[$s]}" ]]; then
|
||||
ssh txjp "docker exec ${DB_CONTAINERS[$s]%%:*} sqlite3 ${DB_CONTAINERS[$s]##*:} 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" &
|
||||
c=$(container_for "$s")
|
||||
if [[ "$s" == "ldap" ]]; then
|
||||
ssh txjp "docker exec ${c} sqlite3 /data/users.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" &
|
||||
else
|
||||
ssh txjp "docker exec ${s} sqlite3 /app/data/*.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" &
|
||||
ssh txjp "docker exec ${c} sqlite3 /app/data/${s}.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" &
|
||||
fi
|
||||
done; wait
|
||||
|
||||
ssh txjp "mkdir -p /tmp/db-backup"
|
||||
for s in $DB_SITES; do
|
||||
if [[ -n "${DB_CONTAINERS[$s]}" ]]; then
|
||||
cname="${DB_CONTAINERS[$s]%%:*}"; dbpath="${DB_CONTAINERS[$s]##*:}"
|
||||
else
|
||||
cname="${s}"; short="${s%-ai}"; dbpath="/app/data/${short}.db"
|
||||
fi
|
||||
c=$(container_for "$s")
|
||||
dbpath="/app/data/${s}.db"
|
||||
[[ "$s" == "ldap" ]] && dbpath="/data/users.db"
|
||||
[[ "$s" == "gitea" ]] && dbpath="/data/gitea.db"
|
||||
log "备份 ${s}..."
|
||||
ssh txjp "docker cp ${cname}:${dbpath} /tmp/db-backup/${s}-${TIMESTAMP}.db"
|
||||
ssh txjp "docker cp ${c}:${dbpath} /tmp/db-backup/${s}-${TIMESTAMP}.db"
|
||||
done
|
||||
|
||||
mkdir -p "${LOCAL_DIR}"
|
||||
|
|
@ -146,19 +153,18 @@ backup_full() {
|
|||
log "开始完整灾难恢复备份..."
|
||||
|
||||
for s in $DB_SITES; do
|
||||
case "$s" in
|
||||
ldap) ssh txjp "docker exec lldap sqlite3 /data/users.db 'PRAGMA wal_checkpoint(TRUNCATE);'" 2>/dev/null || true ;;
|
||||
*) ssh txjp "docker exec ${s} sqlite3 /app/data/*.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" & ;;
|
||||
esac
|
||||
c=$(container_for "$s")
|
||||
[[ "$s" == "ldap" ]] && ssh txjp "docker exec ${c} sqlite3 /data/users.db 'PRAGMA wal_checkpoint(TRUNCATE);'" 2>/dev/null || true
|
||||
[[ "$s" != "ldap" ]] && ssh txjp "docker exec ${c} sqlite3 /app/data/${s}.db 'PRAGMA wal_checkpoint(TRUNCATE);' 2>/dev/null || true" &
|
||||
done; wait
|
||||
|
||||
ssh txjp "mkdir -p /tmp/full-backup/databases"
|
||||
for s in $DB_SITES; do
|
||||
case "$s" in
|
||||
ldap) ssh txjp "docker cp lldap:/data/users.db /tmp/full-backup/databases/ldap.db" ;;
|
||||
gitea) ssh txjp "docker cp gitea-ai:/data/gitea.db /tmp/full-backup/databases/gitea.db" ;;
|
||||
*) short="${s%-ai}"; ssh txjp "docker cp ${s}:/app/data/${short}.db /tmp/full-backup/databases/${short}.db" ;;
|
||||
esac
|
||||
c=$(container_for "$s")
|
||||
dbpath="/app/data/${s}.db"
|
||||
[[ "$s" == "ldap" ]] && dbpath="/data/users.db"
|
||||
[[ "$s" == "gitea" ]] && dbpath="/data/gitea.db"
|
||||
ssh txjp "docker cp ${c}:${dbpath} /tmp/full-backup/databases/${s}.db"
|
||||
done
|
||||
|
||||
# Gitea 代码仓库
|
||||
|
|
|
|||
|
|
@ -1,296 +0,0 @@
|
|||
#!/bin/bash
|
||||
# ============================================
|
||||
# 数据库恢复脚本
|
||||
#
|
||||
# 用法:
|
||||
# 恢复本地数据库: bash scripts/restore-db.sh local <服务名> <备份文件名>
|
||||
# 恢复云端数据库: bash scripts/restore-db.sh cloud <服务名> <备份文件名>
|
||||
#
|
||||
# 服务名: assets / issue / ldap / gitea / monitor
|
||||
#
|
||||
# 示例:
|
||||
# bash scripts/restore-db.sh local issue issue-2026-04-29_1753.db
|
||||
# bash scripts/restore-db.sh cloud assets assets-2026-04-29_1753.db
|
||||
# bash scripts/restore-db.sh cloud monitor monitor-2026-07-08_1200.db
|
||||
# ============================================
|
||||
|
||||
set -e
|
||||
|
||||
# ---- 参数检查 ----
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "用法: $0 <local|cloud> <issue|assets|ldap> <备份文件名>"
|
||||
echo ""
|
||||
echo "支持格式: .db 或 .tar.gz(自动解压)"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 local issue issue-2026-04-29_1753.db"
|
||||
echo " $0 local issue issue-20260622_101858.tar.gz"
|
||||
echo " $0 cloud ldap ldap-2026-05-15_1200.db"
|
||||
echo " $0 cloud assets assets-2026-04-29_1753.db"
|
||||
echo ""
|
||||
echo "可用备份文件:"
|
||||
echo " 本地:"
|
||||
ls -1 /Users/niuniu/programs/docker/db-backups/ 2>/dev/null || echo " (无)"
|
||||
echo " 云端:"
|
||||
ssh txjp "ls -1 /root/docker/db-backups/" 2>/dev/null || echo " (无)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET=$1
|
||||
SERVICE=$2
|
||||
BACKUP_FILE=$3
|
||||
|
||||
# ---- 路径定义 ----
|
||||
LOCAL_BACKUP_DIR="/Users/niuniu/programs/docker/db-backups"
|
||||
CLOUD_BACKUP_DIR="/root/docker/db-backups"
|
||||
|
||||
case $SERVICE in
|
||||
issue)
|
||||
PROJECT_DIR="issue-ai"
|
||||
CONTAINER="issue-ai"
|
||||
DB_PATH_IN_CONTAINER="/app/data/issue.db"
|
||||
LOCAL_DB_PATH="/Users/niuniu/programs/docker/issue-ai/data/issue.db"
|
||||
;;
|
||||
assets)
|
||||
PROJECT_DIR="assets-ai"
|
||||
CONTAINER="assets-ai"
|
||||
DB_PATH_IN_CONTAINER="/app/data/assets.db"
|
||||
LOCAL_DB_PATH="/Users/niuniu/programs/docker/assets-ai/data/assets.db"
|
||||
;;
|
||||
ldap)
|
||||
PROJECT_DIR="ldap-ai"
|
||||
CONTAINER="lldap"
|
||||
DB_PATH_IN_CONTAINER="/data/users.db"
|
||||
LOCAL_DB_PATH="/Users/niuniu/programs/docker/ldap-ai/data/lldap/users.db"
|
||||
;;
|
||||
gitea)
|
||||
PROJECT_DIR="gitea-ai"
|
||||
CONTAINER="gitea-ai"
|
||||
DB_PATH_IN_CONTAINER="/data/gitea.db"
|
||||
LOCAL_DB_PATH="/Users/niuniu/programs/docker/gitea-ai/data/gitea/gitea.db"
|
||||
;;
|
||||
monitor)
|
||||
PROJECT_DIR="monitor-ai"
|
||||
CONTAINER="monitor-ai"
|
||||
DB_PATH_IN_CONTAINER="/app/data/monitor.db"
|
||||
LOCAL_DB_PATH="/Users/niuniu/programs/docker/monitor-ai/data/monitor.db"
|
||||
;;
|
||||
*)
|
||||
echo "错误: 服务名必须是 assets、issue、ldap、gitea 或 monitor"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# ============================================
|
||||
# 本地恢复
|
||||
# ============================================
|
||||
restore_local() {
|
||||
local BACKUP_PATH="${LOCAL_BACKUP_DIR}/${BACKUP_FILE}"
|
||||
local DB_FILE="$BACKUP_PATH"
|
||||
|
||||
echo "========================================"
|
||||
echo " 本地数据库恢复"
|
||||
echo "========================================"
|
||||
echo " 服务: ${SERVICE} (${CONTAINER})"
|
||||
echo " 备份文件: ${BACKUP_PATH}"
|
||||
echo " 目标数据库: ${LOCAL_DB_PATH}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 1. 检查备份文件
|
||||
if [ ! -f "$BACKUP_PATH" ]; then
|
||||
echo "[错误] 备份文件不存在: ${BACKUP_PATH}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 如果是 .tar.gz 文件,解压获取 .db 文件
|
||||
if [[ "$BACKUP_PATH" == *.tar.gz ]]; then
|
||||
echo "[信息] 检测到 tar.gz 压缩包,正在解压..."
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
tar xzf "$BACKUP_PATH" -C "$TEMP_DIR"
|
||||
|
||||
# 查找 .db 文件
|
||||
DB_FILE=$(find "$TEMP_DIR" -name "*.db" -type f | head -1)
|
||||
if [ -z "$DB_FILE" ]; then
|
||||
echo "[错误] 压缩包中未找到 .db 文件"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "[信息] 解压得到数据库文件: $(basename $DB_FILE)"
|
||||
fi
|
||||
|
||||
# 3. 验证备份文件是有效的 SQLite 数据库
|
||||
if ! sqlite3 "$DB_FILE" "SELECT count(*) FROM sqlite_master" > /dev/null 2>&1; then
|
||||
echo "[错误] 备份文件不是有效的 SQLite 数据库"
|
||||
[ -n "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. 确认操作
|
||||
echo "[警告] 这将覆盖本地 ${CONTAINER} 数据库!"
|
||||
echo " 当前数据库: ${LOCAL_DB_PATH}"
|
||||
echo " 将替换为: ${DB_FILE}"
|
||||
echo ""
|
||||
read -p "确认恢复? (输入 yes 继续): " CONFIRM
|
||||
if [ "$CONFIRM" != "yes" ]; then
|
||||
echo "已取消"
|
||||
[ -n "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 5. 备份当前数据库(安全起见)
|
||||
local SAFE_BACKUP="${LOCAL_DB_PATH}.before-restore-$(date +%Y%m%d_%H%M%S)"
|
||||
if [ -f "$LOCAL_DB_PATH" ]; then
|
||||
cp "$LOCAL_DB_PATH" "$SAFE_BACKUP"
|
||||
echo "[备份] 当前数据库已备份至: ${SAFE_BACKUP}"
|
||||
fi
|
||||
|
||||
# 6. WAL checkpoint on backup (确保数据完整)
|
||||
sqlite3 "$DB_FILE" "PRAGMA wal_checkpoint(TRUNCATE)" 2>/dev/null || true
|
||||
|
||||
# 7. 停止本地容器(如果在运行)
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
|
||||
echo "[操作] 停止容器 ${CONTAINER}..."
|
||||
docker stop "$CONTAINER"
|
||||
fi
|
||||
|
||||
# 8. 替换数据库
|
||||
cp "$DB_FILE" "$LOCAL_DB_PATH"
|
||||
rm -f "${LOCAL_DB_PATH}-shm" "${LOCAL_DB_PATH}-wal"
|
||||
echo "[完成] 数据库已替换"
|
||||
|
||||
# 9. 清理临时文件
|
||||
[ -n "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
|
||||
|
||||
# 10. 启动容器
|
||||
echo "[操作] 启动容器 ${CONTAINER}..."
|
||||
docker start "$CONTAINER"
|
||||
|
||||
# 11. 验证
|
||||
sleep 2
|
||||
echo ""
|
||||
echo "[验证] 恢复后数据:"
|
||||
if [ "$SERVICE" = "issue" ]; then
|
||||
echo " 工单数: $(sqlite3 "$LOCAL_DB_PATH" "SELECT COUNT(*) FROM tickets")"
|
||||
echo " 配件名称: $(sqlite3 "$LOCAL_DB_PATH" "SELECT COUNT(*) FROM tickets WHERE parts_name IS NOT NULL AND parts_name != ''") 条"
|
||||
elif [ "$SERVICE" = "ldap" ]; then
|
||||
echo " 用户数: $(sqlite3 "$LOCAL_DB_PATH" "SELECT COUNT(*) FROM users")"
|
||||
else
|
||||
echo " 设备数: $(sqlite3 "$LOCAL_DB_PATH" "SELECT COUNT(*) FROM assets")"
|
||||
fi
|
||||
echo ""
|
||||
echo "恢复完成!"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# 云端恢复
|
||||
# ============================================
|
||||
restore_cloud() {
|
||||
local BACKUP_PATH="${CLOUD_BACKUP_DIR}/${BACKUP_FILE}"
|
||||
|
||||
echo "========================================"
|
||||
echo " 云端数据库恢复 (txjp)"
|
||||
echo "========================================"
|
||||
echo " 服务: ${SERVICE} (${CONTAINER})"
|
||||
echo " 备份文件: ${BACKUP_PATH}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 1. 检查备份文件
|
||||
if ! ssh txjp "test -f ${BACKUP_PATH}"; then
|
||||
echo "[错误] 云端备份文件不存在: ${BACKUP_PATH}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 验证备份文件(通过容器内 better-sqlite3,避免依赖宿主机或容器内 sqlite3 CLI)
|
||||
if ! ssh txjp "docker cp ${BACKUP_PATH} ${CONTAINER}:/tmp/restore-check.db 2>/dev/null && \
|
||||
docker exec ${CONTAINER} node -e \"const D=require('better-sqlite3');const db=new D('/tmp/restore-check.db',{readonly:true});console.log(db.prepare('SELECT count(*) as c FROM sqlite_master').get().c);db.close();\" > /dev/null 2>&1 && \
|
||||
docker exec ${CONTAINER} rm /tmp/restore-check.db"; then
|
||||
echo "[错误] 备份文件不是有效的 SQLite 数据库"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. 显示备份和当前状态
|
||||
echo "[信息] 备份文件信息:"
|
||||
ssh txjp "ls -lh ${BACKUP_PATH}"
|
||||
echo ""
|
||||
echo "[信息] 当前云端数据库状态:"
|
||||
if [ "$SERVICE" = "issue" ]; then
|
||||
ssh txjp "docker exec ${CONTAINER} node -e \"
|
||||
const D=require('better-sqlite3');
|
||||
const db=new D('/app/data/issue.db',{readonly:true});
|
||||
console.log(' 工单数:', db.prepare('SELECT COUNT(*) as c FROM tickets').get().c);
|
||||
db.close();
|
||||
\""
|
||||
else
|
||||
ssh txjp "docker exec ${CONTAINER} node -e \"
|
||||
const D=require('better-sqlite3');
|
||||
const db=new D('/app/data/assets.db',{readonly:true});
|
||||
console.log(' 设备数:', db.prepare('SELECT COUNT(*) as c FROM assets').get().c);
|
||||
db.close();
|
||||
\""
|
||||
fi
|
||||
|
||||
# 4. 确认
|
||||
echo ""
|
||||
echo "[警告] 这将覆盖云端 ${CONTAINER} 数据库!"
|
||||
read -p "确认恢复? (输入 yes 继续): " CONFIRM
|
||||
if [ "$CONFIRM" != "yes" ]; then
|
||||
echo "已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 5. 停止容器(确保数据库不被写入,安全替换)
|
||||
echo "[操作] 停止容器 ${CONTAINER}..."
|
||||
ssh txjp "cd /root/docker/${PROJECT_DIR} && docker compose stop ${CONTAINER}"
|
||||
|
||||
# 6. 备份当前数据库(容器已停止,安全复制)
|
||||
local SAFE_NAME="before-restore-$(date +%Y%m%d_%H%M%S).db"
|
||||
echo "[操作] 备份云端当前数据库..."
|
||||
ssh txjp "docker cp ${CONTAINER}:${DB_PATH_IN_CONTAINER} /root/docker/db-backups/${SAFE_NAME}"
|
||||
echo "[备份] 当前数据库已保存至云端: /root/docker/db-backups/${SAFE_NAME}"
|
||||
|
||||
# 7. 替换数据库
|
||||
echo "[操作] 复制备份文件到容器..."
|
||||
ssh txjp "docker cp ${BACKUP_PATH} ${CONTAINER}:${DB_PATH_IN_CONTAINER}"
|
||||
|
||||
# 8. 删除残留 WAL/SHM 文件,防止旧日志与新数据库不匹配导致损坏
|
||||
echo "[操作] 清理残留 WAL/SHM 文件..."
|
||||
ssh txjp "docker exec ${CONTAINER} rm -f ${DB_PATH_IN_CONTAINER}-shm ${DB_PATH_IN_CONTAINER}-wal" 2>/dev/null || true
|
||||
|
||||
# 9. 启动容器
|
||||
echo "[操作] 启动容器 ${CONTAINER}..."
|
||||
ssh txjp "cd /root/docker/${PROJECT_DIR} && docker compose start ${CONTAINER}"
|
||||
|
||||
# 10. 验证
|
||||
sleep 3
|
||||
echo ""
|
||||
echo "[验证] 恢复后数据:"
|
||||
if [ "$SERVICE" = "issue" ]; then
|
||||
ssh txjp "docker exec ${CONTAINER} node -e \"
|
||||
const D=require('better-sqlite3');
|
||||
const db=new D('/app/data/issue.db',{readonly:true});
|
||||
console.log(' 工单数:', db.prepare('SELECT COUNT(*) as c FROM tickets').get().c);
|
||||
db.close();
|
||||
\""
|
||||
else
|
||||
ssh txjp "docker exec ${CONTAINER} node -e \"
|
||||
const D=require('better-sqlite3');
|
||||
const db=new D('/app/data/assets.db',{readonly:true});
|
||||
console.log(' 设备数:', db.prepare('SELECT COUNT(*) as c FROM assets').get().c);
|
||||
db.close();
|
||||
\""
|
||||
fi
|
||||
echo ""
|
||||
echo "恢复完成!"
|
||||
}
|
||||
|
||||
# ---- 执行 ----
|
||||
case $TARGET in
|
||||
local) restore_local ;;
|
||||
cloud) restore_cloud ;;
|
||||
*)
|
||||
echo "错误: 目标必须是 local 或 cloud"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
#!/bin/bash
|
||||
# 灾难恢复脚本 - 从完整备份恢复站点
|
||||
# 用法:bash restore-full.sh [备份文件名]
|
||||
# 无参数则自动检测当前目录下的最新备份
|
||||
|
||||
set -e
|
||||
|
||||
BACKUP_DIR="/Users/niuniu/programs/docker/db-backups"
|
||||
REMOTE_BASE="/root/docker"
|
||||
|
||||
log() { echo "[$(date '+%H:%M:%S')] $1"; }
|
||||
warn() { echo "[$(date '+%H:%M:%S')] [警告] $1"; }
|
||||
error() { echo "[$(date '+%H:%M:%S')] [错误] $1"; exit 1; }
|
||||
|
||||
# ============================================================
|
||||
# 1. 选择备份文件
|
||||
# ============================================================
|
||||
if [[ -n "$1" ]]; then
|
||||
BACKUP_FILE="$1"
|
||||
else
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 可用的完整备份文件"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 列出本地备份
|
||||
if [[ -d "$BACKUP_DIR" ]]; then
|
||||
local_files=$(ls -t ${BACKUP_DIR}/full-backup-*.tar.gz 2>/dev/null | head -10)
|
||||
if [[ -n "$local_files" ]]; then
|
||||
echo "本地备份:"
|
||||
index=1
|
||||
while IFS= read -r file; do
|
||||
filename=$(basename "$file")
|
||||
filesize=$(du -h "$file" | cut -f1)
|
||||
echo " $index) $filename ($filesize)"
|
||||
files[$index]="$file"
|
||||
((index++))
|
||||
done <<< "$local_files"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# 列出云端备份
|
||||
echo "云端备份:"
|
||||
cloud_files=$(ssh txjp "ls -t /root/docker/db-backups/full-backup-*.tar.gz 2>/dev/null | head -10" 2>/dev/null || true)
|
||||
if [[ -n "$cloud_files" ]]; then
|
||||
while IFS= read -r file; do
|
||||
filename=$(basename "$file")
|
||||
echo " $index) [云端] $filename"
|
||||
files[$index]="cloud:$file"
|
||||
((index++))
|
||||
done <<< "$cloud_files"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#files[@]} -eq 0 ]]; then
|
||||
error "未找到任何备份文件"
|
||||
fi
|
||||
|
||||
printf "请选择备份文件 (1-$((index-1))): "
|
||||
read choice
|
||||
|
||||
if [[ -z "${files[$choice]}" ]]; then
|
||||
error "无效选择"
|
||||
fi
|
||||
|
||||
BACKUP_FILE="${files[$choice]}"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 2. 下载云端备份到临时目录
|
||||
# ============================================================
|
||||
RESTORE_DIR="/tmp/restore-$$"
|
||||
mkdir -p "$RESTORE_DIR"
|
||||
|
||||
if [[ "$BACKUP_FILE" == cloud:* ]]; then
|
||||
cloud_path="${BACKUP_FILE#cloud:}"
|
||||
log "从云端下载备份..."
|
||||
scp "txjp:$cloud_path" "$RESTORE_DIR/"
|
||||
BACKUP_FILE="$RESTORE_DIR/$(basename $cloud_path)"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 3. 解压备份
|
||||
# ============================================================
|
||||
log "解压备份文件..."
|
||||
cd "$RESTORE_DIR"
|
||||
tar xzf "$BACKUP_FILE"
|
||||
|
||||
# 检测备份内容
|
||||
BACKUP_CONTENT="$RESTORE_DIR/full-backup"
|
||||
if [[ ! -d "$BACKUP_CONTENT" ]]; then
|
||||
error "备份文件格式错误"
|
||||
fi
|
||||
|
||||
log "备份内容:"
|
||||
ls -la "$BACKUP_CONTENT/"
|
||||
|
||||
# ============================================================
|
||||
# 4. 选择要恢复的站点
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 选择要恢复的内容"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo " 1) assets - 资产管理系统"
|
||||
echo " 2) issue - 工单系统"
|
||||
echo " 3) ldap - 用户目录服务"
|
||||
echo " 4) gitea - 代码仓库"
|
||||
echo " 5) monitor - 告警监控中心"
|
||||
echo " 6) nginx - 反向代理配置"
|
||||
echo " 7) 全部恢复"
|
||||
echo ""
|
||||
printf "请选择 (1-7): "
|
||||
read site_choice
|
||||
|
||||
# ============================================================
|
||||
# 5. 执行恢复
|
||||
# ============================================================
|
||||
restore_assets() {
|
||||
log "恢复 assets 站点..."
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$BACKUP_CONTENT/databases/assets.db" ]]; then
|
||||
log "恢复 assets 数据库..."
|
||||
scp "$BACKUP_CONTENT/databases/assets.db" txjp:/tmp/assets.db
|
||||
ssh txjp "docker cp /tmp/assets.db assets-ai:/app/data/assets.db && rm /tmp/assets.db"
|
||||
ssh txjp "docker restart assets-ai"
|
||||
log "assets 数据库恢复完成"
|
||||
fi
|
||||
|
||||
# 恢复配置
|
||||
if [[ -d "$BACKUP_CONTENT/configs/assets-ai" ]]; then
|
||||
log "恢复 assets 配置..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/assets-ai"
|
||||
scp "$BACKUP_CONTENT/configs/assets-ai/"* txjp:$REMOTE_BASE/assets-ai/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 恢复上传文件
|
||||
if [[ -d "$BACKUP_CONTENT/uploads/assets-uploads" ]]; then
|
||||
log "恢复 assets 上传文件..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/assets-ai/data"
|
||||
scp -r "$BACKUP_CONTENT/uploads/assets-uploads/"* txjp:$REMOTE_BASE/assets-ai/data/uploads/ 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
restore_issue() {
|
||||
log "恢复 issue 站点..."
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$BACKUP_CONTENT/databases/issue.db" ]]; then
|
||||
log "恢复 issue 数据库..."
|
||||
scp "$BACKUP_CONTENT/databases/issue.db" txjp:/tmp/issue.db
|
||||
ssh txjp "docker cp /tmp/issue.db issue-ai:/app/data/issue.db && rm /tmp/issue.db"
|
||||
ssh txjp "docker restart issue-ai"
|
||||
log "issue 数据库恢复完成"
|
||||
fi
|
||||
|
||||
# 恢复配置
|
||||
if [[ -d "$BACKUP_CONTENT/configs/issue-ai" ]]; then
|
||||
log "恢复 issue 配置..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/issue-ai"
|
||||
scp "$BACKUP_CONTENT/configs/issue-ai/"* txjp:$REMOTE_BASE/issue-ai/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 恢复上传文件
|
||||
if [[ -d "$BACKUP_CONTENT/uploads/issue-uploads" ]]; then
|
||||
log "恢复 issue 上传文件..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/issue-ai/data"
|
||||
scp -r "$BACKUP_CONTENT/uploads/issue-uploads/"* txjp:$REMOTE_BASE/issue-ai/data/uploads/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 恢复报告文件
|
||||
if [[ -d "$BACKUP_CONTENT/uploads/issue-reports" ]]; then
|
||||
log "恢复 issue 报告文件..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/issue-ai/data"
|
||||
scp -r "$BACKUP_CONTENT/uploads/issue-reports/"* txjp:$REMOTE_BASE/issue-ai/data/reports/ 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
restore_ldap() {
|
||||
log "恢复 ldap 站点..."
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$BACKUP_CONTENT/databases/ldap.db" ]]; then
|
||||
log "恢复 ldap 数据库..."
|
||||
scp "$BACKUP_CONTENT/databases/ldap.db" txjp:/tmp/ldap.db
|
||||
ssh txjp "docker cp /tmp/ldap.db lldap:/data/users.db && rm /tmp/ldap.db"
|
||||
ssh txjp "docker restart lldap"
|
||||
log "ldap 数据库恢复完成"
|
||||
fi
|
||||
|
||||
# 恢复配置
|
||||
if [[ -d "$BACKUP_CONTENT/configs/ldap-ai" ]]; then
|
||||
log "恢复 ldap 配置..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/ldap-ai"
|
||||
scp "$BACKUP_CONTENT/configs/ldap-ai/"* txjp:$REMOTE_BASE/ldap-ai/ 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
restore_gitea() {
|
||||
log "恢复 gitea 站点..."
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$BACKUP_CONTENT/databases/gitea.db" ]]; then
|
||||
log "恢复 gitea 数据库..."
|
||||
scp "$BACKUP_CONTENT/databases/gitea.db" txjp:/tmp/gitea.db
|
||||
ssh txjp "docker cp /tmp/gitea.db gitea-ai:/data/gitea.db && rm /tmp/gitea.db"
|
||||
fi
|
||||
|
||||
# 恢复代码仓库
|
||||
if [[ -d "$BACKUP_CONTENT/gitea/git" ]]; then
|
||||
log "恢复 gitea 代码仓库..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/gitea-ai/data"
|
||||
scp -r "$BACKUP_CONTENT/gitea/git" txjp:$REMOTE_BASE/gitea-ai/data/
|
||||
fi
|
||||
|
||||
# 恢复配置
|
||||
if [[ -d "$BACKUP_CONTENT/gitea/conf" ]]; then
|
||||
log "恢复 gitea 配置..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/gitea-ai/data/gitea"
|
||||
scp -r "$BACKUP_CONTENT/gitea/conf" txjp:$REMOTE_BASE/gitea-ai/data/gitea/
|
||||
fi
|
||||
|
||||
# 恢复 docker-compose
|
||||
if [[ -d "$BACKUP_CONTENT/configs/gitea-ai" ]]; then
|
||||
log "恢复 gitea docker-compose..."
|
||||
scp "$BACKUP_CONTENT/configs/gitea-ai/"* txjp:$REMOTE_BASE/gitea-ai/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 重启容器
|
||||
ssh txjp "cd $REMOTE_BASE/gitea-ai && docker compose down && docker compose up -d"
|
||||
log "gitea 恢复完成"
|
||||
}
|
||||
|
||||
restore_nginx() {
|
||||
log "恢复 nginx 配置..."
|
||||
|
||||
if [[ -d "$BACKUP_CONTENT/nginx" ]]; then
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/nginx-proxy-ai/conf.d"
|
||||
scp "$BACKUP_CONTENT/nginx/"* txjp:$REMOTE_BASE/nginx-proxy-ai/conf.d/
|
||||
ssh txjp "docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
||||
log "nginx 配置恢复完成"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_monitor() {
|
||||
log "恢复 monitor 站点..."
|
||||
|
||||
if [[ -f "$BACKUP_CONTENT/databases/monitor.db" ]]; then
|
||||
ssh txjp "docker cp $BACKUP_CONTENT/databases/monitor.db monitor-ai:/app/data/monitor.db"
|
||||
log "monitor 数据库恢复完成"
|
||||
else
|
||||
warn "未找到 monitor 数据库备份"
|
||||
fi
|
||||
|
||||
ssh txjp "cd $REMOTE_BASE/monitor-ai && docker compose down && docker compose up -d --build"
|
||||
log "monitor 恢复完成"
|
||||
}
|
||||
|
||||
# 执行恢复
|
||||
case "$site_choice" in
|
||||
1) restore_assets ;;
|
||||
2) restore_issue ;;
|
||||
3) restore_ldap ;;
|
||||
4) restore_gitea ;;
|
||||
5) restore_monitor ;;
|
||||
6) restore_nginx ;;
|
||||
7)
|
||||
restore_assets
|
||||
restore_issue
|
||||
restore_ldap
|
||||
restore_gitea
|
||||
restore_monitor
|
||||
restore_nginx
|
||||
;;
|
||||
*) error "无效选择" ;;
|
||||
esac
|
||||
|
||||
# ============================================================
|
||||
# 6. 清理临时文件
|
||||
# ============================================================
|
||||
log "清理临时文件..."
|
||||
rm -rf "$RESTORE_DIR"
|
||||
|
||||
echo ""
|
||||
log "恢复完成!"
|
||||
|
|
@ -1,412 +0,0 @@
|
|||
#!/bin/bash
|
||||
# 站点恢复脚本 - 支持选择恢复单个站点或全部
|
||||
# 用法:bash restore-sites.sh [备份时间戳]
|
||||
# 无参数则自动检测最新备份
|
||||
|
||||
set -e
|
||||
|
||||
BACKUP_DIR="/Users/niuniu/programs/docker/db-backups"
|
||||
REMOTE_BASE="/root/docker"
|
||||
|
||||
log() { echo "[$(date '+%H:%M:%S')] $1"; }
|
||||
warn() { echo "[$(date '+%H:%M:%S')] [警告] $1"; }
|
||||
error() { echo "[$(date '+%H:%M:%S')] [错误] $1"; exit 1; }
|
||||
|
||||
# ============================================================
|
||||
# 1. 选择备份时间戳
|
||||
# ============================================================
|
||||
if [[ -n "$1" ]]; then
|
||||
TIMESTAMP="$1"
|
||||
else
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 可用的备份时间戳"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 列出可用的时间戳
|
||||
timestamps=$(ls ${BACKUP_DIR}/assets-*.tar.gz 2>/dev/null | sed 's/.*assets-\(.*\)\.tar\.gz/\1/' | sort -r | head -10)
|
||||
|
||||
if [[ -z "$timestamps" ]]; then
|
||||
error "未找到任何备份文件"
|
||||
fi
|
||||
|
||||
index=1
|
||||
while IFS= read -r ts; do
|
||||
echo " $index) $ts"
|
||||
ts_list[$index]="$ts"
|
||||
((index++))
|
||||
done <<< "$timestamps"
|
||||
|
||||
echo ""
|
||||
printf "请选择备份时间戳 (1-$((index-1))): "
|
||||
read choice
|
||||
|
||||
if [[ -z "${ts_list[$choice]}" ]]; then
|
||||
error "无效选择"
|
||||
fi
|
||||
|
||||
TIMESTAMP="${ts_list[$choice]}"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 2. 检测可用的备份文件
|
||||
# ============================================================
|
||||
echo ""
|
||||
log "检测备份文件: ${TIMESTAMP}"
|
||||
|
||||
LOCAL_BACKUP="${BACKUP_DIR}"
|
||||
CLOUD_BACKUP="txjp:/root/docker/db-backups"
|
||||
|
||||
# 检查本地备份
|
||||
check_backup() {
|
||||
local site="$1"
|
||||
local found=""
|
||||
|
||||
# 检查本地
|
||||
if [[ -f "${LOCAL_BACKUP}/${site}-${TIMESTAMP}.tar.gz" ]]; then
|
||||
found="local:${LOCAL_BACKUP}/${site}-${TIMESTAMP}.tar.gz"
|
||||
# 检查云端
|
||||
elif ssh txjp "test -f ${CLOUD_BACKUP}/${site}-${TIMESTAMP}.tar.gz" 2>/dev/null; then
|
||||
found="cloud:${site}-${TIMESTAMP}.tar.gz"
|
||||
fi
|
||||
|
||||
echo "$found"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 3. 选择要恢复的站点
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 可恢复的站点"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SITES="shared"
|
||||
for dir in "$PROJECT_ROOT"/*-ai/; do
|
||||
s=$(basename "$dir"); [[ "$s" == "nginx-proxy-ai" ]] && continue
|
||||
[ -f "$dir/docker-compose.yml" ] || continue
|
||||
SITES="$SITES $s"
|
||||
done
|
||||
index=1
|
||||
declare -A site_status
|
||||
|
||||
for site in $SITES; do
|
||||
backup=$(check_backup "$site")
|
||||
if [[ -n "$backup" ]]; then
|
||||
if [[ "$backup" == local:* ]]; then
|
||||
status="✅ 本地"
|
||||
else
|
||||
status="✅ 云端"
|
||||
fi
|
||||
echo " $index) ${site} ${status}"
|
||||
site_list[$index]="$site"
|
||||
site_source[$index]="$backup"
|
||||
((index++))
|
||||
else
|
||||
echo " $index) ${site} ❌ 未找到"
|
||||
((index++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo " $index) 全部恢复"
|
||||
echo ""
|
||||
printf "请选择要恢复的内容 (1-$index): "
|
||||
read choice
|
||||
|
||||
if [[ "$choice" == "$index" ]]; then
|
||||
# 全部恢复
|
||||
restore_list=("${site_list[@]}")
|
||||
else
|
||||
restore_list=("${site_list[$choice]}")
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 4. 下载并解压备份文件
|
||||
# ============================================================
|
||||
RESTORE_DIR="/tmp/restore-$$"
|
||||
mkdir -p "$RESTORE_DIR"
|
||||
|
||||
download_and_extract() {
|
||||
local site="$1"
|
||||
local source="${site_source[$choice]}"
|
||||
local file_path=""
|
||||
|
||||
if [[ "$source" == local:* ]]; then
|
||||
file_path="${source#local:}"
|
||||
elif [[ "$source" == cloud:* ]]; then
|
||||
local filename="${source#cloud:}"
|
||||
log "从云端下载 ${site} 备份..."
|
||||
scp "${CLOUD_BACKUP}/${filename}" "$RESTORE_DIR/"
|
||||
file_path="$RESTORE_DIR/${filename}"
|
||||
fi
|
||||
|
||||
# 解压
|
||||
cd "$RESTORE_DIR"
|
||||
tar xzf "$file_path"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 5. 恢复函数
|
||||
# ============================================================
|
||||
restore_shared() {
|
||||
log "恢复共享资源..."
|
||||
|
||||
download_and_extract "shared"
|
||||
|
||||
# 恢复 docker 网络
|
||||
if [[ -f "$RESTORE_DIR/backup-shared/docker-network.sh" ]]; then
|
||||
log "创建 Docker 网络..."
|
||||
bash "$RESTORE_DIR/backup-shared/docker-network.sh"
|
||||
fi
|
||||
|
||||
# 恢复 nginx 配置
|
||||
if [[ -d "$RESTORE_DIR/backup-shared/nginx" ]]; then
|
||||
log "恢复 nginx 配置..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/nginx-proxy-ai/conf.d"
|
||||
scp "$RESTORE_DIR/backup-shared/nginx/"* txjp:$REMOTE_BASE/nginx-proxy-ai/conf.d/
|
||||
fi
|
||||
|
||||
# 恢复 SSL 证书
|
||||
if [[ -d "$RESTORE_DIR/backup-shared/ssl" ]]; then
|
||||
log "恢复 SSL 证书..."
|
||||
ssh txjp "cp -a $RESTORE_DIR/backup-shared/ssl/live /etc/letsencrypt/ 2>/dev/null || true"
|
||||
ssh txjp "cp -a $RESTORE_DIR/backup-shared/ssl/archive /etc/letsencrypt/ 2>/dev/null || true"
|
||||
ssh txjp "cp -a $RESTORE_DIR/backup-shared/ssl/renewal /etc/letsencrypt/ 2>/dev/null || true"
|
||||
fi
|
||||
|
||||
# 恢复 nginx 容器配置
|
||||
if [[ -f "$RESTORE_DIR/backup-shared/docker-compose.yml" ]]; then
|
||||
log "恢复 nginx docker-compose..."
|
||||
scp "$RESTORE_DIR/backup-shared/docker-compose.yml" txjp:$REMOTE_BASE/nginx-proxy-ai/
|
||||
fi
|
||||
}
|
||||
|
||||
restore_assets() {
|
||||
log "恢复 assets 站点..."
|
||||
|
||||
download_and_extract "assets"
|
||||
|
||||
# 创建目录
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/assets-ai/data"
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$RESTORE_DIR/backup-assets/data/assets.db" ]]; then
|
||||
log "恢复 assets 数据库..."
|
||||
scp "$RESTORE_DIR/backup-assets/data/assets.db" txjp:/tmp/assets.db
|
||||
ssh txjp "docker cp /tmp/assets.db assets-ai:/app/data/assets.db && rm /tmp/assets.db"
|
||||
fi
|
||||
|
||||
# 恢复上传文件
|
||||
if [[ -d "$RESTORE_DIR/backup-assets/data/uploads" ]]; then
|
||||
log "恢复 assets 上传文件..."
|
||||
scp -r "$RESTORE_DIR/backup-assets/data/uploads/"* txjp:$REMOTE_BASE/assets-ai/data/uploads/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 恢复配置文件
|
||||
for f in docker-compose.yml .env Dockerfile; do
|
||||
if [[ -f "$RESTORE_DIR/backup-assets/$f" ]]; then
|
||||
scp "$RESTORE_DIR/backup-assets/$f" txjp:$REMOTE_BASE/assets-ai/
|
||||
fi
|
||||
done
|
||||
|
||||
# 重启容器
|
||||
log "重启 assets 容器..."
|
||||
ssh txjp "cd $REMOTE_BASE/assets-ai && docker compose down 2>/dev/null || true && docker compose up -d"
|
||||
}
|
||||
|
||||
restore_issue() {
|
||||
log "恢复 issue 站点..."
|
||||
|
||||
download_and_extract "issue"
|
||||
|
||||
# 创建目录
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/issue-ai/data"
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$RESTORE_DIR/backup-issue/data/issue.db" ]]; then
|
||||
log "恢复 issue 数据库..."
|
||||
scp "$RESTORE_DIR/backup-issue/data/issue.db" txjp:/tmp/issue.db
|
||||
ssh txjp "docker cp /tmp/issue.db issue-ai:/app/data/issue.db && rm /tmp/issue.db"
|
||||
fi
|
||||
|
||||
# 恢复上传文件
|
||||
if [[ -d "$RESTORE_DIR/backup-issue/data/uploads" ]]; then
|
||||
log "恢复 issue 上传文件..."
|
||||
scp -r "$RESTORE_DIR/backup-issue/data/uploads/"* txjp:$REMOTE_BASE/issue-ai/data/uploads/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 恢复报告文件
|
||||
if [[ -d "$RESTORE_DIR/backup-issue/data/reports" ]]; then
|
||||
log "恢复 issue 报告文件..."
|
||||
scp -r "$RESTORE_DIR/backup-issue/data/reports/"* txjp:$REMOTE_BASE/issue-ai/data/reports/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 恢复配置文件
|
||||
for f in docker-compose.yml .env Dockerfile; do
|
||||
if [[ -f "$RESTORE_DIR/backup-issue/$f" ]]; then
|
||||
scp "$RESTORE_DIR/backup-issue/$f" txjp:$REMOTE_BASE/issue-ai/
|
||||
fi
|
||||
done
|
||||
|
||||
# 重启容器
|
||||
log "重启 issue 容器..."
|
||||
ssh txjp "cd $REMOTE_BASE/issue-ai && docker compose down 2>/dev/null || true && docker compose up -d"
|
||||
}
|
||||
|
||||
restore_ldap() {
|
||||
log "恢复 ldap 站点..."
|
||||
|
||||
download_and_extract "ldap"
|
||||
|
||||
# 创建目录
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/ldap-ai/data"
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$RESTORE_DIR/backup-ldap/data/users.db" ]]; then
|
||||
log "恢复 ldap 数据库..."
|
||||
scp "$RESTORE_DIR/backup-ldap/data/users.db" txjp:/tmp/ldap.db
|
||||
ssh txjp "docker cp /tmp/ldap.db lldap:/data/users.db && rm /tmp/ldap.db"
|
||||
fi
|
||||
|
||||
# 恢复配置文件
|
||||
for f in docker-compose.yml .env Dockerfile; do
|
||||
if [[ -f "$RESTORE_DIR/backup-ldap/$f" ]]; then
|
||||
scp "$RESTORE_DIR/backup-ldap/$f" txjp:$REMOTE_BASE/ldap-ai/
|
||||
fi
|
||||
done
|
||||
|
||||
# 重启容器
|
||||
log "重启 ldap 容器..."
|
||||
ssh txjp "cd $REMOTE_BASE/ldap-ai && docker compose down 2>/dev/null || true && docker compose up -d"
|
||||
}
|
||||
|
||||
restore_gitea() {
|
||||
log "恢复 gitea 站点..."
|
||||
|
||||
download_and_extract "gitea"
|
||||
|
||||
# 创建目录
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/gitea-ai/data"
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$RESTORE_DIR/backup-gitea/data/gitea.db" ]]; then
|
||||
log "恢复 gitea 数据库..."
|
||||
scp "$RESTORE_DIR/backup-gitea/data/gitea.db" txjp:/tmp/gitea.db
|
||||
ssh txjp "docker cp /tmp/gitea.db gitea-ai:/data/gitea.db && rm /tmp/gitea.db"
|
||||
fi
|
||||
|
||||
# 恢复代码仓库
|
||||
if [[ -d "$RESTORE_DIR/backup-gitea/git" ]]; then
|
||||
log "恢复 gitea 代码仓库..."
|
||||
scp -r "$RESTORE_DIR/backup-gitea/git" txjp:$REMOTE_BASE/gitea-ai/data/
|
||||
fi
|
||||
|
||||
# 恢复配置
|
||||
if [[ -d "$RESTORE_DIR/backup-gitea/gitea-conf" ]]; then
|
||||
log "恢复 gitea 配置..."
|
||||
ssh txjp "mkdir -p $REMOTE_BASE/gitea-ai/data/gitea"
|
||||
scp -r "$RESTORE_DIR/backup-gitea/gitea-conf" txjp:$REMOTE_BASE/gitea-ai/data/gitea/
|
||||
fi
|
||||
|
||||
# 恢复 docker-compose
|
||||
if [[ -f "$RESTORE_DIR/backup-gitea/docker-compose.yml" ]]; then
|
||||
scp "$RESTORE_DIR/backup-gitea/docker-compose.yml" txjp:$REMOTE_BASE/gitea-ai/
|
||||
fi
|
||||
|
||||
# 重启容器
|
||||
log "重启 gitea 容器..."
|
||||
ssh txjp "cd $REMOTE_BASE/gitea-ai && docker compose down 2>/dev/null || true && docker compose up -d"
|
||||
}
|
||||
|
||||
restore_oa() {
|
||||
log "恢复 oa 站点..."
|
||||
|
||||
download_and_extract "oa"
|
||||
|
||||
# 恢复配置文件
|
||||
for f in docker-compose.yml .env; do
|
||||
if [[ -f "$RESTORE_DIR/backup-oa/$f" ]]; then
|
||||
scp "$RESTORE_DIR/backup-oa/$f" txjp:$REMOTE_BASE/oa-ai/
|
||||
fi
|
||||
done
|
||||
|
||||
# 重启容器
|
||||
log "重启 oa 容器..."
|
||||
ssh txjp "cd $REMOTE_BASE/oa-ai && docker compose down 2>/dev/null || true && docker compose up -d"
|
||||
}
|
||||
|
||||
restore_monitor() {
|
||||
log "恢复 monitor 站点..."
|
||||
|
||||
download_and_extract "monitor"
|
||||
|
||||
for f in docker-compose.yml .env; do
|
||||
if [[ -f "$RESTORE_DIR/backup-monitor/$f" ]]; then
|
||||
scp "$RESTORE_DIR/backup-monitor/$f" txjp:$REMOTE_BASE/monitor-ai/
|
||||
fi
|
||||
done
|
||||
|
||||
# 恢复数据库
|
||||
if [[ -f "$RESTORE_DIR/backup-monitor/data/monitor.db" ]]; then
|
||||
ssh txjp "docker cp $RESTORE_DIR/backup-monitor/data/monitor.db monitor-ai:/app/data/monitor.db"
|
||||
fi
|
||||
|
||||
log "重启 monitor 容器..."
|
||||
ssh txjp "cd $REMOTE_BASE/monitor-ai && docker compose down 2>/dev/null || true && docker compose up -d --build"
|
||||
}
|
||||
|
||||
# 通用恢复(未知站点,仅恢复配置文件)
|
||||
restore_generic() {
|
||||
local site="$1"
|
||||
log "恢复 ${site} 站点(通用模式)..."
|
||||
|
||||
download_and_extract "${site#*-}" # monitor-ai → monitor
|
||||
|
||||
for f in docker-compose.yml .env Dockerfile; do
|
||||
if [[ -f "$RESTORE_DIR/backup-${site#*-}/$f" ]]; then
|
||||
scp "$RESTORE_DIR/backup-${site#*-}/$f" txjp:$REMOTE_BASE/${site#*-}-ai/
|
||||
fi
|
||||
done
|
||||
|
||||
# 尝试重启容器
|
||||
ssh txjp "cd $REMOTE_BASE/${site#*-}-ai && docker compose down 2>/dev/null || true && docker compose up -d" 2>/dev/null || warn "${site} 容器重启失败,请手动检查"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 6. 执行恢复
|
||||
# ============================================================
|
||||
for site in "${restore_list[@]}"; do
|
||||
case "$site" in
|
||||
shared) restore_shared ;;
|
||||
assets) restore_assets ;;
|
||||
issue) restore_issue ;;
|
||||
ldap) restore_ldap ;;
|
||||
gitea) restore_gitea ;;
|
||||
oa) restore_oa ;;
|
||||
monitor) restore_monitor ;;
|
||||
*) warn "${site}: 无专用恢复逻辑,仅恢复配置"; restore_generic "$site" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# 7. 重载 nginx
|
||||
# ============================================================
|
||||
log "重载 nginx 配置..."
|
||||
ssh txjp "docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload" 2>/dev/null || warn "nginx 重载失败,请手动检查"
|
||||
|
||||
# ============================================================
|
||||
# 8. 清理临时文件
|
||||
# ============================================================
|
||||
log "清理临时文件..."
|
||||
rm -rf "$RESTORE_DIR"
|
||||
|
||||
# ============================================================
|
||||
# 9. 验证恢复结果
|
||||
# ============================================================
|
||||
echo ""
|
||||
log "恢复完成!验证容器状态..."
|
||||
ssh txjp "docker ps --format 'table {{.Names}}\t{{.Status}}' | grep -E '(assets|issue|ldap|gitea|oa|nginx)'"
|
||||
|
|
@ -22,6 +22,7 @@ ALL_SITES="shared"
|
|||
for dir in "$PROJECT_ROOT"/*-ai/; do
|
||||
s=$(basename "$dir"); [[ "$s" == "nginx-proxy-ai" ]] && continue
|
||||
[ -f "$dir/docker-compose.yml" ] || continue
|
||||
s="${s%-ai}" # assets-ai → assets
|
||||
ALL_SITES="$ALL_SITES $s"
|
||||
done
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue