docs(3167): the setup guide gains a swarm UI step, and one group name

Per mara on the PR: the guide someone follows on a fresh deploy is a
different page from the one they read while debugging, and only the
second existed.

Also fixes a contradiction the request surfaced: setup.md and
swarm/sso.md have told every operator to create '--group admins' since
the bootstrap step existed, while the new rule required 'operators'.
This is the first rule that CONSUMES a group name, so it takes the one
the guide already creates - inventing a second would have meant every
account made by following the guide silently failing the check it was
supposed to pass, and for mara specifically a migration step that now
may not be needed at all.

setup.md's step 3 says why --group is not decoration; the new step 4
says what decides whether the page opens (the group, and the name
resolving) rather than listing a command, because there is nothing to
run. Steps renumbered, including the matrix block's internal 4a-4e.
This commit is contained in:
atlas 2026-08-12 17:57:52 +02:00
commit 4dd5093c6f
4 changed files with 51 additions and 16 deletions

View file

@ -49,30 +49,51 @@ start. Skipping this step looks like a broken proxy.
swarmctl user add mara --display-name Mara --email mara@example.com --group admins
```
⚠️ **Keep `--group admins`.** It is not decoration: operator-only
surfaces (the swarm UI below) are gated on that group, and an account
without it authenticates successfully and is then refused — which reads
like a broken login rather than a missing group.
Detail, including what the password is and why this stays manual:
[`swarm/sso.md`](swarm/sso.md).
### 4 · Matrix
### 4 · Swarm UI (only when `swarm.ui.enable`, on by default with the controller)
Nothing to run — it is served on the swarm apex
(`https://<swarm.domain>/`) as soon as the host rebuilds. Two things
decide whether you can actually open it:
- **You are in `admins`** (step 3). The gateway asks authelia whether
you have a session; the rule that makes it mean *operator* wants the
group. Without it you log in and still get bounced.
- **The name resolves to this host.** It is published to the hive's own
resolver and to `/etc/hosts` when `gateway.localHostsEntry` is on; from
anywhere else it needs a real DNS record like any other public name.
Detail, including why reachability is deliberately not the access
control: [`swarm/ui.md`](swarm/ui.md).
### 5 · Matrix
```bash
# 4a. Ensure the hive-internal admin account exists first
# 5a. Ensure the hive-internal admin account exists first
hivectl matrix sync-admin
# 4b. Provision ruth's own matrix account
# 5b. Provision ruth's own matrix account
hivectl matrix create-user ruth
# 4c. Create a human matrix account
# 5c. Create a human matrix account
hivectl matrix create-user mara --password hunter2
# 4d. Invite the operator to the hive Space (and optionally to rooms)
# 5d. Invite the operator to the hive Space (and optionally to rooms)
hivectl matrix invite mara
hivectl matrix invite @mara:yourserver --room '#hive-chat:yourserver'
# 4e. Promote the operator to homeserver admin if needed
# 5e. Promote the operator to homeserver admin if needed
hivectl matrix promote-user mara
```
### 5 · Spawn sub-agents
### 6 · Spawn sub-agents
Sub-agent creation goes through the approval queue — ruth proposes, the
operator approves, the container builds. From ruth's own turn (inside
@ -93,7 +114,7 @@ request_init_config(name: "iris")
See [`approvals.md`](approvals.md) for the full flow.
### 6 · Useful host commands
### 7 · Useful host commands
```bash
# Roster: all agents, status, rev, parent, pending reminders

View file

@ -97,7 +97,7 @@ another container, and the three ways that one gets delivered:
## Swarm UI
The operator-only web surface on the swarm apex, why reaching it needs
the `operators` group rather than just a session, and the four sites a
the `admins` group rather than just a session, and the four sites a
swarm service name has to be wired into: [`ui.md`](ui.md).
## The swarm's hive directory

View file

@ -25,21 +25,28 @@ way `swarm.forge.domain` and `swarm.matrix.gatewayHost` can.
> 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 `operators` group
## 🔑 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:operators`. An account without that group authenticates
requiring `group:admins`. An account without that group authenticates
fine and still gets bounced.
```sh
swarmctl user add <you> --group operators
swarmctl user add <you> --group admins
```
An account created before this existed has no groups. Re-add it with the
flag — `swarmctl` treats an existing entry as the canonical store, so the
group is what changes.
`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

View file

@ -43,7 +43,14 @@ let
# here because this module writes the rule that enforces it and
# `swarmctl user add --group <this>` is what grants it — the two must
# agree, and one constant is how they stay agreeing.
operatorGroup = "operators";
#
# ⚠️ `admins` and not a new word, because `docs/setup.md` and
# `docs/swarm/sso.md` have been telling every operator to create
# exactly that group since the bootstrap step existed. This is the
# first rule that CONSUMES a group name; picking a different one would
# have meant every account created by following the guide silently
# failing the check it was supposed to pass.
operatorGroup = "admins";
# Upstream's `services.authelia.instances.<name>` derives the unit,
# user, group and StateDirectory from the instance name