diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 1a3f5360..c0119b40 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -1215,6 +1215,12 @@ in # errors out. Point HOME at the StateDirectory below (persistent, # so the cache survives across rebuilds). HOME = "/var/lib/hive-priv"; + # hive-priv runs as root. Root nix defaults to store=auto which + # resolves to the LOCAL store — bypassing the host daemon, its + # remote builders, and prebuilt derivation outputs. Force daemon + # routing so nixos-container update and the nix prebuild see the + # same store and substituters as every other build context. + NIX_REMOTE = "daemon"; }; serviceConfig = { ExecStart = "${cfg.package}/bin/hive-priv"; diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 9733d9b7..0629116b 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -1394,6 +1394,10 @@ in HIVE_DEFAULT_EFFORT = config.hyperhive.effortLevel; HIVE_ASSETS_DIR = "${pkgs.hyperhive-assets}/share/hyperhive"; SHELL = "${pkgs.bashInteractive}/bin/bash"; + # Route interactive-shell nix invocations through the host daemon. + # Redundant with /etc/profile.d/nix-daemon.sh but ensures it's set + # regardless of which profile files are sourced. + NIX_REMOTE = "daemon"; } // lib.optionalAttrs (!config.hyperhive.autoCompact) { # Zero watermark disables proactive compaction; the reactive path @@ -1449,8 +1453,27 @@ in # local builds rather than failing on the missing user-namespace. # See `docs/gotchas.md::Containerized nix-daemon needs # sandbox-fallback = true` + `docs/security.md` for the rationale. + # + # Note: with NIX_REMOTE=daemon below this becomes a no-op for the + # common case — daemon-routed builds run on the host where sandboxing + # works. It stays as a belt-and-suspenders fallback for any context + # that bypasses the daemon (e.g. direct nix-store invocations). nix.settings.sandbox-fallback = lib.mkForce true; + # Route ALL nix invocations in this container through the host + # nix-daemon socket, regardless of whether the caller is root or + # non-root. Without this, root contexts (PID 1, systemd services + # running as root) default to store=auto which resolves to the LOCAL + # store — bypassing the shared daemon, its remote builders, and the + # host's prebuilt derivation cache, causing spurious full rebuilds. + # + # systemd.globalEnvironment sets DefaultEnvironment in systemd.conf, + # so every unit started by PID 1 inherits NIX_REMOTE=daemon. + # Non-root nix clients already default to the daemon socket, so this + # is a no-op for them; it only matters for root services that would + # otherwise silently use the local store. + systemd.globalEnvironment.NIX_REMOTE = "daemon"; + # `claude-code` is unfree. Each per-agent container's nixosConfiguration # evaluates its own `nixpkgs` instance, so the operator's host-level # `nixpkgs.config.allowUnfreePredicate` does not propagate into here —