hive-priv: extract exec's non-delegating match arms into named functions

This commit is contained in:
damocles 2026-08-29 13:05:14 +02:00
commit 584a973344

View file

@ -332,34 +332,23 @@ async fn write_line_event(writer: &mut OwnedWriteHalf, stream: PrivStream, data:
/// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`)
/// output lines are forwarded to `writer` as `PrivEvent::Line` messages and
/// the returned strings are empty.
// One match arm per priv op — a flat 1:1 dispatch table. The length tracks
// the op count, not complexity; splitting it would just scatter the mapping.
#[allow(clippy::too_many_lines)]
/// Execute one validated request.
///
/// `fd` is the descriptor that arrived with this request, already checked
/// against the operation by [`check_fd_agreement`]: `Some` exactly for
/// the variants that stream into a caller-supplied descriptor, `None`
/// for every other operation.
// One match arm per priv op — a flat 1:1 dispatch table. Every arm either
// delegates directly or validates then delegates; an op whose handling is
// more than that gets its own named function instead, so the match's length
// tracks the op count, not complexity.
#[allow(clippy::too_many_lines)]
async fn exec(
req: PrivRequest,
fd: Option<OwnedFd>,
writer: &mut OwnedWriteHalf,
) -> Result<(String, String)> {
match req {
PrivRequest::StartContainer { ref name } => {
validate_container_name(name)?;
let machine = container_system_name(name);
// Clear any start-limit lockout left by earlier failures so a
// now-correct start isn't blocked. nixos-container start does not
// do this itself. Best-effort: if the unit doesn't exist yet
// (first-time create) reset-failed is a no-op and we proceed.
let _ = Command::new("systemctl")
.args(["reset-failed", &format!("container@{machine}.service")])
.status()
.await;
container_run(&["start", &machine]).await
}
PrivRequest::StartContainer { ref name } => start_container(name).await,
PrivRequest::StopContainer { ref name } => {
validate_container_name(name)?;
@ -419,12 +408,7 @@ async fn exec(
PrivRequest::ReloadGatewayNginx => sync_gateway_nginx().await,
PrivRequest::RunForgeAdmin { ref args } => {
for arg in args {
validate_forge_admin_arg(arg)?;
}
run_forge_admin(args).await
}
PrivRequest::RunForgeAdmin { ref args } => exec_forge_admin(args).await,
PrivRequest::SetAgentPaused {
ref agent_name,
@ -447,36 +431,7 @@ async fn exec(
ref token,
ref account,
ref homeserver,
} => {
validate_agent_name(agent_name)?;
// Build the token filename. `None` → the hive account's
// `matrix-token`; `Some(a)` → `matrix-token-<a>`. The account
// suffix MUST be validated as a plain identifier (no `/`, `.`,
// `..`) before it goes into the filename, or a crafted account
// could traverse out of the state dir — `write_agent_state_file`
// trusts its `filename` argument.
let filename = match account {
None => "matrix-token".to_owned(),
Some(a) => {
validate_name_chars(a)?;
format!("matrix-token-{a}")
}
};
let res = write_agent_state_file(agent_name, &filename, &format!("{token}\n"))?;
// For an extra account, persist its homeserver in a sidecar
// (`matrix-account-<a>.json`) so the daemon can auto-discover the
// account without a static `matrixAccounts` config entry. Only
// when both `account` and `homeserver` are present; the account
// suffix is already validated above.
if let (Some(a), Some(hs)) = (account, homeserver) {
let meta = serde_json::to_string(&MatrixAccountSidecar {
homeserver: hs.as_str(),
})
.context("serialize matrix account sidecar")?;
write_agent_state_file(agent_name, &format!("matrix-account-{a}.json"), &meta)?;
}
Ok(res)
}
} => write_matrix_token(agent_name, token, account.as_deref(), homeserver.as_deref()),
PrivRequest::WriteAgentGithubToken {
ref agent_name,
@ -491,23 +446,7 @@ async fn exec(
ref label,
ref base_url,
ref token,
} => {
validate_agent_name(agent_name)?;
validate_name_chars(label)?;
let res = write_agent_state_file(
agent_name,
&format!("forge-{label}-token"),
&format!("{token}\n"),
)?;
// Sidecar carries the base URL — there's no host-side nix config
// for extra forges, so this is the only place it's persisted.
let meta = serde_json::to_string(&ForgeSidecar {
base_url: base_url.as_str(),
})
.context("serialize forge account sidecar")?;
write_agent_state_file(agent_name, &format!("forge-{label}.json"), &meta)?;
Ok(res)
}
} => write_extra_forge_account(agent_name, label, base_url, token),
PrivRequest::DeleteAgentExtraForgeAccount {
ref agent_name,
@ -622,6 +561,83 @@ async fn exec(
}
}
/// `StartContainer`: clear any start-limit lockout left by earlier failures
/// so a now-correct start isn't blocked (`nixos-container start` does not do
/// this itself). Best-effort — if the unit doesn't exist yet (first-time
/// create), `reset-failed` is a no-op and the start proceeds regardless.
async fn start_container(name: &str) -> Result<(String, String)> {
validate_container_name(name)?;
let machine = container_system_name(name);
let _ = Command::new("systemctl")
.args(["reset-failed", &format!("container@{machine}.service")])
.status()
.await;
container_run(&["start", &machine]).await
}
/// `RunForgeAdmin`: every arg must pass [`validate_forge_admin_arg`] before
/// the admin CLI ever sees it.
async fn exec_forge_admin(args: &[String]) -> Result<(String, String)> {
for arg in args {
validate_forge_admin_arg(arg)?;
}
run_forge_admin(args).await
}
/// `WriteAgentMatrixToken`: `account = None` writes the hive account's
/// `matrix-token`; `Some(a)` writes `matrix-token-<a>`. The account suffix
/// MUST be validated as a plain identifier (no `/`, `.`, `..`) before it goes
/// into the filename, or a crafted account could traverse out of the state
/// dir — `write_agent_state_file` trusts its `filename` argument. When both
/// `account` and `homeserver` are `Some`, also persists a
/// `matrix-account-<a>.json` sidecar so the daemon can auto-discover the
/// extra account without a static `matrixAccounts` config entry.
fn write_matrix_token(
agent_name: &str,
token: &str,
account: Option<&str>,
homeserver: Option<&str>,
) -> Result<(String, String)> {
validate_agent_name(agent_name)?;
let filename = match account {
None => "matrix-token".to_owned(),
Some(a) => {
validate_name_chars(a)?;
format!("matrix-token-{a}")
}
};
let res = write_agent_state_file(agent_name, &filename, &format!("{token}\n"))?;
if let (Some(a), Some(hs)) = (account, homeserver) {
let meta = serde_json::to_string(&MatrixAccountSidecar { homeserver: hs })
.context("serialize matrix account sidecar")?;
write_agent_state_file(agent_name, &format!("matrix-account-{a}.json"), &meta)?;
}
Ok(res)
}
/// `WriteAgentExtraForgeAccount`: writes the token, then a
/// `forge-<label>.json` sidecar carrying the base URL — there's no
/// host-side nix config for extra forges, so this is the only place it's
/// persisted.
fn write_extra_forge_account(
agent_name: &str,
label: &str,
base_url: &str,
token: &str,
) -> Result<(String, String)> {
validate_agent_name(agent_name)?;
validate_name_chars(label)?;
let res = write_agent_state_file(
agent_name,
&format!("forge-{label}-token"),
&format!("{token}\n"),
)?;
let meta = serde_json::to_string(&ForgeSidecar { base_url })
.context("serialize forge account sidecar")?;
write_agent_state_file(agent_name, &format!("forge-{label}.json"), &meta)?;
Ok(res)
}
/// Shared body for `CreateContainer` / `UpdateContainer`: validate the
/// name, build the toplevel ourselves, and run
/// `nixos-container <verb> … --system-path <built>` (streaming line