fix(swarm-authelia): only declare the scrape target where a collector reads it

The comment claimed the option's rule keeps scraper and target on one host
by construction. It does not. Both services default from
enableRequiredServices via mkDefault, which is an invitation to override
rather than a guarantee, so co-location is a property of the auto-deployed
topology and not of the module.

Gating the target on the collector's own enable makes the loopback address
honest: a host running authelia without a collector no longer declares a
target nothing can read. That absence was the part worth fixing, because it
is silent — no error, no metrics, nothing in a log to notice.

This does not make authelia scrapeable from another host. That needs the
endpoint published under a name with a certificate and an audience, which
is separate work; the option's docs now say so where someone splitting the
two would read it.
This commit is contained in:
atlas 2026-08-24 12:30:52 +02:00 committed by mara
commit 541dd29820

View file

@ -43,6 +43,7 @@ let
swarmDomain = hyperhiveCfg.swarm.domain;
uiCfg = hyperhiveCfg.swarm.ui;
forgeCfg = hyperhiveCfg.swarm.forge;
otelCfg = hyperhiveCfg.swarm.otel;
# Group an account must hold to reach operator-only surfaces. Named
# here because this module writes the rule that enforces it and
@ -356,6 +357,16 @@ in
two services defaulting to the same port do not conflict at build
time one simply loses at runtime, with nothing in any log. Check
a new value against the others before changing this.
::: {.note}
Loopback means this endpoint is only reachable by a collector on
the *same host*, so the scrape target is declared only when one is
enabled here. Run the swarm's collector elsewhere and authelia's
metrics are simply not collected no error, and nothing in a log
to say so. Making them reachable across hosts is a different piece
of work: the endpoint would have to be published under a name,
with a certificate and an audience.
:::
'';
};
@ -825,10 +836,24 @@ in
services.hyperhive.gateway.localNames = [ cfg.domain ];
# Declared here rather than in the collector's module, per the option's
# own rule: an entry exists only where the service that named it runs,
# which is what keeps scraper and target on one host by construction
# rather than by the all-local deployment happening to co-locate them.
services.hyperhive.swarm.otel.scrapeTargets.authelia = "127.0.0.1:${toString cfg.metricsPort}";
# own rule: an entry exists only where the service that named it runs.
#
# Gated on the collector's `enable` as well, and that second condition is
# what makes the loopback address honest. Both services default from
# `swarm.enableRequiredServices` — but `mkDefault` is an invitation to
# override, not a guarantee, so "they are on the same host" is a property
# of the auto-deployed topology rather than of the module. Without this
# gate, a host running authelia and no collector would declare a target
# nothing can read, and the absence would be silent: no error, no metrics,
# nothing to notice.
#
# It does not make authelia scrapeable from ANOTHER host — that needs the
# endpoint published under a name with a cert and an audience, which is a
# different piece of work. This only stops the config asserting a
# collection that is not happening.
services.hyperhive.swarm.otel.scrapeTargets = lib.mkIf otelCfg.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 —