nuwax-cli 1.0.123

Docker service management and upgrade CLI
Documentation
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
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
use crate::app::CliApp;
use crate::docker_service::DockerService;
use anyhow::Result;
use client_core::backup::{BackupManager, BackupOptions};
use client_core::constants::docker;
use client_core::database::BackupType;
use rust_i18n::t;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tracing::{error, info, warn};

/// JSON 格式的备份信息(用于 GUI 集成)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonBackupInfo {
    pub id: i64,
    pub backup_type: String,
    pub created_at: String,
    pub service_version: String,
    pub file_path: String,
    pub file_size: Option<u64>,
    pub file_exists: bool,
}

/// JSON 格式的备份列表响应
#[derive(Debug, Serialize, Deserialize)]
pub struct JsonBackupListResponse {
    pub success: bool,
    pub backups: Vec<JsonBackupInfo>,
    pub error: Option<String>,
}

/// 创建备份
pub async fn run_backup(app: &CliApp) -> Result<()> {
    // 1. 检查Docker环境
    let compose_path = Path::new(&app.config.docker.compose_file);

    if !compose_path.exists() {
        error!(
            "❌ Docker Compose file does not exist: {path}",
            path = compose_path.display()
        );
        info!("💡 Please ensure Docker services are properly deployed");
        return Ok(());
    }

    // 2. 使用 DockerService 的 health_check 进行智能状态检查
    info!("🔍 Checking Docker service status...");

    let docker_service = DockerService::new(app.config.clone(), app.docker_manager.clone())?;
    match docker_service.health_check().await {
        Ok(report) => {
            info!(
                "📊 Service status: {status}",
                status = report.get_status_summary()
            );

            // 智能分析服务状态
            let running_containers = report.get_running_containers();
            let completed_containers = report.get_completed_containers();
            let failed_containers = report.get_failed_containers();

            // 🔧 改进:使用restart字段智能判断一次性任务和持续服务
            let persistent_running_services: Vec<_> = running_containers
                .iter()
                .filter(|c| c.is_persistent_service())
                .collect();

            if !persistent_running_services.is_empty() {
                warn!("⚠️  Persistent services are still running!");
                error!("❌ Cold backup requires persistent services to be stopped");

                info!(
                    "📝 Found {count} running persistent services:",
                    count = persistent_running_services.len()
                );
                for container in &persistent_running_services {
                    info!(
                        "   - {name} (status: {status}, restart: {restart})",
                        name = container.name,
                        status = container.status.display_name(),
                        restart = container.get_restart_display()
                    );
                }

                // 显示被忽略的一次性任务
                let oneshot_running_services: Vec<_> = running_containers
                    .iter()
                    .filter(|c| c.is_oneshot())
                    .collect();

                if !oneshot_running_services.is_empty() {
                    info!(
                        "📝 Found {count} running one-shot tasks (ignored):",
                        count = oneshot_running_services.len()
                    );
                    for container in oneshot_running_services {
                        info!(
                            "   - {name} (one-shot task, restart: {restart}, does not affect backup)",
                            name = container.name,
                            restart = container.get_restart_display()
                        );
                    }
                }

                info!("💡 Please stop persistent services before backup");
                return Ok(());
            }

            // 成功:所有持续服务已停止
            info!("✅ All persistent services stopped, ready for backup");

            // 显示已完成和被忽略的容器信息
            if !completed_containers.is_empty() {
                let oneshot_completed: Vec<_> = completed_containers
                    .iter()
                    .filter(|c| c.is_oneshot())
                    .collect();

                let other_completed: Vec<_> = completed_containers
                    .iter()
                    .filter(|c| !c.is_oneshot())
                    .collect();

                if !oneshot_completed.is_empty() {
                    info!(
                        "🔄 Ignoring {count} one-shot task containers:",
                        count = oneshot_completed.len()
                    );
                    for container in oneshot_completed {
                        info!(
                            "   - {name} (status: {status}, restart: {restart})",
                            name = container.name,
                            status = container.status.display_name(),
                            restart = container.get_restart_display()
                        );
                    }
                }

                if !other_completed.is_empty() {
                    info!(
                        "📝 Found {count} other completed containers:",
                        count = other_completed.len()
                    );
                    for container in other_completed {
                        info!(
                            "   - {name} (status: {status}, restart: {restart})",
                            name = container.name,
                            status = container.status.display_name(),
                            restart = container.get_restart_display()
                        );
                    }
                }
            }

            if !failed_containers.is_empty() {
                warn!(
                    "⚠️  Found {count} failed containers (does not affect backup):",
                    count = failed_containers.len()
                );
                for container in failed_containers {
                    warn!(
                        "   - {name} (status: {status}, restart: {restart})",
                        name = container.name,
                        status = container.status.display_name(),
                        restart = container.get_restart_display()
                    );
                }
            }
        }
        Err(e) => {
            error!(
                "❌ Docker service status check failed: {error}",
                error = e.to_string()
            );
            info!("💡 Cannot confirm service status, suggest manual check before backup");
            return Ok(());
        }
    }

    // 3. 执行备份
    info!("🔄 Starting backup creation...");

    // 执行需要备份的目录: app, data 目录
    let source_paths = vec![docker::get_data_dir_path(), docker::get_app_dir_path()];

    let backup_options = BackupOptions {
        backup_type: BackupType::Manual,
        service_version: app.config.get_docker_versions(),
        work_dir: PathBuf::from("./docker"),
        source_paths,
        compression_level: 6, // 平衡压缩率和速度
    };

    // 使用 BackupManager 创建备份
    let backup_manager = BackupManager::new(
        app.config.get_backup_dir(),
        app.database.clone(),
        app.docker_manager.clone(),
    )?;

    match backup_manager.create_backup(backup_options).await {
        Ok(backup_record) => {
            info!(
                "✅ Backup created successfully: {path}",
                path = backup_record.file_path
            );
            info!("📝 Backup ID: {id}", id = backup_record.id);
            info!(
                "📏 Backup service version: {version}",
                version = backup_record.service_version
            );
        }
        Err(e) => {
            error!("❌ Backup creation failed: {error}", error = e.to_string());
            return Err(e);
        }
    }

    Ok(())
}

