fix(meta): forward HIVE_FORGE_URL etc. into globalEnvironment

The forwarded host env vars (HIVE_FORGE_URL, HIVE_FORGE_PUBLIC_URL,
HYPERHIVE_HIVE_DOMAIN, ...) were emitted only into the harness
service's own unit environment. Under network isolation that left the
bash-task runner, the matrix daemon, tea-login and interactive shells
without HIVE_FORGE_URL, so `hive-forge` and friends fell back to the
loopback default (localhost:3000) — unreachable from a private netns,
where the in-cluster gateway URL (forge.<domain>) is the only path.

Emit the forwarded set into systemd.globalEnvironment too, which every
unit + shell in the container inherits, so all in-container surfaces
see the same forge/matrix endpoints. The existing harness-service
forward is kept (harmless redundancy). Shared netns is unaffected (the
localhost default still works there).

Adds a regression test asserting the forwarded vars land inside the
globalEnvironment block, and the generated flake parses.
This commit is contained in:
atlas 2026-06-15 21:52:18 +02:00 committed by mara
commit c27f915e47

View file

@ -686,12 +686,27 @@ where
HIVE_LABEL = name;
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
HYPERHIVE_HARNESS_DIR = "/agents/${name}/harness";
};
"#,
);
// Forwarded vars (HIVE_FORGE_URL etc.) also go into globalEnvironment,
// not just the harness service env below, so EVERY service + shell in
// the container inherits them — crucially the bash-task runner (where
// `hive-forge` + `git` actually run), plus the matrix daemon, tea-login
// and interactive shells. Scoped to the harness service alone they were
// invisible to bash tasks: harmless in shared netns (the localhost
// default works) but broken under isolation, where the in-cluster
// `forge.<domain>` URL is the only reachable path.
for (var, val) in forwarded_env_vars() {
let escaped = val.replace('\\', "\\\\").replace('"', "\\\"");
let _ = writeln!(out, " {var} = \"{escaped}\";");
}
out.push_str(
r" };
systemd.services.${service}.environment = parentEnv // toolGroupsEnv // capabilitiesEnv // {
HIVE_PORT = toString port;
HIVE_LABEL = name;
HIVE_DASHBOARD_PORT = toString dashboardPort;
HIVE_OPERATOR_PRONOUNS = operatorPronouns;"#,
HIVE_OPERATOR_PRONOUNS = operatorPronouns;",
);
// Per-model context-window env vars declared in the host-level
// `services.hive-c0re.contextWindowTokens` option. Use a sorted
@ -1038,4 +1053,48 @@ mod tests {
"alice shouldn't have any inputs follows:\n{out}"
);
}
#[test]
fn render_flake_forwards_env_into_global_environment() {
// Regression for the isolation breakage where forwarded vars
// (HIVE_FORGE_URL etc.) landed only on the harness service env,
// so the bash-task runner / matrix daemon / shells defaulted to
// localhost and couldn't reach the in-cluster gateway. They must
// also appear in `systemd.globalEnvironment`, which every unit +
// shell in the container inherits.
//
// SAFETY: single-threaded mutation of a process env var the other
// tests don't assert the absence of; restored before returning.
unsafe {
std::env::set_var("HIVE_FORGE_URL", "http://forge.example.test");
}
let out = render_flake(
"github:example/hyperhive",
"path:/nix/store/aaaa-nixpkgs-source",
"path:/nix/store/bbbb-nixpkgs-unstable-source",
8000,
"she/her",
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
unsafe {
std::env::remove_var("HIVE_FORGE_URL");
}
// The var must be emitted inside the globalEnvironment block, i.e.
// before the per-service harness env block that follows it.
let global_at = out
.find("systemd.globalEnvironment = {")
.expect("globalEnvironment block must exist");
let service_at = out
.find("systemd.services.${service}.environment")
.expect("harness service env block must exist");
let forge_at = out
.find("HIVE_FORGE_URL = \"http://forge.example.test\"")
.expect("HIVE_FORGE_URL must be forwarded into the flake");
assert!(
forge_at > global_at && forge_at < service_at,
"HIVE_FORGE_URL must land inside systemd.globalEnvironment, \
not only the harness service env:\n{out}"
);
}
}