package cmd import ( "os" "os/signal" "syscall" "time" "github.com/evil7/hostsync/config" "github.com/evil7/hostsync/core" "github.com/evil7/hostsync/service" "github.com/evil7/hostsync/utils" "github.com/spf13/cobra" ) // serviceCmd 系统服务命令 var serviceCmd = &cobra.Command{ Use: "service", Short: "系统服务管理", Long: `管理HostSync系统服务,支持安装、卸载、启动、停止等操作。 支持的操作系统: - Windows (Windows Service) - Linux (systemd) - macOS (LaunchD) 示例: hostsync service install # 安装系统服务 hostsync service start # 启动服务 hostsync service stop # 停止服务 hostsync service status # 查看服务状态 hostsync service run # 以服务模式运行(通常由系统调用)`, } // serviceInstallCmd 安装服务命令 var serviceInstallCmd = &cobra.Command{ Use: "install", Short: "安装系统服务", Long: `将HostSync安装为系统服务,支持开机自启动。`, Run: runServiceInstall, } // serviceUninstallCmd 卸载服务命令 var serviceUninstallCmd = &cobra.Command{ Use: "uninstall", Short: "卸载系统服务", Long: `从系统中卸载HostSync服务。`, Run: runServiceUninstall, } // serviceStartCmd 启动服务命令 var serviceStartCmd = &cobra.Command{ Use: "start", Short: "启动系统服务", Long: `启动HostSync系统服务。`, Run: runServiceStart, } // serviceStopCmd 停止服务命令 var serviceStopCmd = &cobra.Command{ Use: "stop", Short: "停止系统服务", Long: `停止HostSync系统服务。`, Run: runServiceStop, } // serviceRestartCmd 重启服务命令 var serviceRestartCmd = &cobra.Command{ Use: "restart", Short: "重启系统服务", Long: `重启HostSync系统服务。`, Run: runServiceRestart, } // serviceStatusCmd 查看服务状态命令 var serviceStatusCmd = &cobra.Command{ Use: "status", Short: "查看服务状态", Long: `查看HostSync系统服务当前状态。`, Run: runServiceStatus, } // serviceRunCmd 运行服务命令 var serviceRunCmd = &cobra.Command{ Use: "run", Short: "以服务模式运行", Long: `以服务模式运行HostSync,通常由系统服务管理器调用。`, Run: runServiceRun, } var ( serviceConfigDir string ) func init() { serviceRunCmd.Flags().StringVarP(&serviceConfigDir, "config", "c", "", "指定配置文件目录路径") serviceCmd.AddCommand(serviceInstallCmd) serviceCmd.AddCommand(serviceUninstallCmd) serviceCmd.AddCommand(serviceStartCmd) serviceCmd.AddCommand(serviceStopCmd) serviceCmd.AddCommand(serviceRestartCmd) serviceCmd.AddCommand(serviceStatusCmd) serviceCmd.AddCommand(serviceRunCmd) } func runServiceInstall(cmd *cobra.Command, args []string) { utils.CheckAdmin() utils.LogInfo("开始安装HostSync系统服务") // 获取当前执行文件路径 execPath, err := os.Executable() if err != nil { utils.LogError("获取执行文件路径失败: %v", err) os.Exit(1) } utils.LogInfo("执行文件路径: %s", execPath) serviceManager := service.NewServiceManager() if err := serviceManager.Install(execPath); err != nil { utils.LogError("安装服务失败: %v", err) os.Exit(1) } utils.LogSuccess("服务安装成功") utils.LogInfo("💡 可以使用以下命令管理服务:") utils.LogInfo(" hostsync service start # 启动服务") utils.LogInfo(" hostsync service stop # 停止服务") utils.LogInfo(" hostsync service status # 查看状态") } func runServiceUninstall(cmd *cobra.Command, args []string) { utils.CheckAdmin() utils.LogInfo("开始卸载HostSync系统服务") serviceManager := service.NewServiceManager() if err := serviceManager.Uninstall(); err != nil { utils.LogError("卸载服务失败: %v", err) os.Exit(1) } utils.LogSuccess("服务卸载成功") } func runServiceStart(cmd *cobra.Command, args []string) { utils.CheckAdmin() utils.LogInfo("启动HostSync系统服务") serviceManager := service.NewServiceManager() if err := serviceManager.Start(); err != nil { utils.LogError("启动服务失败: %v", err) os.Exit(1) } utils.LogSuccess("服务启动成功") } func runServiceStop(cmd *cobra.Command, args []string) { utils.CheckAdmin() utils.LogInfo("停止HostSync系统服务") serviceManager := service.NewServiceManager() if err := serviceManager.Stop(); err != nil { utils.LogError("停止服务失败: %v", err) os.Exit(1) } utils.LogSuccess("服务停止成功") } func runServiceRestart(cmd *cobra.Command, args []string) { utils.CheckAdmin() utils.LogInfo("重启HostSync系统服务") serviceManager := service.NewServiceManager() if err := serviceManager.Restart(); err != nil { utils.LogError("重启服务失败: %v", err) os.Exit(1) } utils.LogSuccess("服务重启成功") } func runServiceStatus(cmd *cobra.Command, args []string) { serviceManager := service.NewServiceManager() status, err := serviceManager.Status() if err != nil { utils.LogError("查询服务状态失败: %v", err) os.Exit(1) } // 获取状态描述 statusIcon := "" statusText := "" switch status { case service.StatusRunning: statusIcon = "🟢" statusText = "运行中" case service.StatusStopped: statusIcon = "🔴" statusText = "已停止" case service.StatusNotInstalled: statusIcon = "⚪" statusText = "未安装" default: statusIcon = "❓" statusText = "未知" } // 显示基本信息 utils.LogResult("服务状态: %s %s\n", statusIcon, statusText) // 如果服务正在运行,显示详细信息 if status == service.StatusRunning { // 显示运行时信息 if execPath, err := os.Executable(); err == nil { utils.LogResult("可执行文件: %s\n", execPath) } // 显示进程信息 if pid := getServicePID(); pid > 0 { utils.LogResult("进程ID: %d\n", pid) } // 显示配置信息 if config.AppConfig != nil { utils.LogResult("日志级别: %s\n", config.AppConfig.LogLevel) if config.AppConfig.LogPath != "" { utils.LogResult("日志路径: %s\n", config.AppConfig.LogPath) } } // 显示定时任务信息 showCronJobsStatusSimple() } // 如果服务未运行,提示如何启动 if status != service.StatusRunning { utils.LogResult("\n提示:\n") if status == service.StatusNotInstalled { utils.LogResult(" 1. 运行 'hostsync service install' 安装服务\n") utils.LogResult(" 2. 运行 'hostsync service start' 启动服务\n") } else { utils.LogResult(" 运行 'hostsync service start' 启动服务\n") } } } func runServiceRun(cmd *cobra.Command, args []string) { // 添加全局 panic 恢复 defer func() { if r := recover(); r != nil { utils.LogError("服务出现严重错误: %v", r) } }() // 如果指定了配置目录,则使用指定的目录初始化配置 if serviceConfigDir != "" { utils.LogInfo("使用指定配置目录: %s", serviceConfigDir) config.InitWithConfigDir(serviceConfigDir) } else { // 使用默认配置初始化 config.Init() } // 检测运行环境 utils.LogDebug("检测运行环境...") if isSystemdService := os.Getenv("INVOCATION_ID") != ""; isSystemdService { utils.LogInfo("检测到 systemd 服务环境") } // 尝试作为系统服务运行 if handled, err := tryRunAsSystemService(); err != nil { utils.LogError("系统服务运行失败: %v", err) os.Exit(1) } else if handled { // 已作为系统服务运行,直接返回 utils.LogInfo("已由系统服务管理器处理") return } // 作为普通进程运行(开发测试模式) utils.LogInfo("启动HostSync服务模式...") // 启动定时任务管理器 if err := startCronService(); err != nil { utils.LogError("定时任务服务启动失败: %v", err) os.Exit(1) } utils.LogSuccess("HostSync服务已启动") utils.LogInfo("定时任务管理器正在运行") // 等待中断信号 c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) // 定期输出运行状态 (30秒) statusTicker := time.NewTicker(30 * time.Second) defer statusTicker.Stop() // 定期重新同步定时任务配置 (5分钟) syncTicker := time.NewTicker(5 * time.Minute) defer syncTicker.Stop() for { select { case <-c: utils.LogInfo("\n收到停止信号,正在关闭服务...") stopCronService() utils.LogSuccess("服务已安全关闭") return case <-statusTicker.C: utils.LogInfo("%s - HostSync服务运行中", time.Now().Format("2006-01-02 15:04:05")) case <-syncTicker.C: // 定期重新同步定时任务配置 if globalCronManager != nil { utils.LogDebug("开始定期同步定时任务配置...") if err := globalCronManager.ReloadFromHosts(); err != nil { utils.LogError("定时任务配置同步失败: %v", err) } else { utils.LogDebug("定时任务配置同步完成") } } } } } // 全局定时任务管理器 var globalCronManager *core.CronManager // startCronService 启动定时任务服务 func startCronService() error { defer func() { if r := recover(); r != nil { utils.LogError("启动定时任务服务时发生错误: %v", r) } }() // 初始化配置 config.Init() // 创建定时任务管理器 globalCronManager = core.NewCronManager() globalCronManager.Start() // 加载hosts文件中的定时任务 hostsManager := core.NewHostsManager() if err := hostsManager.Load(); err != nil { utils.LogError("加载hosts文件失败: %v", err) // 不立即返回错误,允许服务在没有hosts文件的情况下运行 utils.LogInfo("hosts文件加载失败,服务将在空任务状态下运行") utils.LogSuccess("定时任务管理器已启动,当前无任务") return nil } if err := globalCronManager.LoadFromHosts(hostsManager); err != nil { utils.LogError("加载定时任务失败: %v", err) utils.LogInfo("定时任务加载失败,服务将在空任务状态下运行") utils.LogSuccess("定时任务管理器已启动,当前无任务") return nil } utils.LogSuccess("定时任务管理器已启动,共加载 %d 个任务", len(globalCronManager.ListJobs())) return nil } // stopCronService 停止定时任务服务 func stopCronService() { if globalCronManager != nil { globalCronManager.Stop() utils.LogSuccess("定时任务管理器已停止") } } // 辅助函数:截断字符串到指定长度 func truncateString(s string, maxLen int) string { if len(s) <= maxLen { return s } if maxLen <= 3 { return s[:maxLen] } return s[:maxLen-3] + "..." } // 获取服务进程ID func getServicePID() int { // 这里可以通过系统调用获取服务的PID // 简化实现,返回0表示无法获取 return 0 } // 显示定时任务状态 func showCronJobsStatus() { // 尝试连接到运行中的服务获取定时任务信息 hm := core.NewHostsManager() if err := hm.Load(); err != nil { utils.LogResult("│ 定时任务: 无法加载配置%-36s│\n", "") return } // 统计定时任务 cronCount := 0 var cronBlocks []string for name, block := range hm.Blocks { if block.CronJob != "" { cronCount++ cronBlocks = append(cronBlocks, name) } } utils.LogResult("│ 定时任务: %d 个活跃任务%-36s│\n", cronCount, "") // 显示前3个任务 for i, blockName := range cronBlocks { if i >= 3 { utils.LogResult("│ ... 还有 %d 个任务%-38s│\n", cronCount-3, "") break } block := hm.Blocks[blockName] lastUpdate := "从未" if !block.UpdateAt.IsZero() { lastUpdate = block.UpdateAt.Format("01-02 15:04") } utils.LogResult("│ %s: %s (最后: %s)%*s│\n", blockName, block.CronJob, lastUpdate, 47-len(blockName)-len(block.CronJob)-len(lastUpdate), "") } } // 显示定时任务状态(简化版本) func showCronJobsStatusSimple() { // 尝试连接到运行中的服务获取定时任务信息 hm := core.NewHostsManager() if err := hm.Load(); err != nil { utils.LogResult("定时任务: 无法加载配置\n") return } // 统计定时任务 cronCount := 0 var cronBlocks []string for name, block := range hm.Blocks { if block.CronJob != "" { cronCount++ cronBlocks = append(cronBlocks, name) } } utils.LogResult("定时任务: %d 个活跃任务\n", cronCount) // 显示前3个任务 for i, blockName := range cronBlocks { if i >= 3 { utils.LogResult(" ... 还有 %d 个任务\n", cronCount-3) break } block := hm.Blocks[blockName] lastUpdate := "从未" if !block.UpdateAt.IsZero() { lastUpdate = block.UpdateAt.Format("01-02 15:04") } utils.LogResult(" %s: %s (最后: %s)\n", blockName, block.CronJob, lastUpdate) } }