docs: a first SSO login makes a human's forge account

setup.md said Swarm SSO creates the operator's forge account, which was
not true until the previous commits. It now says how: sign in to the
forge once through authelia, then `swarmctl forge make-admin <you>`.
sso.md says what that first login does and why ACCOUNT_LINKING is
`login`. README, hivectl.md and forge.md drop `hivectl forge
create-user`, and the swarmctl README gains `forge make-admin`.

Refs #3782
This commit is contained in:
atlas 2026-09-25 02:11:44 +02:00 • committed by mara
commit 0cbb7db2c0
6 changed files with 62 additions and 33 deletions

View file

@ -114,16 +114,18 @@ doesn't go through the broker (built alongside `hive-c0re` when the host
module is enabled): module is enabled):
```sh ```sh
sudo hivectl forge create-user mara # provisions a forge user
sudo hivectl forge create-user mara --password 'hunter2' # … with a fixed password
sudo hivectl matrix create-user mara # provisions a matrix user sudo hivectl matrix create-user mara # provisions a matrix user
sudo hivectl matrix create-user mara --password-stdin # … reading one line from stdin sudo hivectl matrix create-user mara --password-stdin # … reading one line from stdin
``` ```
For a name that's a managed agent, `hivectl` persists the resulting token For a name that's a managed agent, `hivectl` persists the resulting token
to that agent's state dir, the same as the boot sweep does. For a to that agent's state dir, the same as the boot sweep does. For a
non-agent name (e.g. the operator's own forge/matrix account), it prints non-agent name (for example the operator's own matrix account), it prints the
the token to stdout and writes nothing. token to stdout and writes nothing.
A human's first SSO login to the forge makes their forge account, not
`hivectl`; `swarmctl forge make-admin <name>` on the swarm-controller's
host makes it a site admin.
## Build / deploy ## Build / deploy

View file

@ -48,8 +48,9 @@ about ten minutes. `swarmctl agent mint-forge-token ruth` skips the wait for
the pass. A hive without a swarm secret store has no path to a forge token the pass. A hive without a swarm secret store has no path to a forge token
for ruth at all. for ruth at all.
Swarm SSO creates the human operator's own forge account instead of Swarm SSO creates the human operator's own forge account: the forge
a manual `hivectl` step — see _Swarm SSO_ below (`swarmctl user add`). makes it on their first login through authelia, and `swarmctl forge
make-admin <you>` then makes it a site admin — see _Swarm SSO_ below.
### 2 · Gateway (HTTP Basic auth) ### 2 · Gateway (HTTP Basic auth)
@ -195,6 +196,22 @@ If an account already exists without it, `user add` refuses rather
than amends — adding the group afterwards is `swarmctl user update mara than amends — adding the group afterwards is `swarmctl user update mara
--add-group admins`. --add-group admins`.
Then sign in to the forge once through authelia, with that account. That
first login creates your forge account, under the same username. Make it
a site admin:
```bash
# On the swarm-controller's host. Fails until that first login has happened.
swarmctl forge make-admin mara
```
⚠️ **Keep `--email` too.** The forge won't create an account without an
email: a subject that has none gets the forge's link-account page and no
account. `swarmctl user update mara --email …` fixes it.
If the forge already has a local account with your username, the first
SSO login asks for that account's forge password once, to link the two.
Detail, including what the password is and why this stays manual: Detail, including what the password is and why this stays manual:
[`swarm/sso.md`](../swarm/sso.md). [`swarm/sso.md`](../swarm/sso.md).

View file

