465 lines
12 KiB
Go
465 lines
12 KiB
Go
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)
|
||
}
|
||
}
|