matrix: create accounts as the appservice, and promote the admin explicitly

Account creation stops presenting a shared registration token in a UIAA
flow and starts acting as the hive's appservice: one POST, typed
`m.login.application_service`, authorised by the `as_token` the
registration file names. The account that comes out is an ordinary user
with its own device and its own access token — nothing about what an agent
holds changes.

Three things get better than "one fewer round-trip":

- An account whose token file was lost is re-tokened by an appservice
  login, which needs neither its password nor admin rights. That was
  previously a stored-password login, and failing that an admin-room
  password reset. Both are kept behind it, for accounts created before
  this existed or named outside the appservice's namespace.
- The hive admin no longer has to be the first account ever registered.
  It could not be, in fact: tuwunel excludes appservice-created users from
  the automatic first-user grant, and on a homeserver that already had
  users the rule never fired anyway. Rights now come from an explicit
  `make_user_admin` — performed by `admin_execute` at homeserver startup,
  and verified here each sweep by reading the account's own joined-rooms
  list. Absent rights are reported with the one command that grants them,
  and are not fatal: agent accounts, the Space and the chat room all work
  without them.
- hive-c0re reads the appservice token and never mints it. The old token
  was the whole agreement, so whoever wrote it first was right; this one
  is also named by a registration file that only the nix side writes, and
  a token minted here would be one the homeserver has never heard of.

Also fixes the `make-user-admin` reply matcher, which recognised neither
spelling tuwunel v1.9.0 uses ("<user> has been granted admin
privileges.") — a promotion that had already taken effect was reported as
a 15-second timeout.

Refs #4402
This commit is contained in:
atlas 2026-09-15 19:29:13 +02:00
commit 43cd8607ba
3 changed files with 379 additions and 153 deletions

View file

@ -253,14 +253,15 @@ pub fn gateway_agents_conf() -> PathBuf {
// `nix/host-modules/hive-c0re/default.nix` and `nix/host-modules/hive-ci.nix` — must match.
pub const FORGE_CORE_TOKEN: &str = "/var/lib/hyperhive/forge-core-token";
/// `matrix-register-token` — shared matrix registration token.
// nix: bind-mounted into the tuwunel/matrix container (hive-matrix.nix) — must match.
// Not operator-option-driven: `registrationTokenFile` is `internal` on the nix
// side, and an `assertions` entry there rejects any attempt to move it, so this
// literal can never diverge from it.
/// `matrix-appservice-token` — the `as_token` of the hive's appservice
/// registration, which authorises every account this daemon creates.
// nix: minted by the `hive-matrix-appservice` activation script in
// `nix/host-modules/hive-matrix.nix`, which renders it into the registration
// file the homeserver loads — must match. Read-only here on purpose: a token
// minted on this side would not be the one in that file.
#[must_use]
pub fn matrix_register_token() -> PathBuf {
state_root().join("matrix-register-token")
pub fn matrix_appservice_token() -> PathBuf {
state_root().join("matrix-appservice-token")
}
/// `/run/hyperhive` — the runtime root (host admin socket + per-agent dirs).