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 + ")" }