hive-c0re: scrub lifecycle + meta cookies (#715 batch 9)

This commit is contained in:
damocles 2026-05-31 18:04:39 +02:00 committed by mara
commit b0495c5167
2 changed files with 73 additions and 86 deletions

View file

@ -94,19 +94,19 @@ pub async fn sync_agents(
// Reconcile topology.json against the live agent set — adds
// entries for newly-spawned agents (default: manager as parent,
// manager itself as root) and drops removed agents. Operator
// overrides via the write API (#361 follow-up) are preserved
// because reconcile only fills in missing entries. Idempotent;
// when nothing changed the file isn't touched.
// overrides via the write API are preserved because reconcile
// only fills in missing entries. Idempotent; when nothing changed
// the file isn't touched.
let agent_names: Vec<String> = agents.iter().map(|a| a.name.clone()).collect();
let topology_changed = crate::topology::reconcile(&agent_names)
.with_context(|| format!("reconcile {}", crate::topology::topology_path().display()))?;
// Refresh /var/lib/hyperhive/agent-ports.json so the hive-gateway
// nginx sees the new agent set (#15 / #740). The file is the
// single source of truth for which agents the gateway proxies to,
// since the gateway container lives in system config and can't be
// rebuilt from meta-flake events. Atomic write (tmp + rename) so
// a partial write never trips the gateway's read.
// nginx sees the new agent set. The file is the single source of
// truth for which agents the gateway proxies to, since the
// gateway container lives in system config and can't be rebuilt
// from meta-flake events. Atomic write (tmp + rename) so a
// partial write never trips the gateway's read.
if let Err(e) = crate::agent_ports::write(&agent_names) {
// Best-effort: a failed write doesn't block the meta-flake
// regen + container ops that follow. The gateway falls back
@ -117,10 +117,11 @@ pub async fn sync_agents(
// Refresh /var/lib/hyperhive/agent-sockets.json — sibling to the
// ports map, drives the gateway's unix-socket upstreams once
// agents opt in to `HIVE_WEB_SOCKET` (PR #800 / #784 phase 1).
// Coexists with the TCP-port map during the transition: the
// gateway picks the socket upstream when one exists, falls back
// to the TCP port otherwise. Same best-effort + non-fatal shape.
// agents opt in to `HIVE_WEB_SOCKET`. Coexists with the TCP-port
// map during the transition: the gateway picks the socket
// upstream when one exists, falls back to the TCP port otherwise.
// Same best-effort + non-fatal shape. See
// `docs/gateway.md::Per-agent unix-socket upstream`.
if let Err(e) = crate::agent_sockets::write(&agent_names) {
tracing::warn!(error = ?e, "agent_sockets::write failed (non-fatal)");
}
@ -301,7 +302,7 @@ fn render_flake(
/// of these as a top-level input in its own `flake.nix` will get a
/// `follows = "<name>"` line emitted in meta — collapsing the
/// otherwise-separate-but-identical `nixpkgs_N` nodes into a single
/// meta-level reference (#355).
/// meta-level reference.
const CANONICAL_INPUTS: &[&str] = &["nixpkgs", "nixpkgs-unstable"];
/// Env vars hive-c0re forwards from its own systemd unit env into every
@ -391,10 +392,8 @@ where
// resolves transitively to hyperhive's pin. One channel decision
// in the whole tree, no second source to drift.
//
// Per mara via triage on #619: "all nixpkgs follow the one
// hyperhive was deployed with — if not, that's what we should
// fix." This is the fix (flip from the concrete-url shape
// #619 originally shipped with).
// All nixpkgs follow the one hyperhive was deployed with —
// anything else would drift.
//
// Operators who want to slide the whole swarm onto a different
// channel do it at the host level via
@ -402,9 +401,9 @@ where
// makes hyperhive's nixpkgs = the host's nixpkgs and cascades
// through to every agent.
//
// closes #317 invariant still satisfied — `nixpkgs` is still a
// single canonical name in the meta tree, it just resolves
// through hyperhive instead of being its own root input.
// `nixpkgs` is still a single canonical name in the meta tree,
// it just resolves through hyperhive instead of being its own
// root input.
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n");
out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n");
@ -417,11 +416,11 @@ where
// For each canonical input the agent declares in its own
// `flake.nix` (detected by reading its applied `flake.lock`),
// emit `inputs.agent-<name>.inputs.<canon>.follows = "<canon>"`.
// Collapses three otherwise-separate-but-identical nixpkgs
// nodes (root + agent-bitburner's + agent-dmatrix's) into one
// (closes #355). Skipped silently for agents that don't
// declare the input — emitting follows on a non-existent
// input would error at `nix flake lock` time.
// Collapses otherwise-separate-but-identical nixpkgs nodes
// (root + every agent's own nixpkgs) into one. Skipped
// silently for agents that don't declare the input — emitting
// follows on a non-existent input would error at
// `nix flake lock` time.
for canon in lookup(&spec.name) {
let _ = writeln!(
out,
@ -452,13 +451,12 @@ where
modules = [
input.nixosModules.default
{
# Drop root (#658): the harness service inside the
# container runs as a non-root unix user named after
# the agent (`damocles`, `iris`, `hm1nd`, ). UID
# auto-assigned by NixOS per mara on #658; the per-
# agent override here is what makes `hyperhive.user.name`
# match the agent's identity instead of the harness-
# base default of `"agent"`.
# The harness service inside the container runs as a
# non-root unix user named after the agent (`damocles`,
# `iris`, `hm1nd`, ). UID auto-assigned by NixOS; the
# per-agent override here is what makes
# `hyperhive.user.name` match the agent's identity
# instead of the harness-base default of `"agent"`.
hyperhive.user.name = name;
programs.git.config.user = {
name = name;
@ -470,7 +468,7 @@ where
# shells); `systemd.globalEnvironment` is the analogue for
# systemd units so tea-login / forge-avatar-sync /
# matrix-avatar-sync etc. can read `$HYPERHIVE_STATE_DIR`
# without each service having to redeclare it (#604).
# without each service having to redeclare it.
environment.variables = {
HIVE_LABEL = name;
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
@ -506,9 +504,9 @@ where
// → in-container accessors fall back to None / defaults gracefully.
//
// - HIVE_FORGE_URL: agents poll this for Forgejo notifications.
// - HYPERHIVE_HIVE_DOMAIN: machine-readable hive DNS (#589).
// - HYPERHIVE_HIVE_DOMAIN: machine-readable hive DNS.
// - HYPERHIVE_HIVE_NAME / HYPERHIVE_SWARM_NAME: human display
// names for hive + swarm (#701).
// names for hive + swarm.
for (var, val) in forwarded_env_vars() {
let escaped = val.replace('\\', "\\\\").replace('"', "\\\"");
let _ = writeln!(out, " {var} = \"{escaped}\";");
@ -526,9 +524,8 @@ where
);
// Pull the topology map once and look up each agent's parent. An
// empty / absent topology.json yields `parent = null` for everyone
// — equivalent to the pre-#361 status quo (every container at root).
// `meta::sync_agents` seeds the file on first run with manager as
// root + everyone else under manager.
// (every container at root). `meta::sync_agents` seeds the file
// on first run with manager as root + everyone else under manager.
let topology = crate::topology::read();
for spec in agents {
let parent_attr = topology
@ -648,8 +645,8 @@ mod tests {
);
// Meta's `nixpkgs` + `nixpkgs-unstable` are aliases for
// hyperhive's sub-inputs. Single channel-pin authority:
// hyperhive's own flake.nix. Per mara via triage on #619:
// "all nixpkgs follow the one hyperhive was deployed with."
// hyperhive's own flake.nix. All nixpkgs follow the one
// hyperhive was deployed with.
assert!(
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""),
"missing nixpkgs follows alias:\n{out}"
@ -659,8 +656,8 @@ mod tests {
"missing nixpkgs-unstable follows alias:\n{out}"
);
// And conversely: no literal channel ref baked into meta. If
// this fails, someone reintroduced a hardcoded ref — see
// #619 follow-up rationale for why that drifts.
// this fails, someone reintroduced a hardcoded ref — would
// drift away from hyperhive's pin.
assert!(
!out.contains("nixpkgs.url ="),
"no literal `nixpkgs.url` should be emitted (hyperhive owns the pin):\n{out}"