# Swarm UI The swarm's own web surface, served by the gateway on the **swarm apex** (`services.hyperhive.swarm.domain`) and readable only by operators. Distinct from the per-hive dashboard, which lives on the hive domain and answers for one host. This one is the view *across* hives. ## Enabling ```nix services.hyperhive.swarm.ui.enable = true; # defaults to swarm.controller.enable ``` Derived from the controller rather than from `enableRequiredServices`: 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. `swarm.ui.domain` defaults to the swarm apex and can be pinned, the same way `swarm.forge.domain` and `swarm.matrix.gatewayHost` can. > 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` do not 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 --group admins ``` `admins` deliberately, not a new word: [`../setup.md`](../setup.md) has told every operator to create exactly that group since the bootstrap step existed, so an account made by following the guide already passes. This is the first rule that *consumes* a group name — inventing a second one would have meant those accounts silently failing a check they were supposed to pass. An account created without any group needs re-adding with the flag — `swarmctl` treats the existing entry as the canonical store, so the group is what changes. Why a group and not a list of usernames: agents are getting authelia accounts of their own (matrix SSO), and *authenticated* would then include every agent in the hive. The group is the only thing standing between "an operator's page" and "anyone with a session". ## What it costs to be reachable The apex is published to the hive's resolver like every other swarm service, so agent containers can resolve it. That is deliberate and it is not a hole: **reachability is not the access control here.** An agent that resolves the name and connects still has no operator session, and the subrequest denies it. ## Four wiring sites Adding a swarm service name means touching all four. Missing one ships as a different flavour of "works from the host, broken from a container": | site | file | | --- | --- | | vhost | `nix/host-modules/hive-gateway/vhosts.nix` | | **certificate name** | `nix/host-modules/swarm.nix` (`serviceDomains`) | | DNS record | `nix/host-modules/hive-gateway/dnsmasq.nix` | | local-dev hosts | `nix/host-modules/hive-gateway/default.nix` | ⚠️ The certificate one is the least obvious and the most visible when missed. `serviceDomains` is *both* the services sub-CA's `nameConstraints` set and the leaf's SAN list, 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. - [`../gateway.md`](../gateway.md) — the full vhost map and TLS modes.