/// 列出备份
pub async fn run_list_backups(app: &CliApp) -> Result<()> {
    let backups = app.backup_manager.list_backups().await?;

    if backups.is_empty() {
        info!("📦 No backup records");
        info!("💡 Use the following command to create backup:");
        info!("   nuwax-cli backup");
        return Ok(());
    }

    info!("📦 Backup List");
    info!("============");

    // 统计信息
    let total_backups = backups.len();
    let mut valid_backups = 0;
    let mut invalid_backups = 0;
    let mut total_size = 0u64;

    // 详细信息表头
    info!(
        "{:<4} {:<12} {:<20} {:<10} {:<8} {:<12} {}",
        t!("backup_cmd.header_id"),
        t!("backup_cmd.header_type"),
        t!("backup_cmd.header_created_at"),
        t!("backup_cmd.header_version"),
        t!("backup_cmd.header_status"),
        t!("backup_cmd.header_size"),
        t!("backup_cmd.header_file_path")
    );
    info!(
        "----------------------------------------------------------------------------------------------------"
    );

    for backup in &backups {
        let backup_path = std::path::Path::new(&backup.file_path);
        let file_exists = backup_path.exists();

        // 文件状态和大小信息
        let (status_display, size_display) = if file_exists {
            valid_backups += 1;

            // 获取文件大小
            let size = if let Ok(metadata) = std::fs::metadata(&backup.file_path) {
                let file_size = metadata.len();
                total_size += file_size;
                if file_size > 1024 * 1024 * 1024 {
                    format!("{:.1}GB", file_size as f64 / (1024.0 * 1024.0 * 1024.0))
                } else if file_size > 1024 * 1024 {
                    format!("{:.1}MB", file_size as f64 / (1024.0 * 1024.0))
                } else if file_size > 1024 {
                    format!("{:.1}KB", file_size as f64 / 1024.0)
                } else {
                    format!("{file_size}B")
                }
            } else {
                t!("backup_cmd.size_unknown").to_string()
            };

            (t!("backup_cmd.status_available").to_string(), size)
        } else {
            invalid_backups += 1;
            (
                t!("backup_cmd.status_file_missing").to_string(),
                "---".to_string(),
            )
        };

        // 备份类型显示
        let backup_type_display = match backup.backup_type {
            client_core::database::BackupType::Manual => t!("backup_cmd.type_manual"),
            client_core::database::BackupType::PreUpgrade => t!("backup_cmd.type_pre_upgrade"),
        };

        // 获取文件名而不是完整路径用于显示
        let filename = backup_path
            .file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| backup.file_path.clone());

        info!(
            "{:<4} {:<12} {:<20} {:<10} {:<8} {:<12} {}",
            backup.id,
            backup_type_display,
            backup.created_at.format("%Y-%m-%d %H:%M:%S"),
            backup.service_version,
            status_display,
            size_display,
            filename
        );

        // 如果文件不存在,显示警告信息
        if !file_exists {
            warn!("     ⚠️  Warning: Backup file does not exist, cannot be used for rollback!");
            warn!("         Expected path: {path}", path = backup.file_path);
        }
    }

    info!(
        "----------------------------------------------------------------------------------------------------"
    );

    // 统计摘要
    info!("📊 Backup Statistics:");
    info!("   Total backups: {count}", count = total_backups);
    info!("   Valid backups: {count} ✅", count = valid_backups);
    if invalid_backups > 0 {
        warn!("   Invalid backups: {count} ❌", count = invalid_backups);
    }

    if total_size > 0 {
        let total_size_display = if total_size > 1024 * 1024 * 1024 {
            format!("{:.2} GB", total_size as f64 / (1024.0 * 1024.0 * 1024.0))
        } else if total_size > 1024 * 1024 {
            format!("{:.2} MB", total_size as f64 / (1024.0 * 1024.0))
        } else {
            format!("{:.2} KB", total_size as f64 / 1024.0)
        };
        info!("   Total size: {size}", size = total_size_display);
    }

    // 操作提示
    if valid_backups > 0 {
        info!("💡 Available operations:");
        info!("   - Interactive rollback: nuwax-cli rollback");
        info!("   - Rollback by ID: nuwax-cli rollback <backup_id>");
        info!("   - Create new backup: nuwax-cli backup");
    }

    if invalid_backups > 0 {
        warn!(
            "⚠️  Found {count} invalid backups (file missing)",
            count = invalid_backups
        );
        info!("💡 Suggestions:");
        info!(
            "   - Check backup directory settings: {dir}",
            dir = app.config.get_backup_dir().display()
        );
        info!("   - If backup files were deleted, these records cannot be used for recovery");
        info!("   - Consider manually cleaning up these invalid records");
    }

    Ok(())
}

