swarm-matrix-ctl: one control binary for the matrix container, not one per job
Renames `swarm-matrix-minter` and reshapes it around subcommands. Minting is now `swarm-matrix-ctl mint`. Running rust inside `containers.hive-matrix` 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 there should be a verb here rather than a new crate. The old name guaranteed the opposite. `main.rs` is clap dispatch; the minting logic moves to `mint.rs` unchanged. A bare invocation is refused: `mint` writes a credential, so "no verb" defaulting to it would make a typo in the unit mint rather than fail. The environment prefix moves with it, `MATRIX_MINTER_*` → `MATRIX_MINT_*`. Scoped to the verb and not to the binary, because a binary-scoped prefix is one the next verb has to share or widen, and a widened one never narrows again. A test asserts every variable carries the verb's prefix. The principal renames too. The cert role, bao policy, granting unit, leaf filename and `certAuthCns` entry all have to spell one string the same way, so leaving them as `swarm-matrix-minter` would have rebuilt the naming split this branch exists to remove. Renaming the nix options alongside is free here: every one of them is introduced by this PR and has never been released, so no operator config names them yet. `ExecStart` now names the verb, which is a contract between a nix string and a clap enum that fails at deploy time with no local signal. Both ends assert it: `mint_is_spelled_the_way_the_unit_invokes_it` in the crate, and a new module-eval arm reading the rendered `ExecStart`. docs/getting-started/setup.md drops the sender token from its "live on the host" list: setup does not touch this credential, so a setup guide has no reason to name it.
This commit is contained in:
parent
fb9c6122df
commit
67ba28448f
23 changed files with 319 additions and 172 deletions
62
swarm-matrix-ctl/README.md
Normal file
62
swarm-matrix-ctl/README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# 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.
|
||||
Loading…
Reference in a new issue