Watch
0
0
Fork
You've already forked hyperhive
0

matrix: swarm-controller is the only minter

Every hive is in a swarm and every swarm runs matrix, so every swarm has a
swarm-controller, and since #4810 its hive_sender pass mints each hive's
@hive-<hive>: sender token into the store every five minutes. The two
other minters of that token go:

- swarm-matrix-ctl mint: the systemd.services.swarm-matrix-ctl unit in the
  hive-matrix container, Command::Mint and src/mint.rs. The binary, its
  appservice render/publish verbs, ctlPackage, ctlActive and the ctl cert
  role stay. bao-matrix-reader's checks on the deleted unit are removed;
  the leaf-identity and no-token-in-env checks now look at
  swarm-matrix-appservice-publish, which runs under the same identity.
- the hive-side mint ladder in hive-c0re's ensure_hive_user
  (register/appservice-login/password-login with the local as_token), with
  read_appservice_token, paths::matrix_appservice_token and the helpers
  only it used. ensure_hive_user now takes the store's token, keeps the
  file when the store has none or can't be reached, and fails otherwise.
- hivectl matrix sync-admin: the verb, HostRequest::MatrixSyncAdmin and
  handle_matrix_sync_admin. The periodic MatrixSweep (ensure_all) is
  unchanged apart from no longer reading the local as_token.

This removes the double-mint race #4810's review flagged: two minters
logging in on one pinned device could leave a dead token in the store
until the next pass.

Closes #4813
Closes #4814
This commit is contained in:
atlas 2026-09-29 23:49:57 +02:00 • committed by mara
commit ddb7d7196d
22 changed files with 187 additions and 1162 deletions

View file

@ -93,9 +93,8 @@ delegation (the latter lives in `gateway.md::Discovery flow`).
## Provisioning flow (appservice)
<!-- vale write-good.Passive = NO -->
Registration is closed. The hive's own **appservice** creates accounts:
hive-c0re holds the appservice token, agents never see
it, and an agent only ever receives its own `access_token`.
Registration is closed. **Appservices** create accounts: agents never see
an appservice token, and an agent only ever receives its own `access_token`.
<!-- vale write-good.Passive = YES -->
The appservice has no URL (`url: null` in its registration), so the
@ -110,7 +109,7 @@ a token.
spec-required `hs_token` sibling, mode `0600 root:root`, then renders
the registration to
`/var/lib/hyperhive/matrix-appservice/hyperhive.yaml` (also `0600`).
hive-c0re mints the tokens only when missing; the registration is
The script mints the tokens only when missing; the registration is
re-rendered every time, because the token file can be overwritten in
place by the swarm secret store and a registration naming a stale
token authenticates nobody. Runs at activation time, before any
@ -129,10 +128,8 @@ a token.
the bind-mount path. It reads only `.yaml`/`.yml` entries from there,
so the sibling credentials are invisible to it. The `.yaml` suffix on
the credential id is what makes this work.
5. **hive-c0re** reads the appservice token and creates its own
`@hive-<hive>:` account. It never mints the token itself: the value
has to be the one the rendered registration names, and only the nix
side writes that.
5. **hive-c0re** doesn't read the appservice token and creates no
account. Its `@hive-<hive>:` token comes from the swarm store (below).
6. **Agents' accounts aren't this hive's.** `swarm-controller` creates each
one with the swarm's own appservice token (next section), stores its
token at `swarm/agents/<agent>/matrix/main`, and the agent's
@ -183,9 +180,8 @@ hive's standing leaves the others alone.
Its access token is the **sender token**, and it's the credential
hive-c0re presents for every homeserver call it makes on the hive's
behalf. It's per hive for the same reason the account is:
`swarm-controller` mints it for every hive with the swarm's appservice token (and
`swarm-matrix-ctl`, inside the `hive-matrix` container, for its own hive) and
publishes it to `swarm/hives/<hive>/matrix/sender-token`, and the hive
`swarm-controller`, its only minter, mints it for every hive with the swarm's
appservice token and publishes it to `swarm/hives/<hive>/matrix/sender-token`, and the hive
reads it from there under its own certificate. That path sits inside the
hive's own read grant (`swarm/hives/<hive>/*`), so a hive fetches its own
token and gets a refusal on any other hive's. The name says what it
@ -212,34 +208,19 @@ Nothing to do, and no window where the hive is without an account.
<!-- vale write-good.Passive = NO -->
- **The old shared value at `swarm/services/matrix/sender-token` is read by
nothing.** `hive-c0re` and `swarm-matrix-ctl` both build the path from the
nothing.** `hive-c0re` and `swarm-controller` both build the path from the
same function, and it now carries the hive's name — so the old object
stays in the store, unread, until an operator deletes it. Delete it or
leave it; the accounts it authenticates as keep their own standing either
way, since an access token lives on the device that minted it.
- **The hive re-mints, per hive, on the next sweep.** `ensure_hive_user`
reads the new per-hive store path **first, on every sweep**, not just when
the file is missing (`sender_source`'s decision). If a per-hive token is
already there — `swarm-controller` mints one for every hive — the sweep
takes it and overwrites the file, so the shared token stops being served
as soon as one exists in the store, no boot required. If the store has
nothing yet, the file is left untouched (still the shared token, right
after the upgrade), so nothing breaks mid-sweep. Only when neither the
store nor the file holds anything does the sweep fall through to the
register-or-appservice-login ladder against `@hive-<hive>:`, using only
the `as_token`, which is per hive and on local disk, so that step works
with or without a reachable store.
- **To move a hive onto its own account now**, clear both copies: the
sender-token file, and the store's `swarm/hives/<hive>/matrix/sender-token`
if `swarm-controller` or `swarm-matrix-ctl` has already published one for
it (otherwise the next sweep just re-adopts that value instead of minting
a new one). With both empty, the next sweep — or `hivectl matrix
sync-admin` — runs the ladder, registers `@hive-<hive>:`, and persists
that account's token to the file. The ladder never writes the store — on
a swarm that runs `swarm-controller`, its own mint pass will reach the
same account on its next tick and write a token there too, on the same
pinned device, which replaces whichever token was minted last. Only once
both copies agree does the hive stop presenting the shared one.
- **The hive switches on the next sweep.** `ensure_hive_user` reads the
per-hive store path **first, on every sweep**, not just when the file is
missing (`sender_source`'s decision). `swarm-controller` mints a token
there for every hive within five minutes; the sweep takes it and
overwrites the file, so the shared token stops being served as soon as
one exists in the store, no boot required. While the store has nothing
yet, the file is left untouched (still the shared token, right after the
upgrade), so nothing breaks mid-sweep.
- **The rooms the shared account created don't follow the new account, and
this is the one step that needs a decision.** Membership is per account.
`ensure_hive_space` takes the stored room id first, so the sweep hands the