/// 从备份恢复
pub async fn run_rollback(
    app: &CliApp,
    backup_id: Option<i64>,
    force: bool,
    list_json: bool,
    auto_start_service: bool,
    rollback_data: bool,
) -> Result<()> {
    // 如果指定了 --list-json,禁用日志输出并输出 JSON 格式的备份列表
    if list_json {
        // 临时设置日志级别为OFF,避免污染JSON输出
        tracing::subscriber::set_global_default(
            tracing_subscriber::FmtSubscriber::builder()
                .with_max_level(tracing::Level::ERROR)
                .finish(),
        )
        .ok();

        return output_backups_as_json(app).await;
    }

    // 如果没有提供backup_id,启动交互式选择
    let selected_backup_id = if let Some(id) = backup_id {
        id
    } else {
        match interactive_backup_selection(app).await? {
            Some(id) => id,
            None => {
                info!("Operation cancelled");
                return Ok(());
            }
        }
    };

    if !force {
        if rollback_data {
            warn!(
                "⚠️  Warning: This operation will overwrite current data directory, Mysql, Redis etc. data will also be rolled back!"
            );
        } else {
            warn!(
                "⚠️  Warning: This operation will rollback backend and frontend application versions, but not Mysql, Redis etc. data!"
            );
        }

        use std::io::{self, Write};
        print!(
            "{}",
            t!("backup_cmd.confirm_restore", id = selected_backup_id)
        );
        io::stdout().flush()?;

        let mut input = String::new();
        std::io::stdin().read_line(&mut input)?;

        if input.trim().to_lowercase() != "y" {
            warn!("Operation cancelled");
            return Ok(());
        }
    }

    info!("Starting data rollback operation...");

    // 🔧 智能回滚
    if rollback_data {
        //data,app 等目录,全部恢复
        run_rollback_with_exculde(app, selected_backup_id, auto_start_service, &[]).await?;
    } else {
        info!(
            "rollback_data is false, not rolling back data directory (mysql, redis etc. data will not be rolled back)"
        );
        //data 数据目录不用恢复,回滚应用业务逻辑, 考虑改写: perform_selective_restore ,增加参数,用于排除 data 目录
        run_rollback_with_exculde(app, selected_backup_id, auto_start_service, &["data"]).await?;
    }

    info!("✅ Data rollback complete");
    Ok(())
}

/// 交互式备份选择
async fn interactive_backup_selection(app: &CliApp) -> Result<Option<i64>> {
    info!("🗂️  Backup Selection");
    info!("============");

    let backups = app.backup_manager.list_backups().await?;

    if backups.is_empty() {
        warn!("❌ No available backups");
        info!("💡 Use the following command to create backup:");
        info!("   nuwax-cli backup");
        return Ok(None);
    }

    // 筛选可用的备份(文件存在且有效)
    let mut valid_backups = Vec::new();
    for backup in &backups {
        let backup_path = std::path::Path::new(&backup.file_path);
        if backup_path.exists() {
            valid_backups.push(backup);
        }
    }

    if valid_backups.is_empty() {
        warn!("❌ No available backup files");
        info!("💡 All backup files are lost or corrupted");
        return Ok(None);
    }

    // 显示备份选择列表
    info!("📋 Available backup list:");
    info!(
        "{:<4} {:<12} {:<20} {:<10} {:<12} {}",
        t!("backup_cmd.header_index"),
        t!("backup_cmd.header_type"),
        t!("backup_cmd.header_created_at"),
        t!("backup_cmd.header_version"),
        t!("backup_cmd.header_size"),
        t!("backup_cmd.header_filename")
    );
    info!("--------------------------------------------------------------------------------");

    for (index, backup) in valid_backups.iter().enumerate() {
        let backup_path = std::path::Path::new(&backup.file_path);

        // 获取文件大小
        let size_display = if let Ok(metadata) = std::fs::metadata(&backup.file_path) {
            let file_size = metadata.len();
            if file_size > 1024 * 1024 * 1024 {
                format!("{:.1}GB", file_size as f64 / (1024.0 * 1024.0 * 1024.0))
            } else if file_size > 1024 * 1024 {
                format!("{:.1}MB", file_size as f64 / (1024.0 * 1024.0))
            } else if file_size > 1024 {
                format!("{:.1}KB", file_size as f64 / 1024.0)
            } else {
                format!("{file_size}B")
            }
        } else {
            t!("backup_cmd.size_unknown").to_string()
        };

        // 备份类型显示
        let backup_type_display = match backup.backup_type {
            client_core::database::BackupType::Manual => t!("backup_cmd.type_manual"),
            client_core::database::BackupType::PreUpgrade => t!("backup_cmd.type_pre_upgrade"),
        };

        // 获取文件名
        let filename = backup_path
            .file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| backup.file_path.clone());

        info!(
            "{:<4} {:<12} {:<20} {:<10} {:<12} {}",
            index + 1,
            backup_type_display,
            backup.created_at.format("%Y-%m-%d %H:%M:%S"),
            backup.service_version,
            size_display,
            filename
        );
    }

    info!("--------------------------------------------------------------------------------");
    info!("💡 Input instructions:");
    info!(
        "   - Enter index (1-{count}) to select backup to restore",
        count = valid_backups.len()
    );
    info!("   - Enter 'q' or 'quit' to exit");
    info!("   - Enter 'l' or 'list' to redisplay list");

    // 交互式选择循环
    use std::io::{self, Write};
    loop {
        print!(
            "\n{}",
            t!("backup_cmd.select_prompt", count = valid_backups.len())
        );
        io::stdout().flush()?;

        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        let input = input.trim();

        // 处理退出命令
        if input.is_empty() || input.eq_ignore_ascii_case("q") || input.eq_ignore_ascii_case("quit")
        {
            info!("👋 Operation cancelled");
            return Ok(None);
        }

        // 处理重新显示列表
        if input.eq_ignore_ascii_case("l") || input.eq_ignore_ascii_case("list") {
            info!("\n📋 Redisplaying backup list:");
            info!(
                "{:<4} {:<12} {:<20} {:<10} {:<12} {}",
                t!("backup_cmd.header_index"),
                t!("backup_cmd.header_type"),
                t!("backup_cmd.header_created_at"),
                t!("backup_cmd.header_version"),
                t!("backup_cmd.header_size"),
                t!("backup_cmd.header_filename")
            );
            info!(
                "--------------------------------------------------------------------------------"
            );

            for (index, backup) in valid_backups.iter().enumerate() {
                let backup_path = std::path::Path::new(&backup.file_path);

                let size_display = if let Ok(metadata) = std::fs::metadata(&backup.file_path) {
                    let file_size = metadata.len();
                    if file_size > 1024 * 1024 * 1024 {
                        format!("{:.1}GB", file_size as f64 / (1024.0 * 1024.0 * 1024.0))
                    } else if file_size > 1024 * 1024 {
                        format!("{:.1}MB", file_size as f64 / (1024.0 * 1024.0))
                    } else if file_size > 1024 {
                        format!("{:.1}KB", file_size as f64 / 1024.0)
                    } else {
                        format!("{file_size}B")
                    }
                } else {
                    t!("backup_cmd.size_unknown").to_string()
                };

                let backup_type_display = match backup.backup_type {
                    client_core::database::BackupType::Manual => t!("backup_cmd.type_manual"),
                    client_core::database::BackupType::PreUpgrade => {
                        t!("backup_cmd.type_pre_upgrade")
                    }
                };

                let filename = backup_path
                    .file_name()
                    .map(|n| n.to_string_lossy().to_string())
                    .unwrap_or_else(|| backup.file_path.clone());

                info!(
                    "{:<4} {:<12} {:<20} {:<10} {:<12} {}",
                    index + 1,
                    backup_type_display,
                    backup.created_at.format("%Y-%m-%d %H:%M:%S"),
                    backup.service_version,
                    size_display,
                    filename
                );
            }
            info!(
                "--------------------------------------------------------------------------------"
            );
            continue;
        }

        // 处理数字选择
        match input.parse::<usize>() {
            Ok(selection) => {
                if selection >= 1 && selection <= valid_backups.len() {
                    let selected_backup = valid_backups[selection - 1];

                    // 显示选择确认
                    info!("✅ You selected backup:");
                    info!("   Backup ID: {id}", id = selected_backup.id);
                    info!(
                        "   Type: {backup_type}",
                        backup_type = match selected_backup.backup_type {
                            client_core::database::BackupType::Manual =>
                                t!("backup_cmd.type_manual"),
                            client_core::database::BackupType::PreUpgrade =>
                                t!("backup_cmd.type_pre_upgrade"),
                        }
                    );
                    info!(
                        "   Created at: {time}",
                        time = selected_backup.created_at.format("%Y-%m-%d %H:%M:%S")
                    );
                    info!(
                        "   Service version: {version}",
                        version = selected_backup.service_version
                    );
                    info!("   File path: {path}", path = selected_backup.file_path);

                    return Ok(Some(selected_backup.id));
                } else {
                    warn!(
                        "❌ Invalid selection, please enter a number between 1-{count}",
                        count = valid_backups.len()
                    );
                }
            }
            Err(_) => {
                warn!(
                    "❌ Invalid input, please enter a number, 'q' (quit) or 'l' (redisplay list)"
                );
            }
        }
    }
}

