Watch
0
0
Fork
You've already forked hyperhive
0

nix: split swarm-grafana into service and deploy-mode files

`swarm.grafana` (what the metrics UI is to every hive: container name,
domain, metrics port, OIDC client) moves to
nix/host-modules/swarm-grafana-service.nix, together with `domainBase`,
the only helper it reads besides `cfg`. Everything else -- the
`deploy.grafana` options, the whole `config` block including
`containers.swarm-grafana`, and the helpers only they read -- stays in
nix/host-modules/swarm-grafana.nix, which default.nix now imports
alongside the new file.

Both halves read `cfg` (`swarm.grafana.oidc.redirectUri` defaults from
`cfg.domain`; the config block reads `cfg` throughout). It is an option
read, so each file binds it from `config.services.hyperhive.swarm.grafana`.
The service file has no `hyperhiveCfg`, so its `swarmDomain` reads
`config.services.hyperhive.swarm.domain` directly, as
swarm-nats-service.nix does.

A pure move: option paths, option definitions and config are unchanged
apart from the two comments on either side of the cut, which now name the
file the other half lives in.

Refs #3742
This commit is contained in:
atlas 2026-10-01 09:30:21 +02:00
commit eed53a2b59
3 changed files with 145 additions and 131 deletions

View file

@ -0,0 +1,139 @@
# The swarm's metrics UI as every hive sees it: the name it is served under,
# the port its `/metrics` is re-served on, and the OIDC client it is
# registered as, identical on every host. What the host running it decides,
# and the container itself, are in ./swarm-grafana.nix.
{
lib,
config,
...
}:
let
cfg = config.services.hyperhive.swarm.grafana;
swarmDomain = config.services.hyperhive.swarm.domain;
# Total on a null swarm domain for the same reason every sibling module is:
# the required-domain assertion in hive-network.nix should be what an
# operator sees, not a coercion error from here.
domainBase = if swarmDomain == null then "invalid" else swarmDomain;
in
{
# `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.
# Here is what the service IS to every hive: the name it is served under,
# the port its `/metrics` is re-served on, and the OIDC client it is
# registered as. What the host running it decides — which build it runs,
# where its datasources point, which plugins are in its store path, and
# the socket directory it shares with nginx — is `deploy.grafana`, in
# ./swarm-grafana.nix.
options.services.hyperhive.swarm.grafana = {
machine = lib.mkOption {
type = lib.types.str;
readOnly = true;
default = "swarm-grafana";
description = ''
Container name. Read-only: the name appears in host paths and in
`machinectl`, so it is a fact other modules may read rather than a
knob.
'';
};
domain = lib.mkOption {
type = lib.types.str;
default = "grafana.${domainBase}";
defaultText = lib.literalExpression ''"grafana.''${services.hyperhive.swarm.domain}"'';
description = ''
Name the gateway serves this on. A sibling of the swarm's other
service names, so the swarm-services sub-CA can issue for it — see
`hive-tls.nix` for why a service name being a sibling rather than a
child decides which CA may sign it.
⚠️ Changing this changes the OAuth redirect URI, which authelia
matches exactly. Both sides move together because both derive from
this option; an operator who pins one by hand breaks the login.
'';
};
metricsPort = lib.mkOption {
type = lib.types.port;
default = 9095;
description = ''
Loopback port on which the gateway's nginx re-serves Grafana's
`/metrics`, and nothing else, so the swarm's collector can scrape it.
⚠️ **This is nginx's port, not Grafana's.** Grafana still claims none —
see {option}`services.hyperhive.deploy.grafana.socketDir` for why that
matters. A prometheus scrape target is a `host:port`, and it cannot
address a unix socket; rather than undo the socket decision, the one
endpoint a scraper needs gets a listener of its own.
Bound to loopback and unauthenticated, which is the same posture every
other entry in
{option}`services.hyperhive.swarm.otel.scrapeTargets` has: those
targets are trusted by *proximity* rather than by credential.
Deliberately **not** the published `grafana.<domain>` vhost, which
would put an authorization decision in front of a scrape.
The number itself is arbitrary and free today;
`state/eval-port-collisions.sh` is what keeps it that way, since a
second claim on a port in this shared namespace produces no bind error
and nothing in any log.
'';
};
oidc = {
clientId = lib.mkOption {
type = lib.types.str;
default = "swarm-grafana";
description = ''
The authelia OIDC client id. Names the application rather than
the protocol, per the convention in
{option}`services.hyperhive.swarm.authelia.oidc.clients`.
'';
};
redirectUri = lib.mkOption {
type = lib.types.str;
readOnly = true;
default = "https://${cfg.domain}/login/generic_oauth";
defaultText = lib.literalExpression ''"https://''${services.hyperhive.swarm.grafana.domain}/login/generic_oauth"'';
description = ''
OAuth callback authelia sends the browser back to, and the URI it
matches **exactly**.
Read-only, like {option}`services.hyperhive.swarm.grafana.machine`
and for the same reason: Grafana derives it from its own
`root_url` (`<root_url>/login/generic_oauth`), so it is a fact
other modules may read rather than a knob. The glue that registers
this client wherever authelia runs reads it from here instead of
restating the format — a second spelling of it is a silently
rejected login.
'';
};
role = lib.mkOption {
type = lib.types.enum [
"Viewer"
"Editor"
"Admin"
];
default = "Admin";
example = "Editor";
description = ''
Grafana org role every SSO user is assigned.
`Admin` by default, and that is a considered default rather than
a permissive one: the login form is disabled whenever SSO is
configured, so this is the *only* way anyone reaches Grafana —
a `Viewer` default would produce a swarm nobody can administer.
Passing authelia already means being an operator of this swarm;
its user store is the small, `swarmctl`-managed one.
Lower it if a swarm ever grows read-only operators, which is a
one-line change here.
'';
};
};
};
}