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:
parent
ba5ab313b3
commit
d3b40da1c8
24 changed files with 137 additions and 121 deletions
|
|
@ -15,10 +15,14 @@
|
|||
# thing that must differ.
|
||||
#
|
||||
# Flat and named for the thing deployed — `deploy.forgejo`, not
|
||||
# `deploy.swarmServices.forgejo`. Grouping by "swarm service" would
|
||||
# re-encode the service-side taxonomy into a layer that does not care
|
||||
# about it: from here, a host deploys forgejo, or a hive, or the lot, and
|
||||
# what *kind* of thing each one is belongs to the service module.
|
||||
# `deploy.swarmServices.forgejo`: grouping by "swarm service" re-encodes
|
||||
# the service-side taxonomy into a layer that does not care about it.
|
||||
#
|
||||
# ⚠️ Each entry is an attrset with an `enable`, not a bare bool, so a
|
||||
# service that grows a second *deployment* decision has somewhere to put
|
||||
# it — `deploy.forgejo = { enable; ci; }` is then an ordinary addition
|
||||
# rather than a migration. `ci` ("does this host run the runner too") is
|
||||
# exactly that shape, and a bare bool leaves it unrepresentable.
|
||||
#
|
||||
# ⚠️ The renames below are deliberately in this one file rather than
|
||||
# spread across the service modules, so the whole move has a single home
|
||||
|
|
@ -35,40 +39,40 @@ in
|
|||
# paths. Precedent: ./hive-forge/default.nix, ./hive-matrix.nix.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "grafana" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" ]
|
||||
[ "services" "hyperhive" "deploy" "grafana" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "victoriametrics" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "victoriametrics" ]
|
||||
[ "services" "hyperhive" "deploy" "victoriametrics" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "victorialogs" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "victorialogs" ]
|
||||
[ "services" "hyperhive" "deploy" "victorialogs" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "controller" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "controller" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-controller" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "ui" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-ui" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-ui" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "authelia" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "authelia" ]
|
||||
[ "services" "hyperhive" "deploy" "authelia" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "nats" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" ]
|
||||
[ "services" "hyperhive" "deploy" "nats" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "otel" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "otel" ]
|
||||
[ "services" "hyperhive" "deploy" "otel" "enable" ]
|
||||
)
|
||||
];
|
||||
|
||||
options.services.hyperhive.deploy = {
|
||||
grafana = lib.mkOption {
|
||||
grafana.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
|
|
@ -81,7 +85,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
victoriametrics = lib.mkOption {
|
||||
victoriametrics.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
|
|
@ -89,14 +93,14 @@ in
|
|||
|
||||
Derives from
|
||||
{option}`services.hyperhive.swarm.enableRequiredServices` together
|
||||
with {option}`services.hyperhive.deploy.grafana`: 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 that
|
||||
switch. Set either directly to run exactly one.
|
||||
with {option}`services.hyperhive.deploy.grafana.enable`: 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
|
||||
that switch. Set either directly to run exactly one.
|
||||
'';
|
||||
};
|
||||
|
||||
victorialogs = lib.mkOption {
|
||||
victorialogs.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
|
|
@ -109,7 +113,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
authelia = lib.mkOption {
|
||||
authelia.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
|
|
@ -127,7 +131,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
otel = lib.mkOption {
|
||||
otel.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
|
|
@ -147,7 +151,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
nats = lib.mkOption {
|
||||
nats.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
|
|
@ -160,7 +164,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
controller = lib.mkOption {
|
||||
swarm-controller.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
|
|
@ -173,15 +177,16 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
swarm-ui = lib.mkOption {
|
||||
swarm-ui.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = deployCfg.controller;
|
||||
defaultText = lib.literalExpression "services.hyperhive.deploy.controller";
|
||||
default = deployCfg.swarm-controller.enable;
|
||||
defaultText = lib.literalExpression "services.hyperhive.deploy.swarm-controller.enable";
|
||||
example = true;
|
||||
description = ''
|
||||
Serve the swarm UI from this host.
|
||||
|
||||
Derived from {option}`services.hyperhive.deploy.controller` rather
|
||||
Derived from
|
||||
{option}`services.hyperhive.deploy.swarm-controller.enable` rather
|
||||
than from
|
||||
{option}`services.hyperhive.swarm.enableRequiredServices`: the UI
|
||||
is a view onto the controller's state and reaches it over that
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ let
|
|||
# authelia, so the secret can be moved without an operator. The other
|
||||
# two cases (swarm side via swarmctl, remote hive) leave
|
||||
# `clientSecretFile` to be set explicitly — see docs/swarm/.
|
||||
ssoLocal = deployCfg.authelia;
|
||||
ssoLocal = deployCfg.authelia.enable;
|
||||
|
||||
# Where the plaintext lands inside the forge container. Under
|
||||
# /var/lib rather than /run: the forge may start before the delivery
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ let
|
|||
# authelia, so the secret can be moved without an operator. The other
|
||||
# two cases (swarm side, remote hive) leave `clientSecretFile` to be set
|
||||
# explicitly — same split the forge module documents.
|
||||
ssoLocal = deployCfg.authelia;
|
||||
ssoLocal = deployCfg.authelia.enable;
|
||||
|
||||
# Where the plaintext lands inside the matrix container. Under /var/lib
|
||||
# rather than /run: the homeserver may start before the delivery unit on
|
||||
|
|
|
|||
|
|
@ -669,6 +669,6 @@ in
|
|||
# in review on this PR; it evaluates and builds clean either way, which
|
||||
# is exactly why it needed a reviewer rather than a check.
|
||||
systemd.services.swarm-controller.environment.SWARM_CONTROLLER_OIDC_CA_FILE =
|
||||
lib.mkIf hyperhiveCfg.deploy.controller "${cfg.stateDir}/trust-bundle.pem";
|
||||
lib.mkIf hyperhiveCfg.deploy.swarm-controller.enable "${cfg.stateDir}/trust-bundle.pem";
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ in
|
|||
shared services
|
||||
(`services.hyperhive.swarm.enableRequiredServices`), the swarm
|
||||
CA (`services.hyperhive.swarm.ca.autoConfigure`), the swarm
|
||||
controller (`services.hyperhive.deploy.controller`), and the
|
||||
controller (`services.hyperhive.deploy.swarm-controller.enable`), and the
|
||||
host's `/etc/hosts` entries for the names this hive serves
|
||||
(`services.hyperhive.gateway.localHostsEntry`) — with no real DNS
|
||||
for those names, the operator is browsing them from the same box
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ in
|
|||
their own containers can reach.
|
||||
|
||||
That collector forwards to the swarm's
|
||||
({option}`services.hyperhive.deploy.otel`), which holds the
|
||||
({option}`services.hyperhive.deploy.otel.enable`), which holds the
|
||||
upstream credential and writes the swarm's store. So an agent never
|
||||
sees the credential, and neither does this tier.
|
||||
|
||||
|
|
@ -81,13 +81,13 @@ in
|
|||
has left the swarm.
|
||||
|
||||
Read by the swarm's collector
|
||||
({option}`services.hyperhive.deploy.otel`), which is the only
|
||||
({option}`services.hyperhive.deploy.otel.enable`), which is the only
|
||||
tier that holds the upstream credential. An agent is handed the
|
||||
*first* hop instead — this hive's own collector — so this value is
|
||||
never given to a container.
|
||||
|
||||
Optional. Leave it empty and the swarm's own metrics store
|
||||
({option}`services.hyperhive.deploy.victoriametrics`) is the
|
||||
({option}`services.hyperhive.deploy.victoriametrics.enable`) is the
|
||||
destination; that is a complete deployment, not a degraded one.
|
||||
Set both and telemetry goes to both.
|
||||
'';
|
||||
|
|
@ -231,7 +231,9 @@ in
|
|||
clientSecretFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default =
|
||||
if config.services.hyperhive.deploy.authelia && config.services.hyperhive.hiveName != null then
|
||||
if
|
||||
config.services.hyperhive.deploy.authelia.enable && config.services.hyperhive.hiveName != null
|
||||
then
|
||||
"${config.services.hyperhive.swarm.authelia.hostClientSecretDir}/"
|
||||
+ "${config.services.hyperhive.swarm.authelia.hiveClientPrefix}${config.services.hyperhive.hiveName}.secret"
|
||||
else
|
||||
|
|
@ -579,8 +581,8 @@ in
|
|||
# Only when the minting container is on THIS host. Elsewhere the file
|
||||
# is operator-provided and there is no local unit to order against —
|
||||
# naming one that does not exist orders nothing, silently.
|
||||
after = lib.optional deployCfg.authelia "container@${autheliaCfg.machine}.service";
|
||||
requires = lib.optional deployCfg.authelia "container@${autheliaCfg.machine}.service";
|
||||
after = lib.optional deployCfg.authelia.enable "container@${autheliaCfg.machine}.service";
|
||||
requires = lib.optional deployCfg.authelia.enable "container@${autheliaCfg.machine}.service";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ let
|
|||
'';
|
||||
in
|
||||
{
|
||||
# `enable` moved to `services.hyperhive.deploy.authelia` — see
|
||||
# `enable` moved to `services.hyperhive.deploy.authelia.enable` — see
|
||||
# ./deploy.nix. Whether this host runs the swarm's SSO provider is a
|
||||
# deployment decision; what stays here is what authelia IS, including
|
||||
# `url` and the OIDC client registry every hive needs as a *client*
|
||||
|
|
@ -427,7 +427,7 @@ in
|
|||
|
||||
url = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = if deployCfg.authelia then "https://${cfg.domain}" else null;
|
||||
default = if deployCfg.authelia.enable then "https://${cfg.domain}" else null;
|
||||
defaultText = lib.literalExpression ''if enable then "https://''${domain}" else null'';
|
||||
example = "https://auth.example.com";
|
||||
description = ''
|
||||
|
|
@ -467,8 +467,8 @@ in
|
|||
|
||||
oidc.hiveIdentities = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = deployCfg.nats;
|
||||
defaultText = lib.literalExpression "services.hyperhive.deploy.nats";
|
||||
default = deployCfg.nats.enable;
|
||||
defaultText = lib.literalExpression "services.hyperhive.deploy.nats.enable";
|
||||
description = ''
|
||||
Mint one machine client per hive in
|
||||
{option}`services.hyperhive.swarm.hives`, so each hive can
|
||||
|
|
@ -810,7 +810,7 @@ in
|
|||
bridgeUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
readOnly = true;
|
||||
default = if deployCfg.authelia then "http://127.0.0.1:${toString cfg.bridgePort}" else null;
|
||||
default = if deployCfg.authelia.enable then "http://127.0.0.1:${toString cfg.bridgePort}" else null;
|
||||
defaultText = lib.literalExpression ''if enable then "http://127.0.0.1:''${bridgePort}" else null'';
|
||||
description = ''
|
||||
Where `swarm-authelia-bridge` answers, **as seen from this
|
||||
|
|
@ -829,7 +829,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia) {
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia.enable) {
|
||||
# The derived half of the client list, declared the same way an
|
||||
# operator declares one. Everything downstream then reads a single
|
||||
# uniformly-typed `cfg.oidc.clients` and cannot tell the parts apart —
|
||||
|
|
@ -964,7 +964,7 @@ in
|
|||
|
||||
# Authelia's own gateway surface: the vhost that fronts it and the
|
||||
# name the hive resolver answers for. Both live here rather than in
|
||||
# the gateway, and both are inside `deployCfg.authelia` — that guard is the
|
||||
# the gateway, and both are inside `deployCfg.authelia.enable` — that guard is the
|
||||
# load-bearing part.
|
||||
#
|
||||
# ⚠️ Every hive in a swarm knows `authelia.url`, but only the host
|
||||
|
|
@ -1009,9 +1009,11 @@ in
|
|||
# Written out in full rather than through a `let` binding so the gate and
|
||||
# the option it gates are visibly the same path: gating the wrong one is
|
||||
# not a build error, it is a target that is always declared or never is.
|
||||
services.hyperhive.swarm.otel.scrapeTargets = lib.mkIf config.services.hyperhive.deploy.otel {
|
||||
authelia = "127.0.0.1:${toString cfg.metricsPort}";
|
||||
};
|
||||
services.hyperhive.swarm.otel.scrapeTargets =
|
||||
lib.mkIf config.services.hyperhive.deploy.otel.enable
|
||||
{
|
||||
authelia = "127.0.0.1:${toString cfg.metricsPort}";
|
||||
};
|
||||
|
||||
# This swarm-ui quick-links entry, same guard as the vhost/DNS name
|
||||
# above (only the host actually running the container claims it —
|
||||
|
|
@ -1397,7 +1399,7 @@ in
|
|||
# collector is registered — see `metricsRule` above,
|
||||
# which is where the reasoning for both halves lives.
|
||||
lib.optional forgeCfg.behindGateway metricsRule
|
||||
++ lib.optional deployCfg.swarm-ui {
|
||||
++ lib.optional deployCfg.swarm-ui.enable {
|
||||
domain = uiCfg.domain;
|
||||
subject = [ "group:${operatorGroup}" ];
|
||||
policy = "one_factor";
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ let
|
|||
# the right behaviour is for `swarmctl user add` to fail saying the
|
||||
# value is unset. A guessed path would resolve cleanly and write a file
|
||||
# nothing reads, which is the failure mode that costs an afternoon.
|
||||
autheliaEnv = lib.optionalAttrs deployCfg.authelia {
|
||||
autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable {
|
||||
# The CONFIGURED authelia, not whatever is on PATH: the argon2
|
||||
# parameters baked into a hash have to match the verifier's.
|
||||
SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.package}/bin/authelia";
|
||||
|
|
@ -130,7 +130,7 @@ let
|
|||
# the moment to add an explicit `publicUrl` option — not before, because
|
||||
# until then there is exactly one derivable answer and an option would only
|
||||
# be a second place to get it wrong.
|
||||
webhookEnv = lib.optionalAttrs deployCfg.swarm-ui {
|
||||
webhookEnv = lib.optionalAttrs deployCfg.swarm-ui.enable {
|
||||
SWARM_CONTROLLER_PUBLIC_URL = "https://${uiCfg.domain}";
|
||||
};
|
||||
|
||||
|
|
@ -174,14 +174,14 @@ let
|
|||
# so the two ends agree on exactly one path, `/v1/metrics`, arrived at
|
||||
# from opposite directions.
|
||||
#
|
||||
# Gated on `deployCfg.otel` alone (a swarm-wide fact, not "enabled
|
||||
# Gated on `deployCfg.otel.enable` alone (a swarm-wide fact, not "enabled
|
||||
# HERE"): the collector is reachable by name wherever it runs, so a
|
||||
# controller not co-located with it now exports over https instead of
|
||||
# exporting nothing — the graceful-absence case left is "no swarm-otel
|
||||
# anywhere in this swarm at all," which `forgeEnv` already models the
|
||||
# same way.
|
||||
otelSwarmCfg = config.services.hyperhive.swarm.otel;
|
||||
otelEnv = lib.optionalAttrs deployCfg.otel {
|
||||
otelEnv = lib.optionalAttrs deployCfg.otel.enable {
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT = "https://${otelSwarmCfg.domain}/${otelSwarmCfg.producerName}";
|
||||
# Checked by `swarm-otel.nix`'s `oidc/${producerName}` authenticator
|
||||
# against exactly this controller's own registered audience (see the
|
||||
|
|
@ -215,7 +215,7 @@ in
|
|||
name = "swarm-controller";
|
||||
consumers = [ "swarm-controller" ];
|
||||
hostUnit = true;
|
||||
enable = deployCfg.controller;
|
||||
enable = deployCfg.swarm-controller.enable;
|
||||
})
|
||||
];
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
# `enable` moved to `services.hyperhive.deploy.controller` — see
|
||||
# `enable` moved to `services.hyperhive.deploy.swarm-controller.enable` — see
|
||||
# ./deploy.nix. `services.hyperhive.enableAllLocalDefaults` still
|
||||
# asserts it, and that was never an exception to "not derived from
|
||||
# services.hyperhive.enable": that mode says "this box is the whole
|
||||
|
|
@ -451,7 +451,7 @@ in
|
|||
|
||||
authBridgeUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = if deployCfg.authelia then autheliaCfg.bridgeUrl else null;
|
||||
default = if deployCfg.authelia.enable then autheliaCfg.bridgeUrl else null;
|
||||
defaultText = lib.literalExpression ''
|
||||
authelia's own `bridgeUrl` when this host also runs
|
||||
`swarm-authelia`, else null
|
||||
|
|
@ -474,7 +474,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.controller) {
|
||||
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-controller.enable) {
|
||||
# The daemon and the oneshot that mints its credential — the second one
|
||||
# failing leaves the first running and unable to authenticate anywhere.
|
||||
services.hyperhive.swarm.otel.journaldUnits = [
|
||||
|
|
@ -499,7 +499,7 @@ in
|
|||
# client list would be a second source of truth for a string whose
|
||||
# mismatch is an opaque 401 from the token endpoint. Same shape as the
|
||||
# queue's own client declaration.
|
||||
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf deployCfg.authelia [
|
||||
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf deployCfg.authelia.enable [
|
||||
{
|
||||
id = queueClientId;
|
||||
description = "HyperHive swarm controller";
|
||||
|
|
@ -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.deploy.nats`). A controller on its
|
||||
(`services.hyperhive.deploy.nats.enable`). A controller on its
|
||||
own host has to be told where the queue is.
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ let
|
|||
# The all-local case: this host runs BOTH Grafana and the swarm's authelia,
|
||||
# so the minted secret can be moved without an operator. Same split the
|
||||
# forge and matrix modules document.
|
||||
ssoLocal = deployCfg.grafana && deployCfg.authelia;
|
||||
ssoLocal = deployCfg.grafana.enable && deployCfg.authelia.enable;
|
||||
autheliaUrl = toString autheliaCfg.url;
|
||||
|
||||
# Where the plaintext lands inside the container. Under /var/lib rather
|
||||
|
|
@ -107,7 +107,7 @@ let
|
|||
|
||||
in
|
||||
{
|
||||
# `enable` moved to `services.hyperhive.deploy.grafana` — see
|
||||
# `enable` moved to `services.hyperhive.deploy.grafana.enable` — see
|
||||
# ./deploy.nix. Whether this host runs the swarm's Grafana is a
|
||||
# deployment decision, and `swarm.*` has to be identical on every host.
|
||||
# What stays here is what the service IS: its package, domain, and
|
||||
|
|
@ -307,7 +307,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.grafana) {
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.grafana.enable) {
|
||||
# The gateway name and the quick-link, both inside `deploy.grafana` — that
|
||||
# guard is the load-bearing part. Every hive in a swarm may know this UI
|
||||
# exists, but only the host that RUNS it may claim the name; a client
|
||||
|
|
@ -408,9 +408,11 @@ in
|
|||
|
||||
# Declared here rather than in the collector's module, per that option's
|
||||
# rule: an entry exists where the service that named it runs.
|
||||
services.hyperhive.swarm.otel.scrapeTargets = lib.mkIf config.services.hyperhive.deploy.otel {
|
||||
grafana = "127.0.0.1:${toString cfg.metricsPort}";
|
||||
};
|
||||
services.hyperhive.swarm.otel.scrapeTargets =
|
||||
lib.mkIf config.services.hyperhive.deploy.otel.enable
|
||||
{
|
||||
grafana = "127.0.0.1:${toString cfg.metricsPort}";
|
||||
};
|
||||
|
||||
# Order the container after the host CA service so the bind source below
|
||||
# exists before nspawn sets the mount up.
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -164,13 +164,14 @@ let
|
|||
# Written once because "which exporters" is a property of this tier, not
|
||||
# of which hive a sample came from.
|
||||
exporterNames =
|
||||
upstreamExporters ++ lib.optional deployCfg.victoriametrics "otlphttp/victoriametrics";
|
||||
upstreamExporters ++ lib.optional deployCfg.victoriametrics.enable "otlphttp/victoriametrics";
|
||||
|
||||
# The same fan-out for logs, and the local store is only ONE of its
|
||||
# destinations. A deployment that turns the swarm's log store off and keeps
|
||||
# an upstream endpoint still collects — the store is where logs may be kept,
|
||||
# not the reason to read the journal at all.
|
||||
logExporterNames = upstreamExporters ++ lib.optional deployCfg.victorialogs "otlphttp/victorialogs";
|
||||
logExporterNames =
|
||||
upstreamExporters ++ lib.optional deployCfg.victorialogs.enable "otlphttp/victorialogs";
|
||||
|
||||
# Collect when there is anywhere to send it, and only then. A pipeline with
|
||||
# an empty exporter list is not a quiet no-op — the collector rejects it —
|
||||
|
|
@ -179,7 +180,7 @@ let
|
|||
collectLogs = logExporterNames != [ ];
|
||||
in
|
||||
{
|
||||
# `enable` moved to `services.hyperhive.deploy.otel` — see ./deploy.nix.
|
||||
# `enable` moved to `services.hyperhive.deploy.otel.enable` — see ./deploy.nix.
|
||||
# ⚠️ That is the SWARM collector. The per-hive one keeps its own
|
||||
# `services.hyperhive.otel.enable` (./otel.nix) and is a different
|
||||
# option entirely — every hive runs that one.
|
||||
|
|
@ -434,8 +435,8 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.otel) {
|
||||
# The gateway name, inside `deployCfg.otel` — that guard is the load-bearing
|
||||
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.otel.enable) {
|
||||
# The gateway name, inside `deployCfg.otel.enable` — that guard is the load-bearing
|
||||
# part. Every hive in a swarm may know this collector exists, but only
|
||||
# the host that RUNS it may claim the name; a client hive declaring the
|
||||
# vhost would answer for a service it does not have.
|
||||
|
|
@ -505,7 +506,7 @@ in
|
|||
# declaration would break every hive that runs a collector and
|
||||
# publishes nothing.
|
||||
services.hyperhive.swarm.authelia.oidc.clients =
|
||||
lib.mkIf (deployCfg.authelia && cfg.publishedScrapeTargets != { })
|
||||
lib.mkIf (deployCfg.authelia.enable && cfg.publishedScrapeTargets != { })
|
||||
[
|
||||
{
|
||||
id = cfg.clientId;
|
||||
|
|
@ -543,7 +544,7 @@ in
|
|||
# after it. On a fresh swarm that is a permanent stall presenting as
|
||||
# "metrics are broken", several layers from its cause.
|
||||
systemd.services.swarm-otel-oidc-secret =
|
||||
lib.mkIf (deployCfg.authelia && cfg.publishedScrapeTargets != { })
|
||||
lib.mkIf (deployCfg.authelia.enable && cfg.publishedScrapeTargets != { })
|
||||
{
|
||||
description = "deliver the swarm collector's OIDC client secret from authelia";
|
||||
after = [ "container@${autheliaCfg.machine}.service" ];
|
||||
|
|
@ -598,12 +599,12 @@ in
|
|||
# The tier exists to hold the upstream credential and to write the
|
||||
# swarm's store. With neither, it is a process that receives
|
||||
# samples and drops them — which looks healthy and loses data.
|
||||
assertion = otelCfg.endpoint != "" || deployCfg.victoriametrics;
|
||||
assertion = otelCfg.endpoint != "" || deployCfg.victoriametrics.enable;
|
||||
message = ''
|
||||
services.hyperhive.deploy.otel is true but this collector
|
||||
services.hyperhive.deploy.otel.enable is true but this collector
|
||||
has nowhere to send what it receives:
|
||||
services.hyperhive.otel.endpoint is empty and
|
||||
services.hyperhive.deploy.victoriametrics is false.
|
||||
services.hyperhive.deploy.victoriametrics.enable is false.
|
||||
|
||||
Set the endpoint to export upstream, or enable the swarm's
|
||||
metrics store.
|
||||
|
|
@ -632,7 +633,7 @@ in
|
|||
# collector would listen on nothing while looking configured.
|
||||
assertion = hyperhiveCfg.swarm.hives != { };
|
||||
message = ''
|
||||
services.hyperhive.deploy.otel is true but
|
||||
services.hyperhive.deploy.otel.enable is true but
|
||||
services.hyperhive.swarm.hives is empty: ingest is authenticated
|
||||
per hive, so an empty roster means this collector accepts nothing
|
||||
from anyone.
|
||||
|
|
@ -744,7 +745,7 @@ in
|
|||
# issuer URL evaluates cleanly and refuses every hive at runtime.
|
||||
assertion = autheliaCfg.url != null;
|
||||
message = ''
|
||||
services.hyperhive.deploy.otel is true but
|
||||
services.hyperhive.deploy.otel.enable is true but
|
||||
services.hyperhive.swarm.authelia.url is null: every hive
|
||||
authenticates to this collector as itself, and the token comes
|
||||
from the swarm's identity provider.
|
||||
|
|
@ -770,7 +771,7 @@ in
|
|||
cfg.producerPort
|
||||
otelCfg.collector.port
|
||||
]
|
||||
++ lib.optional deployCfg.victoriametrics vmCfg.port;
|
||||
++ lib.optional deployCfg.victoriametrics.enable vmCfg.port;
|
||||
all = derived ++ others;
|
||||
in
|
||||
lib.length (lib.unique all) == lib.length all;
|
||||
|
|
@ -1005,7 +1006,7 @@ in
|
|||
};
|
||||
|
||||
exporters =
|
||||
lib.optionalAttrs deployCfg.victoriametrics {
|
||||
lib.optionalAttrs deployCfg.victoriametrics.enable {
|
||||
# `metrics_endpoint`, NOT `endpoint`: the latter is a
|
||||
# base that otlphttp appends `/v1/metrics` to, while
|
||||
# VictoriaMetrics serves OTLP at
|
||||
|
|
@ -1028,7 +1029,7 @@ in
|
|||
}
|
||||
// lib.optionalAttrs (otelCfg.protocol == "http/json") { encoding = "json"; };
|
||||
}
|
||||
// lib.optionalAttrs deployCfg.victorialogs {
|
||||
// lib.optionalAttrs deployCfg.victorialogs.enable {
|
||||
# `logs_endpoint`, NOT `endpoint`, for exactly the reason the
|
||||
# metrics exporter above spells out — and the trap is worse
|
||||
# here, because the two stores' OTLP routes differ. `endpoint`
|
||||
|
|
|
|||
|
|
@ -71,25 +71,27 @@ in
|
|||
# authelia: a swarm has one SSO provider, and this says it lives here.
|
||||
# With it off the hive is a *client* — `swarm.authelia.url` still points
|
||||
# at whoever runs it.
|
||||
config.services.hyperhive.deploy.authelia = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.authelia.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
|
||||
# The queue. Same rule: once per swarm, optional.
|
||||
config.services.hyperhive.deploy.nats = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.nats.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
|
||||
# The swarm collector that feeds the metrics pair, and the only tier
|
||||
# holding the upstream credential. ⚠️ NOT the per-hive collector below,
|
||||
# which every hive runs.
|
||||
config.services.hyperhive.deploy.otel = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.otel.enable = 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
|
||||
# operator who wants exactly one still sets it directly, which
|
||||
# `mkDefault` allows.
|
||||
config.services.hyperhive.deploy.victoriametrics = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.grafana = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.victoriametrics.enable =
|
||||
lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.grafana.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
|
||||
# The log store, from the same switch for the same reason as the rest: a
|
||||
# hive that is not the service host is a *client* of it, not a second one.
|
||||
config.services.hyperhive.deploy.victorialogs = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
config.services.hyperhive.deploy.victorialogs.enable =
|
||||
lib.mkDefault swarmCfg.enableRequiredServices;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ let
|
|||
hiveDomain = config.services.hyperhive.domain;
|
||||
in
|
||||
{
|
||||
# `enable` moved to `services.hyperhive.deploy.swarm-ui` — see
|
||||
# `enable` moved to `services.hyperhive.deploy.swarm-ui.enable` — see
|
||||
# ./deploy.nix, where it still derives from the controller's own deploy
|
||||
# toggle for the same reason. What stays here is what the UI IS: its
|
||||
# domain and wiring.
|
||||
|
|
@ -99,7 +99,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-ui) {
|
||||
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-ui.enable) {
|
||||
assertions = [
|
||||
{
|
||||
# The `_` default server already answers for the hive domain
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ let
|
|||
'';
|
||||
in
|
||||
{
|
||||
# `enable` moved to `services.hyperhive.deploy.victorialogs` — see
|
||||
# `enable` moved to `services.hyperhive.deploy.victorialogs.enable` — see
|
||||
# ./deploy.nix. What stays here is what the store IS: its package,
|
||||
# domain, retention and wiring.
|
||||
options.services.hyperhive.swarm.victorialogs = {
|
||||
|
|
@ -121,7 +121,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.victorialogs) {
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.victorialogs.enable) {
|
||||
# This store publishes its own health as prometheus metrics on the same
|
||||
# listener it serves queries on, so the swarm's collector scrapes it with
|
||||
# no exporter and no extra port — same arrangement as the metrics store.
|
||||
|
|
@ -134,7 +134,7 @@ in
|
|||
# hosts are separate evaluations.
|
||||
services.hyperhive.swarm.otel.scrapeTargets.victorialogs = "127.0.0.1:${toString cfg.port}";
|
||||
|
||||
# The gateway name and the quick-link, both inside `deployCfg.victorialogs` — same
|
||||
# The gateway name and the quick-link, both inside `deployCfg.victorialogs.enable` — same
|
||||
# "only the host that runs the service may claim the name" guard every
|
||||
# sibling swarm-service module uses (`swarm-grafana.nix`,
|
||||
# `swarm-victoriametrics.nix`).
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ let
|
|||
domainBase = if swarmDomain == null then "invalid" else swarmDomain;
|
||||
in
|
||||
{
|
||||
# `enable` moved to `services.hyperhive.deploy.victoriametrics` — see
|
||||
# `enable` moved to `services.hyperhive.deploy.victoriametrics.enable` — see
|
||||
# ./deploy.nix. What stays here is what the store IS: its package,
|
||||
# domain, retention and wiring.
|
||||
options.services.hyperhive.swarm.victoriametrics = {
|
||||
|
|
@ -90,8 +90,8 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.victoriametrics) {
|
||||
# The gateway name and the quick-link, both inside `deployCfg.victoriametrics` — that
|
||||
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.victoriametrics.enable) {
|
||||
# The gateway name and the quick-link, both inside `deployCfg.victoriametrics.enable` — that
|
||||
# guard is the load-bearing part. Every hive in a swarm may know this
|
||||
# store exists, but only the host that RUNS it may claim the name; a
|
||||
# client hive declaring the vhost would answer for a service it does not
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ let
|
|||
# `forge.<apex>` is, and no CA in the hierarchy issues for it
|
||||
# implicitly. Left out, its vhost falls back to the hive leaf and the
|
||||
# swarm's front page opens with a name mismatch.
|
||||
++ lib.optional deployCfg.swarm-ui swarmCfg.ui.domain
|
||||
++ lib.optional deployCfg.swarm-ui.enable swarmCfg.ui.domain
|
||||
# Every swarm service that claims a gateway name belongs here, and
|
||||
# these three were missing it. Membership is what `gateway.lib.tlsFor`
|
||||
# consults to pick the services leaf over the hive one, so a name
|
||||
|
|
@ -58,16 +58,16 @@ let
|
|||
# metrics UI and store looked fine for as long as only people opened
|
||||
# them; the collector's exporter — same defect, no human in the loop —
|
||||
# failed every POST and dropped the samples.
|
||||
++ lib.optional deployCfg.grafana swarmCfg.grafana.domain
|
||||
++ lib.optional deployCfg.victoriametrics swarmCfg.victoriametrics.domain
|
||||
++ lib.optional deployCfg.otel swarmCfg.otel.domain
|
||||
++ lib.optional deployCfg.grafana.enable swarmCfg.grafana.domain
|
||||
++ lib.optional deployCfg.victoriametrics.enable swarmCfg.victoriametrics.domain
|
||||
++ lib.optional deployCfg.otel.enable swarmCfg.otel.domain
|
||||
# VictoriaLogs' vhost is new (was previously unpublished entirely — see
|
||||
# swarm-victorialogs.nix's file-top comment) and needs the same
|
||||
# membership every gateway-published swarm service needs: absent from
|
||||
# this list, `gateway.lib.tlsFor` falls back to the hive leaf, which
|
||||
# cannot cover a name under a different apex — see the ⚠️ above this
|
||||
# list for what that looked like the last time a name was missed here.
|
||||
++ lib.optional deployCfg.victorialogs swarmCfg.victorialogs.domain;
|
||||
++ lib.optional deployCfg.victorialogs.enable swarmCfg.victorialogs.domain;
|
||||
|
||||
# Hives whose entry still carries the removed `certFingerprint`. Scanned
|
||||
# here, at top level, because that is the only place an assertion about a
|
||||
|
|
@ -96,7 +96,7 @@ let
|
|||
# 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 = deployCfg.nats && deployCfg.authelia && cfg.hiveName != null;
|
||||
queueLocal = deployCfg.nats.enable && deployCfg.authelia.enable && cfg.hiveName != null;
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.swarm.hives = lib.mkOption {
|
||||
|
|
|
|||
Loading…
Reference in a new issue