hostSync/cmd/service.go

465 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}