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.
This commit is contained in:
atlas 2026-06-01 16:29:41 +02:00
commit f8c0f64fd4
3 changed files with 54 additions and 10 deletions

View file

@ -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

View file

@ -11,16 +11,33 @@ use crate::coordinator::Coordinator;
use crate::lifecycle;
pub async fn serve(socket: &Path, coord: Arc<Coordinator>) -> 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 {

View file

@ -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";
};
};
};
}