remove the /shared bind from agent containers
This commit is contained in:
parent
044c18c481
commit
7dd5d30da8
3 changed files with 52 additions and 71 deletions
|
|
@ -12,8 +12,7 @@ use hive_types::Permission;
|
|||
use crate::coordinator::{AgentPaths, HiveEnv};
|
||||
|
||||
use super::{
|
||||
AGENT_PREFIX, CONTAINER_RUNTIME_MOUNT, CONTAINER_SHARED_MOUNT, container_claude_mount,
|
||||
container_name, validate,
|
||||
AGENT_PREFIX, CONTAINER_RUNTIME_MOUNT, container_claude_mount, container_name, validate,
|
||||
};
|
||||
|
||||
/// Re-apply the per-container host-side config: nspawn flags (bind
|
||||
|
|
@ -420,26 +419,6 @@ async fn set_nspawn_flags(
|
|||
claude_dir: &Path,
|
||||
notes_dir: &Path,
|
||||
) -> Result<()> {
|
||||
// Ensure /shared directory exists before binding. systemd-nspawn requires the bind source to exist.
|
||||
let shared_root = crate::paths::shared_root();
|
||||
std::fs::create_dir_all(&shared_root)
|
||||
.with_context(|| format!("create {}", shared_root.display()))?;
|
||||
// Make /shared writable by every agent. Containers share host uids (no
|
||||
// PrivateUsers), but each agent is a distinct unix user, so a root-owned
|
||||
// 0755 dir leaves them unable to write — the documented "read/write for
|
||||
// all agents" contract was broken. A setgid group would need a
|
||||
// pinned GID declared in every container plus all agent users joined to
|
||||
// it (cross-container coordination + a rebuild cascade); instead we use
|
||||
// the /tmp model — sticky world-writable (1777). The sticky bit lets any
|
||||
// agent create files while protecting each agent's entries from deletion
|
||||
// by the others, and matches /shared's documented "free-for-all, may be
|
||||
// deleted/lost" semantics without touching any per-agent config.
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
let perms = std::fs::Permissions::from_mode(0o1777);
|
||||
std::fs::set_permissions(&shared_root, perms)
|
||||
.with_context(|| format!("chmod 1777 {}", shared_root.display()))?;
|
||||
}
|
||||
// Ensure /knowledge dir exists. It may be empty until forge seeds it;
|
||||
// nspawn refuses to start if the bind source is missing entirely.
|
||||
std::fs::create_dir_all(crate::knowledge::LOCAL_DIR)
|
||||
|
|
@ -454,7 +433,6 @@ async fn set_nspawn_flags(
|
|||
// harness service's environment sets `HOME` to the same path
|
||||
// (`agent.nix` template), so no `--setenv` plumbing
|
||||
// is needed here — the bind alone is enough.
|
||||
let claude_mount = container_claude_mount(agent_name);
|
||||
|
||||
// The agent's own identity *at* the swarm secret store, collected under
|
||||
// this hive's certificate. Staged here rather than at spawn because this
|
||||
|
|
@ -462,28 +440,7 @@ async fn set_nspawn_flags(
|
|||
// `super::agent_identity` for the whole hop.
|
||||
let load_creds = super::agent_identity::stage(agent_name).await;
|
||||
|
||||
let mut binds: Vec<BindMount> = vec![
|
||||
BindMount {
|
||||
host_path: runtime_dir.to_string_lossy().into_owned(),
|
||||
container_path: CONTAINER_RUNTIME_MOUNT.to_owned(),
|
||||
read_only: false,
|
||||
},
|
||||
BindMount {
|
||||
host_path: claude_dir.to_string_lossy().into_owned(),
|
||||
container_path: claude_mount,
|
||||
read_only: false,
|
||||
},
|
||||
BindMount {
|
||||
host_path: shared_root.to_string_lossy().into_owned(),
|
||||
container_path: CONTAINER_SHARED_MOUNT.to_owned(),
|
||||
read_only: false,
|
||||
},
|
||||
BindMount {
|
||||
host_path: crate::knowledge::LOCAL_DIR.to_owned(),
|
||||
container_path: crate::knowledge::CONTAINER_MOUNT.to_owned(),
|
||||
read_only: true,
|
||||
},
|
||||
];
|
||||
let mut binds = base_binds(runtime_dir, claude_dir, container_claude_mount(agent_name));
|
||||
|
||||
// Own state, harness, work, and config dirs — same for every agent
|
||||
// including the manager. Config is RO: an agent must not edit its own
|
||||
|
|
@ -587,14 +544,55 @@ async fn set_nspawn_flags(
|
|||
.await
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
vec![
|
||||
BindMount {
|
||||
host_path: runtime_dir.to_string_lossy().into_owned(),
|
||||
container_path: CONTAINER_RUNTIME_MOUNT.to_owned(),
|
||||
read_only: false,
|
||||
},
|
||||
BindMount {
|
||||
host_path: claude_dir.to_string_lossy().into_owned(),
|
||||
container_path: claude_mount,
|
||||
read_only: false,
|
||||
},
|
||||
BindMount {
|
||||
host_path: crate::knowledge::LOCAL_DIR.to_owned(),
|
||||
container_path: crate::knowledge::CONTAINER_MOUNT.to_owned(),
|
||||
read_only: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
BindMount, HostMemory, MEMORY_CAP_FILE, bind_child_agent_dirs, bind_own_agent_dirs,
|
||||
holds_manage_root_agent, limits_to_apply, memory_cap_value, stage_limits,
|
||||
BindMount, HostMemory, MEMORY_CAP_FILE, base_binds, bind_child_agent_dirs,
|
||||
bind_own_agent_dirs, holds_manage_root_agent, limits_to_apply, memory_cap_value,
|
||||
stage_limits,
|
||||
};
|
||||
use crate::coordinator::HiveEnv;
|
||||
|
||||
/// No agent container gets a cross-agent scratch dir; `/knowledge` is the
|
||||
/// control that the lookup finds a bind that is there.
|
||||
#[test]
|
||||
fn base_binds_have_no_shared_dir() {
|
||||
let binds = base_binds(
|
||||
std::path::Path::new("/run/x"),
|
||||
std::path::Path::new("/var/lib/x/claude"),
|
||||
"/home/a/.claude".to_owned(),
|
||||
);
|
||||
let targets: Vec<&str> = binds.iter().map(|b| b.container_path.as_str()).collect();
|
||||
assert!(targets.contains(&"/knowledge"), "control: {targets:?}");
|
||||
assert!(!targets.contains(&"/shared"), "{targets:?}");
|
||||
assert!(
|
||||
binds.iter().all(|b| !b.host_path.ends_with("/shared")),
|
||||
"{binds:?}"
|
||||
);
|
||||
}
|
||||
|
||||
fn child_binds() -> Vec<BindMount> {
|
||||
let mut binds = Vec::new();
|
||||
bind_child_agent_dirs("kiddo", &mut binds);
|
||||
|
|
|
|||
|
|
@ -51,11 +51,6 @@ pub fn container_claude_mount(name: &str) -> String {
|
|||
format!("/home/{name}/.claude")
|
||||
}
|
||||
|
||||
/// Mount point of the shared directory accessible to all agents.
|
||||
/// All agents can read/write here; agents should only put things they're
|
||||
/// willing to lose (other agents may delete them).
|
||||
pub const CONTAINER_SHARED_MOUNT: &str = "/shared";
|
||||
|
||||
pub use hive_meta_flake::agent_web_port;
|
||||
|
||||
/// Extract the bridge gateway IP from `HIVE_NETWORK_SUBNET`.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
//! …). This module is the **single Rust-side source** for every host
|
||||
//! path — the strictly host-side ones grouped into subdirs (`db/`,
|
||||
//! `matrix/`, `run/`), plus the **nix-coupled** roots
|
||||
//! (`agents/`, `applied/`, `meta/`, `shared/`, `gateway/`, `knowledge/`,
|
||||
//! (`agents/`, `applied/`, `meta/`, `gateway/`, `knowledge/`,
|
||||
//! the `/run` runtime dirs).
|
||||
//!
|
||||
//! Nix-coupled paths are NOT excluded (the old stance) — they're moved
|
||||
|
|
@ -192,16 +192,6 @@ pub fn meta_rev_dir() -> PathBuf {
|
|||
state_root().join("meta-rev")
|
||||
}
|
||||
|
||||
/// `shared/` — the cross-agent `/shared` scratch space. A `&str`, so it
|
||||
/// stays a const; [`shared_root`] wraps it.
|
||||
// nix: bind-mounted into every agent container as `/shared` (the harness nix modules) — must match.
|
||||
pub const SHARED_ROOT: &str = "/var/lib/hyperhive/shared";
|
||||
|
||||
#[must_use]
|
||||
pub fn shared_root() -> PathBuf {
|
||||
PathBuf::from(SHARED_ROOT)
|
||||
}
|
||||
|
||||
/// `knowledge/` — local checkout of the `internal/knowledge` repo. A
|
||||
/// `&str` (used as a git `-C` arg / clone target throughout the knowledge
|
||||
/// worker), so it stays a const rather than a `PathBuf` fn.
|
||||
|
|
|
|||
Loading…
Reference in a new issue