//go:build windows // +build windows package service import ( "fmt" "time" "github.com/evil7/hostsync/config" "github.com/evil7/hostsync/core" "github.com/evil7/hostsync/utils" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/mgr" ) // NewServiceManager 创建服务管理器 (Windows) func NewServiceManager() ServiceManager { return &WindowsServiceManager{} } // WindowsServiceManager Windows服务管理器 type WindowsServiceManager struct{} // WindowsService 实现 Windows 服务接口 type WindowsService struct { stopChan chan struct{} cronManager *core.CronManager } func (ws *WindowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue changes <- svc.Status{State: svc.StartPending} // 启动服务逻辑 if err := ws.startServiceLogic(); err != nil { utils.LogError("启动服务逻辑失败: %v", err) return true, 1 } changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} for c := range r { switch c.Cmd { case svc.Interrogate: changes <- c.CurrentStatus case svc.Stop, svc.Shutdown: changes <- svc.Status{State: svc.StopPending} ws.stopServiceLogic() return false, 0 case svc.Pause: changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} case svc.Continue: changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} default: utils.LogWarning("unexpected control request #%d", c) } } return false, 0 } func (ws *WindowsService) startServiceLogic() error { utils.LogInfo("启动 HostSync 定时任务服务...") // 初始化配置 config.Init() // 创建定时任务管理器 ws.cronManager = core.NewCronManager() ws.cronManager.Start() // 加载hosts文件中的定时任务 hostsManager := core.NewHostsManager() if err := hostsManager.Load(); err != nil { return fmt.Errorf("加载hosts文件失败: %v", err) } if err := ws.cronManager.LoadFromHosts(hostsManager); err != nil { return fmt.Errorf("加载定时任务失败: %v", err) } utils.LogSuccess("HostSync 定时任务服务已启动,共加载 %d 个任务", len(ws.cronManager.ListJobs())) // 启动状态监控 go func() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for { select { case <-ws.stopChan: return case <-ticker.C: utils.LogDebug("HostSync 定时任务服务运行正常") } } }() return nil } func (ws *WindowsService) stopServiceLogic() { utils.LogInfo("正在停止 HostSync 定时任务服务...") if ws.cronManager != nil { ws.cronManager.Stop() utils.LogInfo("定时任务管理器已停止") } close(ws.stopChan) utils.LogSuccess("HostSync 定时任务服务已停止") } // RunAsWindowsService 作为 Windows 服务运行 func RunAsWindowsService() error { service := &WindowsService{ stopChan: make(chan struct{}), } return svc.Run(getServiceName(), service) } func (m *WindowsServiceManager) Install(execPath string) error { if err := ensureLogDir(); err != nil { return fmt.Errorf("创建日志目录失败: %v", err) } manager, err := mgr.Connect() if err != nil { return fmt.Errorf("连接服务管理器失败: %v", err) } defer manager.Disconnect() serviceName := getServiceName() displayName := getServiceDisplayName() description := getServiceDescription() // 检查服务是否已存在 service, err := manager.OpenService(serviceName) if err == nil { service.Close() return fmt.Errorf("服务 %s 已存在", serviceName) } // 获取用户配置目录 userConfigDir, err := getUserConfigDir() if err != nil { return fmt.Errorf("获取用户配置目录失败: %v", err) } // 服务启动参数 - 包含配置目录参数以确保服务以系统权限运行时能找到配置文件 binPath := fmt.Sprintf(`"%s"`, execPath) args := []string{"service", "run", "--config", userConfigDir} config := mgr.Config{ ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, StartType: windows.SERVICE_AUTO_START, ErrorControl: windows.SERVICE_ERROR_NORMAL, BinaryPathName: binPath, DisplayName: displayName, Description: description, Dependencies: []string{"Tcpip", "Dnscache"}, } service, err = manager.CreateService(serviceName, execPath, config, args...) if err != nil { return fmt.Errorf("创建服务失败: %v", err) } defer service.Close() // 设置服务故障恢复策略 err = service.SetRecoveryActions([]mgr.RecoveryAction{ {Type: windows.SC_ACTION_RESTART, Delay: 5 * time.Second}, {Type: windows.SC_ACTION_RESTART, Delay: 5 * time.Second}, {Type: windows.SC_ACTION_RESTART, Delay: 5 * time.Second}}, uint32((24 * time.Hour).Seconds())) if err != nil { utils.LogWarning("设置服务恢复策略失败: %v", err) } return nil } func (m *WindowsServiceManager) Uninstall() error { manager, err := mgr.Connect() if err != nil { return fmt.Errorf("连接服务管理器失败: %v", err) } defer manager.Disconnect() serviceName := getServiceName() service, err := manager.OpenService(serviceName) if err != nil { // 服务不存在,认为卸载成功 return nil } defer service.Close() // 先停止服务 status, err := service.Query() if err != nil { return fmt.Errorf("查询服务状态失败: %v", err) } if status.State != svc.Stopped { _, err = service.Control(svc.Stop) if err != nil { return fmt.Errorf("停止服务失败: %v", err) } // 等待服务停止 timeout := time.Now().Add(30 * time.Second) for status.State != svc.Stopped { if time.Now().After(timeout) { return fmt.Errorf("等待服务停止超时") } time.Sleep(300 * time.Millisecond) status, err = service.Query() if err != nil { return fmt.Errorf("查询服务状态失败: %v", err) } } } // 删除服务 err = service.Delete() if err != nil { return fmt.Errorf("删除服务失败: %v", err) } return nil } func (m *WindowsServiceManager) Start() error { manager, err := mgr.Connect() if err != nil { return fmt.Errorf("连接服务管理器失败: %v", err) } defer manager.Disconnect() serviceName := getServiceName() service, err := manager.OpenService(serviceName) if err != nil { return fmt.Errorf("打开服务失败: %v", err) } defer service.Close() err = service.Start() if err != nil { return fmt.Errorf("启动服务失败: %v", err) } return nil } func (m *WindowsServiceManager) Stop() error { manager, err := mgr.Connect() if err != nil { return fmt.Errorf("连接服务管理器失败: %v", err) } defer manager.Disconnect() serviceName := getServiceName() service, err := manager.OpenService(serviceName) if err != nil { return fmt.Errorf("打开服务失败: %v", err) } defer service.Close() status, err := service.Query() if err != nil { return fmt.Errorf("查询服务状态失败: %v", err) } if status.State == svc.Stopped { return nil // 服务已经停止 } _, err = service.Control(svc.Stop) if err != nil { return fmt.Errorf("停止服务失败: %v", err) } return nil } func (m *WindowsServiceManager) Restart() error { if err := m.Stop(); err != nil { return err } // 等待一下再启动 time.Sleep(2 * time.Second) return m.Start() } func (m *WindowsServiceManager) Status() (ServiceStatus, error) { manager, err := mgr.Connect() if err != nil { return StatusUnknown, fmt.Errorf("连接服务管理器失败: %v", err) } defer manager.Disconnect() serviceName := getServiceName() service, err := manager.OpenService(serviceName) if err != nil { return StatusNotInstalled, nil } defer service.Close() status, err := service.Query() if err != nil { return StatusUnknown, fmt.Errorf("查询服务状态失败: %v", err) } switch status.State { case svc.Running: return StatusRunning, nil case svc.Stopped: return StatusStopped, nil case svc.StartPending, svc.ContinuePending: return StatusRunning, nil case svc.StopPending, svc.PausePending, svc.Paused: return StatusStopped, nil default: return StatusUnknown, nil } }