diff --git a/docs/gateway.md b/docs/gateway.md index 82eb6c59..e8614a5b 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -130,9 +130,11 @@ logical agent name → TCP web port: Written alongside `agents.conf` on every topology change. Ports come from `lifecycle::agent_web_port(name)` — a pure FNV-1a hash of the name, -reproducible from the name alone. The manager is excluded: the gateway -routes `/` directly to c0re's dashboard upstream, not through a -per-agent `/agent//` prefix. +reproducible from the name alone. The manager is excluded: it always +uses a unix socket (`HIVE_WEB_SOCKET` is unconditionally set for the +manager role), so its TCP port never appears in the fallback map. +The gateway routes `/agent/root/` to the manager's unix socket via +`agents.conf` alongside sub-agents. The file doubles as a human-readable audit artifact — `cat agent-ports.json` shows every registered sub-agent and its deterministic port assignment. TCP diff --git a/hive-c0re/src/gateway_nginx.rs b/hive-c0re/src/gateway_nginx.rs index 06cc2d4d..20a55936 100644 --- a/hive-c0re/src/gateway_nginx.rs +++ b/hive-c0re/src/gateway_nginx.rs @@ -10,7 +10,7 @@ use std::fmt::Write as _; use std::path::PathBuf; use crate::agent_sockets; -use crate::lifecycle::{self, MANAGER_NAME}; +use crate::lifecycle; const HOST_CONF_PATH: &str = "/var/lib/hyperhive/gateway/agents.conf"; @@ -39,7 +39,8 @@ const PROXY_HEADER_BLOCK: &str = " proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;"; -/// Render the nginx include body for `names`. Manager is filtered out. +/// Render the nginx include body for `names`. Manager is included so +/// `/agent/root/` is routable through the gateway. /// /// When `frontend_dir` is `Some(path)` each agent gets split location /// blocks: @@ -66,9 +67,6 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String { \n# Gateway reloads nginx automatically on each update (systemd path unit).\n", ); for name in names { - if name == MANAGER_NAME { - continue; - } let port = lifecycle::agent_web_port(name); let upstream = if agent_sockets::ready_marker_for(name).exists() { format!( @@ -235,6 +233,7 @@ fn reload_gateway_nginx() { #[cfg(test)] mod tests { use super::*; + use crate::lifecycle::MANAGER_NAME; // ── legacy mode (frontend_dir = None) ────────────────────────────── @@ -247,13 +246,13 @@ mod tests { } #[test] - fn render_filters_manager() { + fn render_includes_manager() { let names: Vec = [MANAGER_NAME, "iris"] .iter() .map(|s| (*s).to_owned()) .collect(); let body = render(&names, None); - assert!(!body.contains(&format!("/agent/{MANAGER_NAME}/"))); + assert!(body.contains(&format!("/agent/{MANAGER_NAME}/"))); assert!(body.contains("/agent/iris/")); } @@ -358,13 +357,13 @@ mod tests { } #[test] - fn split_mode_filters_manager() { + fn split_mode_includes_manager() { let names: Vec = [MANAGER_NAME, "iris"] .iter() .map(|s| (*s).to_owned()) .collect(); let body = render(&names, Some(FAKE_FRONTEND)); - assert!(!body.contains(&format!("/agent/{MANAGER_NAME}/"))); + assert!(body.contains(&format!("/agent/{MANAGER_NAME}/"))); assert!(body.contains("/agent/iris/")); }