Files
waf-platform/EdgeHttpDNS/internal/utils/service_linux.go
2026-03-02 20:07:53 +08:00

216 lines
5.5 KiB
Go
Raw 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.

//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`
// 权限 0644systemd 单元文件不需要执行权限
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
}