diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 6da1fd82..69178473 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -1175,53 +1175,24 @@ fn set_nspawn_flags( let _ = write!(binds, " --bind-ro={config_dir}:/agents/{agent_name}/config"); } - - // Per-agent socket subdir for the web UI. Bind-mounts - // `/run/hive-agent//` into the container at the same path so - // the harness's `HIVE_WEB_SOCKET` bind has a stable location both - // sides can see. Applies to both manager and sub-agents — the manager - // has its own per-agent web UI (terminal, inbox, stats) that routes - // through the gateway just like sub-agents. - // - // Bind-mounting the SUBDIR (not the socket file) is mandatory: - // the harness's `bind_unix` helper unlinks any stale socket - // before calling `bind(2)`, and a file bind-mount drops its - // host-side anchor on unlink — the rebind would land in the - // container's private namespace, invisible to the gateway. - // Dir bind keeps the same dir inode visible on both sides, so - // the new `web.sock` shows up on the host the moment the - // harness binds it. - // - // Per-agent dir (rather than a shared `/run/hive-agent/` mount) - // means each container only sees its own subdir — never siblings'. - // See `docs/gateway.md::Per-agent unix-socket upstream`. - // - // mkdir source defensively: nspawn refuses to start when the - // bind source is missing, and on a fresh host `/run/hive-agent/` - // doesn't exist yet. + // Web-socket subdir: bind-mount `/run/hive-agent//` 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 + // visible after the harness unlinks a stale socket on rebind. + // Applies to manager and sub-agents alike. let socket_dir = crate::agent_sockets::agent_dir_for(agent_name); std::fs::create_dir_all(&socket_dir) .with_context(|| format!("create {}", socket_dir.display()))?; - // chown to the in-container agent user so its harness can - // `bind(2)` web.sock here. `create_dir_all` lands the dir at - // 0755 root:root and the harness runs as the non-root agent - // user; without this chown the bind fails with EACCES, the - // gateway's agent-sockets.json stays empty, and the agent - // looks unreachable. uid resolution can return None on the - // very first spawn (container's /etc/passwd not yet rendered) - // — fall back to a permissive 0777 in that window so the - // first harness boot still binds. nspawn shares uids with the - // host (no PrivateUsers), so the in-container uid is the same - // uid we chown to here. + // Chown to the agent user so the non-root harness can bind(2) here. + // Falls back to 0777 on first spawn when uid lookup returns None + // (container /etc/passwd not yet rendered). if let Some((uid, gid)) = agent_uid_gid(agent_name) { std::os::unix::fs::chown(&socket_dir, Some(uid), Some(gid)) .with_context(|| format!("chown {} to {uid}:{gid}", socket_dir.display()))?; } else { use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(&socket_dir, std::fs::Permissions::from_mode(0o777)) - .with_context(|| { - format!("chmod 0777 {} (uid lookup failed)", socket_dir.display()) - })?; + .with_context(|| format!("chmod 0777 {}", socket_dir.display()))?; } let _ = write!( binds, @@ -1469,3 +1440,4 @@ mod tests { ); } } +