NodeHealth.Root: each such account's verdict (healthy, unhealthy with its way to root, unknown while the setuid search runs), the module and resource declaring it. NodeHealth.Contract: the engine's statement contract, so a reader tells an engine older than the judging. Test: a row in TestTheControlRecordsReadTheirWritersValues, read back byte-equal.
175 lines
7.6 KiB
Go
175 lines
7.6 KiB
Go
package delivery
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// **A node's own health report on the bus** (issue 774, the operator's alert table of 2026-10-29): one key per node,
|
|
// `<node>`, of the node-engine's state `health` (NodeHealthBucket), written by that node's engine alone and only when
|
|
// what it says changed. The watchman (seat mesh-watchdogs) reads it for six alerts the controller raised from the
|
|
// passing health message: machine-network (Network), network-rewritten (the resolver file's part, Writer and Owner),
|
|
// network-unreachable (Peers, read across every node's report), machine-units (Units), relogin-needed (Relogin) and
|
|
// directory-used-as-found (Found). Moved here from mesh-host internal/link (65fbc1e6) unchanged; the engine builds a
|
|
// report from its own health statement there (NodeHealthOf).
|
|
|
|
// NodeHealthState and NodeHealthBucket are the node-engine's state health, as a reader names it and as the bus does.
|
|
const (
|
|
NodeHealthState = "health"
|
|
NodeHealthBucket = "mesh-host_" + NodeHealthState
|
|
NodeHealthRead = "mesh-host." + NodeHealthState
|
|
)
|
|
|
|
// NodeHealthVersion is the version of NodeHealth's layout.
|
|
const NodeHealthVersion = 1
|
|
|
|
// NodeHealth is one node's health report, as its engine last found it.
|
|
type NodeHealth struct {
|
|
V int `json:"v"`
|
|
Node string `json:"node"`
|
|
// Network is the machine's networking, its parts without their streaks; nil where the engine does not judge it yet.
|
|
Network *NetworkHealth `json:"network,omitempty"`
|
|
// Peers are the mesh tunnel's peers on the last look that read them, sorted by key.
|
|
Peers []PeerSeen `json:"peers,omitempty"`
|
|
// Units is the machine's service managers: each failed unit no module places.
|
|
Units *UnitsHealth `json:"units,omitempty"`
|
|
// Relogin are the accounts whose running session needs a new login for a group it was given.
|
|
Relogin []NodeAccount `json:"relogin,omitempty"`
|
|
// Found are the directories used as found.
|
|
Found []NodeDirectory `json:"found,omitempty"`
|
|
// Root are the engine's verdicts on each account a module declares never to become root without a person (novox/hq
|
|
// ADR 0266), sorted: the agent account's among them where a module declares it. What the self-check's DA and D-root
|
|
// judge an agent's confinement by, on the machine's own word.
|
|
Root []RootVerdict `json:"root,omitempty"`
|
|
// Contract is the engine's statement contract (mesh-host link's NodeKeyContract): a reader tells an engine older
|
|
// than a judging it reads (RootContract for Root) from one that judged nothing wrong.
|
|
Contract int `json:"contract,omitempty"`
|
|
// At is when the engine wrote this, on a change: never a reason to write.
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// NetworkHealth is the machine's networking (ADR 0241): the worst of its parts, since when, and each part.
|
|
type NetworkHealth struct {
|
|
State string `json:"state"`
|
|
Since time.Time `json:"since"`
|
|
Parts []NetworkPart `json:"parts"`
|
|
}
|
|
|
|
// NetworkPart is one part of a machine's networking, as its engine judged it on its second look.
|
|
type NetworkPart struct {
|
|
// Part is resolv-conf, names, tunnel, bus or route.
|
|
Part string `json:"part"`
|
|
State string `json:"state"`
|
|
// Reason is why it is not healthy, in words with no address, path or domain; Said the detail.
|
|
Reason string `json:"reason,omitempty"`
|
|
Said string `json:"said,omitempty"`
|
|
// Writer is the program that rewrote the resolver file, when it can be named; Owner the module whose file it is.
|
|
Writer string `json:"writer,omitempty"`
|
|
Owner string `json:"owner,omitempty"`
|
|
// Toward is what the failure points at: "hub", or each resolver's address that failed.
|
|
Toward []string `json:"toward,omitempty"`
|
|
// Targets are the mesh machines the failure points at, by node name, as the controller holds a part at the machine
|
|
// it points at: the hub's machine for "hub", and the machine whose tunnel address is each resolver's. Empty where the
|
|
// part is the machine's own: it points at nothing, at this machine, or at an address no other mesh machine has (the
|
|
// resolver a VPN client wrote in). Filled by the node-engine from its tunnel's peers, so the watchman needs no
|
|
// address of any machine (issue 774).
|
|
Targets []string `json:"targets,omitempty"`
|
|
Since time.Time `json:"since"`
|
|
Streak int `json:"streak,omitempty"`
|
|
}
|
|
|
|
// UnitsHealth is the machine's service managers (issue 315).
|
|
type UnitsHealth struct {
|
|
// State is running, degraded or unknown.
|
|
State string `json:"state"`
|
|
// Failed is every unit failed on two looks in a row that no module places.
|
|
Failed []FailedUnit `json:"failed"`
|
|
// Unread names a manager that could not be read, with why; Away an account's own manager not running.
|
|
Unread []string `json:"unread,omitempty"`
|
|
Away []string `json:"away,omitempty"`
|
|
}
|
|
|
|
// FailedUnit is one failed unit no module places.
|
|
type FailedUnit struct {
|
|
Unit string `json:"unit"`
|
|
// Scope is system, or user: an account's own manager.
|
|
Scope string `json:"scope"`
|
|
Load string `json:"load,omitempty"`
|
|
Result string `json:"result,omitempty"`
|
|
Resource string `json:"resource,omitempty"`
|
|
Since time.Time `json:"since"`
|
|
}
|
|
|
|
// PeerSeen is one peer of the mesh's tunnel: its public key, its allowed addresses, and whether this machine saw a
|
|
// handshake with it within five minutes.
|
|
type PeerSeen struct {
|
|
Key string `json:"key"`
|
|
Addresses []string `json:"addresses,omitempty"`
|
|
Seen bool `json:"seen"`
|
|
}
|
|
|
|
// NodeAccount is one account marked relogin needed, with the module whose resource it is and why.
|
|
type NodeAccount struct {
|
|
Account string `json:"account"`
|
|
Module string `json:"module,omitempty"`
|
|
Resource string `json:"resource,omitempty"`
|
|
Reason string `json:"reason"`
|
|
Since time.Time `json:"since"`
|
|
}
|
|
|
|
// RootContract is the statement contract from which an engine judges an account's root (mesh-host link.RootContract).
|
|
const RootContract = 3
|
|
|
|
// RootVerdict is the engine's verdict on one account declared never to become root without a person: healthy (no way
|
|
// found), unhealthy (its reason starts "can become root without a person"), or unknown while its setuid search has not
|
|
// completed, with the module and resource declaring it.
|
|
type RootVerdict struct {
|
|
Account string `json:"account"`
|
|
Module string `json:"module"`
|
|
Resource string `json:"resource"`
|
|
State string `json:"state"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Since time.Time `json:"since"`
|
|
}
|
|
|
|
// NodeDirectory is one directory used as found: the module declaring it, its resource and path, and why.
|
|
type NodeDirectory struct {
|
|
Module string `json:"module"`
|
|
Resource string `json:"resource"`
|
|
Path string `json:"path"`
|
|
Reason string `json:"reason"`
|
|
Since time.Time `json:"since"`
|
|
}
|
|
|
|
// EncodeNodeHealth is h as JSON at its version.
|
|
func EncodeNodeHealth(h NodeHealth) ([]byte, error) {
|
|
h.V = NodeHealthVersion
|
|
if h.Node == "" {
|
|
return nil, errors.New("a node's health report names its node")
|
|
}
|
|
return json.Marshal(h)
|
|
}
|
|
|
|
// DecodeNodeHealth reads a report.
|
|
func DecodeNodeHealth(raw []byte) (NodeHealth, error) {
|
|
var h NodeHealth
|
|
if err := json.Unmarshal(raw, &h); err != nil {
|
|
return NodeHealth{}, fmt.Errorf("a node's health report is not readable: %w", err)
|
|
}
|
|
if h.V != NodeHealthVersion {
|
|
return NodeHealth{}, fmt.Errorf("a node's health report of version %d, and this reads %d", h.V, NodeHealthVersion)
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
// SameNodeHealth says two reports say the same, whenever each was written.
|
|
func SameNodeHealth(a, b NodeHealth) bool {
|
|
a.At, b.At = time.Time{}, time.Time{}
|
|
ra, errA := json.Marshal(a)
|
|
rb, errB := json.Marshal(b)
|
|
return errA == nil && errB == nil && bytes.Equal(ra, rb)
|
|
}
|