//go:build linux // +build linux package utils import ( "errors" "os" "time" teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const" executils "github.com/TeaOSLab/EdgeHttpDNS/internal/utils/exec" "github.com/iwind/TeaGo/Tea" ) var systemdServiceFile = "/etc/systemd/system/" + teaconst.SystemdServiceName + ".service" var initServiceFile = "/etc/init.d/" + teaconst.SystemdServiceName // Install 安装系统服务 func (m *ServiceManager) Install(exePath string, args []string) error { if os.Getgid() != 0 { return errors.New("only root users can install the service") } systemd, err := executils.LookPath("systemctl") if err != nil { // systemd 不可用,降级到 init.d return m.installInitService(exePath, args) } return m.installSystemdService(systemd, exePath, args) } // Start 启动服务 func (m *ServiceManager) Start() error { if os.Getgid() != 0 { return errors.New("only root users can start the service") } // 优先检查 systemd if fileExists(systemdServiceFile) { systemd, err := executils.LookPath("systemctl") if err != nil { return err } return executils.NewTimeoutCmd(10*time.Second, systemd, "start", teaconst.SystemdServiceName+".service").Start() } // 降级到 init.d if fileExists(initServiceFile) { return executils.NewTimeoutCmd(10*time.Second, "service", teaconst.ProcessName, "start").Start() } return errors.New("no service file found, please install the service first") } // Uninstall 删除服务 func (m *ServiceManager) Uninstall() error { if os.Getgid() != 0 { return errors.New("only root users can uninstall the service") } // systemd if fileExists(systemdServiceFile) { systemd, err := executils.LookPath("systemctl") if err == nil { _ = executils.NewTimeoutCmd(10*time.Second, systemd, "stop", teaconst.SystemdServiceName+".service").Run() _ = executils.NewTimeoutCmd(10*time.Second, systemd, "disable", teaconst.SystemdServiceName+".service").Run() _ = executils.NewTimeoutCmd(10*time.Second, systemd, "daemon-reload").Run() } return os.Remove(systemdServiceFile) } // init.d if fileExists(initServiceFile) { _ = executils.NewTimeoutCmd(10*time.Second, "service", teaconst.ProcessName, "stop").Run() chkCmd, err := executils.LookPath("chkconfig") if err == nil { _ = executils.NewTimeoutCmd(10*time.Second, chkCmd, "--del", teaconst.ProcessName).Run() } return os.Remove(initServiceFile) } return nil } // installSystemdService 安装 systemd 服务 func (m *ServiceManager) installSystemdService(systemd, exePath string, args []string) error { shortName := teaconst.SystemdServiceName longName := "GoEdge HTTPDNS" // 用 bash 包装启动命令,兼容路径含空格的场景 var startCmd = exePath + " daemon" bashPath, _ := executils.LookPath("bash") if len(bashPath) > 0 { startCmd = bashPath + " -c \"" + startCmd + "\"" } desc := `### BEGIN INIT INFO # Provides: ` + shortName + ` # Required-Start: $all # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: # Short-Description: ` + longName + ` Service ### END INIT INFO [Unit] Description=` + longName + ` Node Service Before=shutdown.target After=network-online.target [Service] Type=simple Restart=always RestartSec=1s ExecStart=` + startCmd + ` ExecStop=` + exePath + ` stop ExecReload=` + exePath + ` restart [Install] WantedBy=multi-user.target` // 权限 0644,systemd 单元文件不需要执行权限 err := os.WriteFile(systemdServiceFile, []byte(desc), 0644) if err != nil { return err } // 停止已有服务 _ = executils.NewTimeoutCmd(10*time.Second, systemd, "stop", shortName+".service").Run() // 重新加载 _ = executils.NewTimeoutCmd(10*time.Second, systemd, "daemon-reload").Run() // 启用开机自启 return executils.NewTimeoutCmd(10*time.Second, systemd, "enable", shortName+".service").Run() } // installInitService 安装 init.d 服务(降级方案,适用于无 systemd 的旧系统) func (m *ServiceManager) installInitService(exePath string, args []string) error { shortName := teaconst.SystemdServiceName longName := "GoEdge HTTPDNS" // 生成 init.d 脚本 script := `#!/bin/bash ### BEGIN INIT INFO # Provides: ` + shortName + ` # Required-Start: $all # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: ` + longName + ` Service ### END INIT INFO INSTALL_DIR=` + Tea.Root + ` case "$1" in start) cd "${INSTALL_DIR}" ` + exePath + ` daemon & echo "` + longName + ` started" ;; stop) cd "${INSTALL_DIR}" ` + exePath + ` stop echo "` + longName + ` stopped" ;; restart) cd "${INSTALL_DIR}" ` + exePath + ` stop sleep 1 ` + exePath + ` daemon & echo "` + longName + ` restarted" ;; status) cd "${INSTALL_DIR}" ` + exePath + ` status ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0 ` // init.d 脚本需要执行权限 err := os.WriteFile(initServiceFile, []byte(script), 0755) if err != nil { return err } // 尝试用 chkconfig 注册(CentOS/RHEL) chkCmd, err := executils.LookPath("chkconfig") if err == nil { _ = executils.NewTimeoutCmd(10*time.Second, chkCmd, "--add", teaconst.ProcessName).Run() return nil } // 尝试用 update-rc.d 注册(Debian/Ubuntu) updateRcCmd, err := executils.LookPath("update-rc.d") if err == nil { _ = executils.NewTimeoutCmd(10*time.Second, updateRcCmd, teaconst.ProcessName, "defaults").Run() return nil } return nil } // fileExists 检查文件是否存在 func fileExists(path string) bool { _, err := os.Stat(path) return err == nil }