swarm: move the swarm UI's package to deploy, emptying swarm.ui

Same rule as the four movers before it. What makes this one different is
that `package` was the LAST option under `services.hyperhive.swarm.ui` —
`enable` moved in an earlier slice — so the namespace now holds nothing
and survives only as its two rename entries.

Two consequences worth naming:

flake.nix set `swarm.ui.package` directly. Left alone the shim would
have kept it working, which is exactly the trap: this repo's own flake
would then be setting the option through its own deprecation shim and
warning on every eval. It is repointed here. Four sibling assignments in
that file name movers this commit does not touch; they move with theirs.

The `cfg` alias is deleted. With the one code reader repointed it bound
nothing but an empty attrset — which still evaluates, so nothing would
have failed; it would just have sat there implying `swarm.ui` still held
something. Three comments naming `cfg.package` are repointed with it.

`uiOldPath` sets both old paths, so dropping either rename fails the
eval. Its case reads the vhost's rendered `root` rather than the option,
because that is the half a resolving-but-unwired shim would break.

Not touched: swarm-ui.nix's description says its default is wired "the
same way `swarm.controller.package` is", which is true until the
controller moves. It belongs to that commit, not this one.

Refs #3772.
This commit is contained in:
atlas 2026-09-07 19:29:04 +02:00
commit 190763b3a2
4 changed files with 62 additions and 22 deletions

View file

@ -148,7 +148,7 @@
services.hyperhive.swarm.controller.swarmctlPackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl;
services.hyperhive.swarm.ui.package =
services.hyperhive.deploy.swarm-ui.package =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-ui;
services.hyperhive.swarm.nats.authPackage =

View file

@ -362,6 +362,13 @@ in
[ "services" "hyperhive" "swarm" "victoriametrics" "package" ]
[ "services" "hyperhive" "deploy" "victoriametrics" "package" ]
)
# With this one `services.hyperhive.swarm.ui` holds nothing at all — it had
# exactly two options and both have moved, so the namespace now exists only
# as this entry and the `enable` one above it.
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "ui" "package" ]
[ "services" "hyperhive" "deploy" "swarm-ui" "package" ]
)
];
# ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the

View file

@ -13,7 +13,6 @@
...
}:
let
cfg = config.services.hyperhive.swarm.ui;
deployCfg = config.services.hyperhive.deploy;
gatewayCfg = config.services.hyperhive.gateway;
autheliaCfg = config.services.hyperhive.swarm.authelia;
@ -22,11 +21,11 @@ let
# this UI was left out of that overlay entirely (an operator with a
# stylix-themed host saw the dashboard in their own colours but this
# UI still on the default Catppuccin palette), because nothing here
# ever served a themed `colors.css` in place of `cfg.package`'s own.
# ever served a themed `colors.css` in place of `deployCfg.swarm-ui.package`'s own.
# Detection + CSS-generation is shared (`./stylix-theme.nix`).
#
# No package-copy derivation: this vhost has exactly one location
# serving `cfg.package` (unlike hive-c0re's `servedFrontend`, which
# serving `deployCfg.swarm-ui.package` (unlike hive-c0re's `servedFrontend`, which
# backs both the dashboard root AND every per-agent gateway route, so
# a single swapped tree covers both) — an `= /static/colors.css`
# exact-match location overriding just that one file, same idiom
@ -79,22 +78,23 @@ in
'')
];
options.services.hyperhive.swarm.ui = {
package = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
description = ''
Static build of the swarm UI. nginx serves this store path
directly there is no server-side component beyond the
controller's own API.
# ⚠️ `services.hyperhive.swarm.ui` no longer exists as a namespace. `enable`
# moved in an earlier slice and `package` in this one, and those were its
# only two options — so both paths survive purely as renames in ./deploy.nix.
options.services.hyperhive.deploy.swarm-ui.package = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
description = ''
Static build of the swarm UI. nginx serves this store path
directly there is no server-side component beyond the
controller's own API.
Wired by default from this flake's own package set (see
`flake.nix`), the same way `swarm.controller.package` is. There
is deliberately **no overlay** in this project, so a
`pkgs.swarm-ui` default here would name an attribute that does
not exist on any real deployment.
'';
};
Wired by default from this flake's own package set (see
`flake.nix`), the same way `swarm.controller.package` is. There
is deliberately **no overlay** in this project, so a
`pkgs.swarm-ui` default here would name an attribute that does
not exist on any real deployment.
'';
};
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-ui.enable) {
@ -178,7 +178,7 @@ in
extraConfig = gatewayCfg.lib.securityHeaders;
locations = {
"/" = {
root = "${cfg.package}";
root = "${deployCfg.swarm-ui.package}";
extraConfig = ''
${swarmAuthRequest}
# SPA: any path the bundle routes client-side is served the
@ -189,7 +189,7 @@ in
}
// lib.optionalAttrs (stylixThemeColors != null) {
# Stylix theming (see the `let` block above): overrides just
# this one file from `cfg.package`'s own static root, rather
# this one file from `deployCfg.swarm-ui.package`'s own static root, rather
# than copying the whole tree to change one file inside it —
# nginx resolves the more specific `=` exact match over the
# `/` prefix root above, so this simply doesn't exist (falling

View file

@ -16,7 +16,7 @@
#
# ⚠️ **This evaluates, it does not execute** — a command line, a request or
# a certificate needs something that *runs* it. A case needing a rendered
# file must stub what it drags in (`swarm.ui.package = pkgs.emptyDirectory`).
# file must stub what it drags in (`deploy.swarm-ui.package = pkgs.emptyDirectory`).
{
pkgs,
lib,
@ -154,6 +154,14 @@ let
swarm.controller.queue.clientSecretFile = "/run/secrets/ctrl-queue.secret";
};
# The swarm UI, whose namespace this slice empties: `swarm.ui` had exactly
# two options and both have moved, so BOTH old paths are set here and the
# whole namespace now lives or dies by its two rename entries.
uiOldPath = hive {
swarm.ui.enable = true;
swarm.ui.package = pkgs.emptyDirectory;
};
# The two stores, which had NO old-path fixture at all until this slice —
# their `enable` and `retentionPeriod` shims have been uncovered since they
# landed, which is precisely the "a missing shim reads as a clean tree"
@ -490,6 +498,31 @@ let
# host-side half depends on: no store here, so no bind mount and no unit.
# Without it a hive that merely names a token would drag the store's
# container config into its evaluation.
# Reads the vhost's rendered `root`, not the option: the UI is served
# straight out of a store path, so a shim that resolves but stops
# reaching the module would leave nginx pointing at the default build.
name = "a config written against the pre-rename swarm-ui paths still serves the operator's build";
#
# ⚠️ `unsafeDiscardStringContext` on both sides, load-bearing rather than
# tidy: interpolating a derivation carries string *context*, and this
# suite renders its results into a `buildCommand` that may not reference
# store paths. Comparing the paths as plain text is the intent — the case
# asks "does nginx point HERE", not "depend on what it points at".
ok =
let
want = builtins.unsafeDiscardStringContext "${pkgs.emptyDirectory}";
# `or ""` is NOT enough: nginx's `locations.<l>.root` is `nullOr`, so
# a vhost that HAS the attribute set to null skips the default and
# reaches the coercion. Filter nulls, then discard context.
roots = map builtins.unsafeDiscardStringContext (
lib.filter (r: r != null) (
map (v: v.locations."/".root or null) (builtins.attrValues uiOldPath.services.nginx.virtualHosts)
)
);
in
builtins.elem want roots;
}
{
# Reads the package the CONTAINER renders, not the option: a shim that
# resolves but stops reaching the module would leave the store running
# nixpkgs' default while the operator's override read back fine.