fix(swarm): say certFingerprint was removed, not that it does not exist

A deployed config still set `swarm.hives.<hive>.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.<hive>.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.
This commit is contained in:
atlas 2026-08-16 00:31:42 +02:00 committed by mara
commit 6712cdb796

View file

@ -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.<hive>.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.<hive>.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.
'';
}
];
};