Files
mesh-catalog/modules/power/cmd/power/check.go
T
jochen ac1a6fca38 power: the sleep hooks are wanted by the sleep targets, not declared as services
The host reads a one-shot that is not running as having run, so a before-sleep or after-wake unit
declared stopped failed on its first apply; drop-ins on the sleep targets pull them in instead.
2026-10-04 17:22:35 +02:00

91 lines
3.3 KiB
Go

package main
import (
"context"
"fmt"
"sort"
"strings"
)
// Check is one thing the module expects of the machine.
type Check struct {
Name string `json:"name"`
OK bool `json:"ok"`
Detail string `json:"detail"`
}
// Check says what this module expects and whether the machine meets it: its units in place and
// enabled, the runner and the moment files present, logind's file the mesh's, the inhibitor held,
// and the predecessor's leftovers.
func (m *Machine) Check(ctx context.Context, w *Watcher) map[string]any {
var checks []Check
add := func(name string, ok bool, format string, args ...any) {
checks = append(checks, Check{Name: name, OK: ok, Detail: fmt.Sprintf(format, args...)})
}
units := make([]string, 0, len(Units))
for u := range Units {
units = append(units, u)
}
sort.Strings(units)
for _, u := range units {
present := m.exists("/etc/systemd/system/" + u)
enabled, _ := m.Run(ctx, "systemctl", "is-enabled", u)
enabled = strings.TrimSpace(enabled)
want := "enabled"
switch u {
case "mesh-power-supply.service":
want = "static" // started by the udev rule, never by a target
case "mesh-power-before-sleep.service", "mesh-power-after-wake.service":
want = "static" // wanted by the sleep targets through the module's drop-ins, never enabled
}
add(u, present && enabled == want, "%s: file present %v, %s (want %s)", Units[u], present, orWord(enabled, "unknown"), want)
}
add("runner", m.exists(RunnerPath), "%s present: %v", RunnerPath, m.exists(RunnerPath))
for _, moment := range Moments {
p := MomentsDir + "/" + moment
add("moment "+moment, m.exists(p), "%s placed: %v, %d piece(s)", p, m.exists(p), len(Pieces(m.read(p)+"\n")))
}
logind := m.read(LogindFile)
add("logind's power file", strings.Contains(logind, "module power"), "%s is the mesh's (module power): %v",
LogindFile, strings.Contains(logind, "module power"))
var others []string
for _, f := range m.glob("/etc/systemd/logind.conf.d/*.conf") {
if f != LogindFile {
others = append(others, f)
}
}
add("one writer of logind's power handling", len(others) == 0,
"other logind drop-ins: %s (novox/hq ADR 0210: logind's power settings are this module's alone)", orWord(strings.Join(others, ", "), "none"))
hooks := m.glob("/etc/systemd/system-sleep/*")
add("no hand-placed sleep hooks", len(hooks) == 0,
"%s (code around sleep is a contribution to node-power now, novox/hq ADR 0211)", orWord(strings.Join(hooks, ", "), "none"))
add("udev rule", m.exists(UdevRule) || Source(m.Supplies()) == "none", "%s present: %v (only acts where there is a Mains supply)", UdevRule, m.exists(UdevRule))
if w != nil {
s := w.Snapshot()
add("sleep inhibitor", s.Inhibiting, "delay lock on sleep and shutdown held by the watcher: %v%s", s.Inhibiting, orNote(s.Problem))
add("events reach the bus", s.Pending == 0, "%d event(s) waiting for the bus", s.Pending)
}
failing := 0
for _, c := range checks {
if !c.OK {
failing++
}
}
return map[string]any{"checks": checks, "failing": failing,
"not_checked": []string{"that a contribution's own code does what it means (power_run runs one moment now)"}}
}
func orWord(s, word string) string {
if strings.TrimSpace(s) == "" {
return word
}
return s
}
func orNote(s string) string {
if s == "" {
return ""
}
return " (" + s + ")"
}