lifecycle: drop manager port special case (#753) — manager hashes into 8100..8999 like every other agent
This commit is contained in:
parent
f9a492f7a9
commit
4526e40a49
11 changed files with 43 additions and 39 deletions
|
|
@ -21,8 +21,8 @@ the substrate.
|
||||||
host (NixOS, runs hive-c0re.service)
|
host (NixOS, runs hive-c0re.service)
|
||||||
│
|
│
|
||||||
├── operator
|
├── operator
|
||||||
│ ├── browser → :7000 hive-c0re dashboard
|
│ ├── browser → :7000 hive-c0re dashboard
|
||||||
│ ├── browser → :8000 / :8100-8999 per-agent web UIs
|
│ ├── browser → :8100-8999 per-agent web UIs (all agents, FNV-1a hash)
|
||||||
│ └── CLI → /run/hyperhive/host.sock admin protocol
|
│ └── CLI → /run/hyperhive/host.sock admin protocol
|
||||||
│
|
│
|
||||||
├── hive-c0re (Rust daemon: lifecycle / broker / approvals /
|
├── hive-c0re (Rust daemon: lifecycle / broker / approvals /
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,8 @@ Differences from sub-agents:
|
||||||
- `flake.nix` extends `hyperhive.nixosConfigurations.manager`
|
- `flake.nix` extends `hyperhive.nixosConfigurations.manager`
|
||||||
(vs `agent-base`).
|
(vs `agent-base`).
|
||||||
- Container name is `hm1nd` (no `h-` prefix).
|
- 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`
|
- `set_nspawn_flags` adds two extra binds: `/var/lib/hyperhive/agents`
|
||||||
→ `/agents` (RW) so the manager can edit per-agent proposed repos,
|
→ `/agents` (RW) so the manager can edit per-agent proposed repos,
|
||||||
and `/var/lib/hyperhive/applied` → `/applied` (RO) so the manager
|
and `/var/lib/hyperhive/applied` → `/applied` (RO) so the manager
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ exist because something already went wrong without them.
|
||||||
- The manager is `hm1nd` (no `h-` prefix, fixed name).
|
- The manager is `hm1nd` (no `h-` prefix, fixed name).
|
||||||
- `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap.
|
- `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap.
|
||||||
- Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE`
|
- Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE`
|
||||||
(8100..8999); manager fixed at 8000; dashboard `cfg.dashboardPort`
|
(8100..8999) for every agent including the manager (#753); dashboard
|
||||||
(default 7000).
|
`cfg.dashboardPort` (default 7000).
|
||||||
|
|
||||||
## Identity = socket
|
## Identity = socket
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
# Web UI
|
# Web UI
|
||||||
|
|
||||||
Two web surfaces share the same skeleton: the dashboard (port 7000)
|
Two web surfaces share the same skeleton: the dashboard (port 7000)
|
||||||
and the per-agent UIs (manager on :8000, sub-agents on a hashed
|
and the per-agent UIs (every container — including the manager —
|
||||||
:8100-8999). Both are SPAs — `GET /` returns a static shell,
|
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.
|
`/api/state` returns JSON, JS renders. No full-page reloads.
|
||||||
|
|
||||||
## Shape (shared by both)
|
## Shape (shared by both)
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,13 @@
|
||||||
//!
|
//!
|
||||||
//! Ports come from [`crate::lifecycle::agent_web_port`] — pure
|
//! Ports come from [`crate::lifecycle::agent_web_port`] — pure
|
||||||
//! FNV-1a(name) hash so the value is reproducible from the name
|
//! FNV-1a(name) hash so the value is reproducible from the name
|
||||||
//! alone. The manager is intentionally excluded: it sits at the
|
//! alone. The manager is intentionally excluded from the map: the
|
||||||
//! fixed `MANAGER_PORT` (8000) and the gateway routes `/` straight
|
//! gateway routes `/` straight to it via the c0re dashboard upstream
|
||||||
//! to it without a per-agent prefix (see `nix/modules/hive-gateway.nix`
|
//! (see `nix/modules/hive-gateway.nix`, atlas's #740) rather than a
|
||||||
//! upstream config, atlas's #740).
|
//! per-agent `/agent/<name>/` 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
|
//! Atomicity: write to a sibling `.tmp` file + rename so a partial
|
||||||
//! write never leaves an unparseable file in place. The gateway's
|
//! write never leaves an unparseable file in place. The gateway's
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ use crate::container_view::{ContainerView, claude_has_session};
|
||||||
use crate::coordinator::Coordinator;
|
use crate::coordinator::Coordinator;
|
||||||
use crate::lifecycle::{self, MANAGER_NAME};
|
use crate::lifecycle::{self, MANAGER_NAME};
|
||||||
|
|
||||||
const MANAGER_PORT: u16 = 8000;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
coord: Arc<Coordinator>,
|
coord: Arc<Coordinator>,
|
||||||
|
|
@ -416,7 +414,7 @@ async fn api_state(headers: HeaderMap, State(state): State<AppState>) -> axum::J
|
||||||
axum::Json(StateSnapshot {
|
axum::Json(StateSnapshot {
|
||||||
seq,
|
seq,
|
||||||
hostname,
|
hostname,
|
||||||
manager_port: MANAGER_PORT,
|
manager_port: lifecycle::agent_web_port(MANAGER_NAME),
|
||||||
any_stale,
|
any_stale,
|
||||||
containers,
|
containers,
|
||||||
transients,
|
transients,
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,6 @@ pub const MAX_AGENT_NAME: usize = 9;
|
||||||
/// nixosConfiguration (`manager`, not `agent-base`).
|
/// nixosConfiguration (`manager`, not `agent-base`).
|
||||||
pub const MANAGER_NAME: &str = "hm1nd";
|
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.
|
/// Mount point of the per-agent runtime directory inside the container.
|
||||||
pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive";
|
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_MEMORY_MAX: &str = "2G";
|
||||||
const DEFAULT_CPU_QUOTA: &str = "50%";
|
const DEFAULT_CPU_QUOTA: &str = "50%";
|
||||||
|
|
||||||
/// Per-agent web UI port. Manager is fixed at `MANAGER_PORT`; every
|
/// Per-agent web UI port — `WEB_PORT_BASE + FNV-1a(name) %
|
||||||
/// sub-agent is `WEB_PORT_BASE + FNV-1a(name) % WEB_PORT_RANGE`,
|
/// WEB_PORT_RANGE` for every agent including the manager (#753
|
||||||
/// pure and reproducible from just the name. Collisions are
|
/// dropped the pre-#753 "manager pinned at 8000" special case so
|
||||||
/// possible (birthday paradox at ~30 agents); the operator resolves
|
/// the port allocation rule reads the same for every name).
|
||||||
/// them by renaming an agent (different hash → different port).
|
/// Collisions are possible (birthday paradox at ~30 agents); the
|
||||||
/// Stable across hosts, restarts, and dashboard renders — no
|
/// operator resolves them by renaming an agent (different hash →
|
||||||
/// state-file dance.
|
/// different port). Stable across hosts, restarts, and dashboard
|
||||||
|
/// renders — no state-file dance.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn agent_web_port(name: &str) -> u16 {
|
pub fn agent_web_port(name: &str) -> u16 {
|
||||||
if name == MANAGER_NAME {
|
|
||||||
return MANAGER_PORT;
|
|
||||||
}
|
|
||||||
let mut hash: u32 = 2_166_136_261;
|
let mut hash: u32 = 2_166_136_261;
|
||||||
for b in name.bytes() {
|
for b in name.bytes() {
|
||||||
hash ^= u32::from(b);
|
hash ^= u32::from(b);
|
||||||
|
|
|
||||||
|
|
@ -239,9 +239,11 @@ in
|
||||||
];
|
];
|
||||||
|
|
||||||
# Per-container web UIs share the host's network namespace and need
|
# Per-container web UIs share the host's network namespace and need
|
||||||
# their ports reachable when there's no gateway in front. Manager:
|
# their ports reachable when there's no gateway in front. Every
|
||||||
# 8000. Sub-agents: 8100..8999 (deterministic hash; see
|
# container — including the manager (#753 dropped the pre-#753
|
||||||
# `lifecycle::agent_web_port`).
|
# "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*
|
# The dashboard port (`cfg.dashboardPort`, default 7000) is *not*
|
||||||
# listed here — since #652 the dashboard binds `127.0.0.1` only,
|
# 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
|
# open in the host firewall would defeat the gateway's "single
|
||||||
# front door" story (closes #621). Operators who opt out of the
|
# front door" story (closes #621). Operators who opt out of the
|
||||||
# gateway still get those direct ports opened so the legacy
|
# gateway still get those direct ports opened so the legacy
|
||||||
# `http://<host>:8100/` flow works.
|
# `http://<host>:<port>/` flow works.
|
||||||
networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) {
|
networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) {
|
||||||
allowedTCPPorts = [
|
|
||||||
8000
|
|
||||||
];
|
|
||||||
allowedTCPPortRanges = [
|
allowedTCPPortRanges = [
|
||||||
{
|
{
|
||||||
from = 8100;
|
from = 8100;
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ in
|
||||||
default = 3000;
|
default = 3000;
|
||||||
description = ''
|
description = ''
|
||||||
TCP port the forge serves HTTP on. Default 3000 sits outside
|
TCP port the forge serves HTTP on. Default 3000 sits outside
|
||||||
hyperhive's claimed ranges (dashboard 7000, manager 8000,
|
hyperhive's claimed ranges (dashboard 7000, every agent in
|
||||||
sub-agents 8100..8999). Change this if you already have
|
8100..8999 via FNV-1a hash). Change this if you already have
|
||||||
another forgejo bound to 3000.
|
another forgejo bound to 3000.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -246,8 +246,8 @@ in
|
||||||
description = ''
|
description = ''
|
||||||
TCP port tuwunel serves the matrix client-server API on.
|
TCP port tuwunel serves the matrix client-server API on.
|
||||||
Default 8008 is the matrix-spec well-known port. Sits
|
Default 8008 is the matrix-spec well-known port. Sits
|
||||||
outside hyperhive's claimed ranges (dashboard 7000, manager
|
outside hyperhive's claimed ranges (dashboard 7000, every
|
||||||
8000, sub-agents 8100..8999). Federation listens on
|
agent in 8100..8999 via FNV-1a hash). Federation listens on
|
||||||
`federationPort` separately.
|
`federationPort` separately.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1309,7 +1309,14 @@ in
|
||||||
# `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`);
|
# `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`);
|
||||||
# the values here keep the container sensible if anyone
|
# the values here keep the container sensible if anyone
|
||||||
# evaluates the standalone config.
|
# 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";
|
HIVE_LABEL = "hm1nd";
|
||||||
};
|
};
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue