From b7243505ca68c5ddf585e397e392e5ee3fbc2e3f Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 21 Oct 2026 02:05:21 +0200 Subject: [PATCH] Carry brother's certificate check on a watch process beside the device unit check, as fail2ban does (issue 1510) --- modules/brother/cmd/brother/main.go | 16 ++++++++++++++++ modules/brother/cmd/brother/manifest_test.go | 18 +++++++++++++++--- modules/brother/module.json | 14 ++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/modules/brother/cmd/brother/main.go b/modules/brother/cmd/brother/main.go index 1f137aab..d33a82ad 100644 --- a/modules/brother/cmd/brother/main.go +++ b/modules/brother/cmd/brother/main.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "os/signal" + "syscall" "time" stdio "git.novox.be/novox/mesh-sdk/go" @@ -13,12 +15,26 @@ func main() { if len(os.Args) > 1 && os.Args[1] == "device" { os.Exit(device()) } + if len(os.Args) > 1 && os.Args[1] == "watch" { + os.Exit(watch()) + } if err := stdio.Serve("", tools(os.Getenv("BROTHER_SETTINGS"), time.Now)); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } +// watch is `brother watch`: the process that carries the tool check brother_health beside the device process's unit +// check, as fail2ban's watch does (ADR 0240: a tool check is only beside a check the module does not run itself). +// It does nothing itself; the node-engine asks the tool, and this stays up until it is stopped. +func watch() int { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + fmt.Fprintln(os.Stderr, "[brother] watch is up; the node-engine asks brother_health") + <-ctx.Done() + return 0 +} + // intArg reads a whole number out of a tool's arguments: a JSON number, which arrives as a float64. func intArg(args map[string]any, name string) (int, error) { v, ok := args[name] diff --git a/modules/brother/cmd/brother/manifest_test.go b/modules/brother/cmd/brother/manifest_test.go index ccccf852..6782b773 100644 --- a/modules/brother/cmd/brother/manifest_test.go +++ b/modules/brother/cmd/brother/manifest_test.go @@ -256,9 +256,7 @@ func TestTheAdminProcessIsDeclaredAsMeshMailsSenderIs(t *testing.T) { if !reflect.DeepEqual(bus["queues"], []any{"device-asks"}) || !reflect.DeepEqual(bus["state"], []any{"device-answers"}) { t.Errorf("bus = %v", bus) } - // 1510: liveness is judged for every process without a declaration (ADR 0240 ยง1); the declared check is the - // tool that compares the printer's certificates with the pins. - if h, _ := p["health"].(map[string]any); h["kind"] != "tool" || h["tool"] != "brother_health" { + if h, _ := p["health"].(map[string]any); h["kind"] != "unit" { t.Errorf("health = %v", p["health"]) } env, _ := p["env"].(map[string]any) @@ -397,3 +395,17 @@ func TestEachConfigPatternAdmitsARealValueWhole(t *testing.T) { } } } + +// 1510: the certificate check is a tool check on a process of its own beside the device process's unit check, as +// fail2ban's watch is (ADR 0227 rule 8, ADR 0240: a tool check only beside a check of another kind). +func TestTheCertificateCheckIsAToolCheckBesideTheUnitCheck(t *testing.T) { + m := readManifest(t) + w := resource(t, m, "watch") + h, _ := w["health"].(map[string]any) + if w["type"] != "process" || w["artifact"] != "tools" || h["kind"] != "tool" || h["tool"] != "brother_health" || !reflect.DeepEqual(w["run"], []any{"./brother", "watch"}) { + t.Errorf("watch = %v", w) + } + if !slices.Contains(m.Tools, "brother_health") { + t.Errorf("tools = %v", m.Tools) + } +} diff --git a/modules/brother/module.json b/modules/brother/module.json index 0e252c94..57559d56 100644 --- a/modules/brother/module.json +++ b/modules/brother/module.json @@ -230,6 +230,20 @@ "scan.added" ] }, + "health": { + "kind": "unit" + } + }, + { + "id": "watch", + "type": "process", + "name": "brother-watch", + "artifact": "tools", + "run": [ + "./brother", + "watch" + ], + "user": "brother-device", "health": { "kind": "tool", "tool": "brother_health",