From c4a8b90236e2fb0213c82a249a571c6b0eb59ff3 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Jun 2026 21:15:22 +0200 Subject: [PATCH] fix: drop --server flag from hivectl matrix; discover server_name from homeserver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add matrix::discover_server_name() via GET /_matrix/key/v2/server (unauthenticated federation endpoint, always returns server_name). hivectl is always talking to the local hive — no reason to require the operator to spell out the server_name. --- hive-c0re/src/bin/hivectl.rs | 49 +++++++++++------------------------- hive-c0re/src/matrix.rs | 28 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index 99de95d3..9f190cfd 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -158,16 +158,11 @@ enum MatrixCmd { SyncAdmin, /// Promote a matrix user to homeserver admin via the admin API. /// Uses the hive system admin token at - /// `/var/lib/hyperhive/matrix-admin-token`. Requires `--server` to - /// be the matrix `server_name` (e.g. `pr1ma.darkest.space`). + /// `/var/lib/hyperhive/matrix-admin-token`. The server_name is + /// discovered automatically from the running homeserver. PromoteUser { /// Matrix localpart of the user to promote (e.g. `argus`). name: String, - /// Matrix server_name embedded in user IDs (`@user:`). - /// Falls back to the `HYPERHIVE_MATRIX_SERVER_NAME` env var - /// when omitted. - #[arg(long)] - server: Option, }, /// Reset a matrix user's password via the admin API and persist the /// new password to `/var/lib/hyperhive/matrix-creds/-password` @@ -178,10 +173,6 @@ enum MatrixCmd { ResetPassword { /// Matrix localpart of the account to reset (e.g. `argus`). name: String, - /// Matrix server_name embedded in user IDs. Falls back to the - /// `HYPERHIVE_MATRIX_SERVER_NAME` env var when omitted. - #[arg(long)] - server: Option, }, } @@ -284,14 +275,8 @@ async fn main() -> Result<()> { password_stdin, } => matrix_create_user(&name, password.as_deref(), password_stdin).await, MatrixCmd::SyncAdmin => matrix_sync_admin().await, - MatrixCmd::PromoteUser { name, server } => { - let server_name = resolve_server_name(server.as_deref())?; - matrix_promote_user(&name, &server_name).await - } - MatrixCmd::ResetPassword { name, server } => { - let server_name = resolve_server_name(server.as_deref())?; - matrix_reset_password(&name, &server_name).await - } + MatrixCmd::PromoteUser { name } => matrix_promote_user(&name).await, + MatrixCmd::ResetPassword { name } => matrix_reset_password(&name).await, }, Cmd::Gateway { cmd } => match cmd { GatewayCmd::CreateUser { @@ -441,18 +426,6 @@ async fn matrix_create_user( Ok(()) } -/// Resolve matrix server_name from flag or env var. -fn resolve_server_name(flag: Option<&str>) -> Result { - if let Some(s) = flag { - return Ok(s.to_owned()); - } - std::env::var("HYPERHIVE_MATRIX_SERVER_NAME") - .map_err(|_| anyhow::anyhow!( - "matrix server_name required — pass --server or \ - set HYPERHIVE_MATRIX_SERVER_NAME" - )) -} - async fn matrix_sync_admin() -> Result<()> { if !hive_c0re::matrix::is_present().await { bail!( @@ -477,7 +450,7 @@ async fn matrix_sync_admin() -> Result<()> { Ok(()) } -async fn matrix_promote_user(name: &str, server_name: &str) -> Result<()> { +async fn matrix_promote_user(name: &str) -> Result<()> { if !hive_c0re::matrix::is_present().await { bail!( "hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) first" @@ -488,14 +461,17 @@ async fn matrix_promote_user(name: &str, server_name: &str) -> Result<()> { .timeout(std::time::Duration::from_secs(30)) .build() .context("build reqwest client")?; - hive_c0re::matrix::promote_user_to_admin(&client, &admin_token, name, server_name) + let server_name = hive_c0re::matrix::discover_server_name(&client) + .await + .context("discover matrix server_name")?; + hive_c0re::matrix::promote_user_to_admin(&client, &admin_token, name, &server_name) .await .with_context(|| format!("matrix promote-user {name}"))?; println!("matrix: promoted @{name}:{server_name} to admin"); Ok(()) } -async fn matrix_reset_password(name: &str, server_name: &str) -> Result<()> { +async fn matrix_reset_password(name: &str) -> Result<()> { if !hive_c0re::matrix::is_present().await { bail!( "hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) first" @@ -508,11 +484,14 @@ async fn matrix_reset_password(name: &str, server_name: &str) -> Result<()> { .timeout(std::time::Duration::from_secs(30)) .build() .context("build reqwest client")?; + let server_name = hive_c0re::matrix::discover_server_name(&client) + .await + .context("discover matrix server_name")?; hive_c0re::matrix::reset_user_password( &client, &admin_token, name, - server_name, + &server_name, &new_password, ) .await diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index 6d713204..5621e2ba 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -598,6 +598,34 @@ pub async fn reset_user_password( ) } +/// Discover the matrix `server_name` from the running homeserver via +/// `GET /_matrix/key/v2/server` (unauthenticated federation key endpoint). +/// The response JSON always includes `"server_name"` per the matrix spec. +pub async fn discover_server_name(client: &reqwest::Client) -> Result { + let url = format!("{MATRIX_HTTP}/_matrix/key/v2/server"); + let resp = client + .get(&url) + .send() + .await + .context("matrix: GET /_matrix/key/v2/server")?; + let status = resp.status(); + let body = resp + .json::() + .await + .context("matrix: parse /_matrix/key/v2/server response")?; + if !status.is_success() { + anyhow::bail!( + "matrix: /_matrix/key/v2/server returned HTTP {status}, body: {body}" + ); + } + body["server_name"] + .as_str() + .map(str::to_owned) + .with_context(|| { + format!("matrix: /_matrix/key/v2/server response missing server_name field: {body}") + }) +} + /// Read the hive admin access token from disk. Returns an error if it /// is absent — callers should gate their admin-API calls on this. pub fn read_admin_token() -> Result {