refactor(#2352): move matrix provisioning behind host-socket wire commands
This commit is contained in:
parent
5857f1f871
commit
9674fd42ac
3 changed files with 296 additions and 152 deletions
|
|
@ -640,17 +640,7 @@ async fn main() -> Result<()> {
|
||||||
password_stdin,
|
password_stdin,
|
||||||
} => forge_create_user(&name, password.as_deref(), password_stdin).await,
|
} => forge_create_user(&name, password.as_deref(), password_stdin).await,
|
||||||
},
|
},
|
||||||
Cmd::Matrix { cmd } => match cmd {
|
Cmd::Matrix { cmd } => run_matrix_cmd(&socket, cmd).await,
|
||||||
MatrixCmd::CreateUser {
|
|
||||||
name,
|
|
||||||
password,
|
|
||||||
password_stdin,
|
|
||||||
} => matrix_create_user(&name, password.as_deref(), password_stdin).await,
|
|
||||||
MatrixCmd::SyncAdmin => matrix_sync_admin().await,
|
|
||||||
MatrixCmd::PromoteUser { name } => matrix_promote_user(&name).await,
|
|
||||||
MatrixCmd::ResetPassword { name } => matrix_reset_password(&name).await,
|
|
||||||
MatrixCmd::Invite { user, room } => matrix_invite(&user, room.as_deref()).await,
|
|
||||||
},
|
|
||||||
Cmd::Github { cmd } => match cmd {
|
Cmd::Github { cmd } => match cmd {
|
||||||
GithubCmd::SetToken {
|
GithubCmd::SetToken {
|
||||||
agent,
|
agent,
|
||||||
|
|
@ -725,6 +715,22 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Route a `matrix` subcommand to its handler. Extracted from `main`'s
|
||||||
|
/// dispatch match so the top-level router stays small.
|
||||||
|
async fn run_matrix_cmd(socket: &Path, cmd: MatrixCmd) -> Result<()> {
|
||||||
|
match cmd {
|
||||||
|
MatrixCmd::CreateUser {
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
password_stdin,
|
||||||
|
} => matrix_create_user(socket, &name, password.as_deref(), password_stdin).await,
|
||||||
|
MatrixCmd::SyncAdmin => matrix_sync_admin(socket).await,
|
||||||
|
MatrixCmd::PromoteUser { name } => matrix_promote_user(socket, &name).await,
|
||||||
|
MatrixCmd::ResetPassword { name } => matrix_reset_password(socket, &name).await,
|
||||||
|
MatrixCmd::Invite { user, room } => matrix_invite(socket, &user, room.as_deref()).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `open <home|forge|matrix>` — resolve the surface URL from the daemon,
|
/// `open <home|forge|matrix>` — resolve the surface URL from the daemon,
|
||||||
/// print it, then best-effort `xdg-open` it. Printing is the reliable
|
/// print it, then best-effort `xdg-open` it. Printing is the reliable
|
||||||
/// core (headless / SSH hosts where no browser opener exists); the open
|
/// core (headless / SSH hosts where no browser opener exists); the open
|
||||||
|
|
@ -1254,160 +1260,79 @@ fn resolve_password(password: Option<&str>, password_stdin: bool) -> Result<Opti
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a matrix provisioning request to the daemon and print the
|
||||||
|
/// operator-facing result lines it returns. The daemon owns the register +
|
||||||
|
/// admin tokens and the matrix creds dir, so hivectl no longer links the
|
||||||
|
/// matrix machinery — it just forwards the request and renders the reply.
|
||||||
|
async fn matrix_request(socket: &Path, req: hive_host_sock::HostRequest) -> Result<()> {
|
||||||
|
let resp = hive_c0re::client::request(socket, req)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||||
|
if !resp.ok {
|
||||||
|
bail!(
|
||||||
|
"matrix: {}",
|
||||||
|
resp.error.as_deref().unwrap_or("unknown error")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for line in &resp.messages {
|
||||||
|
println!("{line}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn matrix_create_user(
|
async fn matrix_create_user(
|
||||||
|
socket: &Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
password: Option<&str>,
|
password: Option<&str>,
|
||||||
password_stdin: bool,
|
password_stdin: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !hive_c0re::matrix::is_present().await {
|
// Resolve the password client-side (an inline flag or a stdin read);
|
||||||
bail!(
|
// the daemon never touches this process's stdin. The agent-vs-operator
|
||||||
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) before provisioning matrix users"
|
// branch + throwaway-password handling now live in the daemon handler.
|
||||||
);
|
let password = resolve_password(password, password_stdin)?;
|
||||||
}
|
matrix_request(
|
||||||
let register_token =
|
socket,
|
||||||
hive_c0re::matrix::ensure_register_token().context("read matrix register token")?;
|
hive_host_sock::HostRequest::MatrixCreateUser {
|
||||||
let client = reqwest::Client::builder()
|
name: name.to_owned(),
|
||||||
.timeout(std::time::Duration::from_secs(30))
|
password,
|
||||||
.build()
|
},
|
||||||
.context("build reqwest client")?;
|
)
|
||||||
let user_password = resolve_password(password, password_stdin)?;
|
.await
|
||||||
if agent_exists(name)? {
|
|
||||||
if user_password.is_some() {
|
|
||||||
// The boot-sweep / approval-time agent provisioning path
|
|
||||||
// doesn't accept a password — agents auth by access_token,
|
|
||||||
// never by password. Refuse rather than silently dropping it.
|
|
||||||
bail!(
|
|
||||||
"matrix create-user: --password / --password-stdin is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via access_token"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
hive_c0re::matrix::ensure_user_for(&client, name, ®ister_token)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("matrix create-user {name}"))?;
|
|
||||||
let path = Coordinator::agent_notes_dir(name).join("matrix-token");
|
|
||||||
println!("matrix: provisioned agent user '{name}'");
|
|
||||||
println!("token persisted at: {}", path.display());
|
|
||||||
} else {
|
|
||||||
let effective_password = match user_password {
|
|
||||||
Some(p) => p,
|
|
||||||
None => {
|
|
||||||
hive_c0re::matrix::random_password().context("generate random matrix password")?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let token = hive_c0re::matrix::provision_user_token(
|
|
||||||
&client,
|
|
||||||
name,
|
|
||||||
®ister_token,
|
|
||||||
&effective_password,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("matrix create-user {name}"))?;
|
|
||||||
println!("matrix: provisioned user '{name}' (not an agent — token not persisted)");
|
|
||||||
println!("token: {token}");
|
|
||||||
if password.is_some() || password_stdin {
|
|
||||||
println!("password: set as supplied — use it to log into a matrix web client");
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"password: random throwaway (not surfaced — pass --password or --password-stdin to set one you can use)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn matrix_sync_admin() -> Result<()> {
|
async fn matrix_sync_admin(socket: &Path) -> Result<()> {
|
||||||
if !hive_c0re::matrix::is_present().await {
|
matrix_request(socket, hive_host_sock::HostRequest::MatrixSyncAdmin).await
|
||||||
bail!(
|
|
||||||
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) first"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let register_token =
|
|
||||||
hive_c0re::matrix::ensure_register_token().context("read matrix register token")?;
|
|
||||||
let client = reqwest::Client::builder()
|
|
||||||
.timeout(std::time::Duration::from_secs(30))
|
|
||||||
.build()
|
|
||||||
.context("build reqwest client")?;
|
|
||||||
hive_c0re::matrix::ensure_admin_user(&client, ®ister_token)
|
|
||||||
.await
|
|
||||||
.context("matrix sync-admin")?;
|
|
||||||
let path = hive_c0re::matrix::admin_token_path();
|
|
||||||
println!(
|
|
||||||
"matrix: hive admin user '@{}' provisioned",
|
|
||||||
hive_c0re::matrix::HIVE_ADMIN_LOCALPART
|
|
||||||
);
|
|
||||||
println!("token persisted at: {}", path.display());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn matrix_promote_user(name: &str) -> Result<()> {
|
async fn matrix_promote_user(socket: &Path, name: &str) -> Result<()> {
|
||||||
if !hive_c0re::matrix::is_present().await {
|
matrix_request(
|
||||||
bail!(
|
socket,
|
||||||
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) first"
|
hive_host_sock::HostRequest::MatrixPromoteUser {
|
||||||
);
|
name: name.to_owned(),
|
||||||
}
|
},
|
||||||
let admin_token = hive_c0re::matrix::read_admin_token()?;
|
)
|
||||||
let client = reqwest::Client::builder()
|
.await
|
||||||
.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::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_invite(user: &str, room: Option<&str>) -> Result<()> {
|
async fn matrix_invite(socket: &Path, user: &str, room: Option<&str>) -> Result<()> {
|
||||||
if !hive_c0re::matrix::is_present().await {
|
matrix_request(
|
||||||
bail!(
|
socket,
|
||||||
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) first"
|
hive_host_sock::HostRequest::MatrixInvite {
|
||||||
);
|
user: user.to_owned(),
|
||||||
}
|
room: room.map(str::to_owned),
|
||||||
let admin_token = hive_c0re::matrix::read_admin_token()?;
|
},
|
||||||
let client = reqwest::Client::builder()
|
)
|
||||||
.timeout(std::time::Duration::from_secs(30))
|
.await
|
||||||
.build()
|
|
||||||
.context("build reqwest client")?;
|
|
||||||
let server_name = hive_c0re::matrix::discover_server_name(&client)
|
|
||||||
.await
|
|
||||||
.context("discover matrix server_name")?;
|
|
||||||
let room_id = hive_c0re::matrix::invite_user(&client, &admin_token, user, room, &server_name)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("matrix invite {user}"))?;
|
|
||||||
let target = if user.starts_with('@') {
|
|
||||||
user.to_owned()
|
|
||||||
} else {
|
|
||||||
format!("@{user}:{server_name}")
|
|
||||||
};
|
|
||||||
println!("matrix: invited {target} to {room_id}");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn matrix_reset_password(name: &str) -> Result<()> {
|
async fn matrix_reset_password(socket: &Path, name: &str) -> Result<()> {
|
||||||
if !hive_c0re::matrix::is_present().await {
|
matrix_request(
|
||||||
bail!(
|
socket,
|
||||||
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) first"
|
hive_host_sock::HostRequest::MatrixResetPassword {
|
||||||
);
|
name: name.to_owned(),
|
||||||
}
|
},
|
||||||
let admin_token = hive_c0re::matrix::read_admin_token()?;
|
)
|
||||||
let client = reqwest::Client::builder()
|
.await
|
||||||
.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)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("matrix reset-password {name}"))?;
|
|
||||||
// Password is persisted by reset_user_password.
|
|
||||||
let pw_path = hive_c0re::paths::matrix_creds_dir().join(format!("{name}-password"));
|
|
||||||
println!("matrix: password for @{name}:{server_name} reset");
|
|
||||||
println!("password persisted at: {}", pw_path.display());
|
|
||||||
println!("next: hivectl matrix create-user {name} # mints a fresh access token");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,15 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
||||||
.map_err(anyhow::Error::msg)?;
|
.map_err(anyhow::Error::msg)?;
|
||||||
HostResponse::success()
|
HostResponse::success()
|
||||||
}
|
}
|
||||||
|
HostRequest::MatrixCreateUser { name, password } => {
|
||||||
|
handle_matrix_create_user(name, password.as_deref()).await?
|
||||||
|
}
|
||||||
|
HostRequest::MatrixSyncAdmin => handle_matrix_sync_admin().await?,
|
||||||
|
HostRequest::MatrixPromoteUser { name } => handle_matrix_promote_user(name).await?,
|
||||||
|
HostRequest::MatrixResetPassword { name } => handle_matrix_reset_password(name).await?,
|
||||||
|
HostRequest::MatrixInvite { user, room } => {
|
||||||
|
handle_matrix_invite(user, room.as_deref()).await?
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -245,6 +254,167 @@ async fn handle_agent_status(coord: &Arc<Coordinator>) -> HostResponse {
|
||||||
HostResponse::agent_statuses(rows)
|
HostResponse::agent_statuses(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Matrix provisioning handlers
|
||||||
|
//
|
||||||
|
// The `hivectl matrix` subcommands used to run these in-process, which forced
|
||||||
|
// the standalone CLI to link the whole daemon crate (matrix-sdk, reqwest, …).
|
||||||
|
// They now run daemon-side over the host socket: the daemon already holds the
|
||||||
|
// register + admin tokens and the matrix creds dir. Each op returns the
|
||||||
|
// operator-facing lines hivectl used to `println!` in `HostResponse::messages`
|
||||||
|
// for the client to print verbatim.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Shared reqwest client for the matrix admin HTTP calls (30s timeout,
|
||||||
|
/// mirroring the old in-CLI client).
|
||||||
|
fn matrix_http_client() -> Result<reqwest::Client> {
|
||||||
|
reqwest::Client::builder()
|
||||||
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
|
.build()
|
||||||
|
.context("build reqwest client")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True when `name` has a state dir under the agents root, i.e. it's a
|
||||||
|
/// managed agent rather than a bare (operator/human) matrix account.
|
||||||
|
fn agent_exists(name: &str) -> Result<bool> {
|
||||||
|
crate::paths::agent_state_dir(name)
|
||||||
|
.try_exists()
|
||||||
|
.with_context(|| format!("check agent state dir for {name}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guard: matrix provisioning needs the homeserver container running.
|
||||||
|
async fn require_matrix_present() -> Result<()> {
|
||||||
|
if crate::matrix::is_present().await {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
anyhow::bail!(
|
||||||
|
"hive-matrix container not running — start it (services.hyperhive.matrix.enable = true) before provisioning matrix users"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_create_user(name: &str, password: Option<&str>) -> Result<HostResponse> {
|
||||||
|
require_matrix_present().await?;
|
||||||
|
let register_token =
|
||||||
|
crate::matrix::ensure_register_token().context("read matrix register token")?;
|
||||||
|
let client = matrix_http_client()?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if agent_exists(name)? {
|
||||||
|
if password.is_some() {
|
||||||
|
// Agents auth by access_token, never by password — the
|
||||||
|
// boot-sweep provisioning path doesn't accept one. Refuse
|
||||||
|
// rather than silently dropping it.
|
||||||
|
anyhow::bail!(
|
||||||
|
"matrix create-user: a password is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via access_token"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
crate::matrix::ensure_user_for(&client, name, ®ister_token)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("matrix create-user {name}"))?;
|
||||||
|
let path = Coordinator::agent_notes_dir(name).join("matrix-token");
|
||||||
|
out.push(format!("matrix: provisioned agent user '{name}'"));
|
||||||
|
out.push(format!("token persisted at: {}", path.display()));
|
||||||
|
} else {
|
||||||
|
let effective_password = match password {
|
||||||
|
Some(p) => p.to_owned(),
|
||||||
|
None => crate::matrix::random_password().context("generate random matrix password")?,
|
||||||
|
};
|
||||||
|
let token = crate::matrix::provision_user_token(
|
||||||
|
&client,
|
||||||
|
name,
|
||||||
|
®ister_token,
|
||||||
|
&effective_password,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("matrix create-user {name}"))?;
|
||||||
|
out.push(format!(
|
||||||
|
"matrix: provisioned user '{name}' (not an agent — token not persisted)"
|
||||||
|
));
|
||||||
|
out.push(format!("token: {token}"));
|
||||||
|
if password.is_some() {
|
||||||
|
out.push(
|
||||||
|
"password: set as supplied — use it to log into a matrix web client".to_owned(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
out.push(
|
||||||
|
"password: random throwaway (not surfaced — pass --password or --password-stdin to set one you can use)".to_owned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(HostResponse::messages(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_sync_admin() -> Result<HostResponse> {
|
||||||
|
require_matrix_present().await?;
|
||||||
|
let register_token =
|
||||||
|
crate::matrix::ensure_register_token().context("read matrix register token")?;
|
||||||
|
let client = matrix_http_client()?;
|
||||||
|
crate::matrix::ensure_admin_user(&client, ®ister_token)
|
||||||
|
.await
|
||||||
|
.context("matrix sync-admin")?;
|
||||||
|
let path = crate::matrix::admin_token_path();
|
||||||
|
Ok(HostResponse::messages(vec![
|
||||||
|
format!(
|
||||||
|
"matrix: hive admin user '@{}' provisioned",
|
||||||
|
crate::matrix::HIVE_ADMIN_LOCALPART
|
||||||
|
),
|
||||||
|
format!("token persisted at: {}", path.display()),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_promote_user(name: &str) -> Result<HostResponse> {
|
||||||
|
require_matrix_present().await?;
|
||||||
|
let admin_token = crate::matrix::read_admin_token()?;
|
||||||
|
let client = matrix_http_client()?;
|
||||||
|
let server_name = crate::matrix::discover_server_name(&client)
|
||||||
|
.await
|
||||||
|
.context("discover matrix server_name")?;
|
||||||
|
crate::matrix::promote_user_to_admin(&client, &admin_token, name, &server_name)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("matrix promote-user {name}"))?;
|
||||||
|
Ok(HostResponse::messages(vec![format!(
|
||||||
|
"matrix: promoted @{name}:{server_name} to admin"
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_invite(user: &str, room: Option<&str>) -> Result<HostResponse> {
|
||||||
|
require_matrix_present().await?;
|
||||||
|
let admin_token = crate::matrix::read_admin_token()?;
|
||||||
|
let client = matrix_http_client()?;
|
||||||
|
let server_name = crate::matrix::discover_server_name(&client)
|
||||||
|
.await
|
||||||
|
.context("discover matrix server_name")?;
|
||||||
|
let room_id = crate::matrix::invite_user(&client, &admin_token, user, room, &server_name)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("matrix invite {user}"))?;
|
||||||
|
let target = if user.starts_with('@') {
|
||||||
|
user.to_owned()
|
||||||
|
} else {
|
||||||
|
format!("@{user}:{server_name}")
|
||||||
|
};
|
||||||
|
Ok(HostResponse::messages(vec![format!(
|
||||||
|
"matrix: invited {target} to {room_id}"
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix_reset_password(name: &str) -> Result<HostResponse> {
|
||||||
|
require_matrix_present().await?;
|
||||||
|
let admin_token = crate::matrix::read_admin_token()?;
|
||||||
|
let client = matrix_http_client()?;
|
||||||
|
let server_name = crate::matrix::discover_server_name(&client)
|
||||||
|
.await
|
||||||
|
.context("discover matrix server_name")?;
|
||||||
|
crate::matrix::reset_user_password(&client, &admin_token, name, &server_name)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("matrix reset-password {name}"))?;
|
||||||
|
// Password is persisted by reset_user_password.
|
||||||
|
let pw_path = crate::paths::matrix_creds_dir().join(format!("{name}-password"));
|
||||||
|
Ok(HostResponse::messages(vec![
|
||||||
|
format!("matrix: password for @{name}:{server_name} reset"),
|
||||||
|
format!("password persisted at: {}", pw_path.display()),
|
||||||
|
format!("next: hivectl matrix create-user {name} # mints a fresh access token"),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
/// Single-agent queue verbs the admin socket exposes. Each submits the
|
/// Single-agent queue verbs the admin socket exposes. Each submits the
|
||||||
/// matching DAG (persisting the `wanted` intent, serializing on the
|
/// matching DAG (persisting the `wanted` intent, serializing on the
|
||||||
/// agent's lease, with the transient/crash-watch suppression the old
|
/// agent's lease, with the transient/crash-watch suppression the old
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,37 @@ pub enum HostRequest {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
scope: LifecycleScope,
|
scope: LifecycleScope,
|
||||||
},
|
},
|
||||||
|
/// Create or refresh a matrix account + access token for `name`.
|
||||||
|
/// The daemon runs the provisioning (it holds the register + admin
|
||||||
|
/// tokens and the matrix creds dir) and returns the operator-facing
|
||||||
|
/// results (persisted-token path for agents, or the freshly-minted
|
||||||
|
/// token + password for non-agent accounts) in
|
||||||
|
/// [`HostResponse::messages`]. `password` is resolved by the client
|
||||||
|
/// (inline flag or stdin) and `None` requests a random throwaway.
|
||||||
|
MatrixCreateUser {
|
||||||
|
name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
password: Option<String>,
|
||||||
|
},
|
||||||
|
/// Provision (or re-provision) the hive system admin matrix account.
|
||||||
|
/// Daemon-side equivalent of `hivectl matrix sync-admin`.
|
||||||
|
MatrixSyncAdmin,
|
||||||
|
/// Promote a matrix user to homeserver admin via the admin API.
|
||||||
|
/// Uses the daemon's system admin token; `server_name` is discovered
|
||||||
|
/// from the running homeserver.
|
||||||
|
MatrixPromoteUser { name: String },
|
||||||
|
/// Reset a matrix user's password via the admin API and persist the
|
||||||
|
/// new password to the matrix creds dir so a later token mint can
|
||||||
|
/// re-login. Returns the outcome in [`HostResponse::messages`].
|
||||||
|
MatrixResetPassword { name: String },
|
||||||
|
/// Invite a matrix user to the hive Space (default) or a specific
|
||||||
|
/// `room`. Uses the daemon's admin token; idempotent
|
||||||
|
/// (already-member / already-invited is a no-op).
|
||||||
|
MatrixInvite {
|
||||||
|
user: String,
|
||||||
|
#[serde(default)]
|
||||||
|
room: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects which container classes a hive-wide [`HostRequest::Stop`] /
|
/// Selects which container classes a hive-wide [`HostRequest::Stop`] /
|
||||||
|
|
@ -189,6 +220,13 @@ pub struct HostResponse {
|
||||||
/// been evicted from the queue's history tail.
|
/// been evicted from the queue's history tail.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub dags: Option<Vec<jobs::DagView>>,
|
pub dags: Option<Vec<jobs::DagView>>,
|
||||||
|
/// Free-form operator-facing output lines the client prints verbatim
|
||||||
|
/// (one per line). Carries results a request produced daemon-side that
|
||||||
|
/// have no structured home — e.g. a freshly-minted matrix token, a
|
||||||
|
/// reset password, or an invited room id from the `Matrix*` requests.
|
||||||
|
/// Empty for requests that produce no such output.
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub messages: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostResponse {
|
impl HostResponse {
|
||||||
|
|
@ -267,4 +305,15 @@ impl HostResponse {
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A success carrying operator-facing output lines the client prints
|
||||||
|
/// verbatim — the result shape for the `Matrix*` provisioning requests.
|
||||||
|
#[must_use]
|
||||||
|
pub fn messages(messages: Vec<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
ok: true,
|
||||||
|
messages,
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue