deploy: move the queue toggle

Same shape as the others. Three of the eight references were prose
rather than code: two assertion messages naming the option to an
operator, and a `defaultText` in the authelia module, whose
`oidc.hiveIdentities` derives from whether the queue runs here.

Also corrects a comment in swarm.nix that this commit's own hunk sits
directly beneath. It said `enableRequiredServices` turns on matrix and
authelia "but NOT nats (nats has no mode that enables it)", which stopped
being true when the queue was added to that switch — before this change,
not by it. The reasoning it introduces is still right and still worth
keeping; only the premise had expired.
This commit is contained in:
atlas 2026-08-30 03:31:32 +02:00 committed by mara
commit 64e8c62f18
7 changed files with 45 additions and 30 deletions

View file

@ -57,6 +57,10 @@ in
[ "services" "hyperhive" "swarm" "authelia" "enable" ]
[ "services" "hyperhive" "deploy" "authelia" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "nats" "enable" ]
[ "services" "hyperhive" "deploy" "nats" ]
)
];
options.services.hyperhive.deploy = {
@ -119,6 +123,19 @@ in
'';
};
nats = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Run the swarm's message queue in a `swarm-nats` container on this
host. A swarm has one queue, so this belongs on the same host as
the rest of the shared services.
Off by default, and off means *absent*: no container is created
and nothing else in the evaluated config changes.
'';
};
controller = lib.mkOption {
type = lib.types.bool;
default = false;

View file

@ -95,7 +95,7 @@ in
# 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` / `deploy.authelia`
# Deriving them from `deploy.nats` / `deploy.authelia`
# 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

View file

@ -467,8 +467,8 @@ in
oidc.hiveIdentities = lib.mkOption {
type = lib.types.bool;
default = hyperhiveCfg.swarm.nats.enable;
defaultText = lib.literalExpression "services.hyperhive.swarm.nats.enable";
default = deployCfg.nats;
defaultText = lib.literalExpression "services.hyperhive.deploy.nats";
description = ''
Mint one machine client per hive in
{option}`services.hyperhive.swarm.hives`, so each hive can

View file

@ -539,7 +539,7 @@ in
services.hyperhive.swarm.controller.queue.natsUrl is unset.
It defaults to loopback only when this host also runs the queue
(`services.hyperhive.swarm.nats.enable`). A controller on its
(`services.hyperhive.deploy.nats`). A controller on its
own host has to be told where the queue is.
'';
}

View file

@ -49,7 +49,7 @@ let
hostPath = name: "${secretDir}/${name}";
# The responder needs all three credentials. Gating on them rather than
# on `cfg.enable` keeps a half-configured hive at "queue up, denying
# on `deployCfg.nats` keeps a half-configured hive at "queue up, denying
# everyone" instead of "unit crash-looping on a missing file".
#
# In auto mode the seeds are minted on this host before the container
@ -210,18 +210,9 @@ in
# per-service vhost pattern do not apply.
options.services.hyperhive.swarm.nats = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Run the swarm's message queue in a `swarm-nats` container on this
host. A swarm has one queue, so this belongs on the same host as
the rest of the shared services.
Off by default, and off means *absent*: no container is created
and nothing else in the evaluated config changes.
'';
};
# `enable` moved to `services.hyperhive.deploy.nats` — see
# ./deploy.nix. What stays here is what the queue IS: its domain,
# ports, accounts and callout wiring.
# ⚠️ Deliberately NO `package` option, unlike this module's siblings.
# `services.nats` upstream does not expose one — it resolves
@ -408,7 +399,7 @@ in
};
};
config = lib.mkIf cfg.enable {
config = lib.mkIf deployCfg.nats {
# The responder as well as the server: a denial reaches the client as a
# timeout, so the server's own log is the only place it is an error.
services.hyperhive.swarm.otel.journaldUnits = [
@ -423,7 +414,7 @@ in
# several layers from "the operator never set the issuer".
assertion = cfg.autoGenerateCallout || cfg.calloutIssuerPublicKey != "";
message = ''
services.hyperhive.swarm.nats.enable requires
services.hyperhive.deploy.nats requires
nats.calloutIssuerPublicKey the public half of the account
nkey that signs user JWTs for this queue.
@ -440,7 +431,7 @@ in
# somebody connects.
assertion = cfg.autoGenerateCallout || cfg.calloutUserPublicKey != "";
message = ''
services.hyperhive.swarm.nats.enable requires
services.hyperhive.deploy.nats requires
nats.calloutUserPublicKey the public half of the user nkey
the auth-callout responder authenticates as.
@ -485,7 +476,7 @@ in
{
assertion = autheliaUrl != null;
message = ''
services.hyperhive.swarm.nats.enable requires
services.hyperhive.deploy.nats requires
services.hyperhive.swarm.authelia.url the queue authenticates
clients by validating tokens that authelia issued.
@ -684,7 +675,7 @@ in
# door nobody can open, which is the deliberate interim state.
#
# ⚠️ It is gated on the seeds being configured rather than on
# `cfg.enable`, so a half-configured hive gets a running,
# `deployCfg.nats`, so a half-configured hive gets a running,
# refusing queue instead of a unit that crash-loops on a
# missing file. A queue that denies everyone is a legible
# failure; a restart loop is not.

View file

@ -60,7 +60,6 @@ in
# derives `nats.autoGenerateCallout` from the all-local mode, so that
# mode was minting the queue's callout nkeys and then never starting
# the queue they authenticate against.
nats.enable = lib.mkDefault swarmCfg.enableRequiredServices;
# The metrics pair and the log store used to derive here too. They now
# live under `deploy.*` (below, and ./deploy.nix) because "does THIS
@ -89,6 +88,9 @@ in
# at whoever runs it.
config.services.hyperhive.deploy.authelia = lib.mkDefault swarmCfg.enableRequiredServices;
# The queue. Same rule: once per swarm, optional.
config.services.hyperhive.deploy.nats = lib.mkDefault swarmCfg.enableRequiredServices;
# The metrics pair, deriving together on purpose: a store with no UI is
# unreadable and a UI with no store is empty, so there is no sensible
# deployment that takes one and not the other from this switch. An

View file

@ -80,18 +80,23 @@ let
# ONE condition for all three of them, deliberately.
#
# 🩸 They were three independent conditions first, and that was wrong in
# a way only an eval gate finds: `enableRequiredServices` turns on
# matrix and authelia but NOT nats (nats has no mode that enables it —
# see the auto-deploy question on the swarm-queue issue), so an ordinary
# all-local hive resolved authelia's two coordinates and not the queue
# URL. Two of three set is exactly what the assertion below rejects, so
# every `enableAllLocalDefaults` hive would have stopped evaluating.
# a way only an eval gate finds: at the time `enableRequiredServices`
# turned on matrix and authelia but NOT nats, so an ordinary all-local
# hive resolved authelia's two coordinates and not the queue URL. Two of
# three set is exactly what the assertion below rejects, so every
# `enableAllLocalDefaults` hive would have stopped evaluating.
#
# (The queue does derive from that switch now — ./swarm-required-services.nix
# — so the original asymmetry is gone. The reasoning below is why the
# single predicate stays anyway: it makes the partial state
# unrepresentable rather than merely detected, which does not depend on
# which services happen to derive together this month.)
#
# Deriving all three from one predicate makes the partial state
# unrepresentable rather than merely detected: a default set is all or
# nothing, and the assertion is then only ever about what an operator
# typed.
queueLocal = swarmCfg.nats.enable && deployCfg.authelia && cfg.hiveName != null;
queueLocal = deployCfg.nats && deployCfg.authelia && cfg.hiveName != null;
in
{
options.services.hyperhive.swarm.hives = lib.mkOption {