From ce3b3d9467a81b436a552112aec2d692e12100d2 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 19 Sep 2026 03:30:34 +0200 Subject: [PATCH] nix: drop the central-toggle conjunct from four compound gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `services.hyperhive.enable` is going away (Refs #4500). These four blocks ANDed it with a second condition that was already the load-bearing one: the bridge-firewall hole asks whether an operator named any `network.exposeHostPorts`, and the three swarm-level services ask their own `deploy.*.enable`, each of which defaults false (swarm-ui derives from the controller's toggle, a sibling deployment decision) and none of which is derived from the hive toggle. So dropping the conjunct turns nothing on by itself — the remaining condition still decides. The way that claim fails is by something becoming unconditional, so module-eval gets absence arms for all four, each asserted on a host with the hive ON and on one with it OFF, plus the two controls that make the absences mean something. The swarm-controller arm is probed by the credential oneshot and by the daemon's `ExecStart` rather than by the unit name: hive-tls defines an environment key on that name, which leaves an inert fragment behind on any hive with a CA whether or not the controller runs there. --- nix/host-modules/hive-network.nix | 2 +- nix/host-modules/swarm-controller.nix | 2 +- nix/host-modules/swarm-otel.nix | 2 +- nix/host-modules/swarm-ui.nix | 2 +- nix/module-eval.nix | 101 ++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 4 deletions(-) diff --git a/nix/host-modules/hive-network.nix b/nix/host-modules/hive-network.nix index 0a8b1ffa..c37cefe4 100644 --- a/nix/host-modules/hive-network.nix +++ b/nix/host-modules/hive-network.nix @@ -276,7 +276,7 @@ in # is firewall-only by design: a host service that binds `0.0.0.0` already # serves the bridge IP, so an extra bridge-IP proxy would only collide # (EADDRINUSE) with it. Merges with the [ 80 443 ] gateway ports above. - (lib.mkIf (config.services.hyperhive.enable && cfg.exposeHostPorts != [ ]) { + (lib.mkIf (cfg.exposeHostPorts != [ ]) { networking.firewall.interfaces.${cfg.bridgeName}.allowedTCPPorts = cfg.exposeHostPorts; }) ]; diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index e045ee6d..383e768e 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -698,7 +698,7 @@ in }; }; - config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-controller.enable) { + config = lib.mkIf deployCfg.swarm-controller.enable { # The daemon and the oneshot that mints its credential — the second one # failing leaves the first running and unable to authenticate anywhere. services.hyperhive.swarm.otel.journaldUnits = [ diff --git a/nix/host-modules/swarm-otel.nix b/nix/host-modules/swarm-otel.nix index dff944ea..ed7df081 100644 --- a/nix/host-modules/swarm-otel.nix +++ b/nix/host-modules/swarm-otel.nix @@ -563,7 +563,7 @@ in }; }; - config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-otel.enable) { + config = lib.mkIf deployCfg.swarm-otel.enable { # The gateway name, inside `deployCfg.swarm-otel.enable` — that guard is the load-bearing # part. Every hive in a swarm may know this collector exists, but only # the host that RUNS it may claim the name; a client hive declaring the diff --git a/nix/host-modules/swarm-ui.nix b/nix/host-modules/swarm-ui.nix index f50b3eff..44844b87 100644 --- a/nix/host-modules/swarm-ui.nix +++ b/nix/host-modules/swarm-ui.nix @@ -97,7 +97,7 @@ in ''; }; - config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-ui.enable) { + config = lib.mkIf deployCfg.swarm-ui.enable { assertions = [ { # The `_` default server already answers for the hive domain diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 5ab93a11..6db0065f 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -491,6 +491,43 @@ let machine: machine.networking.firewall.interfaces.${machine.services.hyperhive.network.bridgeName}.allowedTCPPorts; + # Same list, on a host that may not declare the interface at all: the + # `[ 80 443 ]` block is what creates the attr, and it is off where the hive + # is. Reading it through `bridgePorts` would throw rather than report an + # empty exposure, which is precisely the state the cases below assert. + bridgePortsOrNone = + machine: + (machine.networking.firewall.interfaces.${machine.services.hyperhive.network.bridgeName} or { + allowedTCPPorts = [ ]; + } + ).allowedTCPPorts; + + # A host that installs the modules and turns nothing on. The per-service + # deployment toggles are what decide whether the swarm-level services render + # here, and none of them is derived from the hive being on, so this renders + # the same absences `bare` does — the arms below assert both, because a + # service that had quietly become unconditional would still look right on + # only one of them. + centralToggleOff = hive { enable = false; }; + + # Ports asked for on such a host. The request is the whole condition: the + # firewall hole exists because an operator named a port, not because the + # hive is running, and an agent reaching a host service is a claim about + # the host's own listeners either way. + exposedPortsNoHive = hive { + enable = false; + network.exposeHostPorts = [ 5432 ]; + }; + + # The swarm UI where its own toggle is on — the control the absence arm + # needs, since nothing else in this suite renders this vhost and an arm + # saying "it is not there" would hold just as well if it were never there. + # Package stubbed per this file's header: the vhost roots at it. + swarmUiHere = hive { + deploy.swarm-ui.enable = true; + deploy.swarm-ui.package = pkgs.emptyDirectory; + }; + baoTwoAddresses = hive { deploy.bao.enable = true; deploy.bao.extraListenAddresses = [ "10.0.0.1" ]; @@ -3032,6 +3069,70 @@ let in !(s.listener ? metrics) && !(s ? telemetry); } + { + # The four blocks below used to be gated on the hive being enabled AND + # their own condition. The second half was always the load-bearing one — + # none of these conditions is derived from the hive toggle — so these + # arms pin what the conjunct was doing: nothing. Each is written against + # a host with the hive OFF as well as one with it on, because the way a + # dropped conjunct fails is by making something unconditional, and that + # shows up as a service appearing where nothing asked for it. + name = "no bridge port is opened for an exposeHostPorts nobody set"; + ok = + !(builtins.elem 5432 (bridgePortsOrNone bare)) + && !(builtins.elem 5432 (bridgePortsOrNone centralToggleOff)); + } + { + # Control for the arm above, and the one place this slice is not inert: + # the ports are opened because they were named, on a host that never + # turned the hive on. The bridge firewall is the host's own, so there is + # nothing here for the hive toggle to have been protecting. + name = "a named exposeHostPorts opens its bridge port on the host's own say-so"; + ok = builtins.elem 5432 (bridgePortsOrNone exposedPortsNoHive); + } + { + # `deploy.swarm-controller.enable`, which defaults false and is + # deliberately not derived from the hive toggle — a swarm has one + # controller, so the host that runs it says so itself. + # + # Probed by what the daemon needs in order to run, not by + # `? swarm-controller`: ./host-modules/hive-tls.nix defines an + # environment key on that unit name, which leaves the attr + # present-but-inert (no `ExecStart`, empty `wantedBy`) on every hive + # that has a CA — see the comment there. The credential oneshot has no + # second definer, so its absence is the unambiguous half. + name = "the swarm controller does not run unless this host is told to run it"; + ok = + let + inert = + machine: + !(machine.systemd.services ? swarm-controller-credential) + && !( + (machine.systemd.services.swarm-controller or { serviceConfig = { }; }).serviceConfig ? ExecStart + ); + in + inert bare && inert centralToggleOff; + } + { + # `deploy.swarm-otel.enable`, same shape: the swarm's collector is one + # host's job, and the container is the whole of what it renders. + name = "the swarm collector container is absent unless this host is told to run it"; + ok = !(bare.containers ? swarm-otel) && !(centralToggleOff.containers ? swarm-otel); + } + { + # `deploy.swarm-ui.enable`, which defaults to the controller's toggle — + # derived from a sibling deployment decision, still not from the hive + # toggle. `t.local` is the fixtures' swarm domain, which is the apex the + # UI claims; the arm below is what proves this vhost renders at all. + name = "the swarm UI vhost is absent unless this host is told to serve it"; + ok = + !(bare.services.nginx.virtualHosts ? "t.local") + && !(centralToggleOff.services.nginx.virtualHosts ? "t.local"); + } + { + name = "the swarm UI claims the swarm apex where this host serves it"; + ok = swarmUiHere.services.nginx.virtualHosts ? "t.local"; + } ]; bad = builtins.filter (c: !c.ok) cases;