lifecycle: bind each sub-agent's config repo read-only at /agents/<name>/config
This commit is contained in:
parent
56e7eb6e73
commit
1529c2d777
4 changed files with 46 additions and 3 deletions
|
|
@ -768,6 +768,8 @@ fn set_nspawn_flags(
|
|||
claude_dir: &Path,
|
||||
notes_dir: &Path,
|
||||
) -> Result<()> {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// Ensure /shared directory exists before binding. systemd-nspawn requires the bind source to exist.
|
||||
std::fs::create_dir_all(HOST_SHARED_ROOT)
|
||||
.with_context(|| format!("create {HOST_SHARED_ROOT}"))?;
|
||||
|
|
@ -775,6 +777,11 @@ fn set_nspawn_flags(
|
|||
let path = format!("/etc/nixos-containers/{container}.conf");
|
||||
let original = std::fs::read_to_string(&path).with_context(|| format!("read {path}"))?;
|
||||
|
||||
// Logical agent name (container name minus the sub-agent prefix).
|
||||
// For the manager the strip is a no-op — harmless, manager paths
|
||||
// below are gated on `container == MANAGER_NAME` anyway.
|
||||
let agent_name = container.strip_prefix(AGENT_PREFIX).unwrap_or(container);
|
||||
|
||||
// Compute the in-container state mount point. Sub-agents get
|
||||
// /agents/<name>/state; the manager keeps the legacy /state path.
|
||||
// Claude credentials always land at /root/.claude for all agents so
|
||||
|
|
@ -783,7 +790,6 @@ fn set_nspawn_flags(
|
|||
let notes_mount = if container == MANAGER_NAME {
|
||||
CONTAINER_NOTES_MOUNT.to_owned()
|
||||
} else {
|
||||
let agent_name = container.strip_prefix(AGENT_PREFIX).unwrap_or(container);
|
||||
format!("/agents/{agent_name}/state")
|
||||
};
|
||||
let claude_mount = CONTAINER_CLAUDE_MOUNT;
|
||||
|
|
@ -796,7 +802,6 @@ fn set_nspawn_flags(
|
|||
shared = HOST_SHARED_ROOT,
|
||||
);
|
||||
if container == MANAGER_NAME {
|
||||
use std::fmt::Write as _;
|
||||
// systemd-nspawn refuses to start a container whose bind
|
||||
// source doesn't exist. The meta repo is created by the
|
||||
// startup migration, but make sure the directory is there
|
||||
|
|
@ -832,6 +837,24 @@ fn set_nspawn_flags(
|
|||
" --bind-ro={HOST_META_ROOT}:{mount}",
|
||||
mount = crate::meta::CONTAINER_MANAGER_META_MOUNT,
|
||||
);
|
||||
} else {
|
||||
// Sub-agents get a READ-ONLY view of their own proposed
|
||||
// config repo at /agents/<name>/config — agent.nix plus
|
||||
// whatever extra files the manager split the config into.
|
||||
// Lets an agent inspect exactly what defines it and request
|
||||
// precise changes from the manager. RO is load-bearing: the
|
||||
// agent must NOT edit its own config — changes only ever flow
|
||||
// through the manager's RW proposed repo + the approval
|
||||
// queue. The manager already has this dir RW via the /agents
|
||||
// tree bind above.
|
||||
let config_dir = format!("{HOST_AGENTS_ROOT}/{agent_name}/config");
|
||||
// nspawn refuses to start when a bind source is missing.
|
||||
// `setup_proposed` seeds this dir before spawn reaches here,
|
||||
// but create defensively so a missing repo degrades to an
|
||||
// empty RO dir instead of a container that won't boot.
|
||||
std::fs::create_dir_all(&config_dir)
|
||||
.with_context(|| format!("create {config_dir}"))?;
|
||||
let _ = write!(binds, " --bind-ro={config_dir}:/agents/{agent_name}/config");
|
||||
}
|
||||
let bind_flag = format!("EXTRA_NSPAWN_FLAGS=\"{binds}\"");
|
||||
let mut lines: Vec<String> = original
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue