Skip to main content

nuwax_cli/docker_service/
manager.rs

1use crate::docker_service::architecture::{Architecture, detect_architecture};
2use crate::docker_service::directory_permissions::DirectoryPermissionManager;
3use crate::docker_service::error::{DockerServiceError, DockerServiceResult};
4use crate::docker_service::health_check::{HealthChecker, HealthReport};
5use crate::docker_service::image_loader::{ImageLoader, LoadResult, TagResult};
6use crate::docker_service::port_manager::PortManager;
7use crate::docker_service::script_permissions::ScriptPermissionManager;
8
9use client_core::config::AppConfig;
10use client_core::constants::timeout;
11use client_core::container::DockerManager;
12use std::path::PathBuf;
13use std::sync::Arc;
14use std::time::Duration;
15use tracing::{error, info, warn};
16
17/// Docker 服务管理器
18pub struct DockerServiceManager {
19    #[allow(dead_code)]
20    config: Arc<AppConfig>,
21    docker_manager: Arc<DockerManager>,
22    work_dir: PathBuf,
23    architecture: Architecture,
24    image_loader: ImageLoader,
25    health_checker: HealthChecker,
26    port_manager: PortManager,
27    script_permission_manager: ScriptPermissionManager,
28    directory_permission_manager: DirectoryPermissionManager,
29}
30
31impl DockerServiceManager {
32    /// 创建新的 Docker 服务管理器
33    pub fn new(
34        config: Arc<AppConfig>,
35        docker_manager: Arc<DockerManager>,
36        work_dir: PathBuf,
37    ) -> Self {
38        let architecture = detect_architecture();
39
40        // 由于 DockerManager 实现了 Clone,我们可以安全地克隆它
41        let image_loader = ImageLoader::new(docker_manager.clone(), work_dir.clone())
42            .expect("Failed to create image loader");
43        let health_checker = HealthChecker::new(docker_manager.clone());
44
45        Self {
46            config,
47            docker_manager,
48            work_dir: work_dir.clone(),
49            architecture,
50            image_loader,
51            health_checker,
52            port_manager: PortManager::new(),
53            script_permission_manager: ScriptPermissionManager::new(work_dir.clone()),
54            directory_permission_manager: DirectoryPermissionManager::new(work_dir.clone()),
55        }
56    }
57
58    /// 获取当前系统架构
59    pub fn get_architecture(&self) -> Architecture {
60        self.architecture
61    }
62
63    /// 获取工作目录
64    pub fn get_work_dir(&self) -> &PathBuf {
65        &self.work_dir
66    }
67
68    /// 执行完整的服务部署流程
69    pub async fn deploy_services(&mut self) -> DockerServiceResult<()> {
70        info!("Starting Docker service deployment...");
71
72        // 1. 环境检查
73        self.check_environment().await?;
74
75        // 2. 自动检测 docker-compose.yml 并创建所有挂载目录
76        self.docker_manager
77            .ensure_host_volumes_exist()
78            .await
79            .map_err(|err| DockerServiceError::DirectorySetup(err.to_string()))?;
80
81        // 3. 设置 MySQL 配置文件权限(644)
82        self.directory_permission_manager
83            .ensure_mysql_config_safe()?;
84
85        // 4. 检查和修复脚本权限
86        self.script_permission_manager
87            .check_and_fix_script_permissions()
88            .await?;
89
90        // 5. 加载镜像并获取映射信息
91        let load_result = self.load_images().await?;
92
93        // 6. 使用ducker验证并设置镜像标签(推荐方法)
94        self.setup_image_tags_with_ducker_validation(&load_result.image_mappings)
95            .await?;
96
97        // 7. 启动服务
98        self.start_services().await?;
99
100        info!("Docker service deployment completed");
101        Ok(())
102    }
103
104    /// 环境检查
105    pub async fn check_environment(&self) -> DockerServiceResult<()> {
106        info!("Checking Docker environment...");
107
108        // 跳过 Docker 状态检查,避免高磁盘 IO 问题
109        // Docker 29+ 版本中,docker info、docker --version 等命令会扫描大量数据
110        // 导致部署过程中磁盘 IO 飙升,影响性能
111        // 如果 Docker 未运行,后续操作会自然暴露错误
112        // self.docker_manager
113        //     .check_docker_status()
114        //     .await
115        //     .map_err(|e| DockerServiceError::EnvironmentCheck(e.to_string()))?;
116
117        // 检查工作目录
118        if !self.work_dir.exists() {
119            return Err(DockerServiceError::EnvironmentCheck(format!(
120                "{}",
121                t!(
122                    "docker_service_manager.work_dir_not_exists",
123                    path = self.work_dir.display()
124                )
125            )));
126        }
127
128        // 检查镜像目录
129        let images_dir = self
130            .work_dir
131            .join(client_core::constants::docker::IMAGES_DIR_NAME);
132        if !images_dir.exists() {
133            return Err(DockerServiceError::EnvironmentCheck(format!(
134                "{}",
135                t!(
136                    "docker_service_manager.images_dir_not_exists",
137                    path = images_dir.display()
138                )
139            )));
140        }
141
142        // 检查 docker-compose.yml
143        let compose_file = self
144            .work_dir
145            .join(client_core::constants::docker::COMPOSE_FILE_NAME);
146        if !compose_file.exists() {
147            return Err(DockerServiceError::EnvironmentCheck(format!(
148                "{}",
149                t!(
150                    "docker_service_manager.compose_file_not_exists",
151                    path = compose_file.display()
152                )
153            )));
154        }
155
156        // 环境信息提示(新增)
157        let runtime_env = self.docker_manager.get_runtime_environment();
158        if runtime_env.needs_special_handling() {
159            info!(
160                "   Environment: {env} - special handling required",
161                env = runtime_env.summary()
162            );
163        } else {
164            info!("   Environment: {env}", env = runtime_env.summary());
165        }
166
167        info!("Environment check passed");
168        Ok(())
169    }
170
171    /// 检查并创建 docker-compose.yml 中所有挂载的目录
172    pub async fn ensure_compose_mount_directories(&self) -> DockerServiceResult<()> {
173        info!("🔍 Checking and creating mount directories from docker-compose.yml...");
174
175        // 使用新的环境检测机制
176        let runtime_env = self.docker_manager.get_runtime_environment();
177
178        if runtime_env.needs_special_handling() {
179            info!("⚠️ Windows Podman Desktop environment detected");
180            info!("   Podman Desktop does not auto-create mount directories, creating proactively");
181        }
182
183        // 设置必要目录
184        self.docker_manager
185            .ensure_host_volumes_exist()
186            .await
187            .map_err(|err| DockerServiceError::DirectorySetup(err.to_string()))?;
188
189        info!("✅ Mount directory check completed");
190        Ok(())
191    }
192
193    /// 加载 Docker 镜像
194    pub async fn load_images(&self) -> DockerServiceResult<LoadResult> {
195        info!("Starting Docker image loading...");
196        let result = self.image_loader.load_all_images().await?;
197
198        if !result.is_all_successful() {
199            warn!(
200                "Some image loading failed: success {success}, failed {failed}",
201                success = result.success_count(),
202                failed = result.failure_count()
203            );
204        }
205
206        Ok(result)
207    }
208
209    /// 基于实际镜像映射设置标签
210    pub async fn setup_image_tags_with_mappings(
211        &self,
212        image_mappings: &[(String, String)],
213    ) -> DockerServiceResult<TagResult> {
214        info!("Starting image tag setup...");
215        let result = self
216            .image_loader
217            .setup_image_tags_with_mappings(image_mappings)
218            .await?;
219
220        if !result.is_all_successful() {
221            warn!(
222                "Some tag setup failed: success {success}, failed {failed}",
223                success = result.success_count(),
224                failed = result.failure_count()
225            );
226        }
227
228        Ok(result)
229    }
230
231    /// 基于 ducker 验证镜像后再设置标签(推荐使用)
232    pub async fn setup_image_tags_with_ducker_validation(
233        &self,
234        image_mappings: &[(String, String)],
235    ) -> DockerServiceResult<TagResult> {
236        info!("Starting validated image tag setup...");
237        let result = self
238            .image_loader
239            .setup_image_tags_with_validation(image_mappings)
240            .await?;
241
242        if !result.is_all_successful() {
243            warn!(
244                "Some tag setup failed: success {success}, failed {failed}",
245                success = result.success_count(),
246                failed = result.failure_count()
247            );
248        }
249
250        Ok(result)
251    }
252
253    /// 使用 ducker 列出当前系统中的所有镜像
254    pub async fn list_docker_images_with_ducker(&self) -> DockerServiceResult<Vec<String>> {
255        info!("Using ducker to list images...");
256        self.image_loader.list_images_with_ducker().await
257    }
258
259    /// 启动所有服务
260    pub async fn start_services(&mut self) -> DockerServiceResult<()> {
261        info!("Starting Docker Compose services...");
262
263        // 1. 检查和修复脚本权限
264        self.script_permission_manager
265            .check_and_fix_script_permissions()
266            .await?;
267
268        // 2. 自动检测 docker-compose.yml 并创建所有挂载目录
269        self.docker_manager
270            .ensure_host_volumes_exist()
271            .await
272            .map_err(|err| DockerServiceError::DirectorySetup(err.to_string()))?;
273
274        // 3. 设置 MySQL 配置文件权限(644)
275        self.directory_permission_manager
276            .ensure_mysql_config_safe()?;
277
278        // 3. 检查端口冲突
279        self.check_port_conflicts().await?;
280
281        // 直接使用已配置的 DockerManager,无需切换目录
282        let result = self.docker_manager.start_services().await;
283
284        match result {
285            Ok(_) => {
286                // 等待服务就绪
287                info!("Waiting for services to become ready...");
288                let check_interval = Duration::from_secs(timeout::HEALTH_CHECK_INTERVAL);
289
290                // 提前检查MySQL状态,如果发现问题立即修复
291                // tokio::time::sleep(Duration::from_secs(10)).await; // 等待10秒让容器启动
292
293                // // 检查并修复MySQL配置文件权限
294                // if let Err(e) = self
295                //     .directory_permission_manager
296                match self
297                    .health_checker
298                    .wait_for_services_ready(check_interval)
299                    .await
300                {
301                    Ok(report) => {
302                        info!("All services started successfully!");
303                        self.print_service_status(&report).await;
304                    }
305                    Err(e) => {
306                        warn!(
307                            "Wait for services failed or timed out: {error}",
308                            error = e.to_string()
309                        );
310
311                        // 即使超时也显示当前状态
312                        if let Ok(report) = self.health_checker.health_check().await {
313                            self.print_service_status_with_failures(&report).await;
314                        }
315                    }
316                }
317
318                Ok(())
319            }
320            Err(e) => {
321                error!("Docker Compose start command failed, checking container status...");
322                error!("Error detail: {error}", error = format!("{e:?}"));
323
324                // 基于 ducker 思路:即使 compose 失败,也要检查是否有部分容器成功启动
325                match self.health_checker.health_check().await {
326                    Ok(report) => {
327                        if report.get_running_count() > 0 {
328                            info!(
329                                "🔍 {running}/{total} containers are running, entering health-check phase",
330                                running = report.get_running_count(),
331                                total = report.get_total_count()
332                            );
333
334                            // 有部分容器成功,进入健康检查阶段
335                            let check_interval =
336                                Duration::from_secs(timeout::HEALTH_CHECK_INTERVAL);
337
338                            match self
339                                .health_checker
340                                .wait_for_services_ready(check_interval)
341                                .await
342                            {
343                                Ok(final_report) => {
344                                    info!("🎉 Some services eventually started successfully!");
345
346                                    // // 执行容器启动后权限维护
347                                    // if let Err(e) = self
348                                    //     .directory_permission_manager
349                                    //     .post_container_start_maintenance()
350                                    //     .await
351                                    // {
352                                    //     warn!("容器启动后权限维护失败: {}", e);
353                                    // }
354
355                                    self.print_service_status(&final_report).await;
356                                    return Ok(()); // 部分成功,返回 Ok
357                                }
358                                Err(_health_error) => {
359                                    warn!(
360                                        "⏰ Health check timed out, but some services are still running"
361                                    );
362
363                                    // // 检查MySQL容器状态,如果失败尝试权限修复
364                                    // if (self.check_and_fix_mysql_if_failed(&report).await).is_err()
365                                    // {
366                                    //     warn!("MySQL权限修复失败,但继续执行");
367                                    // }
368
369                                    // // 即使超时也执行权限维护
370                                    // if let Err(e) = self
371                                    //     .directory_permission_manager
372                                    //     .post_container_start_maintenance()
373                                    //     .await
374                                    // {
375                                    //     warn!("容器启动后权限维护失败: {}", e);
376                                    // }
377
378                                    self.print_service_status_with_failures(&report).await;
379                                    info!(
380                                        "You can inspect logs: nuwax-cli docker-service logs [service]"
381                                    );
382                                    return Ok(()); // 部分成功,返回 Ok
383                                }
384                            }
385                        } else {
386                            error!("No running containers found");
387                            self.print_detailed_error_analysis(&report, &e.to_string())
388                                .await;
389                        }
390                    }
391                    Err(e) => {
392                        error!("❌ Failed to get container status details");
393                        error!("Error detail: {error}", error = format!("{e:?}"));
394                    }
395                }
396
397                Err(DockerServiceError::ServiceManagement(e.to_string()))
398            }
399        }
400    }
401
402    /// 停止所有服务
403    pub async fn stop_services(&self) -> DockerServiceResult<()> {
404        info!("Stopping Docker Compose services...");
405
406        // 直接使用已配置的 DockerManager,无需切换目录
407        let result = self.docker_manager.stop_services().await;
408
409        match result {
410            Ok(_) => {
411                info!("Services stopped successfully");
412                Ok(())
413            }
414            Err(e) => {
415                error!("Failed to stop services: {error}", error = e.to_string());
416                Err(DockerServiceError::ServiceManagement(e.to_string()))
417            }
418        }
419    }
420
421    /// 重启所有服务
422    pub async fn restart_services(&mut self) -> DockerServiceResult<()> {
423        info!("Restarting Docker Compose services...");
424
425        // 使用 compose restart 语义,避免 down+up 触发重新创建和潜在拉取行为
426        let result = self.docker_manager.restart_services().await;
427
428        match result {
429            Ok(_) => {
430                info!("Waiting for services to become ready after restart...");
431                let check_interval = Duration::from_secs(timeout::HEALTH_CHECK_INTERVAL);
432
433                match self
434                    .health_checker
435                    .wait_for_services_ready(check_interval)
436                    .await
437                {
438                    Ok(report) => {
439                        info!("All services restarted successfully!");
440                        self.print_service_status(&report).await;
441                    }
442                    Err(e) => {
443                        warn!(
444                            "Wait for services after restart failed or timed out: {error}",
445                            error = e.to_string()
446                        );
447                        if let Ok(report) = self.health_checker.health_check().await {
448                            self.print_service_status_with_failures(&report).await;
449                        }
450                    }
451                }
452
453                Ok(())
454            }
455            Err(e) => {
456                error!("Failed to restart services: {error}", error = e.to_string());
457                Err(DockerServiceError::ServiceManagement(e.to_string()))
458            }
459        }
460    }
461
462    /// 重启单个容器
463    pub async fn restart_container(&self, container_name: &str) -> DockerServiceResult<()> {
464        info!("Restarting container: {name}", name = container_name);
465
466        // 直接使用已配置的 DockerManager,无需切换目录
467        let result = self.docker_manager.restart_service(container_name).await;
468
469        match result {
470            Ok(_) => {
471                info!(
472                    "Container {name} restarted successfully",
473                    name = container_name
474                );
475                Ok(())
476            }
477            Err(e) => {
478                error!(
479                    "Container {name} restart failed: {error}",
480                    name = container_name,
481                    error = e.to_string()
482                );
483                Err(DockerServiceError::ServiceManagement(e.to_string()))
484            }
485        }
486    }
487
488    /// 执行健康检查
489    pub async fn health_check(&self) -> DockerServiceResult<HealthReport> {
490        self.health_checker.health_check().await
491    }
492
493    /// 获取服务状态摘要
494    pub async fn get_status_summary(&self) -> DockerServiceResult<String> {
495        self.health_checker.get_status_summary().await
496    }
497
498    /// 打印服务状态信息
499    async fn print_service_status(&self, report: &HealthReport) {
500        info!("=== Service Status Overview ===");
501        info!(
502            "Overall status: {status}",
503            status = report.finalize().display_name()
504        );
505        info!(
506            "Running containers: {running}/{total}",
507            running = report.get_running_count(),
508            total = report.get_total_count()
509        );
510
511        if !report.containers.is_empty() {
512            info!("Container details:");
513            for container in &report.containers {
514                info!(
515                    "  • {name} - {status} ({image})",
516                    name = container.name,
517                    status = container.status.display_name(),
518                    image = container.image
519                );
520            }
521        }
522
523        if !report.errors.is_empty() {
524            warn!("Errors:");
525            for error in &report.errors {
526                warn!("  • {error}", error = error);
527            }
528        }
529
530        // 显示访问信息
531        if report.finalize().is_healthy() {
532            info!("=== Service Access Info ===");
533            use client_core::constants::docker::ports;
534            info!(
535                "• Frontend: http://localhost:{port}",
536                port = ports::DEFAULT_FRONTEND_PORT
537            );
538            info!(
539                "• Backend API: http://localhost:{port}",
540                port = ports::DEFAULT_BACKEND_PORT
541            );
542            info!("• Service management complete. Ready to use.");
543        }
544    }
545
546    /// 打印包含失败信息的服务状态
547    async fn print_service_status_with_failures(&self, report: &HealthReport) {
548        info!("=== Service Status Details ===");
549        info!(
550            "Overall status: {status}",
551            status = report.finalize().display_name()
552        );
553        info!(
554            "Health summary: {running}/{total} containers healthy",
555            running = report.get_running_count(),
556            total = report.get_total_count()
557        );
558
559        // 分类显示容器状态
560        let running_containers: Vec<_> = report
561            .containers
562            .iter()
563            .filter(|c| c.status.is_healthy())
564            .collect();
565        let failed_containers: Vec<_> = report
566            .containers
567            .iter()
568            .filter(|c| !c.status.is_healthy() && !c.status.is_transitioning())
569            .collect();
570        let starting_containers: Vec<_> = report
571            .containers
572            .iter()
573            .filter(|c| c.status.is_transitioning())
574            .collect();
575
576        if !running_containers.is_empty() {
577            info!("✅ Running containers:");
578            for container in running_containers {
579                info!(
580                    "  • {name} ({image})",
581                    name = container.name,
582                    image = container.image
583                );
584            }
585        }
586
587        if !starting_containers.is_empty() {
588            warn!("🔄 Starting containers:");
589            for container in starting_containers {
590                warn!(
591                    "  • {name} - {status}",
592                    name = container.name,
593                    status = container.status.display_name()
594                );
595            }
596        }
597
598        if !failed_containers.is_empty() {
599            error!("❌ Failed containers:");
600            for container in failed_containers {
601                error!(
602                    "  • {name} - {status} ({image})",
603                    name = container.name,
604                    status = container.status.display_name(),
605                    image = container.image
606                );
607
608                // 提供针对性的建议
609                self.print_container_troubleshooting(&container.name, &container.image)
610                    .await;
611            }
612        }
613
614        // 显示部分成功时的访问信息
615        if report.get_running_count() > 0 {
616            info!("=== Available Service Access Info ===");
617            use client_core::constants::docker::ports;
618
619            let has_frontend = report
620                .containers
621                .iter()
622                .any(|c| c.status.is_healthy() && c.name.contains("frontend"));
623            let has_backend = report
624                .containers
625                .iter()
626                .any(|c| c.status.is_healthy() && c.name.contains("backend"));
627
628            if has_frontend {
629                info!(
630                    "• Frontend: http://localhost:{port}",
631                    port = ports::DEFAULT_FRONTEND_PORT
632                );
633            }
634            if has_backend {
635                info!(
636                    "• Backend API: http://localhost:{port}",
637                    port = ports::DEFAULT_BACKEND_PORT
638                );
639            }
640            let failed_count = report
641                .containers
642                .iter()
643                .filter(|c| !c.status.is_healthy() && !c.status.is_transitioning())
644                .count();
645
646            if failed_count == 0 {
647                info!("• All services are running normally!");
648            } else {
649                warn!("• Some services failed, but available services remain usable");
650            }
651        }
652    }
653
654    /// 打印详细的错误分析
655    async fn print_detailed_error_analysis(&self, report: &HealthReport, original_error: &str) {
656        error!("=== Startup Failure Analysis ===");
657
658        // 检查是否有具体的容器失败
659        let failed_containers: Vec<_> = report
660            .containers
661            .iter()
662            .filter(|c| !c.status.is_healthy())
663            .collect();
664
665        if failed_containers.is_empty() {
666            error!("❌ Failed to get container status details");
667            error!("❌ Original error: {error}", error = original_error);
668            return;
669        }
670
671        error!(
672            "❌ Failed containers: {failed}/{total}",
673            failed = failed_containers.len(),
674            total = report.get_total_count()
675        );
676
677        for container in failed_containers {
678            error!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
679            error!("Container: {name}", name = container.name);
680            error!("Image: {image}", image = container.image);
681            error!(
682                "Current status: {status}",
683                status = container.status.display_name()
684            );
685
686            // 提供针对性的故障排除建议
687            self.print_container_troubleshooting(&container.name, &container.image)
688                .await;
689        }
690
691        // 分析原始错误中的关键信息
692        self.analyze_docker_error(original_error).await;
693    }
694
695    /// 打印容器故障排除建议
696    async fn print_container_troubleshooting(&self, container_name: &str, image_name: &str) {
697        if container_name.contains("video-analysis-worker") {
698            warn!("💡 Analysis:");
699            warn!(
700                "  - This container requires NVIDIA GPU support, which may be unavailable on this system"
701            );
702            warn!("  - Architecture mismatch detected (amd64 vs arm64)");
703            warn!("💡 Suggested fix:");
704            warn!("  - On Mac ARM64, disable this service or use an ARM64 image");
705            warn!("  - Comment out this service in docker-compose.yml");
706            warn!("  - Or update image version in .env to an ARM64 variant");
707        } else if image_name.contains("amd64") {
708            warn!("💡 Analysis:");
709            warn!("  - Architecture mismatch: image is amd64 but system is arm64");
710            warn!("💡 Suggested fix:");
711            warn!("  - Use an arm64 image");
712            warn!("  - Or add --platform linux/amd64 when running container");
713        } else if container_name.contains("mysql") || container_name.contains("redis") {
714            warn!("💡 Analysis:");
715            warn!(
716                "  - Database startup failed, likely due to port conflict or data directory permissions"
717            );
718            warn!("💡 Suggested fix:");
719            warn!("  - Check whether port 3306(MySQL) or 6379(Redis) is occupied");
720            warn!("  - Check directory permissions: ./data/mysql or ./data/redis");
721        } else if container_name.contains("backend") || container_name.contains("entrypoint") {
722            warn!("💡 Analysis:");
723            warn!("  - Container startup script may be missing execute permission");
724            warn!("💡 Suggested fix:");
725            warn!("  - Check permissions for scripts like docker-entrypoint.sh");
726            warn!("  - Run: chmod +x config/docker-entrypoint.sh");
727            warn!(
728                "  - View logs: docker-compose logs {name}",
729                name = container_name
730            );
731        } else {
732            warn!("💡 Suggestion:");
733            warn!(
734                "  - View logs: docker-compose logs {name}",
735                name = container_name
736            );
737            warn!("  - Verify images were pulled successfully");
738            warn!("  - Verify environment variables");
739        }
740    }
741
742    /// 分析 Docker 错误信息
743    async fn analyze_docker_error(&self, error_message: &str) {
744        error!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
745        error!("🔍 Error analysis:");
746
747        let mut has_issues = false;
748
749        if error_message.contains("nvidia") {
750            error!("  ❌ NVIDIA GPU driver issue");
751            error!("  💡 NVIDIA GPU may be unsupported or driver not installed");
752            error!("  💡 Consider disabling services that require GPU");
753            has_issues = true;
754        }
755
756        if error_message.contains("platform")
757            && error_message.contains("amd64")
758            && error_message.contains("arm64")
759        {
760            error!("  ❌ Container architecture mismatch");
761            error!("  💡 amd64 image cannot run natively on arm64 system");
762            error!("  💡 Use image that matches your architecture");
763            has_issues = true;
764        }
765
766        if error_message.contains("Permission denied") && error_message.contains("entrypoint") {
767            error!("  ❌ Script permission issue");
768            error!("  💡 Startup script lacks execute permission");
769            error!("  💡 Add execute permission with chmod +x");
770            has_issues = true;
771        }
772
773        if error_message.contains("port") || error_message.contains("bind") {
774            error!("  ❌ Port bind failed");
775            error!("  💡 There may be a port conflict");
776            error!("  💡 Check current port occupancy");
777            has_issues = true;
778        }
779
780        if !has_issues {
781            error!("  ❓ Unrecognized error type, key lines:");
782            // 提取关键的错误行
783            let key_lines: Vec<&str> = error_message
784                .lines()
785                .filter(|line| {
786                    line.contains("Error")
787                        || line.contains("failed")
788                        || line.contains("denied")
789                        || line.contains("not found")
790                        || line.contains("connection")
791                        || line.trim().starts_with("Container")
792                })
793                .take(5)
794                .collect();
795
796            if !key_lines.is_empty() {
797                for line in key_lines {
798                    error!("     {line}", line = line.trim());
799                }
800            } else {
801                // 显示前几行作为备选
802                for line in error_message.lines().take(3) {
803                    if !line.trim().is_empty() {
804                        error!("     {line}", line = line.trim());
805                    }
806                }
807            }
808        }
809
810        error!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
811    }
812
813    /// 检查端口冲突
814    async fn check_port_conflicts(&mut self) -> DockerServiceResult<()> {
815        let compose_file = self.docker_manager.get_compose_file();
816        let env_file = self.docker_manager.get_env_file();
817        if !compose_file.exists() {
818            warn!("docker-compose.yml not found, skipping port conflict check");
819            return Ok(());
820        }
821
822        info!("🔍 Starting smart port-conflict check...");
823
824        match self
825            .port_manager
826            .smart_check_compose_port_conflicts(compose_file, env_file)
827            .await
828        {
829            Ok(report) => {
830                if report.has_conflicts {
831                    warn!("⚠️ Port conflict detected, proceeding with smart handling");
832                    self.port_manager.print_smart_conflict_report(&report);
833
834                    // 对于Docker容器启动,我们采用更宽松的策略
835                    // Docker会在实际绑定时处理端口冲突,这里只是警告
836                    warn!("💡 Note: Docker may handle port binding automatically");
837                    warn!(
838                        "   - If occupied by related service, container may reuse existing binding"
839                    );
840                    warn!("   - If occupied by unrelated service, startup may fail");
841                    warn!("   - Check startup result and resolve conflicts manually if needed");
842                } else {
843                    info!("✅ Port check passed, no conflict found");
844                    if report.total_checked > 0 {
845                        info!(
846                            "Checked {total} port mappings in total",
847                            total = report.total_checked
848                        );
849                    }
850                }
851            }
852            Err(e) => {
853                warn!(
854                    "Port check failed: {error}, continuing startup",
855                    error = e.to_string()
856                );
857                // 端口检查失败不应该阻止服务启动,只是警告
858            }
859        }
860
861        Ok(())
862    }
863}