From f8c0f64fd4251d7beb86e79d115e8ca0d6af0d99 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 1 Jun 2026 16:29:41 +0200 Subject: [PATCH] feat: socket-activate the hive-c0re admin socket Add a systemd.sockets.hive-c0re unit that holds /run/hyperhive/host.sock before hive-c0re starts. hive-c0re serve() detects LISTEN_FDS via the listenfd crate and accepts the systemd-handed fd instead of calling bind(). Falls back to the existing bind path when LISTEN_FDS is absent so direct invocation and CI are unaffected. Benefits: hivectl can connect the moment the socket unit activates (no racy window), and a hive-c0re restart never drops the socket inode. --- hive-c0re/Cargo.toml | 1 + hive-c0re/src/server.rs | 37 +++++++++++++++++++++++++++---------- nix/modules/hive-c0re.nix | 26 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/hive-c0re/Cargo.toml b/hive-c0re/Cargo.toml index 308d0c42..ac70638e 100644 --- a/hive-c0re/Cargo.toml +++ b/hive-c0re/Cargo.toml @@ -13,6 +13,7 @@ reqwest.workspace = true clap.workspace = true hive-sh4re.workspace = true libc = "0.2" +listenfd = "1" rusqlite.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 1887fdde..62d7410f 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -11,16 +11,33 @@ use crate::coordinator::Coordinator; use crate::lifecycle; pub async fn serve(socket: &Path, coord: Arc) -> Result<()> { - if let Some(parent) = socket.parent() { - std::fs::create_dir_all(parent) - .with_context(|| format!("create socket parent {}", parent.display()))?; - } - if socket.exists() { - std::fs::remove_file(socket).context("remove stale socket")?; - } - - let listener = UnixListener::bind(socket) - .with_context(|| format!("bind admin socket {}", socket.display()))?; + // Prefer a socket passed by systemd socket-activation (LISTEN_FDS). + // When running under a `.socket` unit, systemd has already created, + // bound, and chmod-ed the socket for us — we just accept on it. + // Fall back to the traditional bind path when not socket-activated + // (direct invocation, dev, tests). + let listener = { + let mut listenfd = listenfd::ListenFd::from_env(); + if let Some(std_listener) = listenfd + .take_unix_listener(0) + .context("take socket-activated unix listener")? + { + std_listener.set_nonblocking(true)?; + UnixListener::from_std(std_listener) + .context("convert socket-activated listener to tokio")? + } else { + // Standalone: create parent dir, remove any stale socket, bind. + if let Some(parent) = socket.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("create socket parent {}", parent.display()))?; + } + if socket.exists() { + std::fs::remove_file(socket).context("remove stale socket")?; + } + UnixListener::bind(socket) + .with_context(|| format!("bind admin socket {}", socket.display()))? + } + }; tracing::info!(socket = %socket.display(), hyperhive_flake = %coord.hyperhive_flake, "hive-c0re admin listening"); loop { diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 126dcc04..81394997 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -370,5 +370,31 @@ in StateDirectory = "hyperhive"; }; }; + + # Socket unit for the hive-c0re admin socket. systemd creates and holds + # `/run/hyperhive/host.sock` before hive-c0re starts, then passes the fd + # via LISTEN_FDS (socket activation). Benefits: `hivectl` can connect + # the moment the socket unit is active — no racy retry window — and a + # hive-c0re restart never drops the socket inode, so queued commands + # drain cleanly. + # + # `hive-c0re serve` reads LISTEN_FDS via the `listenfd` crate and + # accepts the fd in preference to its own `bind()` path. When invoked + # directly (dev, CI, without the socket unit) LISTEN_FDS is absent and + # the traditional bind path runs unchanged — no regression. + systemd.sockets.hive-c0re = { + description = "hive-c0re admin socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = { + # Must match the `--socket` arg passed to `hive-c0re serve`. + ListenStream = "/run/hyperhive/host.sock"; + # 0660 root:root — `hivectl` is a host-only tool run as root. + SocketMode = "0660"; + # Parent dir inherits the RuntimeDirectory mode (0750) set on the + # service unit; DirectoryMode is only consulted when the dir is + # absent at socket-unit activation. + DirectoryMode = "0750"; + }; + }; }; }