swarm-controller: mint each agent's matrix account with the swarm's token

A `MintAgentMatrixAccount` node creates the agent's account on the swarm's
homeserver with the swarm appservice token, stores its token at
`swarm/agents/<agent>/matrix/main`, and reads it back with whoami before
reporting success. It is a root of agent creation, `after_any` into the
deploy, and a five-minute backfill over every agent with a store identity
queues the same node — the shape of the forge-token mint.

The decision reads the stored token back rather than only checking that one
is stored: the swarm and a hive both pin the device `hyperhive-<agent>`, so
each login replaces the other's token. A failed read plans nothing, so an
outage never rotates every agent's token.

`matrixHomeserverUrl` now defaults to the swarm's `chat.` vhost, since the
mint is what consults it.
This commit is contained in:
atlas 2026-09-25 00:25:06 +02:00 • committed by mara
commit 2776e121e5
7 changed files with 626 additions and 36 deletions

View file

@ -227,11 +227,9 @@ let
SWARM_CONTROLLER_AUTH_BRIDGE_URL = deployCfg.swarm-controller.authBridgeUrl;
};
# Swarm-wide default for `PUT .../matrix-accounts/{account}`'s own
# `homeserver` field, for a request that omits one. Same shape as
# `authBridgeEnv` above: genuinely optional, gated on the option
# resolving rather than assumed. Not read by anything yet — see the
# option's own description.
# The swarm's homeserver: where `MintAgentMatrixAccount` creates each
# agent's own account. Gated on the option resolving: a swarm with no
# domain has no homeserver URL, and the mint node then fails by name.
matrixHomeserverEnv = lib.optionalAttrs (deployCfg.swarm-controller.matrixHomeserverUrl != null) {
SWARM_CONTROLLER_MATRIX_HOMESERVER_URL = deployCfg.swarm-controller.matrixHomeserverUrl;
};
@ -561,19 +559,24 @@ in
matrixHomeserverUrl = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
# The same `chat.<swarm domain>` ./hive-matrix.nix serves its vhost on
# (`gatewayHost`): a swarm runs one homeserver.
default = if swarmDomain == null then null else "https://chat.${swarmDomain}";
defaultText = lib.literalExpression ''"https://chat.''${services.hyperhive.swarm.domain}"'';
example = "https://matrix.example.org";
description = ''
Swarm-wide default homeserver for `PUT
Client-server API base of the swarm's homeserver. The controller
creates each agent's own matrix account there, with the swarm's
appservice token, and stores its token where the agent reads it.
`null` leaves agents with no matrix account: the mint node fails,
naming this option's variable, and the agent is deployed anyway.
Also the swarm-wide default homeserver for `PUT
.../matrix-accounts/{account}` requests that omit their own
`homeserver` — see that route's own doc comment
(`swarm-controller/src/matrix_account.rs`) for why the field is
optional in token mode and what omitting it currently resolves to.
`null` (the default) leaves that per-request resolution exactly as
it is today. **Not yet consulted by the route at all**: this option
only exists to carry the value in, ahead of the route being taught
to fall back to it.
That route does not consult it yet.
'';
};

View file

@ -59,6 +59,15 @@ let
baoWrapperCmd = if baoWrapper == null then "" else (baoWrapper.buildCommand or "");
cases = [
{
# No hive mints an agent's matrix account any more, so a controller that
# did not know the homeserver would create every agent without one. The
# default is the swarm's own `chat.` vhost, with no operator setting.
name = "the controller is told the swarm's homeserver by default";
ok =
controllerNoStore.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_MATRIX_HOMESERVER_URL
or null == "https://chat.t.local";
}
{
# Nothing asserted the PKI script before this, so a third leaf could be
# added to it and every case still passed — measured, not assumed: the