#!/bin/bash # OCNGX 完整备份脚本 # 支持多环境部署的数据备份和恢复 set -euo pipefail # 配置变量 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BACKUP_DIR="${BACKUP_DIR:-/var/backups/ocngx}" BACKUP_DATE=$(date '+%Y%m%d_%H%M%S') BACKUP_ID="ocngx_backup_${BACKUP_DATE}" TEMP_BACKUP_DIR="/tmp/${BACKUP_ID}" # 环境检测 detect_environment() { echo "🔍 检测运行环境..." if [[ -f /.dockerenv ]]; then ENVIRONMENT="docker" echo " 🐳 Docker 容器环境" elif [[ -d /app && -f /app/opencode.json ]]; then ENVIRONMENT="huggingface" echo " 🤗 HuggingFace Spaces 环境" else ENVIRONMENT="local" echo " 💻 本地环境" fi # 检测 OpenCode 安装位置 if command -v opencode &> /dev/null; then OPENCODE_CMD="opencode" OPENCODE_DATA_DIR="${HOME}/.opencode" elif npm list -g opencode-ai &> /dev/null; then OPENCODE_CMD="opencode" OPENCODE_DATA_DIR="${HOME}/.opencode" else echo " ❌ 未找到 OpenCode 安装" exit 1 fi echo " 📂 OpenCode 数据目录: ${OPENCODE_DATA_DIR}" } # 创建备份目录 prepare_backup() { echo "📁 准备备份环境..." mkdir -p "$BACKUP_DIR" mkdir -p "$TEMP_BACKUP_DIR" # 创建备份元数据 cat > "${TEMP_BACKUP_DIR}/backup_metadata.json" << EOF { "backup_id": "${BACKUP_ID}", "backup_date": "$(date -Iseconds)", "environment": "${ENVIRONMENT}", "hostname": "$(hostname)", "opencode_version": "$(opencode --version 2>/dev/null || echo 'unknown')", "system_info": { "os": "$(uname -s)", "arch": "$(uname -m)", "kernel": "$(uname -r)" }, "disk_usage": { "total": "$(df -h / | awk 'NR==2 {print $2}')", "used": "$(df -h / | awk 'NR==2 {print $3}')", "available": "$(df -h / | awk 'NR==2 {print $4}')" } } EOF } # 备份配置文件 backup_configs() { echo "⚙️ 备份配置文件..." local config_dir="${TEMP_BACKUP_DIR}/configs" mkdir -p "$config_dir" # 备份项目配置文件 [[ -f "${SCRIPT_DIR}/opencode.json" ]] && cp "${SCRIPT_DIR}/opencode.json" "$config_dir/" [[ -f "${SCRIPT_DIR}/AGENT.md" ]] && cp "${SCRIPT_DIR}/AGENT.md" "$config_dir/" [[ -f "${SCRIPT_DIR}/docker-start.sh" ]] && cp "${SCRIPT_DIR}/docker-start.sh" "$config_dir/" [[ -f "${SCRIPT_DIR}/Dockerfile" ]] && cp "${SCRIPT_DIR}/Dockerfile" "$config_dir/" # 备份 Nginx 配置 if [[ -d "${SCRIPT_DIR}/nginx" ]]; then cp -r "${SCRIPT_DIR}/nginx" "$config_dir/" fi # 备份 Cron 配置 if [[ -d "${SCRIPT_DIR}/cron-jobs" ]]; then cp -r "${SCRIPT_DIR}/cron-jobs" "$config_dir/" fi # 备份环境变量(排除敏感信息) env | grep -E '^(OPENCODE_|PUBLIC_|BASE_|GATEWAY_)' > "$config_dir/environment.txt" 2>/dev/null || true echo " ✅ 配置文件备份完成" } # 备份 OpenCode 数据 backup_opencode_data() { echo "🤖 备份 OpenCode 数据..." if [[ -d "$OPENCODE_DATA_DIR" ]]; then local data_dir="${TEMP_BACKUP_DIR}/opencode_data" mkdir -p "$data_dir" # 备份核心数据目录 for subdir in "projects" "sessions" "config" "cache" "logs"; do if [[ -d "${OPENCODE_DATA_DIR}/${subdir}" ]]; then cp -r "${OPENCODE_DATA_DIR}/${subdir}" "$data_dir/" echo " 📦 已备份: $subdir" fi done # 备份全局配置文件 [[ -f "${OPENCODE_DATA_DIR}/config.json" ]] && cp "${OPENCODE_DATA_DIR}/config.json" "$data_dir/" [[ -f "${OPENCODE_DATA_DIR}/preferences.json" ]] && cp "${OPENCODE_DATA_DIR}/preferences.json" "$data_dir/" echo " ✅ OpenCode 数据备份完成" else echo " ⚠️ OpenCode 数据目录不存在,跳过" fi } # 备份运行时状态 backup_runtime_state() { echo "🔄 备份运行时状态..." local runtime_dir="${TEMP_BACKUP_DIR}/runtime" mkdir -p "$runtime_dir" # 备份当前运行的进程信息 ps aux | grep -E "(opencode|nginx|openresty)" > "$runtime_dir/running_processes.txt" 2>/dev/null || true # 备份网络连接状态 netstat -tlnp 2>/dev/null | grep -E ":(3000|7860)" > "$runtime_dir/network_state.txt" || true # 备份最近的日志 if [[ -d "/var/log" ]]; then find /var/log -name "*nginx*" -o -name "*opencode*" -mtime -7 -exec cp {} "$runtime_dir/" \; 2>/dev/null || true fi echo " ✅ 运行时状态备份完成" } # 创建备份归档 create_archive() { echo "🗜️ 创建备份归档..." local archive_file="${BACKUP_DIR}/${BACKUP_ID}.tar.gz" # 创建压缩归档 tar -czf "$archive_file" -C "/tmp" "$BACKUP_ID" # 验证归档 if [[ -f "$archive_file" ]]; then local archive_size=$(du -h "$archive_file" | cut -f1) echo " ✅ 备份归档创建成功: ${archive_file} (${archive_size})" # 生成校验和 sha256sum "$archive_file" > "${archive_file}.sha256" echo " 🔐 校验和已生成: ${archive_file}.sha256" else echo " ❌ 备份归档创建失败" exit 1 fi } # 清理旧备份 cleanup_old_backups() { echo "🧹 清理旧备份..." cd "$BACKUP_DIR" # 保留最近 10 个备份 local backup_count=$(ls -1 ocngx_backup_*.tar.gz 2>/dev/null | wc -l) if [[ $backup_count -gt 10 ]]; then echo " 🗑️ 删除超过 10 个的旧备份..." ls -t ocngx_backup_*.tar.gz 2>/dev/null | tail -n +11 | while read -r old_backup; do echo " 删除: $old_backup" rm -f "$old_backup" "${old_backup}.sha256" done fi # 显示备份状态 local remaining_count=$(ls -1 ocngx_backup_*.tar.gz 2>/dev/null | wc -l) local total_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1) echo " 📊 当前备份状态: $remaining_count 个文件, 总大小: $total_size" } # 上传到外部存储(可选) upload_to_external() { if [[ -n "${BACKUP_S3_BUCKET:-}" ]]; then echo "☁️ 上传备份到 S3..." local archive_file="${BACKUP_DIR}/${BACKUP_ID}.tar.gz" if command -v aws &> /dev/null; then aws s3 cp "$archive_file" "s3://${BACKUP_S3_BUCKET}/ocngx-backups/" --storage-class STANDARD_IA aws s3 cp "${archive_file}.sha256" "s3://${BACKUP_S3_BUCKET}/ocngx-backups/" echo " ✅ S3 上传完成" else echo " ⚠️ AWS CLI 未安装,跳过 S3 上传" fi fi } # 主函数 main() { echo "🚀 OCNGX 完整备份开始..." echo "📅 备份时间: $(date '+%Y-%m-%d %H:%M:%S')" echo "🆔 备份ID: ${BACKUP_ID}" echo "" detect_environment prepare_backup backup_configs backup_opencode_data backup_runtime_state create_archive cleanup_old_backups upload_to_external # 清理临时目录 rm -rf "$TEMP_BACKUP_DIR" echo "" echo "✅ 备份完成!" echo "📁 备份位置: ${BACKUP_DIR}/${BACKUP_ID}.tar.gz" echo "🔍 验证命令: tar -tzf ${BACKUP_DIR}/${BACKUP_ID}.tar.gz | head -10" } # 执行主函数 main "$@"