From d272803d588a04470aa078fa6efbc4369c132a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Wed, 3 Jun 2026 00:33:15 +0200 Subject: [PATCH] fix(#702): spare agent creds from migration chown; give nix a writable HOME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two privsep follow-ups, both in the hive-c0re service: 1. The migration 'chown -R hive-core /var/lib/hyperhive' (which only started running once ExecStartPre stopped failing 203/EXEC) stomped every agent's bind-mounted creds — agents//{claude,state,harness,config} are owned by the per-agent/manager users — logging all agents out with no way back in. Scope it to everything *except* agents/, plus the agents/ dir node itself so c0re can still create new per-agent subdirs. Each container's hive-agent-user-migrate activation chowns the contents back. 2. nix (prebuild 'nix build', flake-check, meta eval in c0re; nixos-container update->nix in priv) writes its cache under $HOME/.cache. Both services run as users with no home -> HOME=/var/empty (unwritable) -> Lix cache init fails, rebuilds error out. Set HOME to each service's StateDirectory (adding one for hive-priv). --- nix/modules/hive-c0re.nix | 45 +++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 9ff67e4c..be2a4574 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -346,6 +346,12 @@ in "/run/current-system/sw" ]; environment = { + # nix (the prebuild `nix build`, flake-check, and meta eval) writes + # its fetcher/eval cache under $HOME/.cache. As a system user + # hive-core has no home, so HOME defaults to the unwritable + # /var/empty and Lix fails to initialise its cache. Point HOME at + # the writable StateDirectory. + HOME = "/var/lib/hyperhive"; HYPERHIVE_GIT = "${pkgs.git}/bin/git"; # Path to the dashboard static dist. The hive-c0re axum router # serves this via `tower_http::ServeDir` for any path it doesn't @@ -423,15 +429,26 @@ in }; serviceConfig = { ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --nixpkgs-flake ${cfg.nixpkgsFlake} --nixpkgs-unstable-flake ${cfg.nixpkgsUnstableFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; - # One-time migration: chown existing state tree to the service - # user after upgrading from a root-run install. The `+` prefix - # runs this step as root even though User = hive-core; the `-` - # prefix tolerates failure (e.g. an empty tree) without blocking - # startup. coreutils ships `chown` but no `sh`, so invoke chown - # directly rather than through a shell. systemd's StateDirectory - # chowns the top-level dir at every start, but pre-existing files - # inside may still be root-owned. - ExecStartPre = "+-${pkgs.coreutils}/bin/chown -R hive-core:hive-core /var/lib/hyperhive"; + # Migrate hive-c0re's *own* state to the service user after an + # upgrade from a root-run install (systemd's StateDirectory only + # chowns the top-level dir, not pre-existing files inside it). The + # `+` prefix runs as root despite User = hive-core; `-` tolerates + # failure. coreutils ships `chown` but no `sh`, so invoke the + # binaries directly rather than through a shell. + # + # CRITICAL: exclude the per-agent `agents/` subtree. Its contents + # (each agent's `claude/` OAuth creds, `state/`, `harness/`, + # `config/`) are owned by the per-agent / manager users, and each + # container's `hive-agent-user-migrate` activation script chowns + # them back to that user on boot. Blanket-chowning them to hive-core + # makes every agent's `~/.claude` unreadable — logging them all out + # with no way to log back in. So chown everything *except* agents/, + # plus the `agents/` dir node itself (not its contents) so c0re can + # still create new per-agent subdirs. + ExecStartPre = [ + "+-${pkgs.findutils}/bin/find /var/lib/hyperhive -mindepth 1 -maxdepth 1 -not -name agents -exec ${pkgs.coreutils}/bin/chown -R hive-core:hive-core {} +" + "+-${pkgs.coreutils}/bin/chown hive-core:hive-core /var/lib/hyperhive/agents" + ]; Restart = "on-failure"; RestartSec = 2; User = "hive-core"; @@ -521,6 +538,14 @@ in pkgs.util-linux # umount (nsenter is hardcoded in the script) pkgs.e2fsprogs # chattr ]; + environment = { + # `nixos-container update/create` runs `nix`, which writes its + # fetcher/eval cache under $HOME/.cache. With ProtectHome and no + # explicit HOME this lands on the unwritable /var/empty and Lix + # errors out. Point HOME at the StateDirectory below (persistent, + # so the cache survives across rebuilds). + HOME = "/var/lib/hive-priv"; + }; serviceConfig = { ExecStart = "${cfg.package}/bin/hive-priv"; Type = "simple"; @@ -530,6 +555,8 @@ in # hive-priv needs to write to /etc/nixos-containers/ and # /run/systemd/system/ — "strict" would block both. ProtectSystem = "false"; + # Writable HOME for nix's caches (see environment.HOME above). + StateDirectory = "hive-priv"; }; }; };