gateway: fix nginx reload — trigger from host via systemd-run (#889)

The #872 path-unit approach was silently broken: IN_MOVED_TO from an
atomic rename on the host does not propagate across the nspawn
mount-namespace boundary into the container, so the watcher inside
the gateway container never fired.

Fix: after each agents.conf write, c0re calls
`systemd-run --machine=hive-gateway -- nginx -s reload` from the host.
The reload is best-effort (logged on failure, not fatal).

Remove the now-unused `hive-gateway-agents-conf.path` +
`hive-gateway-nginx-reload.service` from the gateway container config.
Update docs/gateway.md + comments to reflect the host-side approach.
This commit is contained in:
atlas 2026-05-31 22:35:06 +02:00 committed by mara
commit 01d7c37af2
3 changed files with 91 additions and 60 deletions

View file

@ -1,7 +1,7 @@
//! Runtime nginx include-file generator for the gateway's per-agent
//! `/agent/<name>/` location blocks (#869).
//!
//! Writes `/var/lib/hyperhive/agents.conf` on every topology change.
//! Writes `/var/lib/hyperhive/gateway/agents.conf` on every topology change.
//! The gateway container bind-mounts the whole `/var/lib/hyperhive/`
//! directory at `/run/hive-state/` and nginx includes
//! `/run/hive-state/agents.conf`. A systemd path unit inside the
@ -105,11 +105,23 @@ fn render(names: &[String]) -> String {
}
/// Atomically write the nginx include file for `names` to
/// [`host_conf_path()`]. Skips the rename when the rendered body
/// matches what's already on disk (idempotent; avoids spurious
/// gateway reloads on a quiet tick). On a fresh install where the
/// file doesn't exist yet, writes an empty-but-valid config so nginx
/// can start before any agents have registered.
/// [`host_conf_path()`]. Skips the write + reload when the rendered
/// body matches what's already on disk (idempotent; avoids spurious
/// gateway reloads on a quiet tick).
///
/// After a successful write, triggers an nginx reload inside the
/// gateway container from the HOST side via
/// `systemd-run --machine=hive-gateway nginx -s reload`. This is
/// intentionally host-side rather than relying on a systemd path unit
/// inside the container watching the bind-mounted file: `IN_MOVED_TO`
/// (fired by the atomic rename) does not reliably propagate across the
/// nspawn mount-namespace boundary, so the path-unit approach was
/// silently broken after #872 merged (#889).
///
/// The `systemd-run` call is best-effort — a failed reload is logged
/// but not fatal. nginx will pick up the new include on its next
/// housekeeping restart or the next manual reload; the host's agent
/// topology has already been written correctly.
pub fn write(names: &[String]) -> Result<()> {
let body = render(names);
let path = host_conf_path();
@ -130,9 +142,49 @@ pub fn write(names: &[String]) -> Result<()> {
path.display()
)
})?;
// Trigger nginx reload from the host. Ignore errors — a failed
// reload is recoverable (nginx keeps serving the previous config).
reload_gateway_nginx();
Ok(())
}
/// Send `nginx -s reload` to the gateway container via systemd-run.
/// Runs non-interactively in a transient scope so it doesn't block
/// c0re's polling loop. Best-effort: errors are logged, not bubbled.
fn reload_gateway_nginx() {
// `--machine=hive-gateway` targets the container by its nspawn
// machine name (same as the nixos-container name). `--quiet`
// suppresses the transient unit name echo. `--` separates
// systemd-run args from the command.
let status = std::process::Command::new("systemd-run")
.args([
"--machine=hive-gateway",
"--quiet",
"--",
"nginx",
"-s",
"reload",
])
.status();
match status {
Ok(s) if s.success() => {
tracing::debug!("gateway nginx reloaded successfully");
}
Ok(s) => {
tracing::warn!(
exit_code = ?s.code(),
"gateway nginx reload exited non-zero; will pick up on next restart"
);
}
Err(e) => {
tracing::warn!(
error = %e,
"failed to invoke systemd-run for gateway nginx reload"
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;