deploy: name the swarm collector swarm-otel, not otel

Per review: `deploy.otel` does not imply swarm level, and there is a
hive-tier collector too -- `services.hyperhive.otel.enable`, which every
hive runs. The tier is the entire distinction between the two, so the
name has to carry it, matching `deploy.swarm-controller` and
`deploy.swarm-ui`.

15 swarm-tier references renamed across 8 files. The hive-tier
collector's 16 references are deliberately untouched, verified as a
control on the same command.

Three spellings needed three different patterns, all inside this one
rename: the dotted path; the `mkRenamedOptionModule` target written as a
nix LIST (`[ ... "deploy" "otel" "enable" ]`), which no dotted grep can
match; and prose in docs/observability.md spelled `deploy.otel` with
neither `.enable` nor a leading dot. Unanchored `deploy\.otel\b` is the
only pattern that finds all three.
This commit is contained in:
atlas 2026-08-30 04:14:20 +02:00 committed by mara
commit 09fe94a8ca
10 changed files with 24 additions and 23 deletions

View file

@ -67,7 +67,7 @@ in
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "otel" "enable" ]
[ "services" "hyperhive" "deploy" "otel" "enable" ]
[ "services" "hyperhive" "deploy" "swarm-otel" "enable" ]
)
];
@ -131,7 +131,7 @@ in
'';
};
otel.enable = lib.mkOption {
swarm-otel.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
@ -142,11 +142,12 @@ in
metrics pair it feeds: a swarm has one of these, and it belongs
wherever the shared services live rather than on every hive.
Not to be confused with
{option}`services.hyperhive.otel.enable`, the **hive-tier**
collector, which every hive runs and which is a different option.
A hive that does not run the swarm collector still runs its own,
and reaches this one by name at
Named `swarm-otel` rather than `otel` because there are two
collectors and the tier is the whole distinction:
{option}`services.hyperhive.otel.enable` is the **hive-tier** one,
which every hive runs. A bare `deploy.otel` would not say which
it meant. A hive that does not run the swarm collector still runs
its own, and reaches this one by name at
{option}`services.hyperhive.swarm.otel.domain`.
'';
};

View file

@ -61,7 +61,7 @@ in
their own containers can reach.
That collector forwards to the swarm's
({option}`services.hyperhive.deploy.otel.enable`), which holds the
({option}`services.hyperhive.deploy.swarm-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,7 +81,7 @@ in
has left the swarm.
Read by the swarm's collector
({option}`services.hyperhive.deploy.otel.enable`), which is the only
({option}`services.hyperhive.deploy.swarm-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.

View file

@ -1010,7 +1010,7 @@ in
# 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.enable
lib.mkIf config.services.hyperhive.deploy.swarm-otel.enable
{
authelia = "127.0.0.1:${toString cfg.metricsPort}";
};

View file

@ -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.enable` alone (a swarm-wide fact, not "enabled
# Gated on `deployCfg.swarm-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.enable {
otelEnv = lib.optionalAttrs deployCfg.swarm-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

View file

@ -409,7 +409,7 @@ 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.enable
lib.mkIf config.services.hyperhive.deploy.swarm-otel.enable
{
grafana = "127.0.0.1:${toString cfg.metricsPort}";
};

View file

@ -536,7 +536,7 @@ in
# 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.enable
lib.mkIf config.services.hyperhive.deploy.swarm-otel.enable
{
nats = "127.0.0.1:${toString cfg.metricsPort}";
};

View file

@ -180,7 +180,7 @@ let
collectLogs = logExporterNames != [ ];
in
{
# `enable` moved to `services.hyperhive.deploy.otel.enable` — see ./deploy.nix.
# `enable` moved to `services.hyperhive.deploy.swarm-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.
@ -435,8 +435,8 @@ in
};
};
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.otel.enable) {
# The gateway name, inside `deployCfg.otel.enable` — that guard is the load-bearing
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-otel.enable) {
# The gateway name, inside `deployCfg.swarm-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.
@ -601,7 +601,7 @@ in
# samples and drops them — which looks healthy and loses data.
assertion = otelCfg.endpoint != "" || deployCfg.victoriametrics.enable;
message = ''
services.hyperhive.deploy.otel.enable is true but this collector
services.hyperhive.deploy.swarm-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.enable is false.
@ -633,7 +633,7 @@ in
# collector would listen on nothing while looking configured.
assertion = hyperhiveCfg.swarm.hives != { };
message = ''
services.hyperhive.deploy.otel.enable is true but
services.hyperhive.deploy.swarm-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.
@ -745,7 +745,7 @@ in
# issuer URL evaluates cleanly and refuses every hive at runtime.
assertion = autheliaCfg.url != null;
message = ''
services.hyperhive.deploy.otel.enable is true but
services.hyperhive.deploy.swarm-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.

View file

@ -79,7 +79,7 @@ in
# 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.enable = lib.mkDefault swarmCfg.enableRequiredServices;
config.services.hyperhive.deploy.swarm-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

View file

@ -60,7 +60,7 @@ let
# failed every POST and dropped the samples.
++ lib.optional deployCfg.grafana.enable swarmCfg.grafana.domain
++ lib.optional deployCfg.victoriametrics.enable swarmCfg.victoriametrics.domain
++ lib.optional deployCfg.otel.enable swarmCfg.otel.domain
++ lib.optional deployCfg.swarm-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