website-scripts/backup/restore-sites.sh

413 lines
13 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
# 站点恢复脚本 - 支持选择恢复单个站点或全部
# 用法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)'"