#!/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) nginx - 反向代理配置" echo " 6) 全部恢复" echo "" printf "请选择 (1-6): " 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 } # 执行恢复 case "$site_choice" in 1) restore_assets ;; 2) restore_issue ;; 3) restore_ldap ;; 4) restore_gitea ;; 5) restore_nginx ;; 6) restore_assets restore_issue restore_ldap restore_gitea restore_nginx ;; *) error "无效选择" ;; esac # ============================================================ # 6. 清理临时文件 # ============================================================ log "清理临时文件..." rm -rf "$RESTORE_DIR" echo "" log "恢复完成!"