File size: 13,536 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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
#!/bin/bash

# OCNGX 迁移工具
# 用于在不同 HuggingFace Spaces 之间快速迁移系统

set -euo pipefail

# 配置变量
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MIGRATION_LOG="/tmp/ocngx_migration_$(date +%s).log"

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$MIGRATION_LOG"
}

log_info() {
    echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$MIGRATION_LOG"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$MIGRATION_LOG"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$MIGRATION_LOG"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" | tee -a "$MIGRATION_LOG"
}

# 显示帮助信息
usage() {
    echo "OCNGX 迁移工具 - 在不同 HuggingFace Spaces 之间迁移系统"
    echo ""
    echo "用法: $0 [选项] <源Space> <目标Space>"
    echo ""
    echo "参数:"
    echo "  <源Space>     源 HuggingFace Space 名称"
    echo "  <目标Space>   目标 HuggingFace Space 名称"
    echo ""
    echo "选项:"
    echo "  -b, --backup-only     仅创建备份,不执行迁移"
    echo "  -r, --restore-only    仅恢复备份,不创建新备份"
    echo "  -k, --keep-backup     保留本地备份文件"
    echo "  -f, --force           强制覆盖目标配置"
    echo "  -d, --dry-run         模拟运行,不执行实际操作"
    echo "  -h, --help            显示帮助信息"
    echo ""
    echo "示例:"
    echo "  $0 source-space target-space"
    echo "  $0 --backup-only source-space"
    echo "  $0 --restore-only --force target-space backup.tar.gz"
    echo ""
    exit 1
}

# 解析命令行参数
SOURCE_SPACE=""
TARGET_SPACE=""
BACKUP_ONLY=false
RESTORE_ONLY=false
KEEP_BACKUP=false
FORCE_MODE=false
DRY_RUN=false
BACKUP_FILE=""

while [[ $# -gt 0 ]]; do
    case $1 in
        -b|--backup-only)
            BACKUP_ONLY=true
            shift
            ;;
        -r|--restore-only)
            RESTORE_ONLY=true
            shift
            ;;
        -k|--keep-backup)
            KEEP_BACKUP=true
            shift
            ;;
        -f|--force)
            FORCE_MODE=true
            shift
            ;;
        -d|--dry-run)
            DRY_RUN=true
            shift
            ;;
        -h|--help)
            usage
            ;;
        -*)
            log_error "未知选项: $1"
            usage
            ;;
        *)
            if [[ -z "$SOURCE_SPACE" ]]; then
                SOURCE_SPACE="$1"
            elif [[ -z "$TARGET_SPACE" ]]; then
                TARGET_SPACE="$1"
            elif [[ -z "$BACKUP_FILE" ]]; then
                BACKUP_FILE="$1"
            else
                log_error "参数过多"
                usage
            fi
            shift
            ;;
    esac
done

# 验证参数
validate_parameters() {
    log_info "验证参数..."
    
    if [[ "$RESTORE_ONLY" == true ]]; then
        if [[ -z "$TARGET_SPACE" && -z "$BACKUP_FILE" ]]; then
            log_error "恢复模式需要指定目标Space或备份文件"
            usage
        fi
    else
        if [[ -z "$SOURCE_SPACE" || -z "$TARGET_SPACE" ]]; then
            log_error "必须指定源Space和目标Space"
            usage
        fi
    fi
    
    log_success "参数验证通过"
}

# 检测当前环境
detect_current_environment() {
    log_info "检测当前环境..."
    
    if [[ -n "${SPACE_ID:-}" ]]; then
        CURRENT_ENV="huggingface"
        CURRENT_SPACE="$SPACE_ID"
        log_info "当前环境: HuggingFace Space ($CURRENT_SPACE)"
    elif [[ -f /.dockerenv ]]; then
        CURRENT_ENV="docker"
        CURRENT_SPACE="docker-container"
        log_info "当前环境: Docker 容器"
    else
        CURRENT_ENV="local"
        CURRENT_SPACE="local-machine"
        log_info "当前环境: 本地机器"
    fi
}

# 检查网络连接
check_network_connectivity() {
    log_info "检查网络连接..."
    
    # 检查 HuggingFace 连通性
    if curl -s --connect-timeout 10 https://huggingface.co >/dev/null 2>&1; then
        log_success "HuggingFace 连接正常"
    else
        log_error "无法连接到 HuggingFace"
        exit 1
    fi
}

