From 072dbd80a73c68b556bb02a7c63cd6aed8489e23 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 16 Aug 2026 19:30:43 +0200 Subject: [PATCH] fix(#3343): move the all-local queue derivations into the deployment mode Review point: the co-located defaults are an auto-deploy special case and belong with the other ones, not inside each option's own default. An option should describe itself; the mode describes what a deployment shape implies. local-defaults.nix already says exactly this in its header. Options now default to empty (= unset, which the assertions refuse), and enableAllLocalDefaults fills in loopback + the minted secret path. --- nix/host-modules/local-defaults.nix | 28 +++++++++++++++++++++++ nix/host-modules/swarm-controller.nix | 32 +++++++++++++-------------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/nix/host-modules/local-defaults.nix b/nix/host-modules/local-defaults.nix index 767de6aa..aab9b180 100644 --- a/nix/host-modules/local-defaults.nix +++ b/nix/host-modules/local-defaults.nix @@ -88,5 +88,33 @@ in # instead would be wrong: a hive in a larger swarm can legitimately # want the shared services without being the host that controls them. controller.enable = lib.mkDefault cfg.enableAllLocalDefaults; + + # The controller's queue coordinates. Co-location is what makes these + # derivable at all — loopback only reaches the queue when the queue is + # here, and the minted client secret only exists on the host authelia + # ran its first boot on — so they belong to the mode that asserts this + # box is the whole deployment, not to the options' own `default`. + # + # Deriving them from `swarm.nats.enable` / `swarm.authelia.enable` + # inside those defaults is the mixing this file exists to prevent: the + # option would be describing a deployment shape instead of describing + # itself, and "what does all-local turn on?" would stop having one + # answer. + # + # ⚠️ Must live INSIDE this attrset, not as a second + # `config.services.hyperhive.swarm.…` path beside it — written that + # way the two definitions of `swarm` collide and the nested one is + # silently lost. The gate caught exactly that: mode on, `natsUrl` + # still "". + # + # The *requirement* stays in `swarm-controller.nix` as an assertion: + # needing a queue is the controller's own property in every topology, + # and only the convenience is local. + controller.queue.natsUrl = lib.mkIf cfg.enableAllLocalDefaults ( + lib.mkDefault "nats://127.0.0.1:${toString config.services.hyperhive.swarm.nats.port}" + ); + controller.queue.clientSecretFile = lib.mkIf cfg.enableAllLocalDefaults ( + lib.mkDefault "${config.services.hyperhive.swarm.authelia.hostClientSecretDir}/swarm-controller.secret" + ); }; } diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index 5d011d76..48e627b6 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -189,19 +189,19 @@ in queue = { natsUrl = lib.mkOption { type = lib.types.str; - default = lib.optionalString natsCfg.enable "nats://127.0.0.1:${toString natsCfg.port}"; - defaultText = lib.literalExpression '' - if swarm.nats.enable then "nats://127.0.0.1:''${toString swarm.nats.port}" else "" - ''; + default = ""; example = "nats://queue.example.com:4222"; description = '' Where the controller reaches the swarm queue. - Defaults to loopback when this host runs the queue — the queue - container shares the host netns, so that address is correct - here and is not the `localhost`-means-the-agent trap that - applies inside agent containers. Set it explicitly when the - queue lives elsewhere. + Empty means unset, which the assertion below refuses — a + controller with no queue is not a lighter controller. + + `enableAllLocalDefaults` fills this in with loopback, because + that address is only correct when the queue is on this host: + its container shares the host netns. That derivation lives with + the mode rather than here, so this option describes itself + rather than a deployment shape. ''; }; @@ -221,20 +221,18 @@ in clientSecretFile = lib.mkOption { type = lib.types.str; - default = lib.optionalString autheliaCfg.enable "${autheliaCfg.hostClientSecretDir}/${queueClientId}.secret"; - defaultText = lib.literalExpression '' - "''${swarm.authelia.hostClientSecretDir}/swarm-controller.secret" - ''; + default = ""; example = "/var/lib/secrets/swarm-controller-queue.secret"; description = '' Path on **this** host holding the plaintext of the controller's OAuth2 client secret. A path, never a value: the secret would otherwise land in the world-readable nix store. - Defaults to the file authelia's first-boot generator mints when - authelia runs here. When it doesn't, the operator places the - secret and names it — the controller cannot mint its own, - because minting happens inside authelia's state directory. + The controller cannot mint its own — minting happens inside + authelia's state directory during its first boot — so away from + that host the operator places the secret and names it here. + `enableAllLocalDefaults` points this at the minted file, which + is exactly the case where one exists locally. Read by `LoadCredential`, so it needs to be readable by root at unit start and nothing more; the daemon's own user never sees