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
96
swarm-matrix-ctl/src/main.rs
Normal file
96
swarm-matrix-ctl/src/main.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//! `swarm-matrix-ctl` — the rust that runs *inside* `containers.hive-matrix`.
|
||||
//!
|
||||
//! One binary with subcommands rather than one binary per job. The container
|
||||
//! is an awkward place to put code — it needs its own store identity, its own
|
||||
//! bind mounts and its own cert role — and all of that is per-*container*, not
|
||||
//! per-task. A second single-purpose crate would have had to duplicate the
|
||||
//! identity plumbing to add one action, so the next thing that has to run in
|
||||
//! here is a verb below, not a new crate.
|
||||
//!
|
||||
//! Today that is one verb, [`mint`]: publish the appservice sender account's
|
||||
//! homeserver access token to the swarm's secret store, once.
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! 🩸 **A secret is a path, never a value.** The only identifier any verb here
|
||||
//! logs is the store path; see `homeserver`'s module doc for the same rule
|
||||
//! applied to error messages.
|
||||
|
||||
mod homeserver;
|
||||
mod mint;
|
||||
mod registration;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "swarm-matrix-ctl",
|
||||
about = "Act on the swarm's matrix homeserver from inside its container"
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||
)
|
||||
.init();
|
||||
|
||||
match Cli::parse().command {
|
||||
Command::Mint => mint::run().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[test]
|
||||
fn the_clap_tree_is_well_formed() {
|
||||
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.
|
||||
#[test]
|
||||
fn an_unknown_verb_is_refused() {
|
||||
Cli::try_parse_from(["swarm-matrix-ctl", "conjure"])
|
||||
.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.
|
||||
#[test]
|
||||
fn no_verb_at_all_is_refused() {
|
||||
Cli::try_parse_from(["swarm-matrix-ctl"]).expect_err("a verb is required");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue