From 6712cdb796e2948d9bdc0ff65f3406c34e92cbc3 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 16 Aug 2026 00:31:42 +0200 Subject: [PATCH] fix(swarm): say certFingerprint was removed, not that it does not exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A deployed config still set `swarm.hives..certFingerprint`, deleted along with the dashboard feature it served. The module system's answer was `The option ... does not exist`, which tells an operator nothing about why it went, whether it moved, or what replaces it. Re-declared invisible and internal, with a top-level assertion naming the hives that still carry it and explaining that the swarm root CA replaces per-hive leaf pinning. `lib.mkRemovedOptionModule` cannot do this job, and it is worth writing down why: neither of its halves survives the move into a submodule. Its `apply = throw` fires only when the value is read, and nothing reads this any more — that being the point of removing it. Its `config.assertions` half would land on a submodule that declares no `assertions` option. It is a top-level tool. This is the same shape swarm-peers-removed.nix already uses for the analogous `peers..caCert`. An error rather than a warning, because re-declaring the option is what stops the unhelpful message — and on its own that would turn a config that used to fail into one that quietly evaluates with the setting ignored, which is worse than the error it replaced. --- nix/host-modules/swarm.nix | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/nix/host-modules/swarm.nix b/nix/host-modules/swarm.nix index 2d785c5f..e2756ca2 100644 --- a/nix/host-modules/swarm.nix +++ b/nix/host-modules/swarm.nix @@ -46,6 +46,13 @@ let # implicitly. Left out, its vhost falls back to the hive leaf and the # swarm's front page opens with a name mismatch. ++ lib.optional swarmCfg.ui.enable swarmCfg.ui.domain; + + # Hives whose entry still carries the removed `certFingerprint`. Scanned + # here, at top level, because that is the only place an assertion about a + # submodule field can actually live — see the option's own comment. + pinnedHives = lib.attrNames ( + lib.filterAttrs (_: hive: hive.certFingerprint != null) swarmCfg.hives + ); in { options.services.hyperhive.swarm.hives = lib.mkOption { @@ -128,6 +135,33 @@ in silently excluded from `wg-hive`). ''; }; + + # Removed, and re-declared only so an existing definition + # produces an error that says *what happened*. Without it the + # module system says `The option ... does not exist`, which + # tells an operator nothing about why it went or what replaced + # it — and this field was set on real deployments. + # + # ⚠️ `lib.mkRemovedOptionModule` cannot do this job here, and + # neither of its two halves survives the move into a submodule: + # its `apply = throw` fires only when the value is READ, and + # nothing reads this any more (that being the point of removing + # it); its `config.assertions` half would land on a submodule + # that declares no `assertions` option at all. It is a + # top-level tool. The assertion below is the working shape, and + # it is the same one ./swarm-peers-removed.nix uses for the + # analogous `peers..caCert`. + certFingerprint = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + visible = false; + internal = true; + description = '' + Removed. Trust inside a swarm comes from the swarm root CA + (`services.hyperhive.swarm.ca`, docs/swarm/ca.md), which + replaces per-hive leaf pinning entirely. + ''; + }; }; } ) @@ -232,6 +266,38 @@ in Declared hives: ${lib.concatStringsSep ", " (lib.attrNames swarmCfg.hives)} ''; } + { + # An error rather than a warning, deliberately. Re-declaring the + # removed option above is what stops "option does not exist"; on + # its own it would also turn a config that used to FAIL into one + # that quietly evaluates with the setting ignored, which is a + # worse answer than the unhelpful error it replaces. The operator + # asked for a removal error, so this stays fatal until the field + # is gone from their config. + # + # Wording follows `lib.mkRemovedOptionModule`'s, so it reads like + # every other removal the module system reports. + assertion = pinnedHives == [ ]; + message = '' + The option definition `services.hyperhive.swarm.hives..certFingerprint' + no longer has any effect; please remove it. + + Still set on: ${lib.concatStringsSep ", " pinnedHives} + + It pinned a peer hive's TLS leaf for hive-c0re's own peer HTTPS + checks, and was removed along with the dashboard feature it + existed to serve — nothing else ever consumed it. + + There is no replacement, and none is needed for a hive inside + this swarm: trust comes from the swarm root CA + (services.hyperhive.swarm.ca — see docs/swarm/ca.md), which + every hive chains to, so one anchor replaces per-hive pinning. + What that genuinely drops is trusting a hive whose root this + swarm does NOT own — another swarm's, or one keeping its own + CA. That is a cross-swarm problem and wants a mechanism + designed for it. + ''; + } ]; };