swarm: move both authelia packages to deploy

`package` (which authelia build runs in the container) and
`bridgePackage` (which `swarm-authelia-bridge` build writes the users
file) are both host decisions, so they join the `deploy.authelia` block
that already holds `enable`, `usersFile` and the two host-side paths.

Six readers, and one of them is why this commit touches a second module:
`swarm-controller.nix` reads `autheliaCfg.package` for
`SWARMCTL_AUTHELIA_BIN`. A per-module sweep never sees that — it is a
cross-reference between two movers, and whichever landed second would
have inherited a dangling alias. The alias itself stays: it still has
three live uses for `url` and `bridgeUrl`.

Two comments needed real edits rather than a rename:

- The `deploy.authelia` header said "all three of these are the same
  kind of thing — a filesystem path". It is five options now and two
  are packages, so the sentence distinguishes the builds from the
  paths, and keeps "a hive that does not run it has nothing at any of
  those paths" scoped to the paths it is true of.
- `module-eval.nix` said "Only `usersFile` has a rename entry",
  explaining that authelia's other slice-7 movers are `readOnly` and a
  rename module cannot contribute a definition to those. That reasoning
  is worth keeping, but the claim stopped being true one line above the
  two entries this commit adds.

The fixture gains both old paths; no new case. `autheliaOldPath`'s
existing arms already fail the eval if either shim goes missing.
This commit is contained in:
atlas 2026-09-07 20:20:41 +02:00
commit 889a46729b
5 changed files with 56 additions and 44 deletions

View file

@ -154,7 +154,7 @@
services.hyperhive.deploy.nats.authPackage = services.hyperhive.deploy.nats.authPackage =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth; self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth;
services.hyperhive.swarm.authelia.bridgePackage = services.hyperhive.deploy.authelia.bridgePackage =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge; self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge;
services.hyperhive.gateway.swaggerUiTheme = services.hyperhive.gateway.swaggerUiTheme =

View file

@ -373,6 +373,14 @@ in
[ "services" "hyperhive" "swarm" "nats" "authPackage" ] [ "services" "hyperhive" "swarm" "nats" "authPackage" ]
[ "services" "hyperhive" "deploy" "nats" "authPackage" ] [ "services" "hyperhive" "deploy" "nats" "authPackage" ]
) )
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "authelia" "package" ]
[ "services" "hyperhive" "deploy" "authelia" "package" ]
)
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "authelia" "bridgePackage" ]
[ "services" "hyperhive" "deploy" "authelia" "bridgePackage" ]
)
]; ];
# ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the

View file

