Watch
0
0
Fork
You've already forked hyperhive
0

add a per-agent nix builds mode, host or none, carried like privateUsers

This commit is contained in:
atlas 2026-11-03 00:30:55 +01:00 • committed by mara
commit 5485151ba6

View file

@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use hive_priv_sock::BindMount;
use hive_types::Permission;
use hive_types::{NixBuilds, Permission};
use crate::coordinator::{AgentPaths, HiveEnv};
@ -451,9 +451,11 @@ 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);
// What the agent's config sets for `privateUsers` and `nixBuilds`, as the
// swarm builder recorded it. Read from the record, never from the agent's nix.
let record = crate::attic::closure::record(agent_name).await;
let private_users = private_users_of(&record);
let nix_builds = nix_builds_of(&record);
// Web-socket subdir: bind-mount `/run/hive-agent/<name>/` into the
// container so the harness can bind `web.sock` there and the host-side
@ -487,6 +489,7 @@ async fn set_nspawn_flags(
&load_creds,
super::hive_facts(hive),
private_users,
nix_builds,
)
.await
}
@ -529,6 +532,21 @@ fn private_users_of(
}
}
/// What hive-priv is told about the agent's nix builds: the record's mode, or
/// `None` (leave the agent as the last write set it) when there is no record or
/// it cannot be read, so a swarm outage never turns an agent's host builds on.
fn nix_builds_of(
record: &Result<Option<swarm_wire::agent_closures::AgentClosure>>,
) -> Option<NixBuilds> {
match record {
Ok(record) => record.as_ref().map(|r| r.nix_builds),
Err(e) => {
tracing::warn!(error = %format!("{e:#}"), "closure record unreadable; the agent's nix builds are left as they are");
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> {
@ -559,8 +577,16 @@ mod tests {
stage_limits,
};
use crate::coordinator::HiveEnv;
use hive_types::NixBuilds;
fn record_with(private_users: bool) -> swarm_wire::agent_closures::AgentClosure {
record_with_builds(private_users, NixBuilds::Host)
}
fn record_with_builds(
private_users: bool,
nix_builds: NixBuilds,
) -> swarm_wire::agent_closures::AgentClosure {
swarm_wire::agent_closures::AgentClosure {
meta_rev: "a".repeat(40),
store_path: "/nix/store/x".to_owned(),
@ -568,6 +594,7 @@ mod tests {
built_unix: 1,
cached: true,
private_users,
nix_builds,
}
}
@ -609,6 +636,38 @@ mod tests {
assert_eq!(super::private_users_of(&Ok(Some(old))), Some(false));
}
/// The record's mode is what hive-priv is told, both ways. No record and an
/// unreadable one say nothing, so a swarm outage changes no agent; a record
/// from before the field reads as host builds.
#[test]
fn the_records_nix_builds_is_what_the_start_path_passes() {
for mode in [NixBuilds::Host, NixBuilds::Disabled] {
assert_eq!(
super::nix_builds_of(&Ok(Some(record_with_builds(false, mode)))),
Some(mode)
);
}
assert_eq!(super::nix_builds_of(&Ok(None)), None);
assert_eq!(super::nix_builds_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::nix_builds_of(&Ok(Some(old))), Some(NixBuilds::Host));
// A mode a newer swarm wrote, which this build does not know, decodes
// and builds nothing.
let newer = serde_json::json!({
"meta_rev": "a", "store_path": "/nix/store/x",
"system": "s", "built_unix": 1, "nix_builds": "container",
});
let newer = serde_json::from_value(newer).expect("decodes");
assert_eq!(
super::nix_builds_of(&Ok(Some(newer))),
Some(NixBuilds::Disabled)
);
}
/// No agent container gets a cross-agent scratch dir; `/knowledge` is the
/// control that the lookup finds a bind that is there.
#[test]