File size: 11,789 Bytes
7f2e1a6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 | #!/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 "$@" |