hive-c0re: stop naming nix implementation files in env-var prose

Six diagnostics and eight comments explained where an environment
variable comes from by naming the module file that sets it. The reader
of a panic is an operator, who does not edit that file; the reader of
the comment is already in the code. Neither can act on a filename, and
a filename in prose is checked by nothing.

Each one now names what the reader can act on: the option where there
is one (services.hyperhive.network.bridgeName, .bridgeIp,
.bridgePrefixLength, c0re.assets, c0re.orgAvatarPng), otherwise the
unit the process is expected to run under. Two of the six diagnostics
had a real option available; the other four have none, so they name
the unit rather than an invented path.

Four of them pointed at hive-c0re.nix, which is a directory. Two of
those spelled it nix/modules/hive-c0re.nix — a directory that does not
exist either, and one of the two was wrapped across a line break, so
no grep for the whole path could have found it. That is the argument
for the change: prose naming a file rots silently because nothing
compiles it.

Scope here is the four files around those diagnostics. The same
pattern still matches elsewhere in the tree; that sweep follows.
This commit is contained in:
atlas 2026-08-30 13:54:20 +02:00 committed by mara
commit ca4b56c006
4 changed files with 41 additions and 33 deletions

View file

@ -39,7 +39,8 @@ use users::{
const FORGE_CONTAINER: &str = "hive-forge"; const FORGE_CONTAINER: &str = "hive-forge";
/// Base HTTP URL for the local Forgejo instance, from `HIVE_FORGE_URL` /// Base HTTP URL for the local Forgejo instance, from `HIVE_FORGE_URL`
/// (set unconditionally by `hive-c0re.nix` to `http://<forge.domain>`). /// (set unconditionally by the hyperhive NixOS module on the `hive-c0re`
/// unit, to `http://<forge.domain>`).
/// ///
/// # Panics /// # Panics
/// ///
@ -54,8 +55,9 @@ pub(crate) fn forge_http_base() -> &'static str {
static BASE: OnceLock<String> = OnceLock::new(); static BASE: OnceLock<String> = OnceLock::new();
BASE.get_or_init(|| { BASE.get_or_init(|| {
std::env::var("HIVE_FORGE_URL").expect( std::env::var("HIVE_FORGE_URL").expect(
"HIVE_FORGE_URL is unset — hive-c0re.nix sets it unconditionally, \ "HIVE_FORGE_URL is unset — the hyperhive NixOS module sets it \
so this process was started outside the NixOS module", unconditionally on the hive-c0re unit, so this process was \
started outside that unit",
) )
}) })
} }

View file

@ -37,8 +37,9 @@ use crate::paths::FORGE_CORE_TOKEN as CORE_TOKEN_PATH;
/// package to change it. /// package to change it.
fn core_avatar_png_path() -> std::path::PathBuf { fn core_avatar_png_path() -> std::path::PathBuf {
let dir = std::env::var("HIVE_ASSETS_DIR").expect( let dir = std::env::var("HIVE_ASSETS_DIR").expect(
"HIVE_ASSETS_DIR is unset — hive-c0re.nix sets it unconditionally, \ "HIVE_ASSETS_DIR is unset — the hyperhive NixOS module sets it from \
so this process was started outside the NixOS module", services.hyperhive.c0re.assets on the hive-c0re unit, so this \
process was started outside that unit",
); );
std::path::PathBuf::from(dir).join("branding/hyperhive.png") std::path::PathBuf::from(dir).join("branding/hyperhive.png")
} }
@ -51,9 +52,10 @@ fn core_avatar_png_path() -> std::path::PathBuf {
/// `assets` package. /// `assets` package.
fn config_org_avatar_png_path() -> std::path::PathBuf { fn config_org_avatar_png_path() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("HIVE_ORG_AVATAR_PNG").expect( std::path::PathBuf::from(std::env::var("HIVE_ORG_AVATAR_PNG").expect(
"HIVE_ORG_AVATAR_PNG is unset — hive-c0re.nix sets it unconditionally \ "HIVE_ORG_AVATAR_PNG is unset — the hyperhive NixOS module sets it \
(from services.hyperhive.c0re.orgAvatarPng or the bundled default), \ unconditionally on the hive-c0re unit (from \
so this process was started outside the NixOS module", services.hyperhive.c0re.orgAvatarPng or the bundled default), so \
this process was started outside that unit",
)) ))
} }
/// Per-agent token scopes (broad-but-not-admin) for tokens hive-c0re /// Per-agent token scopes (broad-but-not-admin) for tokens hive-c0re

View file

@ -126,8 +126,8 @@ pub fn bridge_gateway_ip(subnet_cidr: &str) -> Option<String> {
} }
/// Build the network-isolation settings every container is configured /// Build the network-isolation settings every container is configured
/// with, from the variables `hive-network.nix` sets on the `hive-c0re` /// with, from the variables the hyperhive NixOS module derives from
/// unit. /// `services.hyperhive.network.*` onto the `hive-c0re` unit.
/// ///
/// Isolation is the only supported mode: the on/off toggle is gone, so /// Isolation is the only supported mode: the on/off toggle is gone, so
/// there is no non-isolated branch to fall back to and a missing or /// there is no non-isolated branch to fall back to and a missing or
@ -143,12 +143,14 @@ pub fn network_isolation_from_vars(
subnet: Option<&str>, subnet: Option<&str>,
) -> Result<hive_priv_sock::NetworkIsolation> { ) -> Result<hive_priv_sock::NetworkIsolation> {
let bridge = bridge.filter(|s| !s.is_empty()).context( let bridge = bridge.filter(|s| !s.is_empty()).context(
"HIVE_NETWORK_BRIDGE is unset or empty — hive-network.nix sets it on the \ "HIVE_NETWORK_BRIDGE is unset or empty — the hyperhive NixOS module sets it \
hive-c0re unit, so this means the daemon is running outside its unit or \ from services.hyperhive.network.bridgeName on the hive-c0re unit, so this \
with a broken module evaluation", means the daemon is running outside its unit or with a broken module \
evaluation",
)?; )?;
let subnet = subnet.filter(|s| !s.is_empty()).context( let subnet = subnet.filter(|s| !s.is_empty()).context(
"HIVE_NETWORK_SUBNET is unset or empty — hive-network.nix sets it on the \ "HIVE_NETWORK_SUBNET is unset or empty — the hyperhive NixOS module sets it \
from services.hyperhive.network.bridgeIp and .bridgePrefixLength on the \
hive-c0re unit, so this means the daemon is running outside its unit or \ hive-c0re unit, so this means the daemon is running outside its unit or \
with a broken module evaluation", with a broken module evaluation",
)?; )?;

View file

@ -716,8 +716,8 @@ const FORWARDED_VARS: &[&str] = &[
/// which is how a hive ends up with two disagreeing answers for one value. /// which is how a hive ends up with two disagreeing answers for one value.
/// ///
/// The hive/swarm display names are here for exactly that reason, learned the /// The hive/swarm display names are here for exactly that reason, learned the
/// hard way: they were forwarded as runtime env only, while /// hard way: they were forwarded as runtime env only, while the agent's
/// `claude-settings.nix` read them from the container's `environment.variables` /// claude-settings module read them from the container's `environment.variables`
/// at *eval* time — where they were never set. Every agent baked /// at *eval* time — where they were never set. Every agent baked
/// `hive=unknown,swarm=unknown` into its OTEL resource attributes and shipped /// `hive=unknown,swarm=unknown` into its OTEL resource attributes and shipped
/// that label on every metric, while the same process's env held the right /// that label on every metric, while the same process's env held the right
@ -757,9 +757,9 @@ fn push_forwarded_var_options(out: &mut String, vars: &[(&'static str, String)])
/// Service URLs a running hive must always supply, checked before rendering. /// Service URLs a running hive must always supply, checked before rendering.
/// ///
/// The forge is not optional on a real hive: `hive-c0re.nix` sets /// The forge is not optional on a real hive: the hyperhive NixOS module sets
/// `HIVE_FORGE_URL` unconditionally, so its absence means this daemon was /// `HIVE_FORGE_URL` unconditionally, so its absence means this daemon was
/// started outside the NixOS module. The agent option is nullable — `null` /// started outside its unit. The agent option is nullable — `null`
/// legitimately means "no forge" when the modules are evaluated on their own — /// legitimately means "no forge" when the modules are evaluated on their own —
/// which is exactly why the hive has to assert its own requirement here rather /// which is exactly why the hive has to assert its own requirement here rather
/// than leaning on the module to reject the empty case. /// than leaning on the module to reject the empty case.
@ -780,17 +780,19 @@ const REQUIRED_SERVICE_URL_VARS: &[&str] = &["HIVE_FORGE_URL"];
/// ///
/// # Errors /// # Errors
/// ///
/// When a required var is missing. `hive-c0re.nix` sets it unconditionally, so /// When a required var is missing. The hyperhive NixOS module sets it
/// this means the daemon is running outside the NixOS module; refusing to /// unconditionally, so this means the daemon is running outside its unit;
/// refusing to
/// write the flake beats writing one whose agents would silently all lack a /// write the flake beats writing one whose agents would silently all lack a
/// forge. /// forge.
fn require_service_urls(vars: &[(&'static str, String)]) -> Result<()> { fn require_service_urls(vars: &[(&'static str, String)]) -> Result<()> {
for required in REQUIRED_SERVICE_URL_VARS { for required in REQUIRED_SERVICE_URL_VARS {
if !vars.iter().any(|(name, _)| name == required) { if !vars.iter().any(|(name, _)| name == required) {
anyhow::bail!( anyhow::bail!(
"{required} is unset — hive-c0re.nix sets it unconditionally, so this process \ "{required} is unset — the hyperhive NixOS module sets it unconditionally on \
was started outside the NixOS module. Refusing to write a meta flake whose \ the hive-c0re unit, so this process was started outside that unit. Refusing \
agents would every one of them have no forge configured." to write a meta flake whose agents would every one of them have no forge \
configured."
); );
} }
} }
@ -816,7 +818,7 @@ fn forwarded_env_vars() -> Vec<(&'static str, String)> {
const HIVE_CA_FILE: &str = "hive-ca.pem"; const HIVE_CA_FILE: &str = "hive-ca.pem";
/// Host path of the hive's TLS trust anchors, when self-signed TLS is /// Host path of the hive's TLS trust anchors, when self-signed TLS is
/// active. `hive-tls.nix` sets `HIVE_TLS_CA_PATH` in hive-c0re's service env /// active. The hyperhive TLS module sets `HIVE_TLS_CA_PATH` in hive-c0re's env
/// (to `<tls.stateDir>/trust-bundle.pem`) whenever the gateway serves a /// (to `<tls.stateDir>/trust-bundle.pem`) whenever the gateway serves a
/// self-signed, hive-CA-signed leaf. The file holds one *or more* certs — /// self-signed, hive-CA-signed leaf. The file holds one *or more* certs —
/// the hive CA plus the swarm root it is issued under — and is copied /// the hive CA plus the swarm root it is issued under — and is copied
@ -834,8 +836,8 @@ fn hive_ca_source() -> Option<String> {
} }
/// Hive-wide OTEL config injected into every agent's build, read off /// Hive-wide OTEL config injected into every agent's build, read off
/// hive-c0re's own unit env (set from `services.hyperhive.otel.*` in /// hive-c0re's own unit env (set from `services.hyperhive.otel.*`). A
/// `nix/modules/hive-c0re.nix`). A present, non-empty /// present, non-empty
/// `HYPERHIVE_OTEL_ENDPOINT` is the enable signal — the host module /// `HYPERHIVE_OTEL_ENDPOINT` is the enable signal — the host module
/// asserts the endpoint is set whenever `otel.enable` is true, so /// asserts the endpoint is set whenever `otel.enable` is true, so
/// "endpoint present" == "OTEL on". The optional fields map to the /// "endpoint present" == "OTEL on". The optional fields map to the
@ -887,9 +889,9 @@ fn otel_config() -> Option<OtelConfig> {
/// `(filename, host_source_path)` — now just the hive's own trust /// `(filename, host_source_path)` — now just the hive's own trust
/// anchors as `hive-ca.pem`, when self-signed TLS is active. /// anchors as `hive-ca.pem`, when self-signed TLS is active.
/// ///
/// That single file already carries the swarm root (`hive-tls.nix` /// That single file already carries the swarm root (the hyperhive TLS
/// writes the hive CA *and* the root it is issued under into the trust /// module writes the hive CA *and* the root it is issued under into the
/// bundle), so every hive under the swarm root validates from it. Which /// trust bundle), so every hive under the swarm root validates from it. Which
/// is why the per-peer CAs this used to append are gone: they said the /// is why the per-peer CAs this used to append are gone: they said the
/// same thing once per peer. /// same thing once per peer.
/// ///
@ -1313,8 +1315,8 @@ where
); );
} }
// Forwarded env vars — picked up from hive-c0re's own systemd unit // Forwarded env vars — picked up from hive-c0re's own systemd unit
// env (`services.hyperhive.*` options flow through nix/modules/ // env (`services.hyperhive.*` options flow through the hyperhive
// hive-c0re.nix into the host process). We copy whatever's set into // NixOS module into the host process). We copy whatever's set into
// each sub-agent's harness service env so the in-container surfaces // each sub-agent's harness service env so the in-container surfaces
// (`identity.rs`, `forge_notify`) see a consistent view across the // (`identity.rs`, `forge_notify`) see a consistent view across the
// whole hive. Absent host-side env (option not set) → skip emission // whole hive. Absent host-side env (option not set) → skip emission
@ -1379,7 +1381,7 @@ where
}; };
// Effective `MemoryMax=` for this agent (per-agent override, else // Effective `MemoryMax=` for this agent (per-agent override, else
// the hive-wide default), turned into a raw byte count so // the hive-wide default), turned into a raw byte count so
// `claude-settings.nix` can derive a JSC heap ceiling from it // the agent's claude-settings module can derive a JSC heap ceiling
// (see `hyperhive.claudeMemoryMaxBytes`). `null` when the // (see `hyperhive.claudeMemoryMaxBytes`). `null` when the
// effective value is `"infinity"` or a RAM percentage — no // effective value is `"infinity"` or a RAM percentage — no
// byte count to derive, dependent env var stays unset, same as // byte count to derive, dependent env var stays unset, same as
@ -2127,7 +2129,7 @@ mod tests {
#[test] #[test]
fn render_flake_embeds_hive_ca_when_signalled() { fn render_flake_embeds_hive_ca_when_signalled() {
// When hive-tls.nix signals a self-signed hive CA via // When the hyperhive TLS module signals a self-signed hive CA via
// HIVE_TLS_CA_PATH (and the cert exists), the agent module must // HIVE_TLS_CA_PATH (and the cert exists), the agent module must
// trust it at build time via security.pki.certificateFiles. Absent // trust it at build time via security.pki.certificateFiles. Absent
// the signal, no reference is emitted (so the flake doesn't point at // the signal, no reference is emitted (so the flake doesn't point at