/// 只恢复数据的智能回滚
async fn run_rollback_with_exculde(
    app: &CliApp,
    backup_id: i64,
    auto_start_service: bool,
    dirs_to_exculde: &[&str],
) -> Result<()> {
    info!("🛡️ Using smart data rollback mode");
    info!("   📁 Will restore: data/, app/ directories");
    info!("   🔧 Will keep: docker-compose.yml, .env and other config files");
    info!(
        "   Directories not restored:{dirs}",
        dirs = format!("{:?}", dirs_to_exculde)
    );

    // 使用 BackupManager 的智能数据恢复功能
    let docker_dir = std::path::Path::new("./docker");
    match app
        .backup_manager
        .restore_data_from_backup_with_exculde(
            backup_id,
            docker_dir,
            auto_start_service,
            dirs_to_exculde,
        )
        .await
    {
        Ok(_) => {
            info!("✅ Smart data restore complete");

            // 设置正确的权限
            let mysql_data_dir = docker_dir.join("data/mysql");
            if mysql_data_dir.exists() {
                #[cfg(unix)]
                {
                    use std::os::unix::fs::PermissionsExt;
                    let permissions = std::fs::Permissions::from_mode(0o775);
                    if let Err(e) = std::fs::set_permissions(&mysql_data_dir, permissions) {
                        warn!(
                            "⚠️ Failed to set MySQL permission: {error}",
                            error = e.to_string()
                        );
                    } else {
                        info!("🔒 MySQL data directory permission set to 775");
                    }
                }
            }

            info!("💡 Data restore info:");
            info!("   ✅ All database data restored");
            info!("   ✅ All application files restored");
            info!("   ✅ Config files kept at latest version");

            if auto_start_service {
                info!("   ✅ Docker services auto-started");
            } else {
                info!("   📝 Docker service start skipped (controlled by parent process)");
            }
        }
        Err(e) => {
            error!("❌ Data restore failed: {error}", error = e.to_string());
            warn!("💡 Suggestions:");
            warn!("   1. Check if backup file exists and is complete");
            warn!("   2. Ensure sufficient disk space");
            warn!("   3. Manually start services: nuwax-cli docker-service start");
            return Err(e);
        }
    }

    Ok(())
}

