From 4526e40a49a3750b1a3e412e1ab250ae4a50ca56 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 13:17:32 +0200 Subject: [PATCH] =?UTF-8?q?lifecycle:=20drop=20manager=20port=20special=20?= =?UTF-8?q?case=20(#753)=20=E2=80=94=20manager=20hashes=20into=208100..899?= =?UTF-8?q?9=20like=20every=20other=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- docs/approvals.md | 3 ++- docs/conventions.md | 4 ++-- docs/web-ui.md | 5 +++-- hive-c0re/src/agent_ports.rs | 11 +++++++---- hive-c0re/src/dashboard.rs | 4 +--- hive-c0re/src/lifecycle.rs | 21 ++++++++------------- nix/modules/hive-c0re.nix | 13 ++++++------- nix/modules/hive-forge.nix | 4 ++-- nix/modules/hive-matrix.nix | 4 ++-- nix/templates/harness-base.nix | 9 ++++++++- 11 files changed, 43 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 042ad564..ea6157da 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ the substrate. host (NixOS, runs hive-c0re.service) │ ├── operator -│ ├── browser → :7000 hive-c0re dashboard -│ ├── browser → :8000 / :8100-8999 per-agent web UIs +│ ├── browser → :7000 hive-c0re dashboard +│ ├── browser → :8100-8999 per-agent web UIs (all agents, FNV-1a hash) │ └── CLI → /run/hyperhive/host.sock admin protocol │ ├── hive-c0re (Rust daemon: lifecycle / broker / approvals / diff --git a/docs/approvals.md b/docs/approvals.md index 93574e73..34645bb1 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -343,7 +343,8 @@ Differences from sub-agents: - `flake.nix` extends `hyperhive.nixosConfigurations.manager` (vs `agent-base`). - Container name is `hm1nd` (no `h-` prefix). -- Fixed web UI port (`MANAGER_PORT = 8000`). +- Web UI port via `lifecycle::agent_web_port("hm1nd")` — same + FNV-1a hash as every other agent (8100..8999 range) since #753. - `set_nspawn_flags` adds two extra binds: `/var/lib/hyperhive/agents` → `/agents` (RW) so the manager can edit per-agent proposed repos, and `/var/lib/hyperhive/applied` → `/applied` (RO) so the manager diff --git a/docs/conventions.md b/docs/conventions.md index 37155c62..16c2bbb5 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -10,8 +10,8 @@ exist because something already went wrong without them. - The manager is `hm1nd` (no `h-` prefix, fixed name). - `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap. - Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE` - (8100..8999); manager fixed at 8000; dashboard `cfg.dashboardPort` - (default 7000). + (8100..8999) for every agent including the manager (#753); dashboard + `cfg.dashboardPort` (default 7000). ## Identity = socket diff --git a/docs/web-ui.md b/docs/web-ui.md index 84745b75..99368f6f 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -1,8 +1,9 @@ # Web UI Two web surfaces share the same skeleton: the dashboard (port 7000) -and the per-agent UIs (manager on :8000, sub-agents on a hashed -:8100-8999). Both are SPAs — `GET /` returns a static shell, +and the per-agent UIs (every container — including the manager — +hashes into :8100-8999 via `lifecycle::agent_web_port`'s FNV-1a, +since #753). Both are SPAs — `GET /` returns a static shell, `/api/state` returns JSON, JS renders. No full-page reloads. ## Shape (shared by both) diff --git a/hive-c0re/src/agent_ports.rs b/hive-c0re/src/agent_ports.rs index 6b38049a..7a8dccaf 100644 --- a/hive-c0re/src/agent_ports.rs +++ b/hive-c0re/src/agent_ports.rs @@ -20,10 +20,13 @@ //! //! Ports come from [`crate::lifecycle::agent_web_port`] — pure //! FNV-1a(name) hash so the value is reproducible from the name -//! alone. The manager is intentionally excluded: it sits at the -//! fixed `MANAGER_PORT` (8000) and the gateway routes `/` straight -//! to it without a per-agent prefix (see `nix/modules/hive-gateway.nix` -//! upstream config, atlas's #740). +//! alone. The manager is intentionally excluded from the map: the +//! gateway routes `/` straight to it via the c0re dashboard upstream +//! (see `nix/modules/hive-gateway.nix`, atlas's #740) rather than a +//! per-agent `/agent//` prefix. Post-#753 the manager's port +//! is computed by the same hash as every other agent, but it still +//! doesn't appear here — keeping the routing surface "sub-agents +//! only" matches the gateway's current shape. //! //! Atomicity: write to a sibling `.tmp` file + rename so a partial //! write never leaves an unparseable file in place. The gateway's diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 55e640f3..4381033b 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -30,8 +30,6 @@ use crate::container_view::{ContainerView, claude_has_session}; use crate::coordinator::Coordinator; use crate::lifecycle::{self, MANAGER_NAME}; -const MANAGER_PORT: u16 = 8000; - #[derive(Clone)] struct AppState { coord: Arc, @@ -416,7 +414,7 @@ async fn api_state(headers: HeaderMap, State(state): State) -> axum::J axum::Json(StateSnapshot { seq, hostname, - manager_port: MANAGER_PORT, + manager_port: lifecycle::agent_web_port(MANAGER_NAME), any_stale, containers, transients, diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 2a9d8822..73032c4e 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -16,9 +16,6 @@ pub const MAX_AGENT_NAME: usize = 9; /// nixosConfiguration (`manager`, not `agent-base`). pub const MANAGER_NAME: &str = "hm1nd"; -/// Web UI port reserved for the manager (sub-agents hash into 8100..8999). -pub const MANAGER_PORT: u16 = 8000; - /// Mount point of the per-agent runtime directory inside the container. pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive"; @@ -57,18 +54,16 @@ const WEB_PORT_RANGE: u16 = 900; const DEFAULT_MEMORY_MAX: &str = "2G"; const DEFAULT_CPU_QUOTA: &str = "50%"; -/// Per-agent web UI port. Manager is fixed at `MANAGER_PORT`; every -/// sub-agent is `WEB_PORT_BASE + FNV-1a(name) % WEB_PORT_RANGE`, -/// pure and reproducible from just the name. Collisions are -/// possible (birthday paradox at ~30 agents); the operator resolves -/// them by renaming an agent (different hash → different port). -/// Stable across hosts, restarts, and dashboard renders — no -/// state-file dance. +/// Per-agent web UI port — `WEB_PORT_BASE + FNV-1a(name) % +/// WEB_PORT_RANGE` for every agent including the manager (#753 +/// dropped the pre-#753 "manager pinned at 8000" special case so +/// the port allocation rule reads the same for every name). +/// Collisions are possible (birthday paradox at ~30 agents); the +/// operator resolves them by renaming an agent (different hash → +/// different port). Stable across hosts, restarts, and dashboard +/// renders — no state-file dance. #[must_use] pub fn agent_web_port(name: &str) -> u16 { - if name == MANAGER_NAME { - return MANAGER_PORT; - } let mut hash: u32 = 2_166_136_261; for b in name.bytes() { hash ^= u32::from(b); diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index e1915c03..67995565 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -239,9 +239,11 @@ in ]; # Per-container web UIs share the host's network namespace and need - # their ports reachable when there's no gateway in front. Manager: - # 8000. Sub-agents: 8100..8999 (deterministic hash; see - # `lifecycle::agent_web_port`). + # their ports reachable when there's no gateway in front. Every + # container — including the manager (#753 dropped the pre-#753 + # "manager pinned at 8000" special case) — hashes into + # 8100..8999 via `lifecycle::agent_web_port`'s FNV-1a, so a single + # range opening covers all of them. # # The dashboard port (`cfg.dashboardPort`, default 7000) is *not* # listed here — since #652 the dashboard binds `127.0.0.1` only, @@ -257,11 +259,8 @@ in # open in the host firewall would defeat the gateway's "single # front door" story (closes #621). Operators who opt out of the # gateway still get those direct ports opened so the legacy - # `http://:8100/` flow works. + # `http://:/` flow works. networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) { - allowedTCPPorts = [ - 8000 - ]; allowedTCPPortRanges = [ { from = 8100; diff --git a/nix/modules/hive-forge.nix b/nix/modules/hive-forge.nix index 64ad4ee4..b7e4e3c7 100644 --- a/nix/modules/hive-forge.nix +++ b/nix/modules/hive-forge.nix @@ -42,8 +42,8 @@ in default = 3000; description = '' TCP port the forge serves HTTP on. Default 3000 sits outside - hyperhive's claimed ranges (dashboard 7000, manager 8000, - sub-agents 8100..8999). Change this if you already have + hyperhive's claimed ranges (dashboard 7000, every agent in + 8100..8999 via FNV-1a hash). Change this if you already have another forgejo bound to 3000. ''; }; diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index fe631815..907312aa 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -246,8 +246,8 @@ in description = '' TCP port tuwunel serves the matrix client-server API on. Default 8008 is the matrix-spec well-known port. Sits - outside hyperhive's claimed ranges (dashboard 7000, manager - 8000, sub-agents 8100..8999). Federation listens on + outside hyperhive's claimed ranges (dashboard 7000, every + agent in 8100..8999 via FNV-1a hash). Federation listens on `federationPort` separately. ''; }; diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index b8ac71bb..0c433b59 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -1309,7 +1309,14 @@ in # `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`); # the values here keep the container sensible if anyone # evaluates the standalone config. - HIVE_PORT = "8000"; + # + # `HIVE_PORT` = FNV-1a("hm1nd") % 900 + 8100 = 8875 per + # `lifecycle::agent_web_port` (#753 dropped the + # pre-#753 "manager pinned at 8000" special case). Hardcoded + # here because the standalone-eval path doesn't go through + # `meta::render_flake`; real deploys pick up the rust-computed + # value via meta and never touch this fallback. + HIVE_PORT = "8875"; HIVE_LABEL = "hm1nd"; }; serviceConfig = {