135 lines
3.6 KiB
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
|
|
}
|