@ -15,11 +15,10 @@ each agent on relevant activity.
## Token scopes ## Token scopes
Two scope sets live in `hive-c0re::forge`: Two scope sets:
**`TOKEN_SCOPES`** (per-agent tokens, and `hivectl forge create-user` **`AGENT_TOKEN_SCOPES`** (swarm-controller's `forge::agent_token`, pinned
accounts). swarm-controller mints agent tokens with a byte-identical copy, by a test). swarm-controller mints every agent token with it:
`forge::agent_token::AGENT_TOKEN_SCOPES`, pinned by a test:
| Scope | Why | | Scope | Why |
| -------------------- | ------------------------------------------------------------------------------------------------------- | | -------------------- | ------------------------------------------------------------------------------------------------------- |
@ -32,9 +31,9 @@ accounts). swarm-controller mints agent tokens with a byte-identical copy,
| `read:notification` | Poll `GET /notifications` for unread events. | | `read:notification` | Poll `GET /notifications` for unread events. |
| `write:notification` | Mark notifications read via `PATCH /notifications/threads/{id}`. | | `write:notification` | Mark notifications read via `PATCH /notifications/threads/{id}`. |
**`CORE_TOKEN_SCOPES`** (hive-c0re's own `core` user): everything in **`CORE_TOKEN_SCOPES`** (`hive-c0re::forge`, for its own `core` user):
`TOKEN_SCOPES` plus `read:admin` and `write:admin`. Site-admin everything in `AGENT_TOKEN_SCOPES` plus `read:admin` and `write:admin`.
membership alone isn't sufficient — Forgejo's token scope gate runs Site-admin membership alone isn't sufficient — Forgejo's token scope gate runs
before the user-permission check, so `/api/v1/admin/*` returns before the user-permission check, so `/api/v1/admin/*` returns
`403 Forbidden` for any token without the admin scope bits, even when `403 Forbidden` for any token without the admin scope bits, even when
the bearer is a site admin. the bearer is a site admin.

View file

@ -170,7 +170,16 @@ result isn't.
| callback URL | `<root>/user/oauth2/<source>/callback` | `<homeserver>/_matrix/client/unstable/login/sso/callback/<client_id>`, a shape tuwunel fixes rather than accepts | | callback URL | `<root>/user/oauth2/<source>/callback` | `<homeserver>/_matrix/client/unstable/login/sso/callback/<client_id>`, a shape tuwunel fixes rather than accepts |
| cost of a malformed entry | the login source is missing | the homeserver can refuse to start | | cost of a malformed entry | the login source is missing | the homeserver can refuse to start |
Two consequences worth stating plainly: Three consequences worth stating plainly:
- **A person's first forge login creates their forge account**, named
after authelia's `preferred_username` (`[oauth2_client]` in the forge
module), provided the subject has an email. It starts as an ordinary user; `swarmctl forge make-admin
<name>` makes it a site admin. A name that already has a local forge
account doesn't get it handed over: forgejo asks for that account's
own password first (`ACCOUNT_LINKING = login`). Agents, `core` and
`swarm-controller` all have local accounts, and `swarmctl user add`
refuses none of those names.
- **tuwunel re-reads its secret file on every OAuth exchange**, not only - **tuwunel re-reads its secret file on every OAuth exchange**, not only
at startup, and its own sandboxing hides most paths from it. It gets the at startup, and its own sandboxing hides most paths from it. It gets the

View file

