From 29c57e058abee5b1e6b082eaab69fcf76befef09 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 30 Oct 2026 11:13:16 +0100 Subject: [PATCH] run the platform containers' root check through a build-independent path --- docs/agent-lifecycle/persistence.md | 5 ++++- nix/host-modules/hive-priv.nix | 8 +++++++- .../lib/container-rootfs-check.nix | 13 ++++++++++--- nix/module-eval/core-toggle.nix | 19 +++++++++++++------ 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/docs/agent-lifecycle/persistence.md b/docs/agent-lifecycle/persistence.md index a35e7ac4..93dc3be7 100644 --- a/docs/agent-lifecycle/persistence.md +++ b/docs/agent-lifecycle/persistence.md @@ -418,7 +418,10 @@ until the next collection. A drop-in written by an older hive-priv has no **Hyperhive's platform containers: warn only.** The forge, the matrix server and the swarm services (15 containers, declared by their own modules) run the same check as `hive-priv check-rootfs --warn-only `, from `ExecStartPre=` on -their own `container@` unit. It evaluates every rule, logs each violation +their own `container@` unit, through the symlink `/run/hive-rootfs-check` +that `hive-priv.nix` points at the current build, so a new hyperhive build does +not change the unit or restart the container. It evaluates every rule, logs each +violation as a warning, and exits 0, so the container starts either way. The line reads `rootfs check (warn-only): : ` with journald's warning priority. Strict refusal needs the live roots of all 15 to pass first, and the warnings diff --git a/nix/host-modules/hive-priv.nix b/nix/host-modules/hive-priv.nix index 3975e926..2781b394 100644 --- a/nix/host-modules/hive-priv.nix +++ b/nix/host-modules/hive-priv.nix @@ -9,13 +9,19 @@ }: let controllerCfg = config.services.hyperhive.deploy.hive-controller; + rootfsCheck = import ./lib/container-rootfs-check.nix { inherit config; }; in { config = lib.mkIf config.services.hyperhive.deploy.hive-controller.enable { # The parent of every agent socket dir. hive-priv is its only writer # (`EnsureAgentSocketDir`), and its unit lists it in `ReadWritePaths`, # which fails the unit when the path is missing. - systemd.tmpfiles.rules = [ "d /run/hive-agent 0755 root root - -" ]; + systemd.tmpfiles.rules = [ + "d /run/hive-agent 0755 root root - -" + # The platform containers run their warn-only root check by this path + # (`lib/container-rootfs-check.nix`), so their units carry no store path. + "L+ ${rootfsCheck.hivePriv} - - - - ${controllerCfg.package}/bin/hive-priv" + ]; # Socket unit for hive-priv — the narrow root helper that executes # privileged operations on behalf of hive-c0re. Systemd creates and diff --git a/nix/host-modules/lib/container-rootfs-check.nix b/nix/host-modules/lib/container-rootfs-check.nix index 6f9382b4..f125b405 100644 --- a/nix/host-modules/lib/container-rootfs-check.nix +++ b/nix/host-modules/lib/container-rootfs-check.nix @@ -23,12 +23,19 @@ # `container@` unit (`nixos-containers.nix`, the `mapAttrsToList` that # builds `container@${name}`), so a setting on that unit reaches that # container only. -{ config }: -{ +# +# The binary is run through `hivePriv`, a path that does not change with the hyperhive +# build. `hive-priv.nix` points the symlink at the current package, so the +# unit text is the same across builds and a switch does not restart the +# container for it. +{ ... }: +rec { + hivePriv = "/run/hive-rootfs-check"; + # Attributes for `systemd.services."container@"`. warnOnly = name: { serviceConfig.ExecStartPre = [ - "-${config.services.hyperhive.deploy.hive-controller.package}/bin/hive-priv check-rootfs --warn-only ${name}" + "-${hivePriv} check-rootfs --warn-only ${name}" ]; }; } diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index a9b5a805..ea25aa28 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -1814,7 +1814,7 @@ let # their own unit, warn-only. Every container in these fixtures is one of # hyperhive's, so the names are read off the fixture: a container added # without the check breaks the list, which is the point. The unit's own - # pre-start script is not a `hive-priv` entry, so the count is the check + # pre-start script is not a `check-rootfs` entry, so the count is the check # alone. name = "every hyperhive container runs the root check warn-only from its own unit, and no hyperhive container is left out"; ok = @@ -1824,13 +1824,13 @@ let let pre = m.systemd.services."container@${name}".serviceConfig.ExecStartPre or [ ]; in - builtins.filter (lib.hasInfix "hive-priv") ( + builtins.filter (lib.hasInfix "check-rootfs") ( map (p: builtins.unsafeDiscardStringContext (toString p)) pre ); carries = m: name: checks m name == [ - (builtins.unsafeDiscardStringContext "-${m.services.hyperhive.deploy.hive-controller.package}/bin/hive-priv check-rootfs --warn-only ${name}") + "-/run/hive-rootfs-check check-rootfs --warn-only ${name}" ]; names = lib.attrNames allLocal.containers; in @@ -1853,6 +1853,13 @@ let && lib.all (carries allLocal) names && withCi.containers ? swarm-ci-runner && lib.all (carries withCi) (lib.attrNames withCi.containers) + # The line holds no store path, so a new hyperhive build leaves the + # unit text alone. Presence control: the lookup does find the line, + # and a line with a store path is caught by the same test. + && lib.all (n: !(lib.hasInfix "/nix/store" (lib.head (checks allLocal n)))) names + && lib.hasInfix "/nix/store" "-/nix/store/x-hyperhive/bin/hive-priv check-rootfs --warn-only x" + # The stable path is a link to the current package's hive-priv. + && lib.elem "L+ /run/hive-rootfs-check - - - - ${allLocal.services.hyperhive.deploy.hive-controller.package}/bin/hive-priv" allLocal.systemd.tmpfiles.rules # Control: a name with no unit finds nothing. && checks allLocal "swarm-nope" == [ ]; } @@ -1868,7 +1875,7 @@ let let hivePrivLines = m: name: - builtins.filter (lib.hasInfix "hive-priv") ( + builtins.filter (lib.hasInfix "check-rootfs") ( map (p: builtins.unsafeDiscardStringContext (toString p)) ( m.systemd.services."container@${name}".serviceConfig.ExecStartPre or [ ] ) @@ -1881,7 +1888,7 @@ let && lib.all ignored (allLines allLocal) && lib.all ignored (allLines withCi) # Control: the test discriminates, a line without the prefix is not ignored. - && !(ignored "/nix/store/x-hyperhive/bin/hive-priv check-rootfs --warn-only x"); + && !(ignored "/run/hive-rootfs-check check-rootfs --warn-only x"); } { # The operator's other containers are not hyperhive's to touch: a @@ -1893,7 +1900,7 @@ let let checks = name: - builtins.filter (lib.hasInfix "hive-priv") ( + builtins.filter (lib.hasInfix "check-rootfs") ( map (p: builtins.unsafeDiscardStringContext (toString p)) ( withForeignContainer.systemd.services."container@${name}".serviceConfig.ExecStartPre or [ ] )