Files
jcoffey-dev 4f0da1ae5e Add agent inventory, management, and remote config
Extends the heartbeat mechanism with a second gRPC service on the same
mTLS channel (AgentControl.CheckIn, agent-initiated on the existing
heartbeat ticker -- still push-only, no inbound port on any agent) so
an agent reports its running config and can pick up an operator-set
override. A new web UI section (/agents) lists every agent that's
checked in, shows its reported config, and lets an operator edit a
narrow, deliberately-scoped subset remotely: batch/heartbeat tuning,
and (journald sources only) the unit filter.

TLS material and the ingest endpoint are never reportable or remotely
editable, by proto shape rather than a validation rule -- a bad or
malicious edit there could permanently strand an agent or redirect
where its logs go, unlike every other editable field, which only
degrades behavior.

An override lives only in the agent's memory (agent.toml is never
rewritten) and re-syncs on the agent's own schedule; changing the
journald filter aborts and respawns the source task since there's no
other way to change what's being tailed. Building the hot-reload path
surfaced a real, independent, pre-existing bug: shutdown was using
poll_timeout(), which only drains once flush_interval has elapsed,
silently dropping anything buffered more recently on every graceful
shutdown that landed between flushes -- fixed with a new unconditional
Batcher::flush_all(), now used at both shutdown and hot-reload.

Verified live end-to-end against a real stack: an edited heartbeat
interval changed a running agent's actual send cadence within one
check-in cycle (confirmed by the real timestamps landing in
ClickHouse), and an edited journald filter triggered a real source
restart, both reflected back in the next reported-config snapshot.

See /docs/agent-management-design.md.
2026-08-16 18:08:51 -07:00

38 lines
1.9 KiB
SQL

-- Agent inventory + remote config (see /docs/agent-management-design.md).
-- One row per (tenant, host), upserted by ingest on every CheckIn RPC.
-- tenant_id defaults to 'default' for single-tenant deployments with no
-- TenantResolver configured, same pattern dashboards/alert_rules already
-- established in Phase 3/4.
--
-- desired_override/desired_override_version are written by api (the web
-- UI's edit form); reported_* and last_seen_at are written by ingest (an
-- agent's own CheckIn). applied_override_version is written by ingest
-- too, but its value comes from the agent itself (CheckInRequest.
-- applied_override_version) -- it's what lets the web UI tell "pending"
-- (desired_override_version != applied_override_version) apart from
-- "applied" without either service needing to poll the other.
CREATE TABLE IF NOT EXISTS agents
(
id UUID PRIMARY KEY,
tenant_id TEXT NOT NULL REFERENCES tenants(id),
host TEXT NOT NULL,
service TEXT NOT NULL DEFAULT '',
reported_agent_version TEXT NOT NULL DEFAULT '',
reported_source_kind TEXT NOT NULL DEFAULT '',
reported_source_detail TEXT NOT NULL DEFAULT '',
reported_batch_max_size BIGINT NOT NULL DEFAULT 0,
reported_batch_flush_ms BIGINT NOT NULL DEFAULT 0,
reported_heartbeat_on BOOLEAN NOT NULL DEFAULT true,
reported_heartbeat_ms BIGINT NOT NULL DEFAULT 0,
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
-- NULL desired_override_version means no override has ever been set
-- -- CheckInResponse.has_override is false and the agent runs its
-- local agent.toml untouched.
desired_override JSONB,
desired_override_version TEXT,
applied_override_version TEXT NOT NULL DEFAULT '',
updated_by TEXT,
UNIQUE (tenant_id, host)
)