Watch
0
0
Fork
You've already forked hyperhive
0

read privateUsers from the closure record at start, and move the data owner option under hive-controller

This commit is contained in:
atlas 2026-11-01 16:18:34 +01:00 • committed by mara
commit f5a609911c
2 changed files with 208 additions and 97 deletions

View file

@ -451,6 +451,10 @@ async fn set_nspawn_flags(
});
}
// Whether the agent's config sets `privateUsers`, as the swarm builder
// recorded it. Read from the record, never from the agent's nix.
let private_users = private_users_of(&crate::attic::closure::record(agent_name).await);
// Web-socket subdir: bind-mount `/run/hive-agent/<name>/` into the
// container so the harness can bind `web.sock` there and the host-side
// gateway sees it. Subdir bind (not socket file) keeps the inode
@ -460,7 +464,7 @@ async fn set_nspawn_flags(
// and leaves an existing one alone; the container's activation hands it
// to the agent user. Don't chown or chmod it here.
let socket_dir = crate::agent_sockets::agent_dir_for(agent_name);
crate::priv_client::ensure_agent_socket_dir(agent_name).await?;
crate::priv_client::ensure_agent_socket_dir(agent_name, private_users).await?;
binds.push(BindMount {
host_path: socket_dir.to_string_lossy().into_owned(),
container_path: socket_dir.to_string_lossy().into_owned(),
@ -486,10 +490,26 @@ async fn set_nspawn_flags(
isolation,
&load_creds,
super::hive_facts(hive),
private_users,
)
.await
}
/// What hive-priv is told about the agent's user namespace: the record's flag,
/// or `None` (leave the agent as it is) when there is no record or it cannot be
/// read, so a swarm outage never turns a namespaced agent off or on.
fn private_users_of(
record: &Result<Option<swarm_wire::agent_closures::AgentClosure>>,
) -> Option<bool> {
match record {
Ok(record) => record.as_ref().map(|r| r.private_users),
Err(e) => {
tracing::warn!(error = %format!("{e:#}"), "closure record unreadable; the agent's user namespace is left as it is");
None
}
}
}
/// The binds every agent container gets, before its own state, config and
/// role-specific dirs.
fn base_binds(runtime_dir: &Path, claude_dir: &Path, claude_mount: String) -> Vec<BindMount> {
@ -521,6 +541,40 @@ mod tests {
};
use crate::coordinator::HiveEnv;
fn record_with(private_users: bool) -> swarm_wire::agent_closures::AgentClosure {
swarm_wire::agent_closures::AgentClosure {
meta_rev: "a".repeat(40),
store_path: "/nix/store/x".to_owned(),
system: "x86_64-linux".to_owned(),
built_unix: 1,
cached: true,
private_users,
}
}
/// The flag in the record is what hive-priv is told, both ways. No record
/// and an unreadable one say nothing, so a swarm outage changes no agent's
/// namespace; a record from before the field reads false.
#[test]
fn the_records_flag_is_what_the_start_path_passes() {
assert_eq!(
super::private_users_of(&Ok(Some(record_with(true)))),
Some(true)
);
assert_eq!(
super::private_users_of(&Ok(Some(record_with(false)))),
Some(false)
);
assert_eq!(super::private_users_of(&Ok(None)), None);
assert_eq!(super::private_users_of(&Err(anyhow::anyhow!("down"))), None);
let old = serde_json::json!({
"meta_rev": "a", "store_path": "/nix/store/x",
"system": "s", "built_unix": 1,
});
let old = serde_json::from_value(old).expect("decodes");
assert_eq!(super::private_users_of(&Ok(Some(old))), Some(false));
}
/// No agent container gets a cross-agent scratch dir; `/knowledge` is the
/// control that the lookup finds a bind that is there.
#[test]

View file

@ -306,21 +306,24 @@ pub async fn write_nspawn_flags(
isolation: NetworkIsolation,
load_credentials: &[CredentialMount],
hive_facts: HiveFacts,
private_users: Option<bool>,
) -> Result<()> {
ok(call(&PrivRequest::WriteNspawnFlags {
container: container.to_owned(),
binds: binds.to_vec(),
isolation,
load_credentials: load_credentials.to_vec(),
hive_facts,
hive_facts: Box::new(hive_facts),
private_users,
})
.await?)
}
/// See [`PrivRequest::EnsureAgentSocketDir`].
pub async fn ensure_agent_socket_dir(name: &str) -> Result<()> {
pub async fn ensure_agent_socket_dir(name: &str, private_users: Option<bool>) -> Result<()> {
ok(call(&PrivRequest::EnsureAgentSocketDir {
name: name.to_owned(),
private_users,
})
.await?)
}