safesight-edge/agent/internal/procctl/systemctl.go

135 lines
3.6 KiB
Go

package procctl
import (
"bytes"
"fmt"
"io"
"os/exec"
"strconv"
"strings"
"time"
)
// SystemCtlController 使用 systemctl 管理 Media Server
type SystemCtlController struct {
serviceName string
configPath string
}
func NewSystemCtlController(serviceName string, configPath string) *SystemCtlController {
return &SystemCtlController{
serviceName: serviceName,
configPath: configPath,
}
}
func (s *SystemCtlController) Enabled() bool { return s != nil }
func (s *SystemCtlController) Status() (Status, error) {
// 检查服务是否运行
cmd := exec.Command("systemctl", "is-active", "--quiet", s.serviceName)
err := cmd.Run()
if err != nil {
// 服务未运行
return Status{Running: false}, nil
}
// 获取 PID
pidCmd := exec.Command("systemctl", "show", "--property=MainPID", "--value", s.serviceName)
out, err := pidCmd.Output()
if err != nil {
return Status{Running: true}, nil // 运行但无法获取 PID
}
pid, _ := strconv.Atoi(strings.TrimSpace(string(out)))
if pid <= 0 {
return Status{Running: true}, nil
}
return Status{
Running: true,
Pid: pid,
ConfigPath: s.configPath,
}, nil
}
func (s *SystemCtlController) Version() (string, error) {
// 从 systemctl 获取 ExecStart 路径,再调用 --version
// Output format: { path=/path/to/binary ; argv[]=... }
binOut, err := exec.Command("systemctl", "show", "--property=ExecStart", "--value", s.serviceName).Output()
if err != nil {
return "", fmt.Errorf("get version failed: %w", err)
}
out := strings.TrimSpace(string(binOut))
// Parse path= from systemctl ExecStart format
const prefix = "path="
idx := strings.Index(out, prefix)
if idx < 0 {
return "", fmt.Errorf("get version failed: cannot parse ExecStart")
}
rest := out[idx+len(prefix):]
end := strings.IndexByte(rest, ' ')
if end < 0 {
end = strings.IndexByte(rest, ';')
}
if end < 0 {
end = len(rest)
}
binPath := rest[:end]
if binPath == "" {
return "", fmt.Errorf("get version failed: empty ExecStart")
}
cmd := exec.Command(binPath, "--version")
out2, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("get version failed: %w", err)
}
return strings.TrimSpace(string(out2)), nil
}
func (s *SystemCtlController) Start(configName string) (Status, error) {
// 使用 systemctl start
cmd := exec.Command("systemctl", "start", s.serviceName)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return Status{}, fmt.Errorf("start failed: %w, stderr: %s", err, stderr.String())
}
// 等待服务启动
time.Sleep(500 * time.Millisecond)
return s.Status()
}
func (s *SystemCtlController) Stop() (Status, error) {
cmd := exec.Command("systemctl", "stop", s.serviceName)
if err := cmd.Run(); err != nil {
return Status{}, fmt.Errorf("stop failed: %w", err)
}
return Status{Running: false}, nil
}
func (s *SystemCtlController) Restart(configName string) (Status, error) {
cmd := exec.Command("systemctl", "restart", s.serviceName)
if err := cmd.Run(); err != nil {
return Status{}, fmt.Errorf("restart failed: %w", err)
}
time.Sleep(500 * time.Millisecond)
return s.Status()
}
// 二进制更新相关功能在 systemctl 模式下不支持
func (s *SystemCtlController) BinaryInfo() (BinaryUpdateResult, error) {
return BinaryUpdateResult{}, ErrNotSupported
}
func (s *SystemCtlController) UpdateBinary(r io.Reader, contentLength int64, expectedSha256 string) (BinaryUpdateResult, error) {
return BinaryUpdateResult{}, ErrNotSupported
}
func (s *SystemCtlController) RollbackBinary(backupPath string) (BinaryUpdateResult, error) {
return BinaryUpdateResult{}, ErrNotSupported
}