323 lines
8.0 KiB
Go
323 lines
8.0 KiB
Go
//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
|
|
}
|
|
}
|