163 lines
4.0 KiB
Go
163 lines
4.0 KiB
Go
package procctl
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"rk3588sys/agent/internal/config"
|
|
"rk3588sys/agent/internal/files"
|
|
)
|
|
|
|
var ErrNotSupported = errors.New("not supported")
|
|
var ErrConflict = errors.New("conflict")
|
|
var ErrInvalidConfig = errors.New("invalid config")
|
|
var ErrConfigNotFound = errors.New("config not found")
|
|
|
|
type Status struct {
|
|
Running bool `json:"running"`
|
|
Pid int `json:"pid"`
|
|
ConfigPath string `json:"config_path"`
|
|
}
|
|
|
|
type pidFile struct {
|
|
Pid int `json:"pid"`
|
|
ConfigPath string `json:"config_path"`
|
|
StartedAtMS int64 `json:"started_at_ms"`
|
|
}
|
|
|
|
type Controller struct {
|
|
mu sync.Mutex
|
|
proc config.MediaServerProcessConfig
|
|
defCfg string
|
|
}
|
|
|
|
func New(agentCfg config.AgentConfig) *Controller {
|
|
return &Controller{
|
|
proc: agentCfg.MediaServerProcess,
|
|
defCfg: agentCfg.ConfigPath,
|
|
}
|
|
}
|
|
|
|
func (c *Controller) Enabled() bool { return c != nil && c.proc.Enable }
|
|
|
|
func (c *Controller) Start(configName string) (Status, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
resolved, err := c.resolveConfigPath(configName)
|
|
if err != nil {
|
|
return Status{}, err
|
|
}
|
|
|
|
pf, _ := c.readPidFile()
|
|
if pf != nil {
|
|
alive, _ := isAlive(pf.Pid)
|
|
if alive {
|
|
if filepath.Clean(pf.ConfigPath) == filepath.Clean(resolved) {
|
|
return Status{Running: true, Pid: pf.Pid, ConfigPath: pf.ConfigPath}, nil
|
|
}
|
|
return Status{}, fmt.Errorf("%w: already running with config %s", ErrConflict, pf.ConfigPath)
|
|
}
|
|
_ = os.Remove(c.proc.PidFile)
|
|
}
|
|
|
|
pid, err := startProcess(c.proc.ExecPath, c.proc.WorkDir, resolved)
|
|
if err != nil {
|
|
return Status{}, err
|
|
}
|
|
|
|
pf2 := pidFile{Pid: pid, ConfigPath: resolved, StartedAtMS: time.Now().UnixMilli()}
|
|
b, _ := json.Marshal(pf2)
|
|
b = append(b, '\n')
|
|
if err := files.WriteFileAtomic(c.proc.PidFile, b, 0o644); err != nil {
|
|
return Status{}, fmt.Errorf("write pid file: %w", err)
|
|
}
|
|
return Status{Running: true, Pid: pid, ConfigPath: resolved}, nil
|
|
}
|
|
|
|
func (c *Controller) Stop() (Status, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
pf, err := c.readPidFile()
|
|
if err != nil {
|
|
return Status{}, err
|
|
}
|
|
if pf == nil {
|
|
return Status{Running: false}, nil
|
|
}
|
|
|
|
alive, _ := isAlive(pf.Pid)
|
|
if !alive {
|
|
_ = os.Remove(c.proc.PidFile)
|
|
return Status{Running: false, Pid: pf.Pid, ConfigPath: pf.ConfigPath}, nil
|
|
}
|
|
|
|
if err := stopProcess(pf.Pid, time.Duration(c.proc.GracefulTimeoutMS)*time.Millisecond); err != nil {
|
|
return Status{}, err
|
|
}
|
|
_ = os.Remove(c.proc.PidFile)
|
|
return Status{Running: false, Pid: pf.Pid, ConfigPath: pf.ConfigPath}, nil
|
|
}
|
|
|
|
func (c *Controller) Restart(configName string) (Status, error) {
|
|
_, _ = c.Stop()
|
|
return c.Start(configName)
|
|
}
|
|
|
|
func (c *Controller) resolveConfigPath(name string) (string, error) {
|
|
n := strings.TrimSpace(name)
|
|
if n == "" {
|
|
if strings.TrimSpace(c.defCfg) == "" {
|
|
return "", fmt.Errorf("%w: default config_path is empty", ErrInvalidConfig)
|
|
}
|
|
return c.defCfg, nil
|
|
}
|
|
if strings.Contains(n, "..") || strings.ContainsAny(n, "/\\") {
|
|
return "", fmt.Errorf("%w: contains invalid characters", ErrInvalidConfig)
|
|
}
|
|
if !strings.HasSuffix(n, ".json") {
|
|
n += ".json"
|
|
}
|
|
base := strings.TrimSpace(c.proc.ConfigsDir)
|
|
if base == "" {
|
|
return "", fmt.Errorf("%w: configs_dir is empty", ErrInvalidConfig)
|
|
}
|
|
p := filepath.Join(base, n)
|
|
st, err := os.Stat(p)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("%w: %s", ErrConfigNotFound, p)
|
|
}
|
|
return "", fmt.Errorf("stat config: %w", err)
|
|
}
|
|
if st.IsDir() {
|
|
return "", fmt.Errorf("%w: is a directory", ErrInvalidConfig)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (c *Controller) readPidFile() (*pidFile, error) {
|
|
b, err := os.ReadFile(c.proc.PidFile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("read pid file: %w", err)
|
|
}
|
|
var pf pidFile
|
|
if err := json.Unmarshal(b, &pf); err != nil {
|
|
return nil, fmt.Errorf("parse pid file: %w", err)
|
|
}
|
|
if pf.Pid <= 0 {
|
|
return nil, fmt.Errorf("pid file invalid pid: %d", pf.Pid)
|
|
}
|
|
return &pf, nil
|
|
}
|