| Filename | Latest commit message | Latest commit date |
|---|---|---|
swarm-secret-publish and its path unit now run inside authelia's container, beside the mint, and read each client secret at the path authelia writes it. The host unit and host path unit, which read the secrets through the container's root on the host, are gone, and so is deploy.authelia.hostClientSecretDir, which only they read. The publisher keeps its store identity, the secret-publisher leaf. The host hands the leaf and the store's CA into the container as nspawn system credentials, and the unit loads them; where this host mints the leaf, the container waits for swarm-bao-pki. Authelia does not wait for the publisher, which retries every 30 seconds while the store is down. The container also runs the journal forwarder the other service containers run, pushing to a receiver of its own on the swarm collector (serviceProducers swarm-authelia) as a new machine OIDC client (swarm.authelia.otel.clientId) that authelia registers for itself and the publisher carries to the store. The fetch unit reads it back under the publisher's leaf, also handed to it as a system credential, so the swarm-secret-publisher policy gains read on that one leaf; its stanza keeps create and update, which the exact path would otherwise take from the services/* prefix. Without the leaf the forwarder is off. The container drops --link-journal=host, and the host collector stops reading it. The services' "nothing at <path>" errors name the publisher in authelia's container. |
||
| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
swarm-controller
The swarm-level daemon. Where hive-c0re owns the agents on one host, this
owns what is true across hives — so a swarm runs one of them and most hives
leave it off.
Opt-in per host via services.hyperhive.deploy.swarm-controller.enable, which is
deliberately not derived from
services.hyperhive.deploy.hive-controller.enable: turning it on is a statement
about swarm topology, not about whether this host runs a hive.
What it does
The swarm's control plane. The swarm UI and swarmctl are its clients.
- Hive directory — serves
swarm.hives(GET /api/hives) and what each hive last published about itself (GET /api/hives/status). - Agent roster and wanted state — every agent the swarm knows
(
GET /api/agents,/api/agents/status), and the state it declares for each one on its hive (up/offline/paused/destroyed,PUT /api/hives/{hive}/agents/{agent}/state). - Job graph — a
hive-jobqscheduler, served atGET /api/jobq/graph. Every provisioning step below runs as a node in it. - Agent creation —
POST /api/agentsqueues the SSO identity (throughswarm-authelia-bridge), forge user, config repo, store identity, forge token and matrix account, declares the agentpaused, then sends its hive a deploy message. - Agent credentials — at start and every five minutes it re-checks every agent's forge token and matrix account, and renews store certificates and queue secrets as they age.
- Linked external accounts — an operator-supplied matrix, forge or
GitHub account for one agent, stored in the swarm secret store
(
PUT /api/hives/{hive}/agents/{agent}/matrix-accounts/{account},.../forge-accounts/{label},.../github-account); distinct from the agent's own swarm-minted accounts above.GET .../linked-accountslists them, plus the agent's ownmainmatrix account, by kind, name and host, never with a credential. Each of the three also takes aDELETE; matrix's also takes?revoke=true, which logs the stored token out at its homeserver first and leaves the account in place if that fails. - Config PR status — each agent's open config-repo PR, cached from forge
webhooks (
GET /api/config-prs,/api/agents/{name}/config-pr). - Swarm-wide forge objects and webhooks →
docs/swarm/README.md. - Scheduled prompts — named prompts for one or more agents, kept in
the
schedule-definitionsKV bucket and written into each agent's key ofagent-schedules, both of which this daemon alone writes (GET/POST /api/schedules,PATCH/DELETE /api/schedules/{id}); each agent fires them itself →docs/swarm/schedules.md. - Inference settings — each agent's model, effort, subagent models and
cap on concurrently running subagents, one key per agent in the
agent-inferenceKV bucket, which this daemon alone writes (GET/PUT/DELETE /api/agents/{name}/inference); each agent applies them itself.GETalso answers the model and effort the agent last reported in its turn-state header, kept in memory →docs/swarm/inference.md. - Resource caps — each agent's CPU and memory cap, one key per agent in
the
agent-limitsKV bucket, which this daemon alone writes (GET/PUT/DELETE /api/agents/{name}/limits); the hive running the agent reads it, and each cap replaces the hive's own answer for that field →docs/scheduler/coordinator.md. - Review classifier — try-outs from the swarm UI and reviews on review
requests, and the newest 100 finished runs, one document in the
review-classifier-runsKV bucket, which this daemon alone writes (POST /api/review-classifier/try-out,GET /api/review-classifier/try-out/{id},GET /api/review-classifier/rules,GET /api/review-classifier/runs), and the try-out page's branch for a PR, pushed as the classifier's forge account (POST /api/review-classifier/open-pr) →docs/swarm/review-classifier.md. - Review classifier settings — the classifier's runtime, model, effort and how
many requested reviews run at once, one document in the
review-classifier-settingsKV bucket, which this daemon alone reads and writes (GET/PUT /api/review-classifier/settings) →docs/swarm/review-classifier.md. - Relays — each agent's terminal and turn-state header as SSE, agent icons, the cross-repo issue report, and the UI's quick links.
- Alerts — the swarm Grafana's current alerts (
GET /api/alerts), read at request time with a Viewer service-account token from the secret store →docs/swarm/secrets.md.
It reads its configuration once at startup, from the environment the nix module
sets. It persists webhook-secret and operator-matrix-admins.json in its
state directory.
The job graph lives in memory; hive status and wanted state live in the swarm
queue, so both survive a restart.
Why a unix socket, not a port
The hive-gateway's nginx is the only intended client and reaches the socket through a bind-mount. A listener that is never bound to an address cannot be reached from off-host by mistake.
The socket path is services.hyperhive.deploy.swarm-controller.socketPath, default
/run/swarm-controller/controller.sock, exported to the process as
SWARM_CONTROLLER_SOCKET.
⚠️ The socket's directory is its access control
The socket is 0666. It has to be: nginx runs as a different user and
connect(2) needs write. This matches how hive-c0re publishes the per-agent
sockets, and rests on the same argument — "the bind source dir is per-agent on
host so blast radius is unchanged."
What keeps that safe is that the directory holds one socket. So:
Never point
socketPathat a directory that carries anything else./run/hyperhiveabove all — it holdshost.sock, the host admin socket. Pointing nginx at that directory to reach this socket would put the admin socket within its reach too.
nginx is a host service, so nothing narrows what it can reach except the directory itself — that is the whole of the access control. A unit test pins the default path so a tidying edit fails instead of reviewing cleanly.
RuntimeDirectoryPreserve=yes and the daemon's stale-socket unlink on start are
a pair: preserving the directory without the unlink means bind fails with
EADDRINUSE after a restart.
Packaging
Built by the workspace derivation and extracted as its own package
(nix build .#swarm-controller). Deliberately not in nix/packages'
daemonBins — that list is the core stack and drives the bundle
services.hyperhive.c0re.package points at, so folding this in would put a
swarm-scoped service into every hive's closure.