c0re: chown per-agent state writes to agent uid:gid (#673)

This commit is contained in:
damocles 2026-05-31 00:23:19 +02:00 committed by Mara
commit 0cf703a939
3 changed files with 70 additions and 0 deletions

View file

@ -92,6 +92,42 @@ pub fn is_manager(name: &str) -> bool {
name == MANAGER_NAME
}
/// Read the agent user's `(uid, gid)` from the container's nixos-managed
/// `/etc/passwd`. Returns `None` when the container hasn't been built
/// yet, the passwd file is unparseable, or the agent user is missing
/// (e.g. legacy pre-#658 container that still runs as root).
///
/// Used by `forge` + `matrix` after writing per-agent state files so
/// the bind-mounted host file ends up readable by the agent user
/// without waiting for the next container activation to run the chown
/// fixup (#673).
///
/// Notes:
/// - Reads the *container-local* passwd at
/// `/var/lib/nixos-containers/<container>/etc/passwd`, not the host's.
/// The container's user-namespace shares uids with the host (no
/// `PrivateUsers`), so the uid is directly usable in host-side
/// `chown(2)`.
/// - Best-effort: caller treats `None` as "skip the chown".
#[must_use]
pub fn agent_uid_gid(agent_name: &str) -> Option<(u32, u32)> {
let container = container_name(agent_name);
let passwd_path = format!("/var/lib/nixos-containers/{container}/etc/passwd");
let content = std::fs::read_to_string(&passwd_path).ok()?;
for line in content.lines() {
let mut parts = line.split(':');
let user = parts.next()?;
if user != agent_name {
continue;
}
let _ = parts.next()?; // x (password placeholder)
let uid: u32 = parts.next()?.parse().ok()?;
let gid: u32 = parts.next()?.parse().ok()?;
return Some((uid, gid));
}
None
}
fn validate(name: &str) -> Result<()> {
if name.is_empty() {
bail!("agent name must not be empty");