An operator links an agent's GitHub personal access token in the swarm UI
(LinkGithubAccountForm, "link github account" on /agents). swarm-controller's
PUT /api/hives/{hive}/agents/{agent}/github-account stores it at
swarm/agents/<agent>/github-token (swarm_secret_client::github), a flat leaf
under the agent's prefix that the agent's existing read grant already covers:
no policy change, and no list grant, since there is one token per agent.
In the agent, hive-agent-github-token (oneshot + 2-minute timer, as the agent
user, under its own store certificate, ordered before hive-github-notify)
reads that path and writes <state>/github-token, 0600 and agent-owned, the
file the gh wrapper, git credential helper and hive-github-notify already
read. It replaces the file by rename only when the bytes changed and never
deletes it: a hive-written github-token stays until a token is linked in the
swarm UI. It is installed only with a store address and
services.hyperhive.agent.github.enable.
Removed: the dashboard's CR3D3NTIALS page (credentials.html/js/css, its
build entries and H0M3 tile; GITHUB was its only tab), hive-c0re's
dashboard/matrix_accounts.rs with GET/POST /api/github-account,
priv_client::write_agent_github_token, the host socket's
SetAgentGithubToken and `hivectl github set-token`, and hive-priv's
WriteAgentGithubToken with write_agent_state_file, its only caller gone.
Docs: integrations/github.md and swarm/ui.md describe the swarm path,
swarm/credentials.md gains the store-path row, and the hive UI docs,
hivectl docs and security.md's hive-priv table drop the removed pieces.
Closes #4347
8.1 KiB
Swarm UI
The swarm's own web surface and the operator's day-to-day view: served by
the gateway on the swarm apex (services.hyperhive.swarm.domain),
readable only by operators. The per-hive dashboard, on each hive's own
domain, covers host-level detail for one hive.
What it shows
| route | what |
|---|---|
/ |
the hive directory, each hive with its last reported status |
/agents |
every agent: status, config PR, wanted state; create agents, link forge, matrix and GitHub accounts |
/agents/<name>/terminal |
one agent's live terminal |
/jobs |
the controller's job graph — where agent creation and credential mints show progress |
/issues |
a cross-repo issue report |
Everything it shows comes from swarm-controller.
An agent created here or with swarmctl agent create starts paused; set it
up from its card.
Linking external accounts
Each agent on /agents opens three dialogs that write a credential for it
into the swarm secret store through swarm-controller. All three are blind
set/update actions: no route lists linked accounts or hands a token back.
- link a matrix account —
PUT /api/hives/{hive}/agents/{agent}/matrix-accounts/{account}, either a pasted bearer token or a user id + password that swarm-controller logs in with, storing the token it gets back. - link a forge account —
PUT /api/hives/{hive}/agents/{agent}/forge-accounts/{label}with a forge URL and an access token, stored atswarm/agents/<agent>/forge/<label>. The agent'shive-agent-forge-accountsunit fetches it into<state>/forge-<label>-tokenand<state>/forge-<label>.json, the fileshive-forge -f <label>reads. The unit never deletes a pair: linking the same label again overwrites both files, and a pair whose label the store doesn't list stays untouched. - link a github account —
PUT /api/hives/{hive}/agents/{agent}/github-accountwith a personal access token, stored atswarm/agents/<agent>/github-token. One token per agent: linking again replaces it. The agent'shive-agent-github-tokenunit fetches it into<state>/github-token, the file itsghwrapper, git credential helper and GitHub notification poller read. Agithub-tokenalready in place stays when the store holds none. What the token needs and how the agent uses it: GitHub accounts.
Where each credential lives and who reads it:
credentials.md.
Enabling
services.hyperhive.deploy.swarm-ui.enable = true; # defaults to deploy.swarm-controller.enable
Derived from the controller rather than from allSwarmServices:
the UI is a view onto the controller's state and reaches it over that
daemon's socket, so the host that runs the controller is the host that
can serve the UI. A hive that merely uses a swarm has nothing to serve.
The UI answers on services.hyperhive.swarm.domain and nothing else. It
shares that name with the swarm-controller it fronts — one service to a
reader and to a certificate — so there is no separate option to pin.
The apex must differ from
services.hyperhive.domain. The gateway's default server already answers for the hive domain, and two vhosts claiming oneserver_namedon't error — nginx picks one — so this is an assertion rather than a runtime surprise.
🔑 You must be in the admins group
This is the step that separates "protected" from "locked out." The
vhost's auth_request asks authelia "is there a session"; the rule
that makes it mean "is this an operator" is an access_control entry
requiring group:admins. An account without that group authenticates
fine and still gets bounced.
swarmctl user add <you> --email <you>@example.com --group admins
swarmctl user update <you> --add-group admins # an account that already exists
--email isn't needed for the UI, but the forge won't create your account
without one → setup.md § 2.
Why a group and not "any session": agents are authelia subjects too, so authenticated includes every agent in the swarm. The group is the only thing standing between "an operator's page" and "anyone with a session."
What it costs to be reachable
hyperhive publishes the apex to the hive's resolver like every other swarm service, so agent containers can resolve it. That's deliberate and it's not a hole: reachability isn't the access control here. An agent that resolves the name and connects still has no operator session, and the subrequest denies it.
Quick links
The swarm UI's header carries a single 🔗 button, visible on every route,
opening a popover of links to other swarm-wide services. Backed by
GET /api/links (swarm-controller), which serves
services.hyperhive.swarm.controller.links (a listOf { label, icon, url },
same shape as the per-agent services.hyperhive.agent.dashboardLinks).
Each service's own module contributes its entry when it's enabled on the
controller's host — swarm-authelia.nix, hive-matrix.nix,
hive-forge/default.nix, swarm-grafana.nix, swarm-victorialogs.nix, and
swarm-ui.nix for this UI's own API docs. Adding a link for a new service is
a nix-only change to that service's module, or an operator adding an entry
directly. An empty list hides the button.
The Matrix entry opens the swarm's matrix web client (fluffychat,
services.hyperhive.deploy.matrix.gui.package) at the homeserver's
gateway host, chat.<swarm domain> by default. hive-matrix.nix adds it
only when services.hyperhive.deploy.matrix.gui.enable is on and the
homeserver has a gateway host, the same condition under which that vhost
serves the client at /.
Adding a swarm service name: the two wiring sites
Adding a swarm service name means touching two things. Missing the second ships as a different flavour of "works from the host, broken from a container":
| site | file |
|---|---|
vhost + gateway.localNames |
the service's own module (for example nix/host-modules/swarm-ui.nix) |
| certificate name | nix/host-modules/swarm.nix (serviceDomains) |
The DNS record and the local-dev /etc/hosts entry need no separate
edit: both derive from services.hyperhive.gateway.localNames,
which a service's own module already has to push its domain into to be
resolvable — see nix/host-modules/hive-gateway/dnsmasq.nix and
.../default.nix's networking.hosts. vhosts.nix itself is scoped to
the surface the hive's own domain serves (dashboard, per-agent routing,
matrix discovery); a swarm service declares its own vhost next to its
own options, the way swarm-ui.nix and swarm-authelia.nix do.
⚠️ The certificate one is the hardest to predict and the most visible when
missed. serviceDomains is both the allowed_domains the secret
store's pki/roles/swarm-services narrows to and the list each gateway's
leaf draws its SANs from (it carries the ones that host fronts),
and the apex is a sibling of forge.<swarm> / chat.<swarm> /
auth.<swarm>, not a parent — no CA in the hierarchy issues for it
implicitly. Left out, the vhost falls back to the hive leaf and the
swarm's front page opens with a name mismatch.
Cross-references
sso.md— the authelia instance itself, and the user store.../networking/gateway.md— the full vhost map and TLS modes.