Watch
0
0
Fork
You've already forked hyperhive
0

fix(nix): require swarm domain only when a hyperhive service is enabled

The swarm.domain assertion in hive-network.nix fired on every host that
imported the module, so a host that enables nothing failed eval. It now
fires only when one of the hyperhive service switches is on (every
deploy.*.enable that runs something, gateway, gateway.dns, network,
otel, snapshotStore). The requirement itself is unchanged: any host that
runs a hyperhive service still needs swarm.domain.

The core-toggle module-eval suite gains a case: a missing swarm.domain is
refused on a hive and on a swarm-service-only host, and a host enabling
nothing passes every assertion.

Closes #4887
This commit is contained in:
atlas 2026-10-02 13:09:45 +02:00
commit fb2fff0668
4 changed files with 71 additions and 13 deletions

View file

@ -310,8 +310,9 @@ services.hyperhive = {
<!-- vale write-good.Passive = NO -->
Swarm options are identical on every host in the swarm, so `swarm.domain` is
**required** on every host; `hiveName` is required on a host that runs a
hive, the secret store or the homeserver. Eval fails with a hint naming
**required** on every host that runs any hyperhive service; a host that
imports the module and enables nothing evaluates without it. `hiveName` is
required on a host that runs a hive, the secret store or the homeserver. Eval fails with a hint naming
each. Neither defaults, because a guessed value here is a wrong hostname
that evaluates cleanly and deploys.

View file

@ -27,6 +27,38 @@ let
hostCount = pow2 (32 - cfg.bridgePrefixLength);
# Mask off host bits to get the network base address.
networkBase = builtins.bitAnd (ipToInt cfg.bridgeIp) (4294967295 - hostCount + 1);
# Every switch that runs a hyperhive service on this host. Left out:
# `github.enable` (on by default, runs nothing by itself), the gateway's
# and matrix's sub-features, and `allSwarmServices`/`singleHostSwarm`,
# which only take effect through the switches listed here.
anyServiceEnabled =
let
h = config.services.hyperhive;
d = h.deploy;
in
lib.any lib.id [
d.hive-controller.enable
d.swarm-controller.enable
d.swarm-ui.enable
d.forgejo.enable
d.forgejo.ci.enable
d.matrix.enable
d.authelia.enable
d.bao.enable
d.nats.enable
d.swarm-otel.enable
d.grafana.enable
d.victoriametrics.enable
d.victorialogs.enable
d.swarm-secret-publisher.enable
d.wireguard.enable
h.gateway.enable
h.gateway.dns.enable
h.network.enable
h.otel.enable
h.snapshotStore.enable
];
in
{
# Hive-internal network — the host-side bridge every container in a
@ -187,25 +219,26 @@ in
];
})
# Un-gated: the swarm's options are identical on every host, so a host
# running no hive and no swarm service needs the value too. The
# `.invalid` fallbacks described above keep this message reachable.
{
# Gated on any hyperhive service, not only the hive: the swarm's options
# are identical on every host, so a host running only a swarm service
# needs the value too. The `.invalid` fallbacks described above keep
# this message reachable.
(lib.mkIf anyServiceEnabled {
assertions = [
{
assertion = config.services.hyperhive.swarm.domain != null;
message = ''
hyperhive requires services.hyperhive.swarm.domain to be set
on every host, to the same value on every host of the swarm —
the DNS domain of the swarm, of which each hive occupies one
sub-domain. A guessed value would be a wrong hostname that
evaluates cleanly and deploys. Set it
on every host that runs a hyperhive service, to the same value
on every host of the swarm — the DNS domain of the swarm, of
which each hive occupies one sub-domain. A guessed value would
be a wrong hostname that evaluates cleanly and deploys. Set it
(`services.hyperhive.swarm.domain = "example.com";`) — with
`hiveName` it also derives `services.hyperhive.domain` for you.
'';
}
];
}
})
# The bridge itself, up only where something hangs off it. Private
# netns is still the only container mode.

View file

@ -117,8 +117,10 @@ in
`services.hyperhive.hiveName`, list the hives by name, and no
hive in the swarm states an address at all.
**Required** on every host that imports this module, with the
same value on every host of the swarm, and deliberately not
**Required** on every host that runs a hyperhive service (a
`services.hyperhive.deploy.*` service, the gateway, the bridge,
`otel` or the snapshot store), with the same value on every host
of the swarm, and deliberately not
defaulted. A guessed swarm domain is a wrong hostname that
evaluates cleanly and deploys, which is worse than an eval
failure telling an operator to write down the one address their

View file

@ -503,6 +503,28 @@ let
}
&& !(refusedHiveName { deploy.hive-controller.enable = false; });
}
{
# Refused wherever any hyperhive service runs, the hive or a swarm
# service alone. The host enabling nothing is the control that it is
# gated at all, and has to pass every assertion: it is what an
# operator gets from importing the module.
name = "a missing swarm.domain is refused on a hive and on a swarm-service host, and a host enabling nothing evaluates";
ok =
let
noDomain = extra: hive ({ swarm.domain = null; } // extra);
refusedSwarmDomain =
extra:
lib.any (
a: !a.assertion && lib.hasInfix "services.hyperhive.swarm.domain to be set" a.message
) (noDomain extra).assertions;
in
refusedSwarmDomain { }
&& refusedSwarmDomain {
deploy.hive-controller.enable = false;
deploy.nats.enable = true;
}
&& lib.all (a: a.assertion) (noDomain { deploy.hive-controller.enable = false; }).assertions;
}
];
in
runGroup "core-toggle" cases