1.4.5.2
This commit is contained in:
7
EdgeReporter/internal/utils/command.go
Normal file
7
EdgeReporter/internal/utils/command.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
// 命令定义
|
||||
type Command struct {
|
||||
Name string
|
||||
Args []string
|
||||
}
|
||||
61
EdgeReporter/internal/utils/command_executor.go
Normal file
61
EdgeReporter/internal/utils/command_executor.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// 命令执行器
|
||||
type CommandExecutor struct {
|
||||
commands []*Command
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
func NewCommandExecutor() *CommandExecutor {
|
||||
return &CommandExecutor{}
|
||||
}
|
||||
|
||||
// 添加命令
|
||||
func (this *CommandExecutor) Add(command string, arg ...string) {
|
||||
this.commands = append(this.commands, &Command{
|
||||
Name: command,
|
||||
Args: arg,
|
||||
})
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
func (this *CommandExecutor) Run() (output string, err error) {
|
||||
if len(this.commands) == 0 {
|
||||
return "", errors.New("no commands no run")
|
||||
}
|
||||
var lastCmd *exec.Cmd = nil
|
||||
var lastData []byte = nil
|
||||
for _, command := range this.commands {
|
||||
cmd := exec.Command(command.Name, command.Args...)
|
||||
stdout := bytes.NewBuffer([]byte{})
|
||||
cmd.Stdout = stdout
|
||||
if lastCmd != nil {
|
||||
cmd.Stdin = bytes.NewBuffer(lastData)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
_, ok := err.(*exec.ExitError)
|
||||
if ok {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
lastData = stdout.Bytes()
|
||||
|
||||
lastCmd = cmd
|
||||
}
|
||||
|
||||
return string(bytes.TrimSpace(lastData)), nil
|
||||
}
|
||||
8
EdgeReporter/internal/utils/errors.go
Normal file
8
EdgeReporter/internal/utils/errors.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package utils
|
||||
|
||||
import "github.com/iwind/TeaGo/logs"
|
||||
|
||||
func PrintError(err error) {
|
||||
// TODO 记录调用的文件名、行数
|
||||
logs.Println("[ERROR]" + err.Error())
|
||||
}
|
||||
19
EdgeReporter/internal/utils/ip.go
Normal file
19
EdgeReporter/internal/utils/ip.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
// 将IP转换为整型
|
||||
func IP2Long(ip string) uint32 {
|
||||
s := net.ParseIP(ip)
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(s) == 16 {
|
||||
return binary.BigEndian.Uint32(s[12:16])
|
||||
}
|
||||
return binary.BigEndian.Uint32(s)
|
||||
}
|
||||
111
EdgeReporter/internal/utils/service.go
Normal file
111
EdgeReporter/internal/utils/service.go
Normal 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)
|
||||
}
|
||||
154
EdgeReporter/internal/utils/service_linux.go
Normal file
154
EdgeReporter/internal/utils/service_linux.go
Normal file
@@ -0,0 +1,154 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var systemdServiceFile = "/etc/systemd/system/edge-reporter.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 := os.ReadFile(scriptFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = regexp.MustCompile("INSTALL_DIR=.+").ReplaceAll(data, []byte("INSTALL_DIR="+Tea.Root))
|
||||
err = os.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 Reporter" // 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 := os.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()
|
||||
}
|
||||
19
EdgeReporter/internal/utils/service_others.go
Normal file
19
EdgeReporter/internal/utils/service_others.go
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build !linux && !windows
|
||||
// +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
|
||||
}
|
||||
12
EdgeReporter/internal/utils/service_test.go
Normal file
12
EdgeReporter/internal/utils/service_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServiceManager_Log(t *testing.T) {
|
||||
manager := NewServiceManager(teaconst.ProductName, teaconst.ProductName+" Server")
|
||||
manager.Log("Hello, World")
|
||||
manager.LogError("Hello, World")
|
||||
}
|
||||
175
EdgeReporter/internal/utils/service_windows.go
Normal file
175
EdgeReporter/internal/utils/service_windows.go
Normal file
@@ -0,0 +1,175 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Install 安装服务
|
||||
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
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
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
|
||||
}
|
||||
|
||||
// Uninstall 删除服务
|
||||
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
|
||||
}
|
||||
|
||||
// Run 运行
|
||||
func (this *ServiceManager) Run() {
|
||||
err := svc.Run(this.Name, this)
|
||||
if err != nil {
|
||||
this.LogError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Execute 同服务管理器的交互
|
||||
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())
|
||||
}
|
||||
}
|
||||
10
EdgeReporter/internal/utils/sizes/sizes.go
Normal file
10
EdgeReporter/internal/utils/sizes/sizes.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes
|
||||
|
||||
const (
|
||||
K int64 = 1024
|
||||
M = 1024 * K
|
||||
G = 1024 * M
|
||||
T = 1024 * G
|
||||
)
|
||||
17
EdgeReporter/internal/utils/sizes/sizes_test.go
Normal file
17
EdgeReporter/internal/utils/sizes/sizes_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSizes(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(sizes.K == 1024)
|
||||
a.IsTrue(sizes.M == 1024*1024)
|
||||
a.IsTrue(sizes.G == 1024*1024*1024)
|
||||
a.IsTrue(sizes.T == 1024*1024*1024*1024)
|
||||
}
|
||||
18
EdgeReporter/internal/utils/version.go
Normal file
18
EdgeReporter/internal/utils/version.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VersionToLong 计算版本代号
|
||||
func VersionToLong(version string) uint32 {
|
||||
countDots := strings.Count(version, ".")
|
||||
if countDots == 2 {
|
||||
version += ".0"
|
||||
} else if countDots == 1 {
|
||||
version += ".0.0"
|
||||
} else if countDots == 0 {
|
||||
version += ".0.0.0"
|
||||
}
|
||||
return IP2Long(version)
|
||||
}
|
||||
Reference in New Issue
Block a user