@ -11,7 +11,7 @@ Available via the `hive-c0re` package in the host NixOS config.
Unlike the `hive-c0re` daemon subcommands (which go through the broker), Unlike the `hive-c0re` daemon subcommands (which go through the broker),
`hivectl` covers direct host-side administration: manual provisioning of `hivectl` covers direct host-side administration: manual provisioning of
forge + matrix accounts, gateway htpasswd management, container matrix accounts, gateway htpasswd management, container
lifecycle shortcuts, and interactive agent shell access. lifecycle shortcuts, and interactive agent shell access.
This page is the curated guide. For the exhaustive flag-by-flag This page is the curated guide. For the exhaustive flag-by-flag
@ -24,30 +24,18 @@ markdown-docs > docs/tools/hivectl-cli.md`.
## Forge ## Forge
Manual entry to the same idempotent provisioning flow `hive-c0re` runs Reconciles an agent's config between this hive and the forge. `hivectl`
at boot. Useful for recovery, ad-hoc re-provisioning, or fixing a single makes no forge accounts: a human's first SSO login to the forge makes
agent without bouncing the daemon. theirs, and `swarmctl forge make-admin <name>` makes it a site admin.
swarm-controller makes an agent's, and `swarmctl agent mint-forge-token
<agent>` checks and, if needed, re-mints its token.
```bash ```bash
hivectl forge create-user iris # refused: `iris` is an agent; use `swarmctl agent mint-forge-token iris`
hivectl forge create-user mara # create forge account for a human user; prints token to stdout
hivectl forge create-user mara --password hunter2 # set a web-login password
hivectl forge create-user mara --password-stdin # read password from stdin (safer for scripting)
hivectl forge reconcile-config iris # show local-applied <-> forge config divergence, then prompt hivectl forge reconcile-config iris # show local-applied <-> forge config divergence, then prompt
hivectl forge reconcile-config iris --from forge # reset local applied checkout to forge main (effective next deploy) hivectl forge reconcile-config iris --from forge # reset local applied checkout to forge main (effective next deploy)
hivectl forge reconcile-config iris --verbose # include the full diff, not just --stat hivectl forge reconcile-config iris --verbose # include the full diff, not just --stat
``` ```
- For **agents** (name has a state dir under `/var/lib/hyperhive/agents/`):
`create-user` refuses. swarm-controller mints an agent's token into
the swarm secret store; `swarmctl agent mint-forge-token <agent>` checks
and, if needed, re-mints it.
- For **non-agents** (humans): creates the account and prints the token to
stdout, creating no state dir. Re-running after account already exists
re-mints the token and prints it again — safe for password resets.
- Without `--password` / `--password-stdin` `create-user` uses a random
throwaway password (fine for agents — they auth by token).
- `reconcile-config <agent>` shows the divergence between the agent's local - `reconcile-config <agent>` shows the divergence between the agent's local
applied config checkout and its forge `agent-configs/<agent>` `main`, then applied config checkout and its forge `agent-configs/<agent>` `main`, then
reconciles. `--from forge` resets the local checkout to forge `main` (takes reconciles. `--from forge` resets the local checkout to forge `main` (takes
@ -101,7 +89,7 @@ hivectl matrix invite @mara:server --room '#hive-chat:server' # ...or to a spec
Write an operator-supplied GitHub personal access token (PAT) into an Write an operator-supplied GitHub personal access token (PAT) into an
agent's token file so its `gh` wrapper + git credential helper can act as agent's token file so its `gh` wrapper + git credential helper can act as
the bot account. Unlike forge/matrix there is no account creation — the PAT the bot account. Unlike matrix there is no account creation — the PAT
is for an existing GitHub account. A CLI alternative to the dashboard is for an existing GitHub account. A CLI alternative to the dashboard
credentials tab; the [GitHub integration](../integrations/github.md) is on by default credentials tab; the [GitHub integration](../integrations/github.md) is on by default
(`services.hyperhive.agent.github.enable`), so no per-agent config is needed. (`services.hyperhive.agent.github.enable`), so no per-agent config is needed.

View file

@ -109,3 +109,17 @@ than shared: the controller's own types are private to its binary, this
crate does not link it, and there is no wire-type crate between them. crate does not link it, and there is no wire-type crate between them.
Two fields out, two in, both ends validating — a drift shows up as a Two fields out, two in, both ends validating — a drift shows up as a
400 naming the field. 400 naming the field.
## `forge make-admin`
```console
# swarmctl forge make-admin mara
forge: "mara" is now a site admin
```
`POST /api/forge/users/{name}/admin` on the swarm-controller, over the same
socket as `agent create`. It never creates an account: the forge makes a
person's on their first login through authelia, and until then this fails
saying so. Running it on a site admin changes nothing, and an agent's name is
refused. The response shape is mirrored in `src/forge.rs`, for the same
reason as `agent create`'s.