/// 输出 JSON 格式的备份列表(用于 GUI 集成)
async fn output_backups_as_json(app: &CliApp) -> Result<()> {
    match get_backups_as_json(app).await {
        Ok(response) => {
            // 只输出纯JSON到标准输出,不包含任何日志信息
            match serde_json::to_string(&response) {
                Ok(json_str) => {
                    // 使用 print! 而不是 println! 来避免额外的换行符
                    print!("{json_str}");
                    Ok(())
                }
                Err(e) => {
                    let error_response = JsonBackupListResponse {
                        success: false,
                        backups: vec![],
                        error: Some(
                            t!("backup_cmd.json_serialize_failed", error = e.to_string())
                                .to_string(),
                        ),
                    };
                    if let Ok(error_json) = serde_json::to_string(&error_response) {
                        print!("{error_json}");
                    }
                    Ok(())
                }
            }
        }
        Err(e) => {
            let error_response = JsonBackupListResponse {
                success: false,
                backups: vec![],
                error: Some(e.to_string()),
            };
            if let Ok(error_json) = serde_json::to_string(&error_response) {
                print!("{error_json}");
            }
            Ok(())
        }
    }
}

/// 获取 JSON 格式的备份列表
async fn get_backups_as_json(app: &CliApp) -> Result<JsonBackupListResponse> {
    let backups = app.backup_manager.list_backups().await?;

    let mut json_backups = Vec::new();

    for backup in backups {
        let backup_path = std::path::Path::new(&backup.file_path);
        let file_exists = backup_path.exists();

        // 获取文件大小
        let file_size = if file_exists {
            std::fs::metadata(&backup.file_path).ok().map(|m| m.len())
        } else {
            None
        };

        // 备份类型转换为字符串
        let backup_type_str = match backup.backup_type {
            client_core::database::BackupType::Manual => "Manual",
            client_core::database::BackupType::PreUpgrade => "PreUpgrade",
        };

        json_backups.push(JsonBackupInfo {
            id: backup.id,
            backup_type: backup_type_str.to_string(),
            created_at: backup.created_at.format("%Y-%m-%d %H:%M:%S").to_string(),
            service_version: backup.service_version,
            file_path: backup.file_path,
            file_size,
            file_exists,
        });
    }

    Ok(JsonBackupListResponse {
        success: true,
        backups: json_backups,
        error: None,
    })
}