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

@ -7,20 +7,14 @@
//! identity plumbing to add one action, so the next thing that has to run in
//! here is a verb below, not a new crate.
//!
//! [`mint`] publishes a hive's appservice sender token to the swarm's secret
//! store, once. [`appservice`] mints the **swarm's** own appservice
//! registration and publishes its token for `swarm-controller`.
//!
//! It lives in the container because the appservice `as_token` that authorises
//! the mint is *already* there — the registration tuwunel loads is bind-mounted
//! in — so no second holder of that secret is created.
//! [`appservice`] mints the **swarm's** own appservice registration and
//! publishes its token for `swarm-controller`.
//!
//! 🩸 **A secret is a path, never a value.** The only identifier any verb here
//! logs is the store path; see `swarm_matrix_client`'s module doc for the same rule
//! applied to error messages.
mod appservice;
mod mint;
mod registration;
use anyhow::Result;
@ -38,13 +32,6 @@ struct Cli {
#[derive(Debug, Subcommand)]
enum Command {
/// Publish the appservice sender account's access token to the swarm
/// secret store, once.
///
/// Configured entirely by the `MATRIX_MINT_*` environment the unit sets —
/// no flags, because a systemd `Environment=` block is what a nix module
/// can render and a command line full of paths is not.
Mint,
/// The swarm's own appservice registration, whose sender is the
/// homeserver's admin account. Configured by `MATRIX_APPSERVICE_*`.
#[command(subcommand)]
@ -71,7 +58,6 @@ async fn main() -> Result<()> {
.init();
match Cli::parse().command {
Command::Mint => mint::run().await,
Command::Appservice(Appservice::Render) => appservice::render(),
Command::Appservice(Appservice::Publish) => appservice::publish().await,
}
@ -87,17 +73,8 @@ mod tests {
Cli::command().debug_assert();
}
/// The unit's `ExecStart` names a verb, so a rename of it is a deploy-time
/// failure with no local signal. This is that signal.
#[test]
fn mint_is_spelled_the_way_the_unit_invokes_it() {
let cli = Cli::try_parse_from(["swarm-matrix-ctl", "mint"]).expect("`mint` is a verb");
assert!(matches!(cli.command, Command::Mint));
}
/// The control: without it the case above passes on a parser that accepts
/// anything.
/// The two units name these verbs, same reason as the test above.
/// The two units name these verbs in `ExecStart`, so a rename of one is a
/// deploy-time failure with no local signal. This is that signal.
#[test]
fn the_appservice_verbs_are_spelled_the_way_the_units_invoke_them() {
let cli =
@ -122,9 +99,9 @@ mod tests {
.expect_err("only declared verbs are accepted");
}
/// A bare invocation must not silently do something. `mint` writes a
/// credential, so "no verb" defaulting to it would make a typo in the unit
/// mint rather than fail.
/// A bare invocation must not silently do something. `appservice publish`
/// writes a credential, so "no verb" defaulting to it would make a typo in
/// the unit write rather than fail.
#[test]
fn no_verb_at_all_is_refused() {
Cli::try_parse_from(["swarm-matrix-ctl"]).expect_err("a verb is required");