// inventory.go — the fleet inventory record and the Store abstraction. // // The Host record is the join key the rest of the fleet-manager hangs off. It is // BORN at enrolment approval (identity/binding/enrolled), then FILLED by the // steady-state agent stream (version/health/usage) and by admin action (desired). // Schema mirrors docs/fleet-management-design.md "Inventory record" and // docs/tpm-enrolment-design.md "Inventory record born at enrolment". // // Keyed by the TPM cert SERIAL (lowercase hex) — identical to the value the NVMe // hostnqn derives from and the value the agent's mTLS leaf presents, so the cert // an agent shows IS its inventory key (no identity translation). See §5 identity // discussion. package main import "time" // Identity — minted at enrolment, immutable afterwards. CertSerial is the primary // key; Hostnqn = HOSTNQN_BASE + CertSerial (as the broker computes it on approve). type Identity struct { CertSerial string `json:"cert_serial"` // lowercase hex — PRIMARY KEY Hostnqn string `json:"hostnqn,omitempty"` // nqn...:host- MAC string `json:"mac,omitempty"` // advisory (DeviceContext at enrol) EKHash string `json:"ek_hash,omitempty"` // TPM EK hash (advisory) StarshipName string `json:"starship_name,omitempty"` // dnsmasq fleet-namer join } // Binding — the physical↔crypto tie the human records during the OOB approval. type Binding struct { AssetTag string `json:"asset_tag,omitempty"` SwitchPort string `json:"switch_port,omitempty"` Location string `json:"location,omitempty"` } // Enrolled — provenance of the approval that birthed this record. type Enrolled struct { Approver string `json:"approver,omitempty"` // admin CN from the approve mTLS At time.Time `json:"at,omitzero"` // approval timestamp Fingerprint string `json:"fingerprint,omitempty"` // the human-verified anchor } // Version — what the host is actually running (agent-reported). type Version struct { ImageVersion string `json:"image_version,omitempty"` RootfsDigest string `json:"rootfs_digest,omitempty"` Kernel string `json:"kernel,omitempty"` } // Health — the LATEST snapshot only. The full time-series lives in InfluxDB // (written by the manager as agent reports arrive); this cached last value is // what the fleet-overview list renders without a per-host Flux query. type Health struct { LoadAvg float64 `json:"load_avg,omitempty"` CPUPct float64 `json:"cpu_pct,omitempty"` TempC float64 `json:"temp_c,omitempty"` MemUsedPct float64 `json:"mem_used_pct,omitempty"` TmpfsUsedPct float64 `json:"tmpfs_used_pct,omitempty"` // overlay RAM pressure (ties to #17) NVMeRoot string `json:"nvme_root,omitempty"` // ok|degraded|down Auth string `json:"auth,omitempty"` // 802.1x/macsec: up|down SELinux string `json:"selinux,omitempty"` // enforcing|permissive Chrony string `json:"chrony,omitempty"` // synced|unsynced ChronyOffset float64 `json:"chrony_offset_seconds,omitempty"` // system time offset from NTP (signed, fast=+) ChronySource string `json:"chrony_source,omitempty"` // NTP reference source name/IP ChronyStratum int32 `json:"chrony_stratum,omitempty"` // hops from a reference clock KioskActive bool `json:"kiosk_active"` UptimeSeconds int64 `json:"uptime_seconds,omitempty"` // host uptime at last report; boot_time = last_seen - this (reboot-pending) } // Usage — kiosk-session facts (agent-reported). type Usage struct { KioskURL string `json:"kiosk_url,omitempty"` SessionUptime string `json:"session_uptime,omitempty"` ChromiumCrashes int `json:"chromium_crashes,omitempty"` Display string `json:"display,omitempty"` } // Desired — SERVER-AUTHORITATIVE target state. Set via the admin lifecycle API, // persisted here, and pushed to the host over its live agent stream (or delivered // on its next reconnect if offline). Inventory is the source of truth; the stream // is only the delivery optimisation. type Desired struct { TargetVersion string `json:"target_version,omitempty"` KioskURL string `json:"kiosk_url,omitempty"` RebootAt *time.Time `json:"reboot_at,omitempty"` Drain bool `json:"drain,omitempty"` UpdatedBy string `json:"updated_by,omitempty"` // admin CN UpdatedAt time.Time `json:"updated_at,omitzero"` } // Host is the full inventory record. type Host struct { Identity Identity `json:"identity"` Binding Binding `json:"binding"` Enrolled Enrolled `json:"enrolled"` Version Version `json:"version"` Health Health `json:"health"` Usage Usage `json:"usage"` Desired Desired `json:"desired"` FirstSeen time.Time `json:"first_seen"` LastSeen time.Time `json:"last_seen"` // last agent report / stream activity Online bool `json:"online"` // agent stream currently connected } // Store is the inventory persistence abstraction. The v1 implementation is a // JSON-file store (jsonStore); the interface keeps a DB swap a drop-in later. // All methods are safe for concurrent use. type Store interface { // List returns a snapshot of every host (copies — safe to mutate). List() []*Host // Get returns a copy of one host by cert serial. Get(serial string) (*Host, bool) // Upsert creates or replaces a host record (used by enrolment approval). Upsert(h *Host) error // SetDesired replaces the desired-state block and returns the updated host. SetDesired(serial string, d Desired) (*Host, bool, error) // ReportState applies an agent snapshot (version/health/usage + last-seen). ReportState(serial string, v Version, h Health, u Usage, at time.Time) (*Host, bool, error) // SetOnline flips the live-stream presence flag (and stamps last-seen on connect). SetOnline(serial string, online bool, at time.Time) }