// The dnsmasq module's Go bundle: one of the mesh's resolvers (novox/hq ADR 0194, 0223). // // Started with no arguments it is served by the node's runtime as the operator account, over MCP on stdio // (ADR 0188, ADR 0193): the module's tools and its health, and beside them the events the resolver's // answers give — a machine's name it now answers or no longer does, and a restart the watch made. Started // as `dnsmasq-tools watch` it is the module's long-running process, as root: it asks the resolver a real // question on every listen address and restarts it, within a budget, when it stops answering (watch.go, // novox/hq issue 348). stdout is the MCP channel; everything else is said on stderr. package main import ( "context" "fmt" "os" "os/signal" "syscall" "time" stdio "git.novox.be/novox/mesh-sdk/go" ) func main() { if len(os.Args) > 1 { if os.Args[1] != "watch" || len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "usage: dnsmasq-tools [watch]") os.Exit(2) } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) defer stop() m := Here() if err := m.Watch(ctx, ExecRunner, m.WriteState); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } return } m := Here() go Announce(m, stdio.Emit) if err := stdio.Serve("", Tools(m)); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } // AnnounceEvery is how often the bundle reads the roster and the watch for what to say. var AnnounceEvery = 30 * time.Second // Announcer remembers what was said. The names are primed silently, so the bundle starting again does not // say every name again; the watch's restarts are not — a restart made while the bundle was down is said when // it starts (the watch keeps the last hour's). Nothing is remembered as said until its emit succeeded. type Announcer struct { names map[string]string primed bool // said is every restart said, by when it was made; judged every one whose outcome was said. said, judged map[time.Time]bool } // Event is one event to emit, and what saying it records. type Event struct { Type string Body map[string]any said func() } // Next is what to say since the last read: names the resolver now answers (`name.added`) or no longer does // (`name.removed`) — the mesh's names having reached this resolver, apart from the controller's own events; // restarts the watch made (`restarted`), with why; and, once the looks after a restart decide it, whether it // helped (`restart-judged`). read says the roster was read: an unreadable roster says nothing about names, // rather than every name removed. An event is remembered by Said, once it was emitted. func (a *Announcer) Next(names []Named, read bool, s State) []Event { if a.said == nil { a.said, a.judged, a.names = map[time.Time]bool{}, map[time.Time]bool{}, map[string]string{} } var out []Event if read { now := map[string]string{} for _, n := range names { now[n.Name] = n.Address } if !a.primed { a.names, a.primed = now, true } else { for name, address := range now { if _, had := a.names[name]; !had { out = append(out, Event{"name.added", map[string]any{"name": name, "address": address}, func() { a.names[name] = address }}) } } for name := range a.names { if _, has := now[name]; !has { out = append(out, Event{"name.removed", map[string]any{"name": name}, func() { delete(a.names, name) }}) } } } } for _, r := range s.Restarts { at := r.At if !a.said[at] { body := map[string]any{"unit": Unit, "at": at, "why": r.Why} if r.Error != "" { body["error"] = r.Error } out = append(out, Event{"restarted", body, func() { a.said[at] = true }}) } if r.Helped != nil && !a.judged[at] { out = append(out, Event{"restart-judged", map[string]any{"unit": Unit, "at": at, "helped": *r.Helped}, func() { a.judged[at] = true }}) } } return out } // Said remembers an event as said. func (e Event) Said() { if e.said != nil { e.said() } } // Announce says the events for as long as the bundle runs. func Announce(m *Machine, emit func(string, any, ...stdio.EmitOptions) error) { time.Sleep(2 * time.Second) // the SDK reaches the bus once Serve runs var a Announcer for { Tell(m, &a, emit) time.Sleep(AnnounceEvery) } } // Tell is one read and what it says: an event that could not be emitted is said again at the next read. func Tell(m *Machine, a *Announcer, emit func(string, any, ...stdio.EmitOptions) error) { var names []Named text, err := m.read(NodesFile) if err == nil { names = AnsweredNames(text) } s, _, _ := m.ReadState() for _, e := range a.Next(names, err == nil, s) { if err := emit(e.Type, e.Body); err != nil { fmt.Fprintf(os.Stderr, "dnsmasq: %s not said, said again at the next read: %v\n", e.Type, err) continue } e.Said() } }