From 14c2c6d4a507017e69e7c3a79c9b362e2c11a0dd Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 8 Jul 2026 23:13:03 +0200 Subject: [PATCH] feat(#2290): ConditionPathIsDirectory= in container service drop-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a [Unit] section to hyperhive-limits.conf (the drop-in written by write_resource_limits) with: ConditionPathIsDirectory=/run/hyperhive/agents/ When this condition is not met, systemd skips the unit with result "condition" — NOT a failure, so the start-limit counter is not incremented. Belt-and-braces on top of the tmpfiles.d fix (subtask 2): if a dir is somehow absent at start time, the container idles instead of restart-looping into start-limit-hit. Also promote AGENT_RUNTIME_ROOT to a module-level const (was duplicated inside two functions) and remove the duplicates. --- hive-priv/src/main.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/hive-priv/src/main.rs b/hive-priv/src/main.rs index 3aee0b2c..bc447a7f 100644 --- a/hive-priv/src/main.rs +++ b/hive-priv/src/main.rs @@ -33,6 +33,10 @@ use tokio::process::Command; /// Root of the per-agent unix-socket dirs on the host. const SOCKET_DIR_ROOT: &str = "/run/hive-agent"; +/// Root of the per-agent MCP socket dirs on the host. +/// Matches `coordinator::AGENT_RUNTIME_ROOT` in hive-c0re. +const AGENT_RUNTIME_ROOT: &str = "/run/hyperhive/agents"; + #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() @@ -422,17 +426,40 @@ fn chmod_socket_dir(agent_name: &str, mode: u32) -> Result<(String, String)> { } /// `WriteResourceLimits` — drop a systemd `MemoryMax`/`CPUQuota` -/// override into the container service's drop-in dir. +/// override into the container service's drop-in dir, together with a +/// `ConditionPathIsDirectory=` guard on the agent's MCP runtime dir. +/// +/// The condition causes systemd to *skip* (not *fail*) the unit when the +/// bind-mount source dir is absent — result is `condition`, which does not +/// increment the start-limit counter. This is belt-and-braces on top of +/// the tmpfiles.d entries written by `SyncAgentTmpfiles`: in the unlikely +/// event the dir is missing at start time, the unit idles rather than +/// restart-looping into `start-limit-hit`. fn write_resource_limits( container: &str, memory_max: &str, cpu_quota: &str, ) -> Result<(String, String)> { validate_container_system_name(container)?; + // Derive the logical agent name (strip h- prefix) to form the runtime + // dir path. Falls back to the full container name for infra containers + // that don't use the h- prefix. + let logical = container.strip_prefix(AGENT_PREFIX).unwrap_or(container); + let runtime_dir = format!("{AGENT_RUNTIME_ROOT}/{logical}"); let dir = format!("/run/systemd/system/container@{container}.service.d"); std::fs::create_dir_all(&dir).with_context(|| format!("create {dir}"))?; let path = format!("{dir}/hyperhive-limits.conf"); - let content = format!("[Service]\nMemoryMax={memory_max}\nCPUQuota={cpu_quota}\n"); + // [Unit] section: condition checked at start time — skips (not fails) + // the unit when the MCP socket dir is absent, avoiding restart loops. + // [Service] section: resource caps. + let content = format!( + "[Unit]\n\ + ConditionPathIsDirectory={runtime_dir}\n\ + \n\ + [Service]\n\ + MemoryMax={memory_max}\n\ + CPUQuota={cpu_quota}\n" + ); std::fs::write(&path, content).with_context(|| format!("write {path}"))?; Ok((String::new(), String::new())) } @@ -1512,7 +1539,6 @@ fn write_bridge_dns_marker(container: &str, isolation: Option<&NetworkIsolation> /// as `/run/hive`) /// - `/run/hive-agent/` (web socket dir, bind-mounted into container) const TMPFILES_PATH: &str = "/etc/tmpfiles.d/hyperhive-agents.conf"; -const AGENT_RUNTIME_ROOT: &str = "/run/hyperhive/agents"; async fn sync_agent_tmpfiles(agents: &[String]) -> Result<(String, String)> { for name in agents {