@ -320,22 +320,12 @@ let
''; '';
in in
{ {
# `enable` moved to `services.hyperhive.deploy.authelia.enable` — see # `enable` and both packages moved to `services.hyperhive.deploy.authelia`
# ./deploy.nix. Whether this host runs the swarm's SSO provider is a # — see ./deploy.nix. Whether this host runs the swarm's SSO provider, and
# deployment decision; what stays here is what authelia IS, including # which build it runs, are deployment decisions; what stays here is what
# `url` and the OIDC client registry every hive needs as a *client* # authelia IS, including `url` and the OIDC client registry every hive needs
# whether or not it runs the container. # as a *client* whether or not it runs the container.
options.services.hyperhive.swarm.authelia = { options.services.hyperhive.swarm.authelia = {
package = lib.mkOption {
type = lib.types.package;
default = pkgs.authelia;
defaultText = lib.literalExpression "pkgs.authelia";
description = ''
authelia package to run in the container. Defaults to
nixpkgs's; override to pin a specific upstream.
'';
};
port = lib.mkOption { port = lib.mkOption {
type = lib.types.port; type = lib.types.port;
default = 9091; default = 9091;
@ -699,17 +689,6 @@ in
''; '';
}; };
bridgePackage = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-authelia-bridge";
description = ''
`swarm-authelia-bridge` package the only process allowed to
write `usersFile`. Wired by default from this flake's own
package set (see `flake.nix`); override to run a different
build.
'';
};
bridgePort = lib.mkOption { bridgePort = lib.mkOption {
type = lib.types.port; type = lib.types.port;
default = 9092; default = 9092;
@ -750,16 +729,37 @@ in
# What stays above is what authelia IS to every hive: where it answers # What stays above is what authelia IS to every hive: where it answers
# (`url`), the OIDC register every service checks itself against, its port. # (`url`), the OIDC register every service checks itself against, its port.
# What the host running the container decides is here, and all three of # What the host running the container decides is here — which two builds it
# these are the same kind of thing — a filesystem path that only exists on # runs, and three filesystem paths that only exist on the machine running
# the machine that runs `swarm-authelia`. A hive that does not run it has # `swarm-authelia`. A hive that does not run it has nothing at any of those
# nothing at any of them. `enable` already lives in ./deploy.nix, which # paths. `enable` already lives in ./deploy.nix, which also carries the
# also carries the renames. # renames.
# #
# ⚠️ `usersFile` is a path *inside* the container and still belongs here: # ⚠️ `usersFile` is a path *inside* the container and still belongs here:
# a path's scope is the scope of the filesystem it names, and that # a path's scope is the scope of the filesystem it names, and that
# filesystem is this host's container root. # filesystem is this host's container root.
options.services.hyperhive.deploy.authelia = { options.services.hyperhive.deploy.authelia = {
package = lib.mkOption {
type = lib.types.package;
default = pkgs.authelia;
defaultText = lib.literalExpression "pkgs.authelia";
description = ''
authelia package to run in the container. Defaults to
nixpkgs's; override to pin a specific upstream.
'';
};
bridgePackage = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-authelia-bridge";
description = ''
`swarm-authelia-bridge` package the only process allowed to
write `usersFile`. Wired by default from this flake's own
package set (see `flake.nix`); override to run a different
build.
'';
};
usersFile = lib.mkOption { usersFile = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "${stateDir}/users.yml"; default = "${stateDir}/users.yml";
@ -1111,7 +1111,7 @@ in
# password for the users file. Without it the container runs # password for the users file. Without it the container runs
# authelia and cannot invoke it: the unit's ExecStart resolves # authelia and cannot invoke it: the unit's ExecStart resolves
# through the store path, and nothing puts the CLI on PATH. # through the store path, and nothing puts the CLI on PATH.
environment.systemPackages = [ cfg.package ]; environment.systemPackages = [ deployCfg.authelia.package ];
# This container shares the host netns, so its own # This container shares the host netns, so its own
# firewall.service would rewrite the HOST ruleset at every # firewall.service would rewrite the HOST ruleset at every
@ -1140,16 +1140,16 @@ in
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
before = [ "${unitName}.service" ]; before = [ "${unitName}.service" ];
requiredBy = [ "${unitName}.service" ]; requiredBy = [ "${unitName}.service" ];
# `cfg.package` is here for its CLI, not its daemon: the # `deployCfg.authelia.package` is here for its CLI, not its
# client secrets are minted with `authelia crypto hash # daemon: the client secrets are minted with `authelia crypto
# generate`, which is the only way to produce a digest in # hash generate`, which is the only way to produce a digest in
# the exact form authelia will later verify. # the exact form authelia will later verify.
path = [ path = [
pkgs.coreutils pkgs.coreutils
] ]
++ lib.optionals oidcEnabled [ ++ lib.optionals oidcEnabled [
pkgs.openssl pkgs.openssl
cfg.package deployCfg.authelia.package
]; ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
@ -1216,7 +1216,7 @@ in
"${unitName}.service" "${unitName}.service"
]; ];
serviceConfig = { serviceConfig = {
ExecStart = "${cfg.bridgePackage}/bin/swarm-authelia-bridge"; ExecStart = "${deployCfg.authelia.bridgePackage}/bin/swarm-authelia-bridge";
User = unitName; User = unitName;
Group = unitName; Group = unitName;
Restart = "on-failure"; Restart = "on-failure";
@ -1235,7 +1235,7 @@ in
# argon2 parameters baked into a hash have to match the # argon2 parameters baked into a hash have to match the
# verifier's — same reasoning as `swarmctl`'s own # verifier's — same reasoning as `swarmctl`'s own
# `SWARMCTL_AUTHELIA_BIN`. # `SWARMCTL_AUTHELIA_BIN`.
SWARM_AUTHELIA_BRIDGE_AUTHELIA_BIN = "${cfg.package}/bin/authelia"; SWARM_AUTHELIA_BRIDGE_AUTHELIA_BIN = "${deployCfg.authelia.package}/bin/authelia";
# By name through the gateway, not loopback. A loopback # By name through the gateway, not loopback. A loopback
# literal encodes "authelia is in my netns" at the call site, # literal encodes "authelia is in my netns" at the call site,
# and authelia's OIDC endpoints are https-only in effect — # and authelia's OIDC endpoints are https-only in effect —
@ -1255,7 +1255,7 @@ in
services.authelia.instances.${instance} = { services.authelia.instances.${instance} = {
enable = true; enable = true;
package = cfg.package; package = deployCfg.authelia.package;
# Merged at RUNTIME alongside the nix-generated config, which # Merged at RUNTIME alongside the nix-generated config, which
# is the whole reason the client digests can exist at all: # is the whole reason the client digests can exist at all:

View file

@ -28,7 +28,7 @@ let
autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable { autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable {
# 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 = "${deployCfg.authelia.package}/bin/authelia";
SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile; SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile;
}; };

View file

@ -124,9 +124,11 @@ let
swarm.nats.authPackage = pkgs.emptyDirectory; swarm.nats.authPackage = pkgs.emptyDirectory;
}; };
# Seventh split slice. Only `usersFile` has a rename entry: the other two # Seventh split slice, plus slice 10's two authelia packages. Of slice 7's
# movers are `readOnly`, and a rename module contributes a definition, which # movers only `usersFile` has a rename entry — the other two are `readOnly`,
# a read-only option refuses — see ./host-modules/deploy.nix. So the two arms # and a rename module contributes a definition, which a read-only option
# refuses; see ./host-modules/deploy.nix. `package` and `bridgePackage` are
# ordinary options, so they do carry one. The two arms
# below have different jobs. `usersFile` tests the rename; the nats one tests # below have different jobs. `usersFile` tests the rename; the nats one tests
# that a reader repointed to the new namespace still renders the derived # that a reader repointed to the new namespace still renders the derived
# path, which is the failure this slice could actually have shipped — seven # path, which is the failure this slice could actually have shipped — seven
@ -135,6 +137,8 @@ let
deploy.authelia.enable = true; deploy.authelia.enable = true;
deploy.nats.enable = true; deploy.nats.enable = true;
swarm.authelia.usersFile = "/var/lib/test-authelia/users.yml"; swarm.authelia.usersFile = "/var/lib/test-authelia/users.yml";
swarm.authelia.package = pkgs.emptyDirectory;
swarm.authelia.bridgePackage = pkgs.emptyDirectory;
swarm.nats.autoGenerateCallout = false; swarm.nats.autoGenerateCallout = false;
swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";