# 创建源Space备份
create_source_backup() {
    log_info "在源Space创建备份: $SOURCE_SPACE"
    
    if [[ "$DRY_RUN" == true ]]; then
        log_warning "[DRY-RUN] 模拟在 $SOURCE_SPACE 创建备份"
        return
    fi
    
    # 这里需要SSH或API方式连接到源Space
    # 实际实现取决于具体的访问方式
    
    local source_url="https://$SOURCE_SPACE.hf.space"
    log_info "源Space URL: $source_url"
    
    # 方案1: 通过SSH访问(如果支持)
    # ssh user@$SOURCE_SPACE.hf.space "./backup-scripts/backup.sh"
    
    # 方案2: 通过API触发备份
    # curl -X POST "$source_url/api/backup"
    
    # 方案3: 通过HuggingFace CLI
    # huggingface-cli space create-backup $SOURCE_SPACE
    
    # 临时方案: 假设可以SSH访问
    log_warning "需要手动在源Space执行: ./backup-scripts/backup.sh"
    log_info "等待用户确认备份已完成..."
    read -p "确认备份已完成? (y/N): " -n 1 -r
    echo
    
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        log_success "源Space备份已确认"
    else
        log_error "备份未完成,终止迁移"
        exit 1
    fi
}

# 下载备份文件
download_backup() {
    log_info "下载备份文件..."
    
    if [[ "$DRY_RUN" == true ]]; then
        log_warning "[DRY-RUN] 模拟下载备份文件"
        return
    fi
    
    # 这里需要实现从源Space下载备份的逻辑
    # 可能的实现方式:
    
    # 1. 通过SCP下载
    # scp user@$SOURCE_SPACE.hf.space:/var/backups/ocngx/latest.tar.gz ./
    
    # 2. 通过HTTP下载
    # wget "$source_url/download/backup/latest.tar.gz"
    
    # 3. 通过HuggingFace API下载
    # huggingface-cli space download-backup $SOURCE_SPACE
    
    # 临时方案: 假设用户已手动下载
    log_warning "需要手动从源Space下载备份文件到当前目录"
    log_info "备份文件通常位于: /var/backups/ocngx/ocngx_backup_*.tar.gz"
    
    read -p "请输入备份文件路径: " backup_path
    
    if [[ -f "$backup_path" ]]; then
        BACKUP_FILE="$backup_path"
        log_success "备份文件已找到: $BACKUP_FILE"
    else
        log_error "备份文件不存在: $backup_path"
        exit 1
    fi
}

# 准备目标Space
prepare_target_space() {
    log_info "准备目标Space: $TARGET_SPACE"
    
    if [[ "$DRY_RUN" == true ]]; then
        log_warning "[DRY-RUN] 模拟准备目标Space"
        return
    fi
    
    # 1. 检查目标Space是否存在
    local target_url="https://$TARGET_SPACE.hf.space"
    log_info "目标Space URL: $target_url"
    
    # 2. 部署基础代码到目标Space
    log_info "部署基础代码到目标Space..."
    
    # 这里需要实现部署逻辑
    # 可能的实现方式:
    
    # 1. 通过Git部署
    # git clone <项目仓库> target-deploy
    # cd target-deploy
    # git remote set-url origin git@hf.co:spaces/$TARGET_SPACE
    # git push origin main
    
    # 2. 通过HuggingFace CLI部署
    # huggingface-cli space create $TARGET_SPACE --space-type docker
    
    # 临时方案: 假设用户已手动部署
    log_warning "需要手动将项目代码部署到目标Space: $TARGET_SPACE"
    log_info "步骤:"
    log_info "1. 创建新的Space: https://huggingface.co/new-space"
    log_info "2. 克隆当前项目代码"
    log_info "3. 推送到新Space"
    
    read -p "确认目标Space已准备完成? (y/N): " -n 1 -r
    echo
    
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        log_success "目标Space准备已确认"
    else
        log_error "目标Space未准备完成,终止迁移"
        exit 1
    fi
}

# 配置目标环境
configure_target_environment() {
    log_info "配置目标环境..."
    
    if [[ "$DRY_RUN" == true ]]; then
        log_warning "[DRY-RUN] 模拟配置目标环境"
        return
    fi
    
    # 这里需要在目标Space上执行环境配置
    # 类似于 prepare_target_space 的逻辑
    
    log_warning "需要手动在目标Space配置环境:"
    log_info "1. SSH连接到目标Space"
    log_info "2. 设置环境变量: export SPACE_ID=$TARGET_SPACE"
    log_info "3. 运行: source deploy-config.sh && configure_for_deployment"
    
    read -p "确认环境配置已完成? (y/N): " -n 1 -r
    echo
    
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        log_success "目标环境配置已确认"
    else
        log_error "环境配置未完成,终止迁移"
        exit 1
    fi
}

