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:
parent
07ca06dcca
commit
eed53a2b59
3 changed files with 145 additions and 131 deletions
|
|
@ -47,6 +47,7 @@
|
||||||
./swarm-nats-service.nix
|
./swarm-nats-service.nix
|
||||||
./swarm-nats.nix
|
./swarm-nats.nix
|
||||||
./swarm-controller.nix
|
./swarm-controller.nix
|
||||||
|
./swarm-grafana-service.nix
|
||||||
./swarm-grafana.nix
|
./swarm-grafana.nix
|
||||||
./swarm-otel.nix
|
./swarm-otel.nix
|
||||||
./swarm-snapshot-store.nix
|
./swarm-snapshot-store.nix
|
||||||
|
|
|
||||||
139
nix/host-modules/swarm-grafana-service.nix
Normal file
139
nix/host-modules/swarm-grafana-service.nix
Normal 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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,6 @@ let
|
||||||
gatewayCfg = hyperhiveCfg.gateway;
|
gatewayCfg = hyperhiveCfg.gateway;
|
||||||
baoCfg = hyperhiveCfg.swarm.bao;
|
baoCfg = hyperhiveCfg.swarm.bao;
|
||||||
baoDeploy = deployCfg.bao;
|
baoDeploy = deployCfg.bao;
|
||||||
swarmDomain = hyperhiveCfg.swarm.domain;
|
|
||||||
|
|
||||||
caTrust = import ./lib/hive-ca-trust.nix {
|
caTrust = import ./lib/hive-ca-trust.nix {
|
||||||
inherit lib gatewayCfg;
|
inherit lib gatewayCfg;
|
||||||
|
|
@ -205,11 +204,6 @@ let
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
# 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;
|
|
||||||
|
|
||||||
# A reader of the store is defined by holding a certificate the store
|
# A reader of the store is defined by holding a certificate the store
|
||||||
# accepts, never by standing next to it — the rule
|
# accepts, never by standing next to it — the rule
|
||||||
# ./glue-matrix-bao-token.nix states in full.
|
# ./glue-matrix-bao-token.nix states in full.
|
||||||
|
|
@ -278,131 +272,11 @@ let
|
||||||
storeRetry = import ./lib/store-retry.nix { };
|
storeRetry = import ./lib/store-retry.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# `enable` moved to `services.hyperhive.deploy.grafana.enable` — see
|
# What the service IS to every hive is `swarm.grafana` in
|
||||||
# ./deploy.nix. Whether this host runs the swarm's Grafana is a
|
# ./swarm-grafana-service.nix. What the host running it decides is here:
|
||||||
# deployment decision, and `swarm.*` has to be identical on every host.
|
# which build it runs, where its datasources point, which plugins sit in its
|
||||||
# What stays here is what the service IS to every hive: the name it is
|
# store path, and the directory it shares a socket with nginx through.
|
||||||
# served under, the port its `/metrics` is re-served on, and the OIDC
|
# `enable` already lives in ./deploy.nix, which also carries the renames.
|
||||||
# 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 below,
|
|
||||||
# under `deploy.grafana`.
|
|
||||||
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.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
# What stays above is what the service IS to every hive. What the host
|
|
||||||
# running it decides is here: which build it runs, where its datasources
|
|
||||||
# point, which plugins sit in its store path, and the directory it shares a
|
|
||||||
# socket with nginx through. `enable` already lives in ./deploy.nix, which
|
|
||||||
# also carries the renames.
|
|
||||||
#
|
#
|
||||||
# ⚠️ Both datasource URLs are wiring and still move. A URL's scope is the
|
# ⚠️ Both datasource URLs are wiring and still move. A URL's scope is the
|
||||||
# scope of what it ADDRESSES, not the fact that it is a URL: both stores
|
# scope of what it ADDRESSES, not the fact that it is a URL: both stores
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue