fix: drop --server flag from hivectl matrix; discover server_name from homeserver

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.
This commit is contained in:
atlas 2026-06-03 21:15:22 +02:00 committed by mara
commit c4a8b90236
2 changed files with 42 additions and 35 deletions

View file

@ -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:<server>`).
/// Falls back to the `HYPERHIVE_MATRIX_SERVER_NAME` env var
/// when omitted.
#[arg(long)]
server: Option<String>,
},
/// Reset a matrix user's password via the admin API and persist the
/// new password to `/var/lib/hyperhive/matrix-creds/<name>-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<String>,
},
}
@ -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<String> {
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 <name> 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

View file

@ -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<String> {
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::<serde_json::Value>()
.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<String> {