ocngx / backup-scripts /restore.sh
tanbushi's picture
update
7f2e1a6
#!/bin/bash
# OCNGX 恢复脚本
# 用于在不同的 HuggingFace Space 上恢复 OCNGX 系统
set -euo pipefail
# 配置变量
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# 参数解析
BACKUP_FILE=""
FORCE_RESTORE=false
SKIP_CONFIG=false
usage() {
echo "用法: $0 [选项] <备份文件>"
echo ""
echo "选项:"
echo " -f, --force 强制恢复,覆盖现有配置"
echo " -s, --skip-config 跳过配置文件恢复"
echo " -h, --help 显示帮助信息"
echo ""
echo "示例:"
echo " $0 /path/to/ocngx_backup_20240101_120000.tar.gz"
echo " $0 --force ocngx_backup_20240101_120000.tar.gz"
exit 1
}
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case $1 in
-f|--force)
FORCE_RESTORE=true
shift
;;
-s|--skip-config)
SKIP_CONFIG=true
shift
;;
-h|--help)
usage
;;
-*)
echo "未知选项: $1"
usage
;;
*)
if [[ -z "$BACKUP_FILE" ]]; then
BACKUP_FILE="$1"
else
echo "错误: 只能指定一个备份文件"
usage
fi
shift
;;
esac
done
# 检查备份文件
if [[ -z "$BACKUP_FILE" ]]; then
echo "❌ 错误: 必须指定备份文件"
usage
fi
if [[ ! -f "$BACKUP_FILE" ]]; then
echo "❌ 错误: 备份文件不存在: $BACKUP_FILE"
exit 1
fi
# 验证备份文件完整性
verify_backup() {
echo "🔍 验证备份文件..."
# 检查文件格式
if [[ ! "$BACKUP_FILE" =~ \.tar\.gz$ ]]; then
echo "❌ 错误: 备份文件格式不正确,应为 .tar.gz 文件"
exit 1
fi
# 验证校验和(如果存在)
local checksum_file="${BACKUP_FILE}.sha256"
if [[ -f "$checksum_file" ]]; then
echo "🔐 验证校验和..."
if sha256sum -c "$checksum_file" >/dev/null 2>&1; then
echo " ✅ 校验和验证通过"
else
echo " ❌ 校验和验证失败,备份文件可能已损坏"
exit 1
fi
else
echo " ⚠️ 未找到校验和文件,跳过验证"
fi
# 检查归档内容
echo "📦 检查归档内容..."
if ! tar -tzf "$BACKUP_FILE" >/dev/null 2>&1; then
echo "❌ 错误: 备份文件无法正常解压"
exit 1
fi
echo " ✅ 备份文件验证通过"
}
# 检查环境
check_environment() {
echo "🔍 检查恢复环境..."
# 检查是否为 HuggingFace Spaces 环境
if [[ -d "/app" && -f "/app/opencode.json" ]]; then
echo " 🤗 HuggingFace Spaces 环境检测到"
RESTORE_TARGET="/app"
else
echo " 💻 本地环境,恢复到项目目录"
RESTORE_TARGET="$PROJECT_DIR"
fi
echo " 📂 恢复目标: $RESTORE_TARGET"
# 检查磁盘空间
local backup_size=$(du -h "$BACKUP_FILE" | cut -f1)
local available_space=$(df -h "$RESTORE_TARGET" | awk 'NR==2 {print $4}')
echo " 💾 备份大小: $backup_size, 可用空间: $available_space"
}
# 预检查
pre_restore_checks() {
echo "⚠️ 执行恢复前检查..."
# 检查关键文件是否存在
local critical_files=("$RESTORE_TARGET/opencode.json" "$RESTORE_TARGET/docker-start.sh")
for file in "${critical_files[@]}"; do
if [[ -f "$file" ]] && [[ "$FORCE_RESTORE" != true ]]; then
echo " ⚠️ 发现现有配置: $file"
echo " 使用 --force 选项强制覆盖"
echo " 或使用 --skip-config 选项跳过配置恢复"
exit 1
fi
done
# 检查 OpenCode 是否运行
if pgrep -f "opencode" >/dev/null 2>&1; then
echo " ⚠️ 检测到 OpenCode 正在运行"
echo " 建议停止服务后再执行恢复"
read -p "是否继续? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "恢复已取消"
exit 0
fi
fi
echo " ✅ 预检查完成"
}
# 创建临时恢复目录
prepare_restore() {
echo "📁 准备恢复环境..."
TEMP_RESTORE_DIR="/tmp/ocngx_restore_$(date +%s)"
mkdir -p "$TEMP_RESTORE_DIR"
echo " 📂 临时目录: $TEMP_RESTORE_DIR"
# 解压备份文件
echo "📦 解压备份文件..."
tar -xzf "$BACKUP_FILE" -C "$TEMP_RESTORE_DIR"
# 查找备份根目录
BACKUP_ROOT=$(find "$TEMP_RESTORE_DIR" -name "ocngx_backup_*" -type d | head -1)
if [[ -z "$BACKUP_ROOT" ]]; then
echo "❌ 错误: 无法找到备份根目录"
exit 1
fi
echo " ✅ 备份解压完成: $BACKUP_ROOT"
}
# 显示备份信息
show_backup_info() {
echo "📋 备份信息:"
local metadata_file="$BACKUP_ROOT/backup_metadata.json"
if [[ -f "$metadata_file" ]]; then
echo " 🆔 备份ID: $(jq -r '.backup_id' "$metadata_file" 2>/dev/null || echo "未知")"
echo " 📅 备份时间: $(jq -r '.backup_date' "$metadata_file" 2>/dev/null || echo "未知")"
echo " 🌍 环境: $(jq -r '.environment' "$metadata_file" 2>/dev/null || echo "未知")"
echo " 🖥️ 主机名: $(jq -r '.hostname' "$metadata_file" 2>/dev/null || echo "未知")"
echo " 🤖 OpenCode版本: $(jq -r '.opencode_version' "$metadata_file" 2>/dev/null || echo "未知")"
else
echo " ⚠️ 未找到备份元数据"
fi
echo ""
}
# 恢复配置文件
restore_configs() {
if [[ "$SKIP_CONFIG" == true ]]; then
echo "⏭️ 跳过配置文件恢复"
return
fi
echo "⚙️ 恢复配置文件..."
local config_source="$BACKUP_ROOT/configs"
if [[ ! -d "$config_source" ]]; then
echo " ⚠️ 未找到配置文件备份"
return
fi
# 备份现有配置
if [[ "$FORCE_RESTORE" == true ]] && [[ -f "$RESTORE_TARGET/opencode.json" ]]; then
echo " 💾 备份现有配置..."
mkdir -p "$RESTORE_TARGET/.backup_$(date +%s)"
cp -r "$RESTORE_TARGET"/opencode.json "$RESTORE_TARGET"/docker-start.sh "$RESTORE_TARGET/.backup_$(date +%s)/" 2>/dev/null || true
fi
# 恢复核心配置文件
local files_to_restore=("opencode.json" "AGENT.md" "docker-start.sh" "Dockerfile")
for file in "${files_to_restore[@]}"; do
if [[ -f "$config_source/$file" ]]; then
cp "$config_source/$file" "$RESTORE_TARGET/"
echo " ✅ 已恢复: $file"
fi
done
# 恢复 Nginx 配置
if [[ -d "$config_source/nginx" ]]; then
cp -r "$config_source/nginx"/* "$RESTORE_TARGET/nginx/" 2>/dev/null || true
echo " ✅ 已恢复: Nginx 配置"
fi
# 恢复 Cron 配置
if [[ -d "$config_source/cron-jobs" ]]; then
cp -r "$config_source/cron-jobs"/* "$RESTORE_TARGET/cron-jobs/" 2>/dev/null || true
echo " ✅ 已恢复: Cron 配置"
fi
echo " ✅ 配置文件恢复完成"
}
# 恢复 OpenCode 数据
restore_opencode_data() {
echo "🤖 恢复 OpenCode 数据..."
local data_source="$BACKUP_ROOT/opencode_data"
if [[ ! -d "$data_source" ]]; then
echo " ⚠️ 未找到 OpenCode 数据备份"
return
fi
# 确定 OpenCode 数据目录
local opencode_data_dir="${HOME}/.opencode"
mkdir -p "$opencode_data_dir"
# 恢复数据目录
for subdir in "projects" "sessions" "config" "cache" "logs"; do
if [[ -d "$data_source/$subdir" ]]; then
cp -r "$data_source/$subdir" "$opencode_data_dir/"
echo " 📦 已恢复: $subdir"
fi
done
# 恢复配置文件
local config_files=("config.json" "preferences.json")
for file in "${config_files[@]}"; do
if [[ -f "$data_source/$file" ]]; then
cp "$data_source/$file" "$opencode_data_dir/"
echo " ⚙️ 已恢复: $file"
fi
done
# 设置正确的权限
chmod -R 755 "$opencode_data_dir" 2>/dev/null || true
echo " ✅ OpenCode 数据恢复完成"
}
# 更新环境配置
update_environment_config() {
echo "🔄 更新环境配置..."
# 检测当前 HuggingFace Space URL
if [[ -n "${SPACE_ID:-}" ]]; then
local space_url="https://${SPACE_ID}.hf.space"
echo " 🌐 检测到 HuggingFace Space URL: $space_url"
# 更新 docker-start.sh 中的 URL 配置
local docker_start_file="$RESTORE_TARGET/docker-start.sh"
if [[ -f "$docker_start_file" ]]; then
sed -i.bak "s|https://[^.]*\.hf\.space|$space_url|g" "$docker_start_file"
echo " ✅ 已更新 Space URL 配置"
fi
fi
# 设置正确的文件权限
chmod +x "$RESTORE_TARGET/docker-start.sh" 2>/dev/null || true
chmod +x "$RESTORE_TARGET/cron-jobs"/*.sh 2>/dev/null || true
echo " ✅ 环境配置更新完成"
}
# 验证恢复结果
verify_restore() {
echo "🔍 验证恢复结果..."
local errors=0
# 检查关键文件
local critical_files=(
"$RESTORE_TARGET/opencode.json"
"$RESTORE_TARGET/docker-start.sh"
"$RESTORE_TARGET/Dockerfile"
"$RESTORE_TARGET/nginx/conf.d/default.conf"
)
for file in "${critical_files[@]}"; do
if [[ -f "$file" ]]; then
echo " ✅ 存在: $file"
else
echo " ❌ 缺失: $file"
((errors++))
fi
done
# 检查数据目录
local opencode_data_dir="${HOME}/.opencode"
if [[ -d "$opencode_data_dir" ]]; then
echo " ✅ OpenCode 数据目录存在"
else
echo " ⚠️ OpenCode 数据目录不存在"
fi
if [[ $errors -eq 0 ]]; then
echo " ✅ 恢复验证通过"
else
echo " ❌ 恢复验证失败,发现 $errors 个错误"
return 1
fi
}
# 清理临时文件
cleanup() {
echo "🧹 清理临时文件..."
rm -rf "$TEMP_RESTORE_DIR"
echo " ✅ 清理完成"
}
# 显示恢复后说明
show_post_restore_info() {
echo ""
echo "🎉 恢复完成!"
echo ""
echo "📋 后续步骤:"
echo "1. 🔄 重启服务以应用新配置:"
echo " docker-compose down && docker-compose up -d"
echo ""
echo "2. 🔍 验证服务状态:"
echo " curl http://localhost:7860/health"
echo ""
echo "3. 📖 检查 OpenCode 状态:"
echo " curl http://localhost:3000/global/health"
echo ""
echo "4. 🌐 访问 Web 界面:"
echo " http://localhost:7860"
echo ""
if [[ "$SKIP_CONFIG" == true ]]; then
echo "⚠️ 注意: 配置文件恢复已跳过,请手动检查配置"
fi
if [[ "$FORCE_RESTORE" == true ]]; then
echo "💾 原始配置已备份到 .backup_*/ 目录"
fi
}
# 主函数
main() {
echo "🚀 OCNGX 恢复开始..."
echo "📂 备份文件: $BACKUP_FILE"
echo "📅 恢复时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
verify_backup
check_environment
pre_restore_checks
prepare_restore
show_backup_info
restore_configs
restore_opencode_data
update_environment_config
if verify_restore; then
cleanup
show_post_restore_info
echo ""
echo "✅ 恢复成功完成!"
else
echo ""
echo "❌ 恢复过程中发现问题,请检查上述错误信息"
cleanup
exit 1
fi
}
# 错误处理
trap 'echo "❌ 恢复过程中发生错误"; cleanup; exit 1' ERR
# 执行主函数
main "$@"