deploy: give every option an enable, and name the controller one

Two corrections from review, applied forward on this branch rather than
by rewriting it.

`deploy.<service>` was a bare bool, which makes
`deploy.forgejo = { enable; ci; }` unrepresentable -- the nested
CI-runner sub-option this namespace was designed around. Every entry is
now an attrset with an `enable`, so a second per-host deployment
decision becomes an ordinary addition rather than a migration.

`deploy.controller` is now `deploy.swarm-controller`, consistent with
`deploy.swarm-ui`, which was introduced in the same commit.

89 references rewritten across 24 files -- nix, Rust, docs, and the
repo's own CLAUDE.md.

The prefix-anchored sweep missed exactly one, and it was live code:
hive-tls.nix spells it `hyperhiveCfg.deploy.controller` -- the only
`hyperhiveCfg` prefix among 45 references. A suffix grep
(`\.deploy\.<name>`) finds it; a path-anchored one cannot, because the
head of a reference is whatever alias the reading file happens to bind.
This commit is contained in:
atlas 2026-08-30 04:05:28 +02:00 committed by mara
commit d3b40da1c8
24 changed files with 137 additions and 121 deletions

View file

@ -49,7 +49,7 @@ let
hostPath = name: "${secretDir}/${name}";
# The responder needs all three credentials. Gating on them rather than
# on `deployCfg.nats` keeps a half-configured hive at "queue up, denying
# on `deployCfg.nats.enable` 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,7 +210,7 @@ in
# per-service vhost pattern do not apply.
options.services.hyperhive.swarm.nats = {
# `enable` moved to `services.hyperhive.deploy.nats` — see
# `enable` moved to `services.hyperhive.deploy.nats.enable` — see
# ./deploy.nix. What stays here is what the queue IS: its domain,
# ports, accounts and callout wiring.
@ -399,7 +399,7 @@ in
};
};
config = lib.mkIf deployCfg.nats {
config = lib.mkIf deployCfg.nats.enable {
# 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 = [
@ -414,7 +414,7 @@ in
# several layers from "the operator never set the issuer".
assertion = cfg.autoGenerateCallout || cfg.calloutIssuerPublicKey != "";
message = ''
services.hyperhive.deploy.nats requires
services.hyperhive.deploy.nats.enable requires
nats.calloutIssuerPublicKey the public half of the account
nkey that signs user JWTs for this queue.
@ -431,7 +431,7 @@ in
# somebody connects.
assertion = cfg.autoGenerateCallout || cfg.calloutUserPublicKey != "";
message = ''
services.hyperhive.deploy.nats requires
services.hyperhive.deploy.nats.enable requires
nats.calloutUserPublicKey the public half of the user nkey
the auth-callout responder authenticates as.
@ -476,7 +476,7 @@ in
{
assertion = autheliaUrl != null;
message = ''
services.hyperhive.deploy.nats requires
services.hyperhive.deploy.nats.enable requires
services.hyperhive.swarm.authelia.url the queue authenticates
clients by validating tokens that authelia issued.
@ -506,7 +506,7 @@ in
# authorization grant 'client_credentials'`. Introspection — which
# is all the responder needs today — worked throughout, which is why
# nothing was visibly broken while the comment was untrue.
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf deployCfg.authelia [
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf deployCfg.authelia.enable [
{
id = cfg.clientId;
description = "HyperHive swarm queue";
@ -535,9 +535,11 @@ in
# ⚠️ `swarm.otel`, not `hyperhive.otel` — two collectors one word apart,
# and only this one reads `scrapeTargets`. Written in full so the gate
# and the option it gates are visibly the same path.
services.hyperhive.swarm.otel.scrapeTargets = lib.mkIf config.services.hyperhive.deploy.otel {
nats = "127.0.0.1:${toString cfg.metricsPort}";
};
services.hyperhive.swarm.otel.scrapeTargets =
lib.mkIf config.services.hyperhive.deploy.otel.enable
{
nats = "127.0.0.1:${toString cfg.metricsPort}";
};
containers.swarm-nats = {
autoStart = true;
@ -675,7 +677,7 @@ in
# door nobody can open, which is the deliberate interim state.
#
# ⚠️ It is gated on the seeds being configured rather than on
# `deployCfg.nats`, so a half-configured hive gets a running,
# `deployCfg.nats.enable`, 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.
@ -759,7 +761,7 @@ in
# being up says nothing about whether its in-container secrets unit
# has finished. The wait in the script is what actually closes it;
# this only stops us spinning for the full timeout on every boot.
++ lib.optional deployCfg.authelia "container@${autheliaCfg.machine}.service";
++ lib.optional deployCfg.authelia.enable "container@${autheliaCfg.machine}.service";
requires = lib.optional cfg.autoGenerateCallout "swarm-nats-callout-keys.service";
serviceConfig = {
Type = "oneshot";