Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef968c11dd | ||
|
|
0cf703a939 |
3 changed files with 55 additions and 0 deletions
|
|
@ -335,6 +335,7 @@ async fn mint_and_persist_token(name: &str, path: &Path, scopes: &str) -> Result
|
||||||
std::fs::write(path, format!("{token}\n"))
|
std::fs::write(path, format!("{token}\n"))
|
||||||
.with_context(|| format!("write token to {}", path.display()))?;
|
.with_context(|| format!("write token to {}", path.display()))?;
|
||||||
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
|
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
|
||||||
|
crate::lifecycle::chown_to_agent(name, path, "forge");
|
||||||
tracing::info!(%name, path = %path.display(), "forge: persisted access token");
|
tracing::info!(%name, path = %path.display(), "forge: persisted access token");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,59 @@ pub fn is_manager(name: &str) -> bool {
|
||||||
name == MANAGER_NAME
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<()> {
|
fn validate(name: &str) -> Result<()> {
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
bail!("agent name must not be empty");
|
bail!("agent name must not be empty");
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,7 @@ pub async fn ensure_user_for(
|
||||||
std::fs::write(&path, format!("{access_token}\n"))
|
std::fs::write(&path, format!("{access_token}\n"))
|
||||||
.with_context(|| format!("matrix: write token to {}", path.display()))?;
|
.with_context(|| format!("matrix: write token to {}", path.display()))?;
|
||||||
let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600));
|
let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600));
|
||||||
|
crate::lifecycle::chown_to_agent(name, &path, "matrix");
|
||||||
tracing::info!(%name, path = %path.display(), "matrix: registered user + persisted access token");
|
tracing::info!(%name, path = %path.display(), "matrix: registered user + persisted access token");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue