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 =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth;
services.hyperhive.swarm.authelia.bridgePackage =
services.hyperhive.deploy.authelia.bridgePackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge;
services.hyperhive.gateway.swaggerUiTheme =

View file

@ -373,6 +373,14 @@ in
[ "services" "hyperhive" "swarm" "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

View file

@ -320,22 +320,12 @@ let
'';
in
{
# `enable` moved to `services.hyperhive.deploy.authelia.enable` — 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.
# `enable` and both packages moved to `services.hyperhive.deploy.authelia`
# — see ./deploy.nix. Whether this host runs the swarm's SSO provider, and
# which build it runs, are deployment decisions; 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 = {
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 {
type = lib.types.port;
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 {
type = lib.types.port;
default = 9092;
@ -750,16 +729,37 @@ in
# What stays above is what authelia IS to every hive: where it answers
# (`url`), the OIDC register every service checks itself against, its port.
# What the host running the container decides is here, and all three of
# these are the same kind of thing — a filesystem path that only exists on
# the machine that runs `swarm-authelia`. A hive that does not run it has
# nothing at any of them. `enable` already lives in ./deploy.nix, which
# also carries the renames.
# What the host running the container decides is here — which two builds it
# runs, and three filesystem paths that only exist on the machine running
# `swarm-authelia`. A hive that does not run it has nothing at any of those
# paths. `enable` already lives in ./deploy.nix, which also carries the
# renames.
#
# ⚠️ `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
# filesystem is this host's container root.
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 {
type = lib.types.str;
default = "${stateDir}/users.yml";
@ -1111,7 +1111,7 @@ in
# password for the users file. Without it the container runs
# authelia and cannot invoke it: the unit's ExecStart resolves
# 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
# firewall.service would rewrite the HOST ruleset at every
@ -1140,16 +1140,16 @@ in
wantedBy = [ "multi-user.target" ];
before = [ "${unitName}.service" ];
requiredBy = [ "${unitName}.service" ];
# `cfg.package` is here for its CLI, not its daemon: the
# client secrets are minted with `authelia crypto hash
# generate`, which is the only way to produce a digest in
# `deployCfg.authelia.package` is here for its CLI, not its
# daemon: the client secrets are minted with `authelia crypto
# hash generate`, which is the only way to produce a digest in
# the exact form authelia will later verify.
path = [
pkgs.coreutils
]
++ lib.optionals oidcEnabled [
pkgs.openssl
cfg.package
deployCfg.authelia.package
];
serviceConfig = {
Type = "oneshot";
@ -1216,7 +1216,7 @@ in
"${unitName}.service"
];
serviceConfig = {
ExecStart = "${cfg.bridgePackage}/bin/swarm-authelia-bridge";
ExecStart = "${deployCfg.authelia.bridgePackage}/bin/swarm-authelia-bridge";
User = unitName;
Group = unitName;
Restart = "on-failure";
@ -1235,7 +1235,7 @@ in
# argon2 parameters baked into a hash have to match the
# verifier's — same reasoning as `swarmctl`'s own
# `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
# literal encodes "authelia is in my netns" at the call site,
# and authelia's OIDC endpoints are https-only in effect —
@ -1255,7 +1255,7 @@ in
services.authelia.instances.${instance} = {
enable = true;
package = cfg.package;
package = deployCfg.authelia.package;
# Merged at RUNTIME alongside the nix-generated config, which
# is the whole reason the client digests can exist at all:

View file

@ -28,7 +28,7 @@ let
autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable {
# The CONFIGURED authelia, not whatever is on PATH: the argon2
# 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;
};

View file

@ -124,9 +124,11 @@ let
swarm.nats.authPackage = pkgs.emptyDirectory;
};
# Seventh split slice. Only `usersFile` has a rename entry: the other two
# movers are `readOnly`, and a rename module contributes a definition, which
# a read-only option refuses — see ./host-modules/deploy.nix. So the two arms
# Seventh split slice, plus slice 10's two authelia packages. Of slice 7's
# movers only `usersFile` has a rename entry — the other two are `readOnly`,
# 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
# that a reader repointed to the new namespace still renders the derived
# path, which is the failure this slice could actually have shipped — seven
@ -135,6 +137,8 @@ let
deploy.authelia.enable = true;
deploy.nats.enable = true;
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.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";