216 lines
5.5 KiB
Go
216 lines
5.5 KiB
Go
//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
|
||
}
|