增加service命令

This commit is contained in:
GoEdgeLab
2021-01-12 11:49:14 +08:00
parent 27e3a7badb
commit 5437fdcdae
9 changed files with 594 additions and 3 deletions

View File

@@ -22,7 +22,7 @@ func main() {
app := apps.NewAppCmd() app := apps.NewAppCmd()
app.Version(teaconst.Version) app.Version(teaconst.Version)
app.Product(teaconst.ProductName) app.Product(teaconst.ProductName)
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade]") app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon]")
app.On("setup", func() { app.On("setup", func() {
setupCmd := setup.NewSetupFromCmd() setupCmd := setup.NewSetupFromCmd()
err := setupCmd.Run() err := setupCmd.Run()
@@ -56,6 +56,16 @@ func main() {
} }
fmt.Println("finished!") fmt.Println("finished!")
}) })
app.On("daemon", func() {
nodes.NewAPINode().Daemon()
})
app.On("service", func() {
err := nodes.NewAPINode().InstallSystemService()
if err != nil {
fmt.Println("[ERROR]install failed: " + err.Error())
}
fmt.Println("done")
})
app.Run(func() { app.Run(func() {
nodes.NewAPINode().Start() nodes.NewAPINode().Start()
}) })

View File

@@ -13,4 +13,6 @@ const (
EncryptMethod = "aes-256-cfb" EncryptMethod = "aes-256-cfb"
ErrServer = "服务器出了点小问题,请稍后重试" ErrServer = "服务器出了点小问题,请稍后重试"
SystemdServiceName = "edge-api"
) )

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAPI/internal/configs" "github.com/TeaOSLab/EdgeAPI/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const" teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/events"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs" "github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services" "github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeAPI/internal/setup" "github.com/TeaOSLab/EdgeAPI/internal/setup"
@@ -14,14 +15,18 @@ import (
"github.com/go-yaml/yaml" "github.com/go-yaml/yaml"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
stringutil "github.com/iwind/TeaGo/utils/string" stringutil "github.com/iwind/TeaGo/utils/string"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"os" "os"
"os/exec"
"strconv" "strconv"
"time"
) )
var sharedAPIConfig *configs.APIConfig = nil var sharedAPIConfig *configs.APIConfig = nil
@@ -36,8 +41,15 @@ func NewAPINode() *APINode {
func (this *APINode) Start() { func (this *APINode) Start() {
logs.Println("[API_NODE]start api node, pid: " + strconv.Itoa(os.Getpid())) logs.Println("[API_NODE]start api node, pid: " + strconv.Itoa(os.Getpid()))
// 本地Sock
err := this.listenSock()
if err != nil {
logs.Println("[API_NODE]" + err.Error())
return
}
// 自动升级 // 自动升级
err := this.autoUpgrade() err = this.autoUpgrade()
if err != nil { if err != nil {
logs.Println("[API_NODE]auto upgrade failed: " + err.Error()) logs.Println("[API_NODE]auto upgrade failed: " + err.Error())
return return
@@ -86,6 +98,68 @@ func (this *APINode) Start() {
select {} select {}
} }
// 实现守护进程
func (this *APINode) Daemon() {
path := os.TempDir() + "/edge-api.sock"
isDebug := lists.ContainsString(os.Args, "debug")
isDebug = true
for {
conn, err := net.DialTimeout("unix", path, 1*time.Second)
if err != nil {
if isDebug {
log.Println("[DAEMON]starting ...")
}
// 尝试启动
err = func() error {
exe, err := os.Executable()
if err != nil {
return err
}
cmd := exec.Command(exe)
err = cmd.Start()
if err != nil {
return err
}
err = cmd.Wait()
if err != nil {
return err
}
return nil
}()
if err != nil {
if isDebug {
log.Println("[DAEMON]", err)
}
time.Sleep(1 * time.Second)
} else {
time.Sleep(5 * time.Second)
}
} else {
_ = conn.Close()
time.Sleep(5 * time.Second)
}
}
}
// 安装系统服务
func (this *APINode) InstallSystemService() error {
shortName := teaconst.SystemdServiceName
exe, err := os.Executable()
if err != nil {
return err
}
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
err = manager.Install(exe, []string{})
if err != nil {
return err
}
return nil
}
// 启动RPC监听 // 启动RPC监听
func (this *APINode) listenRPC(listener net.Listener, tlsConfig *tls.Config) error { func (this *APINode) listenRPC(listener net.Listener, tlsConfig *tls.Config) error {
var rpcServer *grpc.Server var rpcServer *grpc.Server
@@ -338,3 +412,40 @@ func (this *APINode) listenPorts(apiNode *models.APINode) (isListening bool) {
return return
} }
// 监听本地sock
func (this *APINode) listenSock() error {
path := os.TempDir() + "/edge-api.sock"
// 检查是否已经存在
_, err := os.Stat(path)
if err == nil {
conn, err := net.Dial("unix", path)
if err != nil {
_ = os.Remove(path)
} else {
_ = conn.Close()
}
}
// 新的监听任务
listener, err := net.Listen("unix", path)
if err != nil {
return err
}
events.On(events.EventQuit, func() {
remotelogs.Println("API_NODE", "quit unix sock")
_ = listener.Close()
})
go func() {
for {
_, err := listener.Accept()
if err != nil {
return
}
}
}()
return nil
}

File diff suppressed because one or more lines are too long

111
internal/utils/service.go Normal file
View File

@@ -0,0 +1,111 @@
package utils
import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"log"
"os"
"path/filepath"
"runtime"
"sync"
)
// 服务管理器
type ServiceManager struct {
Name string
Description string
fp *os.File
logger *log.Logger
onceLocker sync.Once
}
// 获取对象
func NewServiceManager(name, description string) *ServiceManager {
manager := &ServiceManager{
Name: name,
Description: description,
}
// root
manager.resetRoot()
return manager
}
// 设置服务
func (this *ServiceManager) setup() {
this.onceLocker.Do(func() {
logFile := files.NewFile(Tea.Root + "/logs/service.log")
if logFile.Exists() {
logFile.Delete()
}
//logger
fp, err := os.OpenFile(Tea.Root+"/logs/service.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
logs.Error(err)
return
}
this.fp = fp
this.logger = log.New(fp, "", log.LstdFlags)
})
}
// 记录普通日志
func (this *ServiceManager) Log(msg string) {
this.setup()
if this.logger == nil {
return
}
this.logger.Println("[info]" + msg)
}
// 记录错误日志
func (this *ServiceManager) LogError(msg string) {
this.setup()
if this.logger == nil {
return
}
this.logger.Println("[error]" + msg)
}
// 关闭
func (this *ServiceManager) Close() error {
if this.fp != nil {
return this.fp.Close()
}
return nil
}
// 重置Root
func (this *ServiceManager) resetRoot() {
if !Tea.IsTesting() {
exePath, err := os.Executable()
if err != nil {
exePath = os.Args[0]
}
link, err := filepath.EvalSymlinks(exePath)
if err == nil {
exePath = link
}
fullPath, err := filepath.Abs(exePath)
if err == nil {
Tea.UpdateRoot(filepath.Dir(filepath.Dir(fullPath)))
}
}
Tea.SetPublicDir(Tea.Root + Tea.DS + "web" + Tea.DS + "public")
Tea.SetViewsDir(Tea.Root + Tea.DS + "web" + Tea.DS + "views")
Tea.SetTmpDir(Tea.Root + Tea.DS + "web" + Tea.DS + "tmp")
}
// 保持命令行窗口是打开的
func (this *ServiceManager) PauseWindow() {
if runtime.GOOS != "windows" {
return
}
b := make([]byte, 1)
_, _ = os.Stdin.Read(b)
}

View File

@@ -0,0 +1,154 @@
// +build linux
package utils
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"io/ioutil"
"os"
"os/exec"
"regexp"
)
var systemdServiceFile = "/etc/systemd/system/edge-api.service"
var initServiceFile = "/etc/init.d/" + teaconst.SystemdServiceName
// 安装服务
func (this *ServiceManager) Install(exePath string, args []string) error {
if os.Getgid() != 0 {
return errors.New("only root users can install the service")
}
systemd, err := exec.LookPath("systemctl")
if err != nil {
return this.installInitService(exePath, args)
}
return this.installSystemdService(systemd, exePath, args)
}
// 启动服务
func (this *ServiceManager) Start() error {
if os.Getgid() != 0 {
return errors.New("only root users can start the service")
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
if err != nil {
return err
}
return exec.Command(systemd, "start", teaconst.SystemdServiceName+".service").Start()
}
return exec.Command("service", teaconst.ProcessName, "start").Start()
}
// 删除服务
func (this *ServiceManager) Uninstall() error {
if os.Getgid() != 0 {
return errors.New("only root users can uninstall the service")
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
if err != nil {
return err
}
// disable service
exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Start()
// reload
exec.Command(systemd, "daemon-reload")
return files.NewFile(systemdServiceFile).Delete()
}
f := files.NewFile(initServiceFile)
if f.Exists() {
return f.Delete()
}
return nil
}
// install init service
func (this *ServiceManager) installInitService(exePath string, args []string) error {
shortName := teaconst.SystemdServiceName
scriptFile := Tea.Root + "/scripts/" + shortName
if !files.NewFile(scriptFile).Exists() {
return errors.New("'scripts/" + shortName + "' file not exists")
}
data, err := ioutil.ReadFile(scriptFile)
if err != nil {
return err
}
data = regexp.MustCompile("INSTALL_DIR=.+").ReplaceAll(data, []byte("INSTALL_DIR="+Tea.Root))
err = ioutil.WriteFile(initServiceFile, data, 0777)
if err != nil {
return err
}
chkCmd, err := exec.LookPath("chkconfig")
if err != nil {
return err
}
err = exec.Command(chkCmd, "--add", teaconst.ProcessName).Start()
if err != nil {
return err
}
return nil
}
// install systemd service
func (this *ServiceManager) installSystemdService(systemd, exePath string, args []string) error {
shortName := teaconst.SystemdServiceName
longName := "GoEdge API" // TODO 将来可以修改
desc := `# 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 + ` Service
Before=shutdown.target
After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=1s
ExecStart=` + exePath + ` daemon
ExecStop=` + exePath + ` stop
ExecReload=` + exePath + ` reload
[Install]
WantedBy=multi-user.target`
// write file
err := ioutil.WriteFile(systemdServiceFile, []byte(desc), 0777)
if err != nil {
return err
}
// stop current systemd service if running
exec.Command(systemd, "stop", shortName+".service")
// reload
exec.Command(systemd, "daemon-reload")
// enable
cmd := exec.Command(systemd, "enable", shortName+".service")
return cmd.Run()
}

View File

@@ -0,0 +1,18 @@
// +build !linux,!windows
package utils
// 安装服务
func (this *ServiceManager) Install(exePath string, args []string) error {
return nil
}
// 启动服务
func (this *ServiceManager) Start() error {
return nil
}
// 删除服务
func (this *ServiceManager) Uninstall() error {
return nil
}

View File

@@ -0,0 +1,12 @@
package utils
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"testing"
)
func TestServiceManager_Log(t *testing.T) {
manager := NewServiceManager(teaconst.ProductName, teaconst.ProductName+" Server")
manager.Log("Hello, World")
manager.LogError("Hello, World")
}

View File

@@ -0,0 +1,173 @@
// +build windows
package utils
import (
"fmt"
"github.com/iwind/TeaGo/Tea"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
"os/exec"
)
// 安装服务
func (this *ServiceManager) Install(exePath string, args []string) error {
m, err := mgr.Connect()
if err != nil {
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
}
defer m.Disconnect()
s, err := m.OpenService(this.Name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", this.Name)
}
s, err = m.CreateService(this.Name, exePath, mgr.Config{
DisplayName: this.Name,
Description: this.Description,
StartType: windows.SERVICE_AUTO_START,
}, args...)
if err != nil {
return fmt.Errorf("creating: %s", err.Error())
}
defer s.Close()
return nil
}
// 启动服务
func (this *ServiceManager) Start() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(this.Name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
err = s.Start("service")
if err != nil {
return fmt.Errorf("could not start service: %v", err)
}
return nil
}
// 删除服务
func (this *ServiceManager) Uninstall() error {
m, err := mgr.Connect()
if err != nil {
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
}
defer m.Disconnect()
s, err := m.OpenService(this.Name)
if err != nil {
return fmt.Errorf("open service: %s", err.Error())
}
// shutdown service
_, err = s.Control(svc.Stop)
if err != nil {
fmt.Printf("shutdown service: %s\n", err.Error())
}
defer s.Close()
err = s.Delete()
if err != nil {
return fmt.Errorf("deleting: %s", err.Error())
}
return nil
}
// 运行
func (this *ServiceManager) Run() {
err := svc.Run(this.Name, this)
if err != nil {
this.LogError(err.Error())
}
}
// 同服务管理器的交互
func (this *ServiceManager) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
changes <- svc.Status{
State: svc.StartPending,
}
changes <- svc.Status{
State: svc.Running,
Accepts: cmdsAccepted,
}
// start service
this.Log("start")
this.cmdStart()
loop:
for {
select {
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
this.Log("cmd: Interrogate")
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
this.Log("cmd: Stop|Shutdown")
// stop service
this.cmdStop()
break loop
case svc.Pause:
this.Log("cmd: Pause")
// stop service
this.cmdStop()
changes <- svc.Status{
State: svc.Paused,
Accepts: cmdsAccepted,
}
case svc.Continue:
this.Log("cmd: Continue")
// start service
this.cmdStart()
changes <- svc.Status{
State: svc.Running,
Accepts: cmdsAccepted,
}
default:
this.LogError(fmt.Sprintf("unexpected control request #%d\r\n", c))
}
}
}
changes <- svc.Status{
State: svc.StopPending,
}
return
}
// 启动Web服务
func (this *ServiceManager) cmdStart() {
cmd := exec.Command(Tea.Root+Tea.DS+"bin"+Tea.DS+teaconst.SystemdServiceName+".exe", "start")
err := cmd.Start()
if err != nil {
this.LogError(err.Error())
}
}
// 停止Web服务
func (this *ServiceManager) cmdStop() {
cmd := exec.Command(Tea.Root+Tea.DS+"bin"+Tea.DS+teaconst.SystemdServiceName+".exe", "stop")
err := cmd.Start()
if err != nil {
this.LogError(err.Error())
}
}