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" "swarm" "authelia" "enable" ]
[ "services" "hyperhive" "deploy" "authelia" ] [ "services" "hyperhive" "deploy" "authelia" ]
) )
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "nats" "enable" ]
[ "services" "hyperhive" "deploy" "nats" ]
)
]; ];
options.services.hyperhive.deploy = { 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 { controller = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;

View file

@ -95,7 +95,7 @@ in
# ran its first boot on — so they belong to the mode that asserts this # 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`. # 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 # inside those defaults is the mixing this file exists to prevent: the
# option would be describing a deployment shape instead of describing # option would be describing a deployment shape instead of describing
# itself, and "what does all-local turn on?" would stop having one # itself, and "what does all-local turn on?" would stop having one

View file

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

View file

@ -539,7 +539,7 @@ in
services.hyperhive.swarm.controller.queue.natsUrl is unset. services.hyperhive.swarm.controller.queue.natsUrl is unset.
It defaults to loopback only when this host also runs the queue 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. own host has to be told where the queue is.
''; '';
} }

View file

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

View file

@ -60,7 +60,6 @@ in
# derives `nats.autoGenerateCallout` from the all-local mode, so that # derives `nats.autoGenerateCallout` from the all-local mode, so that
# mode was minting the queue's callout nkeys and then never starting # mode was minting the queue's callout nkeys and then never starting
# the queue they authenticate against. # 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 # The metrics pair and the log store used to derive here too. They now
# live under `deploy.*` (below, and ./deploy.nix) because "does THIS # live under `deploy.*` (below, and ./deploy.nix) because "does THIS
@ -89,6 +88,9 @@ in
# at whoever runs it. # at whoever runs it.
config.services.hyperhive.deploy.authelia = lib.mkDefault swarmCfg.enableRequiredServices; 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 # 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 # 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 # 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. # ONE condition for all three of them, deliberately.
# #
# 🩸 They were three independent conditions first, and that was wrong in # 🩸 They were three independent conditions first, and that was wrong in
# a way only an eval gate finds: `enableRequiredServices` turns on # a way only an eval gate finds: at the time `enableRequiredServices`
# matrix and authelia but NOT nats (nats has no mode that enables it — # turned on matrix and authelia but NOT nats, so an ordinary all-local
# see the auto-deploy question on the swarm-queue issue), so an ordinary # hive resolved authelia's two coordinates and not the queue URL. Two of
# all-local hive resolved authelia's two coordinates and not the queue # three set is exactly what the assertion below rejects, so every
# URL. Two of three set is exactly what the assertion below rejects, so # `enableAllLocalDefaults` hive would have stopped evaluating.
# 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 # Deriving all three from one predicate makes the partial state
# unrepresentable rather than merely detected: a default set is all or # unrepresentable rather than merely detected: a default set is all or
# nothing, and the assertion is then only ever about what an operator # nothing, and the assertion is then only ever about what an operator
# typed. # typed.
queueLocal = swarmCfg.nats.enable && deployCfg.authelia && cfg.hiveName != null; queueLocal = deployCfg.nats && deployCfg.authelia && cfg.hiveName != null;
in in
{ {
options.services.hyperhive.swarm.hives = lib.mkOption { options.services.hyperhive.swarm.hives = lib.mkOption {