# 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 and matrix accounts | | `/agents//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`](../../swarm-controller/README.md). An agent created here or with `swarmctl agent create` starts `paused`; set it `up` from its card. ## Enabling ```nix 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. ```sh swarmctl user add --email @example.com --group admins swarmctl user update --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](../getting-started/setup.md#2--your-sso-account). 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.
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.` / `chat.` / `auth.`, 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`](sso.md) — the authelia instance itself, and the user store. - [`../networking/gateway.md`](../networking/gateway.md) — the full vhost map and TLS modes.