fix(agent): systemctl 模式 Status 补充失败原因(LastError)

enable=false 时走 NewSystemCtlController,其 Status() 在服务未运行时
只返回 Running:false 无原因。现通过 systemctl show 获取
ActiveState/SubState/Result/ExecMainStatus/NRestarts,
给出可读提示: '启动失败(退出码 X, 已重启 N 次)' 或 '未运行(systemctl: x/y)'
This commit is contained in:
tian 2026-08-02 12:10:44 +08:00
parent 6a995db6e2
commit aae5bc36de

View File

@ -29,24 +29,24 @@ 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
// 服务未运行:附带失败原因(退出码/重启次数),供 UI 展示
return Status{Running: false, LastError: s.failureHint()}, 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,
@ -54,6 +54,36 @@ func (s *SystemCtlController) Status() (Status, error) {
}, nil
}
// failureHint 查询 systemctl 状态,给出服务未运行的可用原因(如启动失败/退出码)
func (s *SystemCtlController) failureHint() string {
out, err := exec.Command("systemctl", "show", "--property=ActiveState,SubState,Result,ExecMainStatus,NRestarts", s.serviceName).Output()
if err != nil {
return fmt.Sprintf("媒体服务未运行 (%s)", s.serviceName)
}
props := map[string]string{}
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
if idx := strings.IndexByte(line, '='); idx > 0 {
props[strings.TrimSpace(line[:idx])] = strings.TrimSpace(line[idx+1:])
}
}
switch {
case props["SubState"] == "failed" || props["Result"] == "exit-code":
code := props["ExecMainStatus"]
if code == "" {
code = "?"
}
restarts := props["NRestarts"]
if restarts == "" {
restarts = "0"
}
return fmt.Sprintf("媒体服务启动失败(退出码 %s已重启 %s 次)", code, restarts)
case props["ActiveState"] != "" || props["SubState"] != "":
return fmt.Sprintf("媒体服务未运行systemctl: %s/%s", props["ActiveState"], props["SubState"])
default:
return fmt.Sprintf("媒体服务未运行 (%s)", s.serviceName)
}
}
func (s *SystemCtlController) Version() (string, error) {
// 从 systemctl 获取 ExecStart 路径,再调用 --version
// Output format: { path=/path/to/binary ; argv[]=... }