Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/docs/swarm/ui.md
atlas b90be9e65e swarm UI: delete linked accounts — R2 fixes
Addresses argus review comment 90297 on PR #4899:

- swarm-controller/README.md: list the three DELETE routes (including
  matrix's ?revoke=true) beside the PUT/GET ones already documented.
- LinkedAccounts.tsx: a delete answering 404 means the account is
  already gone, so treat it as the delete's end state — re-fetch and
  close the dialog instead of showing an error.
- matrix_account.rs: matrix_logout treats a 401 M_UNKNOWN_TOKEN as the
  token already being revoked and proceeds with the delete; every
  other logout failure still keeps the account. Adds unit tests and
  updates docs/swarm/ui.md to match.
2026-10-03 00:56:39 +02:00

11 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 hands a token back.

The agent's detail panel lists its linked accounts under accounts, one row per account: kind, name and host. Opening an agent makes one request, GET /api/hives/{hive}/agents/{agent}/linked-accounts, which returns every account of that agent as names and hosts and never a credential.

kind one row per name host
matrix swarm/agents/<agent>/matrix/<account> the account its homeserver, if set
forgejo swarm/agents/<agent>/forge/<label> the label its url
github swarm/agents/<agent>/github-token, if any github github.com

The matrix main row is the agent's own account, which the swarm mints; it carries an own account badge. The panel requests the list again when a link dialog closes.

  • 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 at swarm/agents/<agent>/forge/<label>. The agent's hive-agent-forge-accounts unit fetches it into <state>/forge-<label>-token and <state>/forge-<label>.json, the files hive-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-account with a personal access token, stored at swarm/agents/<agent>/github-token. One token per agent: linking again replaces it. The agent's hive-agent-github-token unit fetches it into <state>/github-token, the file its gh wrapper, git credential helper and GitHub notification poller read. A github-token already in place stays when the store holds none. What the token needs and how the agent uses it: GitHub accounts.

Deleting a linked account

Every row except the matrix main row has a delete action. Its confirmation names the account and its host, and confirming sends one request:

kind route
matrix DELETE /api/hives/{hive}/agents/{agent}/matrix-accounts/{account}
forgejo DELETE /api/hives/{hive}/agents/{agent}/forge-accounts/{label}
github DELETE /api/hives/{hive}/agents/{agent}/github-account

swarm-controller removes every version of the entry from the swarm secret store, and answers 404 when the store holds nothing there. It refuses main: the swarm mints that account and would re-mint it. The panel requests the list again after a delete, including a 404 one: the account being already gone is the end state the delete wanted, so the dialog closes without an error.

The matrix confirmation has a checkbox, off by default, that logs the token out at its homeserver first (?revoke=true). A 401 M_UNKNOWN_TOKEN from that logout means the homeserver already doesn't recognise the token, so the delete proceeds as if it had succeeded. Any other failed logout, or an account with no homeserver stored, leaves the account in the store and the dialog shows why. Deleting a forge account or GitHub token leaves the token valid at its provider; revoke it there.

The agent isn't told about a delete. Its matrix daemon drops the account when it next lists the store, within two minutes. Its forge and GitHub units never delete a file, so a token already fetched into <state> stays there.

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 one server_name don'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.

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).

Links to swarm services come from swarm-level options, so the list is the same whichever host runs each service:

link URL built from
Forge services.hyperhive.swarm.forge.domain
Authelia services.hyperhive.swarm.authelia.domain
Grafana services.hyperhive.swarm.grafana.domain
Metrics services.hyperhive.swarm.victoriametrics.domain
Logs services.hyperhive.swarm.victorialogs.domain
Matrix services.hyperhive.swarm.matrix.gatewayHost
Bao services.hyperhive.swarm.bao.ui.domain

Each entry is present whenever its option has a value — every option in the table defaults to one, so the link appears even on a swarm that runs no instance of that service.

nix/host-modules/swarm-controller.nix builds these entries. swarm-ui.nix adds one more, for this UI's own API docs. An operator can add entries 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. The homeserver's host always serves the client at / on that vhost, so the entry is present whenever the swarm names a gateway host.

The Bao entry opens the secret store's web UI, which admits members of authelia's admins group only; see secrets.md.

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