# 恢复数据到目标Space
restore_to_target() {
    log_info "恢复数据到目标Space..."
    
    if [[ "$DRY_RUN" == true ]]; then
        log_warning "[DRY-RUN] 模拟恢复数据到目标Space"
        return
    fi
    
    # 上传备份文件到目标Space
    log_info "上传备份文件到目标Space..."
    
    # 实现上传逻辑
    # scp backup.tar.gz user@$TARGET_SPACE.hf.space:/tmp/
    
    # 在目标Space执行恢复
    log_info "在目标Space执行数据恢复..."
    
    # 实现恢复逻辑
    # ssh user@$TARGET_SPACE.hf.space "./backup-scripts/restore.sh --force /tmp/backup.tar.gz"
    
    log_warning "需要手动在目标Space执行:"
    log_info "1. 上传备份文件: scp $BACKUP_FILE user@$TARGET_SPACE.hf.space:/tmp/"
    log_info "2. SSH连接到目标Space"
    log_info "3. 运行: ./backup-scripts/restore.sh --force /tmp/$(basename $BACKUP_FILE)"
    
    read -p "确认数据恢复已完成? (y/N): " -n 1 -r
    echo
    
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        log_success "数据恢复已确认"
    else
        log_error "数据恢复未完成,终止迁移"
        exit 1
    fi
}

# 验证迁移结果
verify_migration() {
    log_info "验证迁移结果..."
    
    local target_url="https://$TARGET_SPACE.hf.space"
    
    # 检查目标Space健康状态
    if curl -s --connect-timeout 10 "$target_url/health" >/dev/null 2>&1; then
        log_success "目标Space健康检查通过"
    else
        log_warning "目标Space健康检查失败,请手动验证"
    fi
    
    # 检查OpenCode状态
    if curl -s --connect-timeout 10 "$target_url/global/health" >/dev/null 2>&1; then
        log_success "OpenCode服务状态正常"
    else
        log_warning "OpenCode服务状态异常,请手动检查"
    fi
}

# 清理临时文件
cleanup() {
    log_info "清理临时文件..."
    
    if [[ "$KEEP_BACKUP" != true ]] && [[ -n "$BACKUP_FILE" ]] && [[ -f "$BACKUP_FILE" ]]; then
        if [[ "$DRY_RUN" != true ]]; then
            rm -f "$BACKUP_FILE"
            log_success "已清理备份文件: $BACKUP_FILE"
        fi
    fi
    
    # 清理日志文件
    # rm -f "$MIGRATION_LOG"
}

# 生成迁移报告
generate_report() {
    log_info "生成迁移报告..."
    
    local report_file="migration_report_$(date +%Y%m%d_%H%M%S).txt"
    
    cat > "$report_file" << EOF
OCNGX 迁移报告
===============

迁移时间: $(date '+%Y-%m-%d %H:%M:%S')
源Space: $SOURCE_SPACE
目标Space: $TARGET_SPACE
备份文件: $BACKUP_FILE
当前环境: $CURRENT_ENV ($CURRENT_SPACE)

迁移参数:
- 强制模式: $FORCE_MODE
- 仅备份: $BACKUP_ONLY
- 仅恢复: $RESTORE_ONLY
- 保留备份: $KEEP_BACKUP
- 模拟运行: $DRY_RUN

迁移日志:
$(cat "$MIGRATION_LOG")

后续步骤:
1. 访问目标Space: https://$TARGET_SPACE.hf.space
2. 验证所有功能正常
3. 更新DNS或代理配置(如需要)
4. 通知用户迁移完成

注意事项:
- 请保留此报告用于记录
- 建议在迁移后24小时内监控系统状态
- 如有问题,请检查迁移日志
EOF

    log_success "迁移报告已生成: $report_file"
}

# 主迁移流程
execute_migration() {
    log_info "开始迁移流程..."
    log_info "源Space: $SOURCE_SPACE -> 目标Space: $TARGET_SPACE"
    
    if [[ "$BACKUP_ONLY" == true ]]; then
        create_source_backup
        download_backup
        log_success "备份完成"
        return
    fi
    
    if [[ "$RESTORE_ONLY" == true ]]; then
        if [[ -z "$TARGET_SPACE" ]]; then
            # 本地恢复模式
            log_info "本地恢复模式"
            if [[ -z "$BACKUP_FILE" ]]; then
                log_error "本地恢复需要指定备份文件"
                exit 1
            fi
            ./backup-scripts/restore.sh ${FORCE_MODE:+--force} "$BACKUP_FILE"
        else
            prepare_target_space
            configure_target_environment
            restore_to_target
        fi
        verify_migration
        log_success "恢复完成"
        return
    fi
    
    # 完整迁移流程
    create_source_backup
    download_backup
    prepare_target_space
    configure_target_environment
    restore_to_target
    verify_migration
    
    log_success "迁移完成!"
}

# 错误处理
handle_error() {
    log_error "迁移过程中发生错误"
    cleanup
    exit 1
}

# 主函数
main() {
    echo "🚀 OCNGX 迁移工具"
    echo "=================="
    
    validate_parameters
    detect_current_environment
    check_network_connectivity
    
    trap handle_error ERR
    
    execute_migration
    generate_report
    cleanup
    
    echo ""
    log_success "迁移流程执行完成!"
    echo "📊 迁移日志: $MIGRATION_LOG"
    echo "📋 如有疑问,请查看迁移报告"
}

# 执行主函数
main "$@"