No need to preserve agent IPs across this deploy — nothing outside a
container depends on a specific agent IP. Simpler approach: subtract
DHCP_POOL_SIZE from the usable count before hashing so agents only ever
land in [2, usable - DHCP_POOL_SIZE + 1], never in the DHCP pool.
Removes the secondary-hash remap block (~10 lines). Returns None for
subnets too small to hold both agent slots and the pool (edge case;
practical subnets are /24).
Move the DHCP pool size constant out of the two separate definitions
(Nix literal + Rust const) into a shared data file: nix/dhcp-pool-size.
- nix/dhcp-pool-size: new file, contains '14'
- hive-gateway.nix: reads via builtins.readFile + toIntBase10
- lifecycle/mod.rs: parses via include_bytes! const block at compile time
Cargo automatically tracks include_bytes! as a file dependency so a
change to nix/dhcp-pool-size triggers recompilation without build.rs.
Add a DHCP pool to the gateway's dnsmasq so bridge-attached service
containers (hive-ci and future equivalents) get their addresses from
a proper DHCP server instead of a brittle static derivation.
gateway (hive-gateway.nix):
- Add IPv4 arithmetic helpers (ipToInt, intToIp, pow2) to compute the
DHCP pool range at nix eval time from bridgeIp + bridgePrefixLength.
- Reserve the last dhcpPoolSize (14) usable host addresses as the DHCP
pool (e.g. .241-.254 on a /24 with 10.42.0.0 network).
- Add dhcp-range and dhcp-leasefile to the dnsmasq settings block.
The pool is active whenever services.hyperhive.network.enable is true.
hive-ci (hive-ci.nix):
- Remove the ciBridgeIp / ciBridgeOctets static derivation and the
brittle top-of-/24 comment block.
- Switch networking.interfaces.eth0 to useDHCP = true so hive-ci gets
its address from the gateway DHCP pool.
lifecycle (mod.rs, tests.rs):
- Add DHCP_POOL_SIZE = 14 constant (must stay in sync with
dhcpPoolSize in hive-gateway.nix).
- Remap agents whose FNV-1a hash falls in the DHCP pool into the
agent-only window [2, dhcp_start - 1]. Only the rare agent whose
name hashes into the pool is affected; all others keep their IPs.
- Update and extend tests: agent range is now .2-.240 on /24;
add agent_network_ip_never_in_dhcp_pool covering 18 agent names.
New POST /api/infra-container/{name}/{action} dashboard route (start/
stop/restart on hive-ci/hive-forge/hive-gateway/hive-matrix), reusing
the existing priv_client::control_infra_container helper the
infra_admin agent path already uses, plus an audit_log entry per
attempt. Adds infra_containers to the /api/state StateSnapshot (name +
live running status via systemctl is-active). New 1NFR4 sub-tab on the
C0R3 dashboard page: one row per infra container with a running/
stopped badge and start/stop/restart buttons, polled every 5s while
the sub-tab is open.
Collapse the scattered ensure_agent_runtime_dir calls into the lifecycle
functions themselves so callers have a single responsibility:
- lifecycle::spawn: calls ensure_agent_runtime_dir before write_dropins.
Callers (handle_spawn, ensure_root_agent) no longer need a separate
preamble step.
- lifecycle::rebuild_no_meta spawn path: calls ensure_agent_runtime_dir
before write_dropins. apply_commit / merge_config_pr flows no longer
need a manual ensure_agent_runtime_dir.
- run_create (job-queue): drops ensure_agent_runtime_dir + register_agent.
The tail Reconcile's converge_start_preamble handles the runtime dir
and mcp_sockets::spawn_poll handles the listener. Create stays purely
'provision + create', not 'create + start'.
- handle_spawn (server.rs): drops manual preamble; lifecycle::spawn owns it.
Drops unneeded unregister_agent on failure (supervisor handles listener).
- ensure_root_agent (auto_update.rs): drops manual ensure_agent_runtime_dir.
- actions.rs apply_commit / merge_config_pr: drop manual
ensure_agent_runtime_dir; rebuild_no_meta's spawn path handles it.
Result: ensure_agent_runtime_dir lives in exactly two places —
lifecycle::spawn (direct spawn) and converge_start_preamble (start/reconcile
path). All other callers are clean call sites.
- lifecycle::StartableAgent: opaque token produced only by
converge_start_preamble. #[must_use] with a hint to call
start_with_fallback(token).
- lifecycle::converge_start_preamble(name, hive, paths): runs
ensure_agent_runtime_dir + write_dropins, returns StartableAgent.
The only way to obtain a token.
- lifecycle::start_with_fallback(token: StartableAgent): public API
now requires the token. Callers that skip the preamble get a compile
error, not a runtime outage.
- lifecycle::start_with_fallback_inner(name): private; used internally
by rebuild_no_meta where the preamble is already enforced structurally
(write_dropins was called on the line above).
- exec.rs ReconcileAction::Start: migrated to converge_start_preamble
+ start_with_fallback(token). The write_dropins + start_with_fallback
two-step is now a single typed pipeline.
- lifecycle::ensure_agent_runtime_dir(name): pure filesystem op, no
Coordinator dep. Creates /run/hyperhive/agents/<name> without touching
the MCP listener map.
- workers/mcp_sockets::spawn_poll(coord): 10 s reconcile loop (same shape
as agent_sockets::spawn_poll). Converges 'agent running => MCP listener
bound'. First tick is immediate so hive-c0re restarts re-register all
running agents without waiting a full interval. Fixes the dead-listener-
after-daemon-restart gap.
- All ensure_runtime() call sites updated:
- Prebuild/Swap/WriteDropin: Coordinator::agent_dir() (pure, no IO)
- Reconcile-Start: ensure_agent_runtime_dir + agent_dir (dir may be
missing after reboot; listener deferred to supervisor)
- run_create / handle_spawn: ensure_agent_runtime_dir + register_agent
(eager on first spawn so socket ready before harness first turn)
- apply_commit / merge_config_pr: ensure_agent_runtime_dir + agent_dir
- Manager (auto_update): ensure_agent_runtime_dir + agent_dir
(manager has no MCP listener; socket_server::start_manager owns it)
- ensure_runtime() retained in Coordinator with updated doc pointing at
the preferred split form. No callers remain outside tests.
Root cause of the boot outage: container@h-* units try to start before
hive-c0re reaches ensure_runtime, so bind-mount source dirs are missing.
Fix: hive-c0re (via hive-priv, which runs as root) writes
/etc/tmpfiles.d/hyperhive-agents.conf whenever the agent set changes.
systemd-tmpfiles-setup.service (sysinit.target) reads it at every boot
BEFORE any container units start, pre-creating:
/run/hyperhive/agents/<name> — MCP socket dir (bind -> /run/hive)
/run/hive-agent/<name> — web socket dir (bind -> /run/hive-agent)
This alone removes the outage class: even if hive-c0re is slow to start,
the bind-mount sources exist and container units can activate.
Added:
- PrivRequest::SyncAgentTmpfiles { agents } in hive-sh4re
- sync_agent_tmpfiles() in hive-priv: generates content, writes atomically,
calls systemd-tmpfiles --create to apply immediately
- priv_client::sync_agent_tmpfiles() wrapper
- lifecycle::sync_tmpfiles() best-effort helper (list + priv call)
- Call sites: hive-c0re startup, handle_spawn success, destroy success
every agent lifecycle verb on the admin socket (rebuild / restart /
restart-all / kill / stop / start) now submits job-queue DAGs and
returns their ids; hivectl polls the new HostRequest::QueueDag and
prints a live node-chain progress line per DAG (fan-out children
included), exiting non-zero on failure — --no-wait opts out. DagView
and the queue wire enums move to hive_sh4re::jobs (wire types live in
the shared crate); the last fused rebuild path (lifecycle::rebuild)
is gone. tracker: #2166