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";
/// 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
///
@ -54,8 +55,9 @@ pub(crate) fn forge_http_base() -> &'static str {
static BASE: OnceLock<String> = OnceLock::new();
BASE.get_or_init(|| {
std::env::var("HIVE_FORGE_URL").expect(
"HIVE_FORGE_URL is unset — hive-c0re.nix sets it unconditionally, \
so this process was started outside the NixOS module",
"HIVE_FORGE_URL is unset — the hyperhive NixOS module sets it \
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.
fn core_avatar_png_path() -> std::path::PathBuf {
let dir = std::env::var("HIVE_ASSETS_DIR").expect(
"HIVE_ASSETS_DIR is unset — hive-c0re.nix sets it unconditionally, \
so this process was started outside the NixOS module",
"HIVE_ASSETS_DIR is unset — the hyperhive NixOS module sets it from \
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")
}
@ -51,9 +52,10 @@ fn core_avatar_png_path() -> std::path::PathBuf {
/// `assets` package.
fn config_org_avatar_png_path() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("HIVE_ORG_AVATAR_PNG").expect(
"HIVE_ORG_AVATAR_PNG is unset — hive-c0re.nix sets it unconditionally \
(from services.hyperhive.c0re.orgAvatarPng or the bundled default), \
so this process was started outside the NixOS module",
"HIVE_ORG_AVATAR_PNG is unset — the hyperhive NixOS module sets it \
unconditionally on the hive-c0re unit (from \
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

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
/// with, from the variables `hive-network.nix` sets on the `hive-c0re`
/// unit.
/// with, from the variables the hyperhive NixOS module derives from
/// `services.hyperhive.network.*` onto the `hive-c0re` unit.
///
/// 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
@ -143,12 +143,14 @@ pub fn network_isolation_from_vars(
subnet: Option<&str>,
) -> Result<hive_priv_sock::NetworkIsolation> {
let bridge = bridge.filter(|s| !s.is_empty()).context(
"HIVE_NETWORK_BRIDGE is unset or empty — hive-network.nix sets it on the \
hive-c0re unit, so this means the daemon is running outside its unit or \
with a broken module evaluation",
"HIVE_NETWORK_BRIDGE is unset or empty — the hyperhive NixOS module sets it \
from services.hyperhive.network.bridgeName on the hive-c0re unit, so this \
means the daemon is running outside its unit or with a broken module \
evaluation",
)?;
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 \
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.
///
/// The hive/swarm display names are here for exactly that reason, learned the
/// hard way: they were forwarded as runtime env only, while
/// `claude-settings.nix` read them from the container's `environment.variables`
/// hard way: they were forwarded as runtime env only, while the agent's
/// claude-settings module read them from the container's `environment.variables`
/// at *eval* time — where they were never set. Every agent baked
/// `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
@ -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.
///
/// 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
/// 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 —
/// which is exactly why the hive has to assert its own requirement here rather
/// than leaning on the module to reject the empty case.
@ -780,17 +780,19 @@ const REQUIRED_SERVICE_URL_VARS: &[&str] = &["HIVE_FORGE_URL"];
///
/// # Errors
///
/// When a required var is missing. `hive-c0re.nix` sets it unconditionally, so
/// this means the daemon is running outside the NixOS module; refusing to
/// When a required var is missing. The hyperhive NixOS module sets it
/// 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
/// forge.
fn require_service_urls(vars: &[(&'static str, String)]) -> Result<()> {
for required in REQUIRED_SERVICE_URL_VARS {
if !vars.iter().any(|(name, _)| name == required) {
anyhow::bail!(
"{required} is unset — hive-c0re.nix sets it unconditionally, so this process \
was started outside the NixOS module. Refusing to write a meta flake whose \
agents would every one of them have no forge configured."
"{required} is unset — the hyperhive NixOS module sets it unconditionally on \
the hive-c0re unit, so this process was started outside that unit. Refusing \
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";
/// 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
/// 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
@ -834,8 +836,8 @@ fn hive_ca_source() -> Option<String> {
}
/// Hive-wide OTEL config injected into every agent's build, read off
/// hive-c0re's own unit env (set from `services.hyperhive.otel.*` in
/// `nix/modules/hive-c0re.nix`). A present, non-empty
/// hive-c0re's own unit env (set from `services.hyperhive.otel.*`). A
/// present, non-empty
/// `HYPERHIVE_OTEL_ENDPOINT` is the enable signal — the host module
/// asserts the endpoint is set whenever `otel.enable` is true, so
/// "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
/// anchors as `hive-ca.pem`, when self-signed TLS is active.
///
/// That single file already carries the swarm root (`hive-tls.nix`
/// writes the hive CA *and* the root it is issued under into the trust
/// bundle), so every hive under the swarm root validates from it. Which
/// That single file already carries the swarm root (the hyperhive TLS
/// module writes the hive CA *and* the root it is issued under into the
/// 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
/// same thing once per peer.
///
@ -1313,8 +1315,8 @@ where
);
}
// Forwarded env vars — picked up from hive-c0re's own systemd unit
// env (`services.hyperhive.*` options flow through nix/modules/
// hive-c0re.nix into the host process). We copy whatever's set into
// env (`services.hyperhive.*` options flow through the hyperhive
// NixOS module into the host process). We copy whatever's set into
// each sub-agent's harness service env so the in-container surfaces
// (`identity.rs`, `forge_notify`) see a consistent view across the
// 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
// 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
// effective value is `"infinity"` or a RAM percentage — no
// byte count to derive, dependent env var stays unset, same as
@ -2127,7 +2129,7 @@ mod tests {
#[test]
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
// trust it at build time via security.pki.certificateFiles. Absent
// the signal, no reference is emitted (so the flake doesn't point at