# swarm-logs An agent's CLI for the swarm log store. One verb — `swarm-logs query ''` — so reading logs pipes and greps like any other command instead of being a hand-rolled token request plus a curl, per query. Read-only and query-only. There is no ingest path here (that is the collector's), and no `tail`: streaming is a different LogsQL endpoint with a different response shape, left for a follow-up rather than folded in. Distinct from `hive-metric`, the other half of the same surface from an agent's seat: that one _writes_ a custom metric, this one _reads_ logs. ## ⚠️ There is no scoping The gateway forwards the query unmodified, so **any authenticated caller reads the whole swarm's logs** — every hive's, not only its own. That is the rule in force rather than an omission here; when read permissions exist they attach at the gateway location, not in this binary. ## How it authenticates As the agent itself. On each query it reads the agent's own queue secret, `swarm/agents//queue`, from the swarm secret store with the agent's store certificate (`nix/agent-modules/bao.nix`), the same read `hive-agent` makes for its queue connection. It sends the secret as `Authorization: Bearer swarm-agent..`, spelled by `swarm_queue_client::agent_token`. At the gateway, nginx hands a bearer of that shape to `swarm-logs-auth`, which compares it with the stored secret (`swarm-agent-verify`, the check the queue's auth callout also uses). A 401 makes `swarm-logs` read the secret again and retry once, which covers a secret re-minted between the read and the check. `swarm-logs-auth` grants a secret it has verified for 60 s by default without asking the store again, so a replaced secret also keeps working for up to that long. A 403 is the gateway's per-address rate limit on these checks; it is not retried. 🩸 The secret is never logged or rendered into an error. ## Configuration Supplied by `nix/agent-modules/logs.nix`, which wraps the binary — the same shape `swarmctl` is configured in, and for the same reason: every value is derived from an option that module owns, so a default here would be an address we _hope_ points at something. | variable | what | | --------------------------- | ------------------------------------------------ | | `HIVE_AGENT_LOGS_QUERY_URL` | the LogsQL query endpoint | | `HIVE_AGENT_NAME` | this agent's name; the store path and role | | `BAO_ADDR` | the secret store's address | | `BAO_CLIENT_CERT` | path of the agent's live store certificate | | `BAO_CLIENT_KEY` | path of its key | | `BAO_CACERT` | path of the store's CA; missing or empty is fine | With no `HIVE_AGENT_LOGS_QUERY_URL` the binary says there is no log store; with the URL and no store address it reports a deployment bug by name. ## Output ```console $ swarm-logs query 'atlas-otel-probe' --limit 5 ``` One log message per line by default (the record's `_msg`), which is what a grep pattern is written against. `--format json` passes the store's NDJSON through unmodified, for `_time`, `_stream` and `jq`. A record the projection cannot understand — no string `_msg`, or not JSON at all — is printed whole rather than dropped. Silently losing a row would make an incomplete answer indistinguishable from a complete one. A non-200 is reported with its body and a non-zero exit, never as an empty result. That is the whole reason the gateway grew a separate machine route: the operator's browser route answers an unauthenticated caller with authelia's login page as a **200 with an HTML body**, and a client that reads only the status code records a query that succeeded and matched nothing.