package main // The watch (novox/hq ADR 0339 §2 and §4, issue 238): the module's long-running process, as root. Every look it // // - writes the operator's own addresses from the setting into the list every jail ignores (own.go); // - asks the daemon whether every jail's ignore list names that list, so no jail leaves it out; // - reads what the daemon logged since the last look, and keeps every failure a jail ignored because the // address is one of the operator's own, for a day. // // What it saw goes to StateFile, which the health tool reads as the operator's account. A failure from one // of the operator's own addresses is never banned — that is the point — and never silent: the module's health // says it, so the controller raises it as a warning naming the node, the jail, the address and the count, and // it clears a day after the last one. A loop like the one of 2026-10-04 is then seen within minutes. import ( "bufio" "context" "encoding/json" "fmt" "io" "os" "path/filepath" "regexp" "sort" "strings" "time" ) // The watch's timing. var ( // LookEvery is how often the watch looks. LookEvery = 30 * time.Second // Remembered is how long an ignored failure is said: a day, as the record has it. Remembered = 24 * time.Hour // StaleAfter is how old the watch's last look may be before the health says it stopped looking. StaleAfter = 3 * time.Minute // keptPerAddress bounds what is remembered of one address in one jail; the count says when it met it. keptPerAddress = 10000 ) // IgnoredFailures is the failures one jail ignored from one of the operator's own addresses, in the last day. type IgnoredFailures struct { Jail string `json:"jail"` Address string `json:"address"` Count int `json:"count"` First time.Time `json:"first"` Last time.Time `json:"last"` // AtLeast says the count met what is remembered of one address and is a floor. AtLeast bool `json:"at-least,omitempty"` } // State is what the watch saw at its last look. type State struct { Looked time.Time `json:"looked"` // Own is the operator's own addresses as written into the list, and Problems the entries left out. Own []string `json:"own"` Problems []string `json:"problems,omitempty"` // Unread is why something the watch reads could not be read at this look. Unread []string `json:"unread,omitempty"` // Jails is how many jails the daemon runs; Missing those whose ignore list does not name the list. Jails int `json:"jails"` Missing []string `json:"missing,omitempty"` // Ignored is every jail and address of the operator's own with a failure ignored in the last day. Ignored []IgnoredFailures `json:"ignored"` } // ignoredLine is the daemon's line for a failure it ignored: `