From c27f915e4737c1369c9f94864a6ddaf8f9bcfe81 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 15 Jun 2026 21:52:18 +0200 Subject: [PATCH] fix(meta): forward HIVE_FORGE_URL etc. into globalEnvironment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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.) 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. --- hive-c0re/src/meta.rs | 63 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 3ec17332..51abe78e 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -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.` 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}" + ); + } }