diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 5b017afe..29d70fa2 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -49,19 +49,42 @@ let }; boot.loader.grub.enable = false; system.stateVersion = "25.11"; - services.hyperhive = { + # `recursiveUpdate`, not `//`: a plain `//` only merges the + # *top-level* keys of `extra` in, so an `extra` that touches a + # nested attr under an existing top-level key (e.g. `swarm.*`) + # silently drops every sibling under that key instead of merging + # into it — the same class of bug as the `services.hyperhive` + # double-nesting mistake this stub already had to dodge once, + # just one level down. `recursiveUpdate` merges nested attrsets + # all the way down instead. + services.hyperhive = lib.recursiveUpdate { enable = true; hiveName = "h1"; swarm.domain = "t.local"; swarm.hives.h1.domain = "h1.t.local"; - } - // extra; + } extra; } ]; }).config; allLocal = hive { enableAllLocalDefaults = true; }; bare = hive { }; + withCi = hive { swarm.forge.ci.enable = true; }; + + # A priority collision is a property of the *option*, not + # of the merged value's interior — nix throws the moment the value is + # demanded at all, so `seq`-ing each `serviceConfig` value to WHNF is + # both necessary and sufficient. `deepSeq` over-specifies this: it keeps + # walking *into* the resulting value after the merge already succeeded, + # and a package/derivation-shaped value's `override`/`overrideAttrs` + # self-reference sends it into nixpkgs' fixpoint machinery and blows the + # stack (measured — this is not a hypothetical). + forceCiServiceConfigs = + let + svcs = withCi.containers.hive-ci.config.systemd.services; + vals = lib.concatMap (s: builtins.attrValues (s.serviceConfig or { })) (builtins.attrValues svcs); + in + builtins.foldl' (acc: v: builtins.seq v acc) true vals; # Each case: a name stating the property, and `ok`. cases = [ @@ -100,6 +123,17 @@ let ok = !(builtins.hasAttr "= /.well-known/matrix/client" bare.services.nginx.virtualHosts."_".locations); } + { + # main got eval-borked twice by this exact class of bug (once on the + # unit's `Restart` key, once on `RestartSec`) — a nixpkgs bump to + # `gitea-actions-runner.nix` adds a plain `serviceConfig.*` + # definition that collides with one of ours, and nix refuses to + # merge two plain definitions at *host* eval. No other check + # instantiates a host with `containers.hive-ci` actually enabled, so + # the collision only surfaces on operator deploy, not in CI. + name = "the CI container's unit definitions merge without a priority collision"; + ok = forceCiServiceConfigs; + } ]; bad = builtins.filter (c: !c.ok) cases;