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

@ -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`