From ef968c11dd1f97d3b802dc6426c6b47175f15c44 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 00:28:04 +0200 Subject: [PATCH] c0re: dedupe chown_to_agent into lifecycle (argus #678) --- hive-c0re/src/forge.rs | 20 +------------------- hive-c0re/src/lifecycle.rs | 17 +++++++++++++++++ hive-c0re/src/matrix.rs | 16 +--------------- 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/hive-c0re/src/forge.rs b/hive-c0re/src/forge.rs index 95343ad8..b08440c8 100644 --- a/hive-c0re/src/forge.rs +++ b/hive-c0re/src/forge.rs @@ -162,24 +162,6 @@ fn extract_token(output: &str) -> Option { .map(str::to_owned) } -/// Best-effort chown `path` to the agent's container-local uid/gid. -/// Closes the gap where c0re (running as root on the host) writes -/// per-agent state files that the agent's non-root unix user then -/// can't read until the next container activation runs the chown -/// fixup in `harness-base.nix` (#673). -/// -/// Silently no-ops when the container hasn't been built yet (passwd -/// file absent) or when the chown syscall fails — the activation -/// script remains the safety net. -fn chown_to_agent(name: &str, path: &Path) { - let Some((uid, gid)) = crate::lifecycle::agent_uid_gid(name) else { - return; - }; - if let Err(e) = std::os::unix::fs::chown(path, Some(uid), Some(gid)) { - tracing::debug!(%name, path = %path.display(), error = %e, "forge: chown to agent failed"); - } -} - /// Canonical email address for a hive agent's Forgejo account. /// Must match the `user.email` set by `meta::render_flake` so commits /// by the agent link back to their Forgejo profile page. @@ -353,7 +335,7 @@ async fn mint_and_persist_token(name: &str, path: &Path, scopes: &str) -> Result std::fs::write(path, format!("{token}\n")) .with_context(|| format!("write token to {}", path.display()))?; let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)); - chown_to_agent(name, path); + crate::lifecycle::chown_to_agent(name, path, "forge"); tracing::info!(%name, path = %path.display(), "forge: persisted access token"); Ok(()) } diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 93e10594..e3051517 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -128,6 +128,23 @@ pub fn agent_uid_gid(agent_name: &str) -> Option<(u32, u32)> { None } +/// Best-effort `chown(path, agent_uid, agent_gid)`. Resolves the agent's +/// uid/gid via [`agent_uid_gid`] and shells out to `std::os::unix::fs::chown`. +/// Silently no-ops when the container isn't built yet (`None` from +/// [`agent_uid_gid`]) and logs at debug on chown syscall failure — the +/// activation script in `harness-base.nix` is the steady-state safety +/// net. Used by per-agent state writers in `forge` + `matrix` so the +/// agent can read the file without waiting for the next container +/// rebuild (#673). +pub fn chown_to_agent(name: &str, path: &Path, subsystem: &str) { + let Some((uid, gid)) = agent_uid_gid(name) else { + return; + }; + if let Err(e) = std::os::unix::fs::chown(path, Some(uid), Some(gid)) { + tracing::debug!(%name, %subsystem, path = %path.display(), error = %e, "chown to agent failed"); + } +} + fn validate(name: &str) -> Result<()> { if name.is_empty() { bail!("agent name must not be empty"); diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index 86948f63..0e0099b6 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -274,25 +274,11 @@ pub async fn ensure_user_for( std::fs::write(&path, format!("{access_token}\n")) .with_context(|| format!("matrix: write token to {}", path.display()))?; let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)); - chown_to_agent(name, &path); + crate::lifecycle::chown_to_agent(name, &path, "matrix"); tracing::info!(%name, path = %path.display(), "matrix: registered user + persisted access token"); Ok(()) } -/// Best-effort chown `path` to the agent's container-local uid/gid. -/// Mirrors `forge::chown_to_agent` for the matrix access-token write — -/// closes the gap where c0re (root on host) writes a file the agent's -/// non-root unix user then can't read until the next activation runs -/// the harness-base.nix chown fixup (#673). -fn chown_to_agent(name: &str, path: &Path) { - let Some((uid, gid)) = crate::lifecycle::agent_uid_gid(name) else { - return; - }; - if let Err(e) = std::os::unix::fs::chown(path, Some(uid), Some(gid)) { - tracing::debug!(%name, path = %path.display(), error = %e, "matrix: chown to agent failed"); - } -} - /// Register a matrix account for `name` with the supplied `password` /// and return the freshly-minted access token. Unlike [`ensure_user_for`], /// the token is **not** persisted to disk — the caller is responsible