hyperhive/swarm-matrix-ctl
Repository files (latest commit first)
Filename Latest commit message Latest commit date
atlas 89aff8d613 swarm-matrix-ctl: mint the swarm's own appservice registration
The swarm gets an appservice identity of its own, separate from each hive's
`hyperhive` registration. `swarm-matrix-ctl appservice render` mints its
tokens inside the matrix container when they are absent and renders the
registration tuwunel loads; `appservice publish` writes its as_token to
`swarm/controller/swarm-controller/matrix/appservice-token`, the one kind no
hive's policy grants.

The homeserver calls move out of swarm-matrix-ctl into swarm-matrix-client,
with a `whoami`, so swarm-controller can mint agents' accounts through the
same pinned device id instead of a copy of them.
2026-09-25 08:31:01 +02:00
..
src swarm-matrix-ctl: mint the swarm's own appservice registration 2026-09-25 08:31:01 +02:00
Cargo.toml swarm-matrix-ctl: mint the swarm's own appservice registration 2026-09-25 08:31:01 +02:00
README.md swarm-matrix-ctl: one control binary for the matrix container, not one per job 2026-09-20 22:07:16 +02:00

swarm-matrix-ctl

The rust that runs inside containers.hive-matrix, beside the homeserver.

One binary with subcommands rather than one binary per job. Running code in that container is not free: it needs its own store identity, its own cert role and its own bind mounts, and every one of those is per-container, not per-task. A second single-purpose crate would have had to duplicate that plumbing to add one action, so the next thing that has to run in here is a verb, not a new crate.

Verbs

mint

Puts the appservice sender account's access token into the swarm's secret store, under an identity of its own. A boot-time oneshot.

Configured entirely by the MATRIX_MINT_* environment the unit sets — no flags. A systemd Environment= block is what a nix module can render; a command line full of paths is not. The prefix is scoped to the verb rather than to the binary so the next verb brings its own, instead of widening a shared one nobody can then narrow.

Why this lives in the matrix container

The credential mint writes is authorised by the appservice as_token, and the container already holds that: nix/host-modules/hive-matrix.nix bind-mounts the rendered appservice registration into it read-only, because that is how tuwunel itself is handed the registration. Minting anywhere else would mean copying the as_token to a second holder — and the point of this component is that the hive stops being one.

It is not the swarm controller for the same reason, plus a structural one: a homeserver has exactly one appservice registration and so one sender account, and a swarm runs one homeserver, so "mint it once" needs no lock, no lease and no trigger surface — it is a property of the thing being minted.

Idempotency

The store is the key, not the homeserver. A mint run reads swarm/services/matrix/sender-token first and returns without touching the homeserver when something is already there. Only an empty path reaches the mint ladder:

  1. POST /_matrix/client/v3/register with "type": "m.login.application_service" — one round trip, no UIAA.
  2. M_USER_IN_USE (the expected arm on a homeserver that has already loaded the registration, since the account is the appservice's own sender_localpart) → POST /_matrix/client/v3/login as the appservice, same pinned device_id, so the old device is replaced rather than duplicated.
  3. Write the result to the store.

A crash between the homeserver call and the store write is recoverable: the next run takes arm 2.

🩸 A secret is a path, never a value

Nothing here logs, prints or interpolates a token. The mint ladder's errors are built from the homeserver's status and its errcode, never its body, because a /login response body is an access token. The one identifier this binary logs is the store path it wrote.