Account minting, knowledge sync, the archetype refresh, the matrix appservice flow and the SSO secret delivery move to pages under 4-implementation/swarm. Each handbook page keeps what an operator does or sees, with a pointer.
10 KiB
Matrix homeserver: account provisioning
How the swarm's appservice creates accounts on the homeserver, where its tokens live, and the settings the homeserver starts with. The operator's view is the matrix page.
Provisioning flow (appservice)
Registration is closed. The swarm's appservice creates accounts: agents
never see an appservice token, and an agent only ever receives its own
access_token.
The appservice has no URL (url: null in its registration), so the
homeserver never calls out to it and there is no service to run. What the
registration buys is an identity the homeserver recognises — which is why
no secret has to be equal on both sides of the wire, and why account
creation doesn't depend on registration being open to anyone who learns
a token.
swarm-matrix-ctl appservice render, inside thehive-matrixcontainer and before tuwunel starts, fetches theas_tokenand spec-requiredhs_tokenfrom the store and renders the registration to/run/swarm-matrix-appservice/swarm.yaml(mode0600 root:root) on the container's tmpfs, so no token sits on disk. It mints a pair only when the store answers and holds none. While the store doesn't answer,renderfails and retries, and tuwunel stays down.- systemd
LoadCredential=copies the registration into/run/credentials/tuwunel.service/swarm-appservice.yaml, owned by tuwunel's dynamic user with mode0400, at service start. The source file staysroot:root 0600— nochown :tuwunel/chmod 0640/ GID-pin gymnastics required. KeepsDynamicUser = true+PrivateUsers = trueintact. - tuwunel's
appservice_dirpoints at the credentials directory. It reads only.yaml/.ymlentries from there, so the sibling credentials are invisible to it. The.yamlsuffix on the credential id is what makes this work. - hive-c0re holds no appservice token, creates no account and does not talk to matrix.
swarm-controllercreates every account: each agent's, with its token stored atswarm/agents/<agent>/matrix/mainfor the agent'shive-matrix-daemonto read as the agent itself, and each hive's sender account (below).
A homeserver whose swarm-matrix-ctl has no store identity
(services.hyperhive.deploy.matrix.ctlBaoClientCertFile) loads no
registration, and nothing creates accounts on it.
On the store's host ctlBaoClientCertFile / ctlBaoClientKeyFile default
to the leaf glue-bao-tls.nix mints there. A homeserver host without the
store gets no default, even when it holds the hive's own reader
deploy.bao.clientCertFile: issue a leaf whose subject is
deploy.bao.matrixCtlCommonName, place it, and name it in both options.
Until you do, swarm-bao-matrix-oidc fails naming both.
The swarm's appservice, and its admin sender
The registration's id is swarm, its sender @swarm, and its user
namespace non-exclusive. It's the swarm's identity on the homeserver:
swarm-controller creates every agent's account and every hive's sender
account with it.
The swarm secret store holds the registration's tokens, at
swarm/controller/swarm-controller/matrix/appservice-registration.
swarm-matrix-ctl appservice render fetches them and renders the
registration tuwunel loads as a credential onto the hive-matrix
container's tmpfs, before tuwunel starts.
A render that can't reach the store fails and retries, and tuwunel, which fails to start without the registration, retries until one has landed.
appservice sync runs beside tuwunel on a timer. The store's tokens win:
when they differ from the rendered copy, it rewrites it. It
restarts tuwunel whenever the registration is newer than tuwunel's last
start, so the next run retries one that failed after writing, and starts
one that failed because render couldn't reach the store. A store that
holds none takes the tokens an earlier version kept in
/var/lib/swarm-matrix-appservice with a create-only write, so a running
swarm keeps its value; the files stay, unused. A host with neither files nor stored tokens mints a pair. sync then writes the as_token
to swarm/controller/swarm-controller/matrix/appservice-token. No hive's
policy reaches either path: only matrix-ctl (which holds create and
read on the registration, and writes the token) and swarm-controller
(which reads only the token) hold a grant.
@swarm is the one account the homeserver promotes to admin at boot
(admin_execute). Only those two principals read its token, which is
the difference from the hive sender below: every hive reads its own
sender's token. swarm is a reserved name, so nothing can create an agent
as this account.
swarm-controller mints each agent's account with the device id
hyperhive-<agent>, and a login on that device replaces its token. The controller therefore reads
the stored token back with whoami every five minutes and re-mints only
when it's missing, unknown to the homeserver, or someone else's. An
agent's daemon picks a re-minted token up on its next start, and a timer
restarts it while it's down.
A hive's sender account, and why it isn't an admin
@hive-<hive>:<server_name> is the account a hive acts as.
swarm-controller creates it with the swarm's appservice token, for every
hive in its directory. It's an ordinary account: nothing promotes it;
the homeserver's admin_execute promotes only @swarm.
One account per hive. The localpart carries the hive's name, so a swarm whose hives share a homeserver gives each of them its own identity: the homeserver attributes an action to the hive that took it, and revoking one hive's standing leaves the others alone.
Its access token is the sender token. 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, inside the hive's own
read grant (swarm/hives/<hive>/*). Nothing on the hive uses it any more:
hive-c0re used to present it for its Space and invite calls and no longer
makes any. The name says what it authenticates as — an account the
swarm's appservice creates — rather than any privilege level, because it
carries none.
Promoting a user to homeserver admin and resetting a password both need an
admin sender: !admin … messages into #admins:<server_name>, and
tuwunel only treats a message as a command when its sender is already an
admin. Of the swarm's own accounts only @swarm is a homeserver admin
(operators are too, see
Operators are homeserver admins), so
both are swarm-level operations.
Appservice identity
swarm-matrix-ctl appservice render renders the registration from the
store's copy of the appservice tokens before tuwunel starts, so every
boot has both halves. With the store down or sealed, tuwunel waits for it.
- Access tokens live on the device that minted them.
login_with_passwordstays on, so the password fallback works too. - The homeserver's
admin_executepromotes only@swarmat boot (above); a hive's sender account is never promoted. - The value that matters lives at
swarm/controller/swarm-controller/matrix/appservice-token.appservice syncwrites it there whenever the store's differs from the registration'sas_token, andswarm-controllerreads it from there.
Rollout settings
allow_federation = trueat the protocol level so swarms can be wired up later by extendingtrustedServerswithout a homeserver restart.trusted_servers = []keeps it effectively closed until you list peers.allow_registration = false. tuwunel checks this flag only for requests that arrive without an appservice token, so the swarm's appservice still creates accounts and tuwunel refuses everyone else. With no registration token configured,allow_registration = truemakes tuwunel refuse to start unlessyes_i_am_very_very_sure_…_open_registration_…is also set.allow_encryption— server-side E2EE switch, sourced fromservices.hyperhive.deploy.matrix.allowEncryption(defaultfalse, opt-in). Off by default because on the swarm's own homeserver the operator already controls the transport; turn it on for encrypted rooms on external / federated homeservers or to keep contents opaque to the homeserver admin. The agent matrix client always supports decryption regardless of this flag — it uses thee2e-encryptionfeature ofmatrix-sdkso it can read encrypted rooms it's invited to even when this homeserver doesn't permit room encryption. matrix-sdk stores crypto keys in the per-agent sqlite store under the state dir; they persist across restarts.read_roomdecrypts viaroom.messages()— UTD events surface asevent_type = "m.room.encrypted"withbody = "[unable to decrypt]". The daemon bootstraps cross-signing on the bearer token alone, which works where the homeserver skips re-auth for a first key upload (MSC3967, as tuwunel does).- Key backup.
swarm-controllermints a random recovery passphrase for every matrix account an agent has, atswarm/agents/<agent>/matrix-recovery/<account>in the swarm secret store, and never replaces it. Whenhive-matrix-daemonbrings an account up it reads that passphrase: with no backup on the account's homeserver it turns on server-side key backup and secret storage (log linematrix recovery enabled), unless the account already has secret storage, which it never replaces (aWARNinstead). With a backup there it recovers from it (recovered from the server-side key backup), which downloads the room keys and signs the new device with the account's existing cross-signing identity. A crypto store lost to--purgeor to a primary-token reset therefore comes back with its history. With no passphrase stored yet the account comes up without backup and picks it up on the next daemon start. A failure is aWARNand never stops the daemon.
The module's retired hive-side code, its evaluation assertions and the fluffychat-web build fixes are in the implementation notes.