deploy: move the SSO provider toggle
The largest of these moves: sixteen references spelled through `let` aliases across eight modules, plus eight more spelled as a path, plus five documentation pages. authelia is also the clearest case for why the two namespaces exist. `swarm.authelia.url` is needed by *every* hive in the swarm — it says where to send a browser to authenticate — while running the container is the business of exactly one host. The client half and the server half were sharing a namespace whose whole contract is "identical everywhere", and only one of them could honour it. `swarm.authelia.oidc.clients` stays where it is for the same reason: several modules register a client there, gated on authelia running here, and the registry itself is what the service *is* rather than a decision about this machine. One sweep note worth recording: a grep for `swarm.authelia.enable` misses `swarmCfg.authelia.enable`, because the prefix is whatever the reading file bound. Grepping the suffix `.authelia.enable` finds both, and found a reference in swarm.nix that the path-shaped pattern did not.
This commit is contained in:
parent
0b7357d4b8
commit
37ca7676d6
16 changed files with 77 additions and 50 deletions
|
|
@ -14,7 +14,7 @@ Single nginx in front of every hyperhive web surface. Runs on the **host**, next
|
||||||
| `chat.<swarm>/_matrix/*` | `chat.<swarm>` | tuwunel (`8008`) | `matrix.gatewayHost != null` |
|
| `chat.<swarm>/_matrix/*` | `chat.<swarm>` | tuwunel (`8008`) | `matrix.gatewayHost != null` |
|
||||||
| `chat.<swarm>/` | `chat.<swarm>` | fluffychat-web static | `matrix.gui.enable` |
|
| `chat.<swarm>/` | `chat.<swarm>` | fluffychat-web static | `matrix.gui.enable` |
|
||||||
| `chat.<swarm>/config.json` | `chat.<swarm>` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` |
|
| `chat.<swarm>/config.json` | `chat.<swarm>` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` |
|
||||||
| `auth.<swarm>/` | `auth.<swarm>` | authelia (`9091`) | `swarm.authelia.enable` |
|
| `auth.<swarm>/` | `auth.<swarm>` | authelia (`9091`) | `deploy.authelia` |
|
||||||
| `<swarm>/` | `<swarm>` | swarm-ui dist (static), behind an authelia subrequest | `deploy.swarm-ui` |
|
| `<swarm>/` | `<swarm>` | swarm-ui dist (static), behind an authelia subrequest | `deploy.swarm-ui` |
|
||||||
|
|
||||||
The authelia vhost is declared only by the host that **runs** authelia, not by every hive that uses it — a client hive knows the swarm's `authelia.url` but must not answer for a name it doesn't serve. Its server name is exactly `swarm.authelia.domain`: authelia validates `authelia_url ⊂ session cookie domain` at startup, so a near-miss is a container that refuses to boot. It carries no `auth_basic` — the login page must not sit behind the login mechanism it replaces — and sets the four `X-Forwarded-{Proto,Host,Uri,For}` headers, since authelia decides by the *original* request rather than the hop it sees.
|
The authelia vhost is declared only by the host that **runs** authelia, not by every hive that uses it — a client hive knows the swarm's `authelia.url` but must not answer for a name it doesn't serve. Its server name is exactly `swarm.authelia.domain`: authelia validates `authelia_url ⊂ session cookie domain` at startup, so a near-miss is a container that refuses to boot. It carries no `auth_basic` — the login page must not sit behind the login mechanism it replaces — and sets the four `X-Forwarded-{Proto,Host,Uri,For}` headers, since authelia decides by the *original* request rather than the hop it sees.
|
||||||
|
|
@ -64,7 +64,7 @@ Each location carries a duplicated `auth_basic` block (separate locations don't
|
||||||
- `<hive-domain>` → `127.0.0.1`
|
- `<hive-domain>` → `127.0.0.1`
|
||||||
- `forge.<swarm>` → `127.0.0.1` (when forge.behindGateway)
|
- `forge.<swarm>` → `127.0.0.1` (when forge.behindGateway)
|
||||||
- `chat.<swarm>` → `127.0.0.1` (when matrix.gatewayHost set)
|
- `chat.<swarm>` → `127.0.0.1` (when matrix.gatewayHost set)
|
||||||
- `auth.<swarm>` → `127.0.0.1` (when swarm.authelia.enable)
|
- `auth.<swarm>` → `127.0.0.1` (when deploy.authelia)
|
||||||
|
|
||||||
`lib.unique` de-dupes if any sub-domain happens to equal another entry. Operators with real DNS leave it off.
|
`lib.unique` de-dupes if any sub-domain happens to equal another entry. Operators with real DNS leave it off.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ echo "hunter2" | hivectl gateway create-user mara --password-stdin
|
||||||
hivectl gateway list-users
|
hivectl gateway list-users
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3 · Swarm SSO (only when `swarm.authelia.enable`)
|
### 3 · Swarm SSO (only when `deploy.authelia`)
|
||||||
|
|
||||||
⚠️ **Required to finish the install, not optional.** Authelia treats an
|
⚠️ **Required to finish the install, not optional.** Authelia treats an
|
||||||
empty user store as a fatal startup error, so until this runs the
|
empty user store as a fatal startup error, so until this runs the
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ One authelia per swarm, in a `swarm-authelia` container, at
|
||||||
provider, differentiated by roles and claims rather than by mechanism —
|
provider, differentiated by roles and claims rather than by mechanism —
|
||||||
there is one IdP and one auth path.
|
there is one IdP and one auth path.
|
||||||
|
|
||||||
- **`swarm.authelia.enable`** — run the container here. Defaults from
|
- **`deploy.authelia`** — run the container here. Defaults from
|
||||||
`swarm.enableRequiredServices`.
|
`swarm.enableRequiredServices`.
|
||||||
- **`swarm.authelia.url`** — where clients are sent to authenticate.
|
- **`swarm.authelia.url`** — where clients are sent to authenticate.
|
||||||
Present on **every** hive, defaulting to this host's own instance only
|
Present on **every** hive, defaulting to this host's own instance only
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ separate `enable` would be a second fact free to disagree with the first.
|
||||||
|
|
||||||
authelia binds loopback only. The **gateway** on the host running it
|
authelia binds loopback only. The **gateway** on the host running it
|
||||||
publishes it as `auth.<swarm.domain>` — vhost, dnsmasq record and TLS
|
publishes it as `auth.<swarm.domain>` — vhost, dnsmasq record and TLS
|
||||||
name all follow `swarm.authelia.enable`, so there is nothing to turn on
|
name all follow `deploy.authelia`, so there is nothing to turn on
|
||||||
separately. (Details, including why a client hive must not declare that
|
separately. (Details, including why a client hive must not declare that
|
||||||
vhost: [`../gateway.md`](../gateway.md).)
|
vhost: [`../gateway.md`](../gateway.md).)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ in
|
||||||
[ "services" "hyperhive" "swarm" "ui" "enable" ]
|
[ "services" "hyperhive" "swarm" "ui" "enable" ]
|
||||||
[ "services" "hyperhive" "deploy" "swarm-ui" ]
|
[ "services" "hyperhive" "deploy" "swarm-ui" ]
|
||||||
)
|
)
|
||||||
|
(lib.mkRenamedOptionModule
|
||||||
|
[ "services" "hyperhive" "swarm" "authelia" "enable" ]
|
||||||
|
[ "services" "hyperhive" "deploy" "authelia" ]
|
||||||
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
options.services.hyperhive.deploy = {
|
options.services.hyperhive.deploy = {
|
||||||
|
|
@ -97,6 +101,24 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
authelia = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
example = true;
|
||||||
|
description = ''
|
||||||
|
Run the swarm's authelia in a `swarm-authelia` container on this
|
||||||
|
host. {option}`services.hyperhive.swarm.enableRequiredServices`
|
||||||
|
turns this on — a swarm has one SSO provider, and that says it
|
||||||
|
lives here.
|
||||||
|
|
||||||
|
With it off, this hive is a *client*:
|
||||||
|
{option}`services.hyperhive.swarm.authelia.url` still points at
|
||||||
|
whoever runs it, and no container is created. That asymmetry is
|
||||||
|
why the two live in different namespaces — every hive needs the
|
||||||
|
client half, only one runs the server half.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
controller = lib.mkOption {
|
controller = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ let
|
||||||
# assertion below turns into an eval failure rather than a discovery
|
# assertion below turns into an eval failure rather than a discovery
|
||||||
# request to `null/.well-known/...`.
|
# request to `null/.well-known/...`.
|
||||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||||
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
autheliaUrl = autheliaCfg.url;
|
autheliaUrl = autheliaCfg.url;
|
||||||
autheliaDiscoveryUrl = "${toString autheliaUrl}/.well-known/openid-configuration";
|
autheliaDiscoveryUrl = "${toString autheliaUrl}/.well-known/openid-configuration";
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ let
|
||||||
# authelia, so the secret can be moved without an operator. The other
|
# authelia, so the secret can be moved without an operator. The other
|
||||||
# two cases (swarm side via swarmctl, remote hive) leave
|
# two cases (swarm side via swarmctl, remote hive) leave
|
||||||
# `clientSecretFile` to be set explicitly — see docs/swarm/.
|
# `clientSecretFile` to be set explicitly — see docs/swarm/.
|
||||||
ssoLocal = autheliaCfg.enable;
|
ssoLocal = deployCfg.authelia;
|
||||||
|
|
||||||
# Where the plaintext lands inside the forge container. Under
|
# Where the plaintext lands inside the forge container. Under
|
||||||
# /var/lib rather than /run: the forge may start before the delivery
|
# /var/lib rather than /run: the forge may start before the delivery
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,14 @@ let
|
||||||
# below turns into an eval failure rather than a discovery request to
|
# below turns into an eval failure rather than a discovery request to
|
||||||
# `null/.well-known/…`.
|
# `null/.well-known/…`.
|
||||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||||
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
autheliaUrl = autheliaCfg.url;
|
autheliaUrl = autheliaCfg.url;
|
||||||
|
|
||||||
# The all-local case: this host runs BOTH the homeserver and the swarm's
|
# The all-local case: this host runs BOTH the homeserver and the swarm's
|
||||||
# authelia, so the secret can be moved without an operator. The other
|
# authelia, so the secret can be moved without an operator. The other
|
||||||
# two cases (swarm side, remote hive) leave `clientSecretFile` to be set
|
# two cases (swarm side, remote hive) leave `clientSecretFile` to be set
|
||||||
# explicitly — same split the forge module documents.
|
# explicitly — same split the forge module documents.
|
||||||
ssoLocal = autheliaCfg.enable;
|
ssoLocal = deployCfg.authelia;
|
||||||
|
|
||||||
# Where the plaintext lands inside the matrix container. Under /var/lib
|
# Where the plaintext lands inside the matrix container. Under /var/lib
|
||||||
# rather than /run: the homeserver may start before the delivery unit on
|
# rather than /run: the homeserver may start before the delivery unit on
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ in
|
||||||
# ran its first boot on — so they belong to the mode that asserts this
|
# ran its first boot on — so they belong to the mode that asserts this
|
||||||
# box is the whole deployment, not to the options' own `default`.
|
# box is the whole deployment, not to the options' own `default`.
|
||||||
#
|
#
|
||||||
# Deriving them from `swarm.nats.enable` / `swarm.authelia.enable`
|
# Deriving them from `swarm.nats.enable` / `deploy.authelia`
|
||||||
# inside those defaults is the mixing this file exists to prevent: the
|
# inside those defaults is the mixing this file exists to prevent: the
|
||||||
# option would be describing a deployment shape instead of describing
|
# option would be describing a deployment shape instead of describing
|
||||||
# itself, and "what does all-local turn on?" would stop having one
|
# itself, and "what does all-local turn on?" would stop having one
|
||||||
|
|
|
||||||
|
|
@ -231,9 +231,7 @@ in
|
||||||
clientSecretFile = lib.mkOption {
|
clientSecretFile = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default =
|
default =
|
||||||
if
|
if config.services.hyperhive.deploy.authelia && config.services.hyperhive.hiveName != null then
|
||||||
config.services.hyperhive.swarm.authelia.enable && config.services.hyperhive.hiveName != null
|
|
||||||
then
|
|
||||||
"${config.services.hyperhive.swarm.authelia.hostClientSecretDir}/"
|
"${config.services.hyperhive.swarm.authelia.hostClientSecretDir}/"
|
||||||
+ "${config.services.hyperhive.swarm.authelia.hiveClientPrefix}${config.services.hyperhive.hiveName}.secret"
|
+ "${config.services.hyperhive.swarm.authelia.hiveClientPrefix}${config.services.hyperhive.hiveName}.secret"
|
||||||
else
|
else
|
||||||
|
|
@ -284,6 +282,7 @@ in
|
||||||
let
|
let
|
||||||
otel = config.services.hyperhive.otel;
|
otel = config.services.hyperhive.otel;
|
||||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||||
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
swarmOtelCfg = config.services.hyperhive.swarm.otel;
|
swarmOtelCfg = config.services.hyperhive.swarm.otel;
|
||||||
hiveName = config.services.hyperhive.hiveName;
|
hiveName = config.services.hyperhive.hiveName;
|
||||||
listen = "${config.services.hyperhive.network.bridgeIp}:${toString otel.collector.port}";
|
listen = "${config.services.hyperhive.network.bridgeIp}:${toString otel.collector.port}";
|
||||||
|
|
@ -580,8 +579,8 @@ in
|
||||||
# Only when the minting container is on THIS host. Elsewhere the file
|
# Only when the minting container is on THIS host. Elsewhere the file
|
||||||
# is operator-provided and there is no local unit to order against —
|
# is operator-provided and there is no local unit to order against —
|
||||||
# naming one that does not exist orders nothing, silently.
|
# naming one that does not exist orders nothing, silently.
|
||||||
after = lib.optional autheliaCfg.enable "container@${autheliaCfg.machine}.service";
|
after = lib.optional deployCfg.authelia "container@${autheliaCfg.machine}.service";
|
||||||
requires = lib.optional autheliaCfg.enable "container@${autheliaCfg.machine}.service";
|
requires = lib.optional deployCfg.authelia "container@${autheliaCfg.machine}.service";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
RemainAfterExit = true;
|
RemainAfterExit = true;
|
||||||
|
|
|
||||||
|
|
@ -345,22 +345,12 @@ let
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
# `enable` moved to `services.hyperhive.deploy.authelia` — see
|
||||||
|
# ./deploy.nix. Whether this host runs the swarm's SSO provider is a
|
||||||
|
# deployment decision; what stays here is what authelia IS, including
|
||||||
|
# `url` and the OIDC client registry every hive needs as a *client*
|
||||||
|
# whether or not it runs the container.
|
||||||
options.services.hyperhive.swarm.authelia = {
|
options.services.hyperhive.swarm.authelia = {
|
||||||
enable = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
example = true;
|
|
||||||
description = ''
|
|
||||||
Run the swarm's authelia in a `swarm-authelia` container on this
|
|
||||||
host. `services.hyperhive.swarm.enableRequiredServices` turns
|
|
||||||
this on — a swarm has one SSO provider, and that says it lives
|
|
||||||
here.
|
|
||||||
|
|
||||||
With it off, this hive is a *client*: `url` below still points
|
|
||||||
at whoever runs it, and no container is created.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
package = lib.mkOption {
|
package = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
default = pkgs.authelia;
|
default = pkgs.authelia;
|
||||||
|
|
@ -437,7 +427,7 @@ in
|
||||||
|
|
||||||
url = lib.mkOption {
|
url = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = if cfg.enable then "https://${cfg.domain}" else null;
|
default = if deployCfg.authelia then "https://${cfg.domain}" else null;
|
||||||
defaultText = lib.literalExpression ''if enable then "https://''${domain}" else null'';
|
defaultText = lib.literalExpression ''if enable then "https://''${domain}" else null'';
|
||||||
example = "https://auth.example.com";
|
example = "https://auth.example.com";
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -820,7 +810,7 @@ in
|
||||||
bridgeUrl = lib.mkOption {
|
bridgeUrl = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = if cfg.enable then "http://127.0.0.1:${toString cfg.bridgePort}" else null;
|
default = if deployCfg.authelia then "http://127.0.0.1:${toString cfg.bridgePort}" else null;
|
||||||
defaultText = lib.literalExpression ''if enable then "http://127.0.0.1:''${bridgePort}" else null'';
|
defaultText = lib.literalExpression ''if enable then "http://127.0.0.1:''${bridgePort}" else null'';
|
||||||
description = ''
|
description = ''
|
||||||
Where `swarm-authelia-bridge` answers, **as seen from this
|
Where `swarm-authelia-bridge` answers, **as seen from this
|
||||||
|
|
@ -839,7 +829,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf (hyperhiveCfg.enable && cfg.enable) {
|
config = lib.mkIf (hyperhiveCfg.enable && deployCfg.authelia) {
|
||||||
# The derived half of the client list, declared the same way an
|
# The derived half of the client list, declared the same way an
|
||||||
# operator declares one. Everything downstream then reads a single
|
# operator declares one. Everything downstream then reads a single
|
||||||
# uniformly-typed `cfg.oidc.clients` and cannot tell the parts apart —
|
# uniformly-typed `cfg.oidc.clients` and cannot tell the parts apart —
|
||||||
|
|
@ -974,7 +964,7 @@ in
|
||||||
|
|
||||||
# Authelia's own gateway surface: the vhost that fronts it and the
|
# Authelia's own gateway surface: the vhost that fronts it and the
|
||||||
# name the hive resolver answers for. Both live here rather than in
|
# name the hive resolver answers for. Both live here rather than in
|
||||||
# the gateway, and both are inside `cfg.enable` — that guard is the
|
# the gateway, and both are inside `deployCfg.authelia` — that guard is the
|
||||||
# load-bearing part.
|
# load-bearing part.
|
||||||
#
|
#
|
||||||
# ⚠️ Every hive in a swarm knows `authelia.url`, but only the host
|
# ⚠️ Every hive in a swarm knows `authelia.url`, but only the host
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ let
|
||||||
# the right behaviour is for `swarmctl user add` to fail saying the
|
# the right behaviour is for `swarmctl user add` to fail saying the
|
||||||
# value is unset. A guessed path would resolve cleanly and write a file
|
# value is unset. A guessed path would resolve cleanly and write a file
|
||||||
# nothing reads, which is the failure mode that costs an afternoon.
|
# nothing reads, which is the failure mode that costs an afternoon.
|
||||||
autheliaEnv = lib.optionalAttrs autheliaCfg.enable {
|
autheliaEnv = lib.optionalAttrs deployCfg.authelia {
|
||||||
# The CONFIGURED authelia, not whatever is on PATH: the argon2
|
# The CONFIGURED authelia, not whatever is on PATH: the argon2
|
||||||
# parameters baked into a hash have to match the verifier's.
|
# parameters baked into a hash have to match the verifier's.
|
||||||
SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.package}/bin/authelia";
|
SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.package}/bin/authelia";
|
||||||
|
|
@ -451,7 +451,7 @@ in
|
||||||
|
|
||||||
authBridgeUrl = lib.mkOption {
|
authBridgeUrl = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = if autheliaCfg.enable then autheliaCfg.bridgeUrl else null;
|
default = if deployCfg.authelia then autheliaCfg.bridgeUrl else null;
|
||||||
defaultText = lib.literalExpression ''
|
defaultText = lib.literalExpression ''
|
||||||
authelia's own `bridgeUrl` when this host also runs
|
authelia's own `bridgeUrl` when this host also runs
|
||||||
`swarm-authelia`, else null
|
`swarm-authelia`, else null
|
||||||
|
|
@ -499,7 +499,7 @@ in
|
||||||
# client list would be a second source of truth for a string whose
|
# client list would be a second source of truth for a string whose
|
||||||
# mismatch is an opaque 401 from the token endpoint. Same shape as the
|
# mismatch is an opaque 401 from the token endpoint. Same shape as the
|
||||||
# queue's own client declaration.
|
# queue's own client declaration.
|
||||||
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf autheliaCfg.enable [
|
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf deployCfg.authelia [
|
||||||
{
|
{
|
||||||
id = queueClientId;
|
id = queueClientId;
|
||||||
description = "HyperHive swarm controller";
|
description = "HyperHive swarm controller";
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ let
|
||||||
# The all-local case: this host runs BOTH Grafana and the swarm's authelia,
|
# The all-local case: this host runs BOTH Grafana and the swarm's authelia,
|
||||||
# so the minted secret can be moved without an operator. Same split the
|
# so the minted secret can be moved without an operator. Same split the
|
||||||
# forge and matrix modules document.
|
# forge and matrix modules document.
|
||||||
ssoLocal = deployCfg.grafana && autheliaCfg.enable;
|
ssoLocal = deployCfg.grafana && deployCfg.authelia;
|
||||||
autheliaUrl = toString autheliaCfg.url;
|
autheliaUrl = toString autheliaCfg.url;
|
||||||
|
|
||||||
# Where the plaintext lands inside the container. Under /var/lib rather
|
# Where the plaintext lands inside the container. Under /var/lib rather
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
let
|
let
|
||||||
cfg = config.services.hyperhive.swarm.nats;
|
cfg = config.services.hyperhive.swarm.nats;
|
||||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||||
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
autheliaUrl = autheliaCfg.url;
|
autheliaUrl = autheliaCfg.url;
|
||||||
networkCfg = config.services.hyperhive.network;
|
networkCfg = config.services.hyperhive.network;
|
||||||
# Read even when the controller runs on a different host: what is needed
|
# Read even when the controller runs on a different host: what is needed
|
||||||
|
|
@ -514,7 +515,7 @@ in
|
||||||
# authorization grant 'client_credentials'`. Introspection — which
|
# authorization grant 'client_credentials'`. Introspection — which
|
||||||
# is all the responder needs today — worked throughout, which is why
|
# is all the responder needs today — worked throughout, which is why
|
||||||
# nothing was visibly broken while the comment was untrue.
|
# nothing was visibly broken while the comment was untrue.
|
||||||
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf autheliaCfg.enable [
|
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf deployCfg.authelia [
|
||||||
{
|
{
|
||||||
id = cfg.clientId;
|
id = cfg.clientId;
|
||||||
description = "HyperHive swarm queue";
|
description = "HyperHive swarm queue";
|
||||||
|
|
@ -767,7 +768,7 @@ in
|
||||||
# being up says nothing about whether its in-container secrets unit
|
# being up says nothing about whether its in-container secrets unit
|
||||||
# has finished. The wait in the script is what actually closes it;
|
# has finished. The wait in the script is what actually closes it;
|
||||||
# this only stops us spinning for the full timeout on every boot.
|
# this only stops us spinning for the full timeout on every boot.
|
||||||
++ lib.optional autheliaCfg.enable "container@${autheliaCfg.machine}.service";
|
++ lib.optional deployCfg.authelia "container@${autheliaCfg.machine}.service";
|
||||||
requires = lib.optional cfg.autoGenerateCallout "swarm-nats-callout-keys.service";
|
requires = lib.optional cfg.autoGenerateCallout "swarm-nats-callout-keys.service";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
|
|
|
||||||
|
|
@ -517,7 +517,7 @@ in
|
||||||
# declaration would break every hive that runs a collector and
|
# declaration would break every hive that runs a collector and
|
||||||
# publishes nothing.
|
# publishes nothing.
|
||||||
services.hyperhive.swarm.authelia.oidc.clients =
|
services.hyperhive.swarm.authelia.oidc.clients =
|
||||||
lib.mkIf (autheliaCfg.enable && cfg.publishedScrapeTargets != { })
|
lib.mkIf (deployCfg.authelia && cfg.publishedScrapeTargets != { })
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id = cfg.clientId;
|
id = cfg.clientId;
|
||||||
|
|
@ -555,7 +555,7 @@ in
|
||||||
# after it. On a fresh swarm that is a permanent stall presenting as
|
# after it. On a fresh swarm that is a permanent stall presenting as
|
||||||
# "metrics are broken", several layers from its cause.
|
# "metrics are broken", several layers from its cause.
|
||||||
systemd.services.swarm-otel-oidc-secret =
|
systemd.services.swarm-otel-oidc-secret =
|
||||||
lib.mkIf (autheliaCfg.enable && cfg.publishedScrapeTargets != { })
|
lib.mkIf (deployCfg.authelia && cfg.publishedScrapeTargets != { })
|
||||||
{
|
{
|
||||||
description = "deliver the swarm collector's OIDC client secret from authelia";
|
description = "deliver the swarm collector's OIDC client secret from authelia";
|
||||||
after = [ "container@${autheliaCfg.machine}.service" ];
|
after = [ "container@${autheliaCfg.machine}.service" ];
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,13 @@ in
|
||||||
description = ''
|
description = ''
|
||||||
Host the swarm's shared services on this hive. The services that
|
Host the swarm's shared services on this hive. The services that
|
||||||
exist once per swarm rather than once per hive and are *optional*
|
exist once per swarm rather than once per hive and are *optional*
|
||||||
— the matrix homeserver, the SSO provider — have their `enable`
|
— the matrix homeserver, the SSO provider, the queue, the metrics
|
||||||
asserted from this, so a swarm's service host is declared in one
|
and log stores — have their toggle asserted from this, so a
|
||||||
place.
|
swarm's service host is declared in one place.
|
||||||
|
|
||||||
|
Those toggles live in two namespaces and the split is deliberate:
|
||||||
|
{option}`services.hyperhive.deploy.*` for "does THIS host run it",
|
||||||
|
`swarm.*.enable` for the ones not yet moved. See ./deploy.nix.
|
||||||
|
|
||||||
The forge is swarm-wide too but has nothing to assert: it is the
|
The forge is swarm-wide too but has nothing to assert: it is the
|
||||||
canonical store for the meta flake and every agent's config repo,
|
canonical store for the meta flake and every agent's config repo,
|
||||||
|
|
@ -46,7 +50,6 @@ in
|
||||||
# operator who hasn't spoken, yields to one who has.
|
# operator who hasn't spoken, yields to one who has.
|
||||||
config.services.hyperhive.swarm = {
|
config.services.hyperhive.swarm = {
|
||||||
matrix.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
matrix.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||||
authelia.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
|
||||||
# The queue. Added later than the two above and missed at the time —
|
# The queue. Added later than the two above and missed at the time —
|
||||||
# this file predates the `swarm-nats` container by nine days and had
|
# this file predates the `swarm-nats` container by nine days and had
|
||||||
# not been revisited since, so its absence was sequence rather than
|
# not been revisited since, so its absence was sequence rather than
|
||||||
|
|
@ -75,16 +78,26 @@ in
|
||||||
# this is ./otel.nix's existing per-hive option).
|
# this is ./otel.nix's existing per-hive option).
|
||||||
config.services.hyperhive.otel.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
config.services.hyperhive.otel.enable = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||||
|
|
||||||
|
# The rest of the shared services, deriving from the same switch as the
|
||||||
|
# `swarm.*` ones above. They read differently only because "does THIS
|
||||||
|
# host run it" lives in `deploy.*` (./deploy.nix) — `swarm.*` has to be
|
||||||
|
# identical on every host, and these are exactly the values that must
|
||||||
|
# differ.
|
||||||
|
#
|
||||||
|
# authelia: a swarm has one SSO provider, and this says it lives here.
|
||||||
|
# With it off the hive is a *client* — `swarm.authelia.url` still points
|
||||||
|
# at whoever runs it.
|
||||||
|
config.services.hyperhive.deploy.authelia = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||||
|
|
||||||
# The metrics pair, deriving together on purpose: a store with no UI is
|
# 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
|
# unreadable and a UI with no store is empty, so there is no sensible
|
||||||
# deployment that takes one and not the other from this switch. An
|
# deployment that takes one and not the other from this switch. An
|
||||||
# operator who wants exactly one still sets it directly, which
|
# operator who wants exactly one still sets it directly, which
|
||||||
# `mkDefault` allows.
|
# `mkDefault` allows.
|
||||||
#
|
|
||||||
# The log store derives from the same switch for the same reason as the
|
|
||||||
# rest: a hive that is not the service host is a *client* of it, not a
|
|
||||||
# second one.
|
|
||||||
config.services.hyperhive.deploy.victoriametrics = lib.mkDefault swarmCfg.enableRequiredServices;
|
config.services.hyperhive.deploy.victoriametrics = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||||
config.services.hyperhive.deploy.victorialogs = lib.mkDefault swarmCfg.enableRequiredServices;
|
|
||||||
config.services.hyperhive.deploy.grafana = lib.mkDefault swarmCfg.enableRequiredServices;
|
config.services.hyperhive.deploy.grafana = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||||
|
|
||||||
|
# The log store, from the same switch for the same reason as the rest: a
|
||||||
|
# hive that is not the service host is a *client* of it, not a second one.
|
||||||
|
config.services.hyperhive.deploy.victorialogs = lib.mkDefault swarmCfg.enableRequiredServices;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ let
|
||||||
# unrepresentable rather than merely detected: a default set is all or
|
# unrepresentable rather than merely detected: a default set is all or
|
||||||
# nothing, and the assertion is then only ever about what an operator
|
# nothing, and the assertion is then only ever about what an operator
|
||||||
# typed.
|
# typed.
|
||||||
queueLocal = swarmCfg.nats.enable && swarmCfg.authelia.enable && cfg.hiveName != null;
|
queueLocal = swarmCfg.nats.enable && deployCfg.authelia && cfg.hiveName != null;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.hyperhive.swarm.hives = lib.mkOption {
|
options.services.hyperhive.swarm.hives = lib.mkOption {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue