Documentation
¶
Overview ¶
Package fleetreg pushes this host's fleet inventory — the tools, agents, and skills it has installed — up to cloudbox.
It is the counterpart of internal/agent/ollama's model-registry watcher, and deliberately the same shape: the host is the source of truth about itself, cloudbox caches that truth behind a freshness clock, and a content hash lets an unchanged snapshot cost one UPDATE instead of a rewrite.
The inventory is read straight out of coreutils/pkg/fleet rather than by shelling out to `bashy`. Outpost already depends on coreutils, so the registry is a library call — and a host that has no bashy binary installed still has a fleet, because the compiled-in baseline is part of the library.
Index ¶
Constants ¶
const ( // PlacementSelf — this host hosts the apiserver. PlacementSelf = "self" // PlacementPeer — a control plane on another of the user's own machines, // reached through a tunnel that lands on loopback. PlacementPeer = "peer" // PlacementCloudbox — the hosted control plane. PlacementCloudbox = "cloudbox" // PlacementExternal — anything else: a rented always-on box, a managed // provider cluster. PlacementExternal = "external" )
Placement names WHERE the control plane this host joined actually runs.
It exists because placement became a user choice (dhnt/docs/dks-control-plane-on-sphere.md): the same host, running the same agent, may be attached to a cloudbox-hosted plane today and a peer-hosted one tomorrow. Reporting it is what makes that move VISIBLE — otherwise a node silently changes which cluster it belongs to and the only way to find out is to go read its config.
Variables ¶
This section is empty.
Functions ¶
func ClassifyPlacement ¶
ClassifyPlacement decides where the joined control plane runs.
Order matters: the control-plane host's own endpoint is loopback too, so the ControlPlane flag has to be consulted before the loopback test or every control plane would report itself as a peer.
KNOWN LIMIT, stated rather than papered over: a host that joins a k3s it runs itself but never sets control_plane reports `peer`. The cluster IDENTITY is still correct (it comes from the CA), so grouping holds — only the placement label is wrong, and it is wrong because the config omitted the fact.
func ContentHash ¶
ContentHash is a deterministic sha256 over the reported inventory.
The push order cannot change it (assets are sorted), and no timestamp enters it — the whole point is that an unchanged host hashes the same across polls, so cloudbox can skip the rewrite and just move the freshness clock.
Types ¶
type Asset ¶
type Asset struct {
Kind string `json:"kind"`
Name string `json:"name"`
Display string `json:"display,omitempty"`
Detail string `json:"detail,omitempty"`
}
Asset is one reported tool / agent / skill.
type ClusterInfo ¶
type ClusterInfo struct {
// Placement is one of the constants above.
Placement string `json:"placement"`
// Endpoint is the apiserver URL as THIS host reaches it. For a peer-hosted
// plane that is a loopback address whose port is a local tunnel artifact —
// meaningful for debugging this host, meaningless as a cluster identity,
// which is precisely why it is not the identity.
Endpoint string `json:"endpoint,omitempty"`
// ControlPlane reports whether this host runs the apiserver.
ControlPlane bool `json:"control_plane,omitempty"`
// Nodes are the Kubernetes node names this host registers. One host owns
// many nodes: one k3s agent plus one per virtual-kubelet backend.
Nodes []string `json:"nodes,omitempty"`
// Runtimes are the enabled backends (agent, vk-native, vk-podman, …).
Runtimes []string `json:"runtimes,omitempty"`
// HostsControlPlane reports that this host RUNS a control plane. It is a
// fact about the host, deliberately separate from the row's own cluster:
// hosting and joining became independent decisions, so a host can run a
// plane for others while being a node of somebody else's.
//
// ControlPlane above stays the narrower claim — "this host hosts THE
// CLUSTER IN THIS ROW" — because that is what the inventory needs to name
// a cluster's control-plane member.
HostsControlPlane bool `json:"hosts_control_plane,omitempty"`
// ControlPlaneEndpoint is where that hosted plane's tunnel listens, when
// this host runs one.
ControlPlaneEndpoint string `json:"control_plane_endpoint,omitempty"`
// CA is the cluster's TLS CA bundle. Never serialized — it is read only to
// derive the cluster identity below.
CA []byte `json:"-"`
// NodeToken is the k3s join token. Never serialized — it is a CREDENTIAL,
// read only for the CA hash it embeds.
NodeToken string `json:"-"`
}
ClusterInfo is what a host reports about its Kubernetes participation.
It is deliberately a REPORT, not a definition: an outpost may say which cluster it is in, it may not author the cluster. That is the same report/author split the *:registry vs *:write scopes encode, and it is why this rides the existing fleet-registry push instead of getting an endpoint that could be mistaken for a control API.
func (*ClusterInfo) ClusterID ¶
func (c *ClusterInfo) ClusterID() string
ClusterID is the stable identity of the cluster this host joined.
IT IS THE CA FINGERPRINT, NOT THE URL, and that choice is load-bearing. Every member of a cluster trusts the SAME apiserver CA, but members do not agree on a URL: a node reaching a peer-hosted plane through a tunnel sees `https://127.0.0.1:<locally-derived-port>`, and two nodes on one cluster derive DIFFERENT ports. Keying on the URL would split one cluster into one pseudo-cluster per node — the inventory would report N clusters of one node each and the grouping the page exists for would be silently wrong.
The CA is a public certificate, so hashing it leaks nothing; the hash is used only so the identity is short and fixed-width.
Falling back to the URL host is correct for the case that produces an empty CA — cloudbox fronting the apiserver behind a real, publicly-trusted cert — because there every member DOES agree on the URL. A JOIN TOKEN ALREADY CARRIES THE CLUSTER'S CA HASH, and it is the right source when joining a peer plane. k3s node tokens are `K10<sha256-of-cluster-CA>::<user>:<secret>`, so the identity is available even though the peer's CA bundle itself is not — a worker joining someone else's plane holds a token but no CA (its ClusterConfig.CA belongs to the cloudbox pairing, a DIFFERENT cluster). Reading it here is what stops a peer-joined host from being grouped under the cloudbox cluster whose CA it happens to be carrying.
type Config ¶
type Config struct {
// CloudboxURL is the base URL (e.g. https://ai.dhnt.io).
CloudboxURL string
// AccessToken is the per-outpost bearer JWT. It must carry
// fleet:registry — the scope minted for a HOST reporting a fact about
// itself, not for a user authoring a definition.
AccessToken string
// AgentName is the host name cloudbox knows this outpost by.
AgentName string
PollInterval time.Duration
HeartbeatInterval time.Duration
HTTP *http.Client
// Catalog is indirected for tests. Nil uses the host's real registry.
Catalog func() *fleet.Catalog
// Skills lists the skills installed in the host's local store. Nil uses
// the default reader.
//
// It reads directory names rather than importing coreutils/pkg/skills:
// that package pulls the dhnt skill-CNL runtime, and outpost is the lean
// mesh supervisor. A name is all the inventory needs — a peer asking
// "does host-a have the conductor skill" wants a yes, not the procedure.
Skills func() []string
// Cluster reports this host's Kubernetes participation, or nil when it
// joins none. Indirected like Catalog/Skills so the watcher stays free of
// conf coupling and testable without a config file.
Cluster func() *ClusterInfo
}
Config configures the watcher.
type PushError ¶
PushError carries the HTTP status so the caller can honor the endpoint's contract: 401 means stop (revoked or unknown token), 403 means the token lacks fleet:registry, 404 means this host is not paired under that owner. Only 5xx is worth retrying.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher polls the local fleet registry and pushes changes.
func (*Watcher) Push ¶
Push sends one snapshot. It returns the server's `applied` verdict ("replaced" or "touched") so a caller can log what actually happened.
func (*Watcher) Run ¶
Run polls until ctx is cancelled, pushing when the inventory changes and on a heartbeat so the freshness clock keeps ticking.
A failed push is logged and retried on the next tick; it never takes the daemon down. The inventory is a convenience for peers, not a dependency of anything running on this host.