Files
mesh-catalog/modules/systemd/cmd/systemd-tools/main.go
T
jochen 73bb597c16
mesh/merge-gate pass: builds docker, gitea, lab, nftables, slack, systemd → ace, g14, novox, shanks; no bus step; 4 wait(s) for a person; every machine com…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
Hold what the tools say to the glossary's retired words (hq ADR 0244)
An agent meets the mesh's words most often in tool descriptions, and
nothing compared them with the glossary: several still said "the host"
for the node-engine and the forge's pull request comment was headed
"Change plan", a word retired twice over. retired-words is the copy of
the words the glossary retires for the tools, and checks/words fails the
repository check when any string a module's code can show, or any
manifest description, uses one. Those found are reworded here.
2026-10-07 20:02:09 +02:00

151 lines
5.2 KiB
Go

// systemd's tools: the node-service-manager seat's eight verbs — the units on this machine in both scopes,
// read and acted on by name — and the module's own reading of what has failed (novox/hq ADR 0177). The node
// tools runtime launches this bundle as a process of its own and serves what it serves (ADR 0188, ADR 0193);
// it runs as the operator account, so acts on the system manager, and reads of its journal, escalate with
// sudo -n, and the user scope is the account's own manager (client.go). The host applies units; this answers
// about them. stdout is the MCP channel; this says nothing else.
package main
import (
"fmt"
"os"
"os/user"
"strconv"
"strings"
stdio "git.novox.be/novox/mesh-sdk/go"
)
const seat = "node-service-manager"
func str(description string) map[string]any {
return map[string]any{"type": "string", "description": description}
}
var (
scopeArg = str(`"system" (the default) or "user": the operator account's own manager`)
unitArgS = str("the unit's name, as the service manager knows it")
)
func scopeOf(a map[string]any) (Scope, error) {
s, _ := a["scope"].(string)
switch s {
case "", "system":
return System, nil
case "user":
return User, nil
}
return "", fmt.Errorf("scope %q: \"system\" or \"user\"", s)
}
func unitOf(a map[string]any) (string, error) {
u, _ := a["unit"].(string)
if u = strings.TrimSpace(u); u == "" {
return "", fmt.Errorf("a unit is required")
}
return u, nil
}
func tools(m *Manager) []stdio.Tool {
act := func(verb, description string) stdio.Tool {
return stdio.Tool{Name: seat + "." + verb, Description: description,
Input: map[string]any{"scope": scopeArg, "unit": unitArgS},
Run: func(a map[string]any) (any, error) {
scope, err := scopeOf(a)
if err != nil {
return nil, err
}
unit, err := unitOf(a)
if err != nil {
return nil, err
}
return m.Act(scope, verb, unit)
}}
}
return []stdio.Tool{
{Name: seat + ".units",
Description: "The units the service manager knows in a scope, each with its load, active and sub state; narrowed to a pattern when asked.",
Input: map[string]any{"scope": scopeArg, "pattern": str("a glob the unit's name must match (optional)")},
Run: func(a map[string]any) (any, error) {
scope, err := scopeOf(a)
if err != nil {
return nil, err
}
pattern, _ := a["pattern"].(string)
units, err := m.Units(scope, pattern)
if err != nil {
return nil, err
}
return map[string]any{"scope": string(scope), "units": units}, nil
}},
{Name: seat + ".status",
Description: "One unit as the service manager sees it now: its states, whether it starts at boot, its main process, and mesh_declared — true when its unit file is one the mesh wrote (a unit the mesh only puts into a state is not recognised from here).",
Input: map[string]any{"scope": scopeArg, "unit": unitArgS},
Run: func(a map[string]any) (any, error) {
scope, err := scopeOf(a)
if err != nil {
return nil, err
}
unit, err := unitOf(a)
if err != nil {
return nil, err
}
return m.Status(scope, unit)
}},
act("start", "Start one unit. For a unit the mesh declares, the answer says the node-engine will restore what its declaration says at its next apply."),
act("stop", "Stop one unit; for a unit the mesh declares, the answer says the node-engine will restore its declared state."),
act("restart", "Restart one unit."),
act("enable", "Make one unit start at boot (or at the account's login, in user scope)."),
act("disable", "Stop one unit starting at boot (or at login, in user scope)."),
{Name: seat + ".journal",
Description: "The last lines of one unit's journal (at most 2000) — a system service's included: the read is escalated, so it is the service's own lines and not only the operator account's.",
Input: map[string]any{"scope": scopeArg, "unit": unitArgS,
"lines": map[string]any{"type": "number", "description": "how many lines from the end (default 100, at most 2000)"}},
Run: func(a map[string]any) (any, error) {
scope, err := scopeOf(a)
if err != nil {
return nil, err
}
unit, err := unitOf(a)
if err != nil {
return nil, err
}
// Bounded so the answer stays well below what the runtime carries back in one reply.
n := 100
if v, ok := a["lines"].(float64); ok && v >= 1 {
n = min(int(v), 2000)
}
return m.Journal(scope, unit, n)
}},
{Name: "systemd_failed",
Description: "Every failed unit on this machine, in the system manager and in the operator account's; a manager that does not answer is reported with its error, not as nothing failed.",
Run: func(map[string]any) (any, error) { return m.Failed(), nil }},
}
}
func fromEnv() *Manager {
me, _ := user.Current()
name := ""
if me != nil {
name = me.Username
}
account := strings.TrimSpace(os.Getenv("MESH_OPERATOR_ACCOUNT"))
if account == "" {
account = name
}
uid := os.Getuid()
if me != nil {
if n, err := strconv.Atoi(me.Uid); err == nil {
uid = n
}
}
return &Manager{Account: account, UID: uid, User: name, Run: execRunner, Read: readFile, Env: os.Environ()}
}
func main() {
if err := stdio.Serve("", tools(fromEnv())); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}