refactor(#1003): nixpkgs + nixpkgs-unstable as top-level meta inputs
Per mara's direction: both nixpkgs and nixpkgs-unstable are now
top-level meta flake inputs with explicit store-path URLs. Hyperhive
follows them rather than the other way around:
inputs.nixpkgs.url = "path:${pkgs.path}";
inputs.nixpkgs-unstable.url = "path:${nixpkgs-unstable}";
inputs.hyperhive.url = "...";
inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs";
inputs.hyperhive.inputs.nixpkgs-unstable.follows = "nixpkgs-unstable";
New NixOS host options (auto-set at build time, overridable):
services.hyperhive.c0re.nixpkgsFlake
default: "path:${pkgs.path}" — host's evaluated nixpkgs.
services.hyperhive.c0re.nixpkgsUnstableFlake
default: "path:${nixpkgs-unstable}" from hyperhive's flake.nix —
the channel that carries claude-code. Operators can override to
track a different unstable snapshot.
Legacy fallback (both args empty) preserved for backward compat.
Two new Rust tests cover the full-URL and fallback paths.
This commit is contained in:
parent
fe5a41288d
commit
db50da570a
10 changed files with 124 additions and 28 deletions
|
|
@ -53,6 +53,7 @@ pub fn meta_dir() -> PathBuf {
|
|||
pub async fn sync_agents(
|
||||
hyperhive_flake: &str,
|
||||
nixpkgs_flake: &str,
|
||||
nixpkgs_unstable_flake: &str,
|
||||
dashboard_port: u16,
|
||||
operator_pronouns: &str,
|
||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||
|
|
@ -65,6 +66,7 @@ pub async fn sync_agents(
|
|||
let new_flake = render_flake(
|
||||
hyperhive_flake,
|
||||
nixpkgs_flake,
|
||||
nixpkgs_unstable_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
context_window_tokens,
|
||||
|
|
@ -303,6 +305,7 @@ pub async fn lock_update_hyperhive() -> Result<()> {
|
|||
fn render_flake(
|
||||
hyperhive_flake: &str,
|
||||
nixpkgs_flake: &str,
|
||||
nixpkgs_unstable_flake: &str,
|
||||
dashboard_port: u16,
|
||||
operator_pronouns: &str,
|
||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||
|
|
@ -311,6 +314,7 @@ fn render_flake(
|
|||
render_flake_with_lookup(
|
||||
hyperhive_flake,
|
||||
nixpkgs_flake,
|
||||
nixpkgs_unstable_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
context_window_tokens,
|
||||
|
|
@ -397,6 +401,7 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
|
|||
fn render_flake_with_lookup<F>(
|
||||
hyperhive_flake: &str,
|
||||
nixpkgs_flake: &str,
|
||||
nixpkgs_unstable_flake: &str,
|
||||
dashboard_port: u16,
|
||||
operator_pronouns: &str,
|
||||
context_window_tokens: &std::collections::HashMap<String, u64>,
|
||||
|
|
@ -409,31 +414,35 @@ where
|
|||
use std::fmt::Write as _;
|
||||
let mut out = String::new();
|
||||
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
|
||||
// `hyperhive` is the single channel-pin authority. `nixpkgs` is wired
|
||||
// to the exact nixpkgs store path hive-c0re was evaluated with — which
|
||||
// is the host's nixpkgs when the operator sets
|
||||
// `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` in their host
|
||||
// flake, or hyperhive's own pin otherwise. Using an explicit `path:`
|
||||
// URL instead of `follows = "hyperhive/nixpkgs"` is essential here:
|
||||
// meta points to hyperhive's store path, so nix would otherwise
|
||||
// resolve hyperhive's own pinned lock rather than the host-substituted
|
||||
// version that `follows` produced.
|
||||
// `nixpkgs` + `nixpkgs-unstable` are top-level meta inputs with explicit
|
||||
// store-path URLs. `hyperhive` then follows them via
|
||||
// `hyperhive.inputs.*.follows`. This cascades through to every agent
|
||||
// because `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to
|
||||
// the same top-level node.
|
||||
//
|
||||
// `nixpkgs-unstable` still follows hyperhive (claude-code lives there;
|
||||
// no same-channel requirement from the host side).
|
||||
// Why explicit `path:` URLs instead of
|
||||
// `nixpkgs.follows = "hyperhive/nixpkgs"`:
|
||||
// meta points to hyperhive's *store path* as its flake input, so nix
|
||||
// reads hyperhive's own pinned lock when evaluating that input — the
|
||||
// host-level `follows` the operator set never propagates. Injecting the
|
||||
// evaluated `pkgs.path` / nixpkgs-unstable path directly at nix-module
|
||||
// evaluation time is the only reliable way to honour the host's channel
|
||||
// choice.
|
||||
//
|
||||
// `nixpkgs` is the single canonical name in the meta tree — every
|
||||
// agent that declares it in its own `flake.nix` gets a
|
||||
// `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` directive that
|
||||
// collapses all per-agent nixpkgs nodes into one.
|
||||
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
|
||||
// Fallback (both flake args empty): legacy `follows` wiring — used when
|
||||
// hive-c0re is not built with this option wired up.
|
||||
if nixpkgs_flake.is_empty() {
|
||||
// Fallback: legacy behaviour when nixpkgs_flake not injected.
|
||||
// Legacy path: meta defers to hyperhive's own lock.
|
||||
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");
|
||||
} else {
|
||||
let _ = writeln!(out, " nixpkgs.url = \"{nixpkgs_flake}\";");
|
||||
let _ = writeln!(out, " nixpkgs-unstable.url = \"{nixpkgs_unstable_flake}\";");
|
||||
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
|
||||
out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n");
|
||||
out.push_str(" hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\";\n");
|
||||
}
|
||||
out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n");
|
||||
for spec in agents {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
|
|
@ -699,27 +708,33 @@ mod tests {
|
|||
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)],
|
||||
);
|
||||
// Explicit nixpkgs_flake → meta uses `nixpkgs.url`, NOT follows.
|
||||
// This is the path taken when hive-c0re.nix injects `pkgs.path`:
|
||||
// the URL is the exact nixpkgs evaluated with the host's nixpkgs
|
||||
// (which IS the host's version when `follows` is set).
|
||||
// Both nixpkgs + nixpkgs-unstable are top-level inputs with
|
||||
// explicit URLs; hyperhive follows them.
|
||||
assert!(
|
||||
out.contains("nixpkgs.url = \"path:/nix/store/aaaa-nixpkgs-source\""),
|
||||
"expected explicit nixpkgs.url:\n{out}"
|
||||
);
|
||||
assert!(
|
||||
!out.contains("nixpkgs.follows"),
|
||||
"follows must not appear when nixpkgs_flake is set:\n{out}"
|
||||
out.contains("nixpkgs-unstable.url = \"path:/nix/store/bbbb-nixpkgs-unstable-source\""),
|
||||
"expected explicit nixpkgs-unstable.url:\n{out}"
|
||||
);
|
||||
// nixpkgs-unstable still follows hyperhive (claude-code lives there).
|
||||
assert!(
|
||||
out.contains("nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\""),
|
||||
"missing nixpkgs-unstable follows:\n{out}"
|
||||
out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\""),
|
||||
"expected hyperhive.inputs.nixpkgs.follows:\n{out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\""),
|
||||
"expected hyperhive.inputs.nixpkgs-unstable.follows:\n{out}"
|
||||
);
|
||||
assert!(
|
||||
!out.contains("nixpkgs.follows = \"hyperhive"),
|
||||
"old-style follows must not appear when flake args are set:\n{out}"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -730,6 +745,7 @@ mod tests {
|
|||
let out = render_flake(
|
||||
"github:example/hyperhive",
|
||||
"",
|
||||
"",
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
|
|
@ -739,6 +755,10 @@ mod tests {
|
|||
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""),
|
||||
"expected fallback follows:\n{out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\""),
|
||||
"expected fallback unstable follows:\n{out}"
|
||||
);
|
||||
assert!(
|
||||
!out.contains("nixpkgs.url ="),
|
||||
"no explicit url should be emitted in fallback mode:\n{out}"
|
||||
|
|
@ -759,6 +779,7 @@ mod tests {
|
|||
let out = render_flake_with_lookup(
|
||||
"github:example/hyperhive",
|
||||
"path:/nix/store/aaaa-nixpkgs-source",
|
||||
"path:/nix/store/bbbb-nixpkgs-unstable-source",
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
|
|
@ -793,6 +814,7 @@ mod tests {
|
|||
let out = render_flake_with_lookup(
|
||||
"github:example/hyperhive",
|
||||
"path:/nix/store/aaaa-nixpkgs-source",
|
||||
"path:/nix/store/bbbb-nixpkgs-unstable-source",
|
||||
8000,
|
||||
"she/her",
|
||||
&std::collections::HashMap::new(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue