hive-c0re/matrix.rs: trim UIAA prose to docs pointer (#715 batch 1)

This commit is contained in:
damocles 2026-05-31 16:48:08 +02:00 committed by mara
commit 91e50d843c

View file

@ -1,35 +1,11 @@
//! Optional matrix-tuwunel wiring. When the `hive-matrix` nixos-container
//! is present and running, hive-c0re ensures:
//! Optional matrix-tuwunel wiring: shared registration token (host) +
//! per-agent UIAA registration → `<agent-state>/matrix-token`. No-op
//! when the `hive-matrix` container isn't running, so operators who
//! haven't flipped `hyperhive.matrix.enable = true` pay nothing.
//!
//! 1. A shared `registration_token` exists at
//! `/var/lib/hyperhive/matrix-register-token` (mode 0600, generated
//! once on first boot). The hive-matrix module bind-mounts that file
//! read-only into the tuwunel container so tuwunel can resolve its
//! `registration_token_file` setting against it.
//! 2. Every agent (and the manager) has a matrix account on the local
//! homeserver with an `access_token` written to
//! `<agent-state>/matrix-token`. Idempotent: skips registration when
//! the token file already exists.
//!
//! Agents never see the registration token — only their own `access_token`.
//! Account provisioning rides the matrix-spec UIAA flow:
//!
//! ```text
//! POST /_matrix/client/v3/register
//! {"username": "<agent>", "password": "<random>"}
//! → 401 {"flows":[{"stages":["m.login.registration_token"]}],
//! "session": "<id>", ...}
//!
//! POST /_matrix/client/v3/register
//! {"username": "<agent>", "password": "<random>",
//! "auth": {"type": "m.login.registration_token",
//! "token": "<reg_token>", "session": "<id>"}}
//! → 200 {"user_id": "@agent:server", "access_token": "<at>", ...}
//! ```
//!
//! No-op when the `hive-matrix` container isn't running (detected via
//! `nixos-container list`), so operators who haven't flipped
//! `hyperhive.matrix.enable = true` pay nothing.
//! See `docs/matrix.md::Provisioning flow (registration token)` for
//! the full UIAA round-trip, token-file shape, and host/container
//! bind-mount layout.
use std::path::{Path, PathBuf};
@ -123,15 +99,12 @@ pub fn ensure_register_token() -> Result<String> {
}
std::fs::write(path, format!("{token}\n"))
.with_context(|| format!("write registration token to {}", path.display()))?;
// Mode 0640: tuwunel inside the hive-matrix container runs as a
// non-root user; the file gets `chown :tuwunel` via the activation
// script in `nix/modules/hive-matrix.nix` so the tuwunel group
// gains read. 0600 would block tuwunel with `Permission denied
// (os error 13)` (#644); 0644 would world-read the token (mara
// veto: footgun). 0640 with a pinned group is the sweet spot.
// The host-side activation script also chowns + chmods on every
// boot, so this is best-effort: hive-c0re may write the file
// before the chown lands, but the next activation reconciles it.
// Mode 0640 (group-readable for tuwunel via the host-side
// activation script's chown). 0600 blocks tuwunel; 0644
// world-reads the token. The activation script reconciles
// chown + chmod on every boot, so this write is best-effort.
// See `docs/matrix.md::Provisioning flow (registration token)`
// for the host/container permission split.
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o640));
tracing::info!(path = %path.display(), "matrix: generated registration token");
Ok(token)
@ -172,9 +145,9 @@ async fn register_post(
/// Generate a throwaway random password for matrix UIAA registration.
/// `PASSWORD_BYTES` raw bytes ⇒ 64-char hex string. Agents authenticate
/// by `access_token` so the password is protocol overhead we never
/// persist; the operator path in `hivectl` (#663) lets the caller
/// supply a real password instead so they can log into a matrix web
/// client (`m.login.password`).
/// persist; the operator path in `hivectl` lets the caller supply a
/// real password instead so they can log into a matrix web client
/// (`m.login.password`).
pub fn random_password() -> Result<String> {
random_hex(PASSWORD_BYTES)
}
@ -188,7 +161,7 @@ pub fn random_password() -> Result<String> {
///
/// Caller picks the password: agents use [`random_password`] (throwaway
/// — they auth by `access_token`), operators on the `hivectl` path
/// supply their own so they can log into matrix web clients (#663).
/// supply their own so they can log into matrix web clients.
async fn register_user(
client: &reqwest::Client,
agent: &str,
@ -250,8 +223,7 @@ fn extract_access_token(body: &serde_json::Value) -> Result<String> {
/// To force re-registration, delete the token file.
///
/// `client` is shared across the sweep so we build one reqwest
/// connection pool for all agents rather than one per call (argus
/// nit on #565 — bounded but wasteful).
/// connection pool for all agents rather than one per call.
pub async fn ensure_user_for(
client: &reqwest::Client,
name: &str,
@ -285,11 +257,11 @@ pub async fn ensure_user_for(
/// for storing it. Used by `hivectl matrix create-user` for human
/// (non-agent) accounts so we don't create stray
/// `/var/lib/hyperhive/agents/<name>/` directories for users that
/// aren't agents (#662). For operator accounts the caller passes a
/// real password so the operator can `m.login.password` into matrix
/// web clients afterwards (#663); for headless agent re-provisioning
/// the caller can pass [`random_password`] to keep the existing
/// throwaway behaviour.
/// aren't agents. For operator accounts the caller passes a real
/// password so the operator can `m.login.password` into matrix web
/// clients afterwards; for headless agent re-provisioning the caller
/// can pass [`random_password`] to keep the existing throwaway
/// behaviour.
///
/// **Not idempotent** (unlike [`forge::provision_user_token`]): the
/// matrix UIAA `/register` endpoint returns `M_USER_IN_USE` (HTTP 400)