From d6f23ff371af7457c2cd28166478c16f06a3179c Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 18:35:34 +0200 Subject: [PATCH 01/10] swarm: move the matrix packages to deploy, where their enable already lives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Which build a service runs is a decision of the host that runs it, so `*.package` belongs with `enable` rather than in the namespace every hive must agree on byte for byte. The `gui` pair was the sharpest case and the reason this starts here: `gui.enable` moved to `deploy.*` in an earlier slice while `gui.package` stayed behind, so one web client's two halves sat in two namespaces — and hive-matrix.nix said so, in a comment directly above the option. The diff removes a stated inconsistency rather than introducing a rule. Both old paths keep working through `mkRenamedOptionModule`; both are settable (neither is `readOnly`), so both get a shim. The `matrixOldPath` fixture in module-eval.nix now sets eight options through their pre-rename paths instead of six, so dropping either new shim fails the eval rather than silently dropping a definition. Prose swept in the same commit: three comments claimed the package was swarm-wide or named the pre-rename path, and docs/web-ui/dashboard.md named `swarm.matrix.gui.package` two lines above `deploy.matrix.gui.enable` — the same asymmetry this commit exists to remove, displayed in the docs. A shim keeps the old spelling valid, so no build, lint or broken-reference search would have found that line. Refs #3772. --- docs/web-ui/dashboard.md | 2 +- nix/host-modules/deploy.nix | 14 ++++++ nix/host-modules/hive-matrix.nix | 80 ++++++++++++++++---------------- nix/module-eval.nix | 11 +++-- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 9ee832b3..37b4414f 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -625,7 +625,7 @@ existing in the page DOM. A static matrix web client (default `pkgs.fluffychat-web` rebuilt with `--base-href /matrix/`, swappable via -`services.hyperhive.swarm.matrix.gui.package`) served by the hive-gateway +`services.hyperhive.deploy.matrix.gui.package`) served by the hive-gateway nginx container at `/matrix/` when `services.hyperhive.deploy.matrix.gui.enable` is on (defaults to `matrix.enable`). c0re signals availability via the diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index a4e23af7..2dfe4c15 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -335,6 +335,20 @@ in [ "services" "hyperhive" "swarm" "wireguard" "persistentKeepalive" ] [ "services" "hyperhive" "deploy" "wireguard" "persistentKeepalive" ] ) + + # Which build a service runs is a decision of the host that runs it, so + # every `*.package` follows its `enable` across. The split these replace + # was the sharpest case: `gui.enable` had already moved while + # `gui.package` stayed, so one client's two halves sat in two namespaces + # and ./hive-matrix.nix said so in a comment above the option. + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "matrix" "package" ] + [ "services" "hyperhive" "deploy" "matrix" "package" ] + ) + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "matrix" "gui" "package" ] + [ "services" "hyperhive" "deploy" "matrix" "gui" "package" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index d1964cb8..8d087b0a 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -172,8 +172,9 @@ in [ "services" "hyperhive" "swarm" "matrix" ] ) # "does THIS host run it" is a per-host decision, and `swarm.*` is the - # namespace every host in the swarm agrees on. The rest of the matrix - # settings genuinely are swarm-wide and stay where they are. + # namespace every host in the swarm agrees on. What stays under + # `swarm.matrix` is the homeserver's identity as every hive sees it — + # its server name, its addresses, its federation policy. (lib.mkRenamedOptionModule [ "services" "hyperhive" "swarm" "matrix" "enable" ] [ "services" "hyperhive" "deploy" "matrix" "enable" ] @@ -191,17 +192,6 @@ in ]; options.services.hyperhive.swarm.matrix = { - package = lib.mkOption { - type = lib.types.package; - default = pkgs.matrix-tuwunel; - defaultText = lib.literalExpression "pkgs.matrix-tuwunel"; - description = '' - matrix-tuwunel package to run inside the container. Defaults - to nixpkgs's `pkgs.matrix-tuwunel`. Override to pin a - specific upstream if you need an unreleased feature. - ''; - }; - serverName = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; @@ -338,26 +328,6 @@ in ''; }; - gui = { - # Whether this host serves it is `deploy.matrix.gui.enable`. - package = lib.mkOption { - type = lib.types.package; - default = fluffychat-web-fixed; - defaultText = lib.literalMD '' - `pkgs.fluffychat-web` with a `postInstall` patch that adds - the three files `flutter341.buildFlutterApplication` skips. - ''; - description = '' - Static web client dist served at `gatewayHost`'s vhost - (`chat.` by default). Override - to swap fluffychat for hydrogen-web, cinny, element-web, or - an out-of-tree dist — any replacement is mounted at the - sub-domain root with the upstream-default ``, - no sub-path gymnastics needed. - ''; - }; - }; - # This homeserver always delegates login to the swarm's authelia, as # an OIDC relying party — matrix SSO (`m.login.sso`), offered # alongside password login. No toggle: a homeserver in a swarm is a @@ -398,13 +368,24 @@ in }; # What stays above is what the homeserver IS from any hive's point of view: - # its package, the name it answers to, the ports and URLs it is reached on, - # and the client id it is registered under. What lives here is what the host - # running it decides — whether it is exposed, which peers it trusts, how large - # a request it accepts, and where its host-local secrets sit. Same rule as + # the name it answers to, the ports and URLs it is reached on, and the client + # id it is registered under. What lives here is what the host running it + # decides — which build it runs, whether it is exposed, which peers it trusts, + # how large a request it accepts, and where its host-local secrets sit. Same rule as # ./swarm-victorialogs.nix; `enable` already lives in ./deploy.nix, which also # carries the renames. options.services.hyperhive.deploy.matrix = { + package = lib.mkOption { + type = lib.types.package; + default = pkgs.matrix-tuwunel; + defaultText = lib.literalExpression "pkgs.matrix-tuwunel"; + description = '' + matrix-tuwunel package to run inside the container. Defaults + to nixpkgs's `pkgs.matrix-tuwunel`. Override to pin a + specific upstream if you need an unreleased feature. + ''; + }; + openFirewall = lib.mkOption { type = lib.types.bool; default = false; @@ -509,11 +490,28 @@ in off, the dashboard's `M4TR1X →` tab is hidden. See `docs/networking/gateway.md` for the discovery flow that lets clients auto-find the sub-domain. The client build itself is - `swarm.matrix.gui.package` — which client, as opposed to whether + `deploy.matrix.gui.package` — which client, as opposed to whether this host serves it. ''; }; + gui.package = lib.mkOption { + type = lib.types.package; + default = fluffychat-web-fixed; + defaultText = lib.literalMD '' + `pkgs.fluffychat-web` with a `postInstall` patch that adds + the three files `flutter341.buildFlutterApplication` skips. + ''; + description = '' + Static web client dist served at `gatewayHost`'s vhost + (`chat.` by default). Override + to swap fluffychat for hydrogen-web, cinny, element-web, or + an out-of-tree dist — any replacement is mounted at the + sub-domain root with the upstream-default ``, + no sub-path gymnastics needed. + ''; + }; + sso.clientSecretFile = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; @@ -634,7 +632,7 @@ in # fluffychat at sub-domain root, SPA-fallback via the # Accept-header `$matrix_spa_target` map above. "/" = { - alias = "${cfg.gui.package}/"; + alias = "${deployCfg.matrix.gui.package}/"; extraConfig = '' try_files $uri $uri/ $matrix_spa_target =404; ''; @@ -999,7 +997,7 @@ in services.matrix-tuwunel = { enable = true; - package = cfg.package; + package = deployCfg.matrix.package; settings.global = { server_name = effectiveServerName; # `address` + `port` are upstream `listOf` — wrap singles. @@ -1084,7 +1082,7 @@ in "oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}" ]; - environment.systemPackages = [ cfg.package ]; + environment.systemPackages = [ deployCfg.matrix.package ]; }; }; diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 610ae2b8..109cc52f 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -89,11 +89,12 @@ let ]; }; - # The homeserver's turn to split. All six host-side options are set through + # The homeserver's turn to split. All eight host-side options are set through # their pre-rename paths — including `gui.enable`, whose value is deliberately - # the opposite of its default so the definition has to actually land. All six - # so that dropping any single shim entry fails the eval, not just the two the - # assertion reads. + # the opposite of its default so the definition has to actually land, and both + # packages, stubbed to a derivation neither option defaults to for the same + # reason. All eight so that dropping any single shim entry fails the eval, not + # just the ones the assertions read. matrixOldPath = hive { deploy.matrix.enable = true; swarm.matrix.openFirewall = true; @@ -101,6 +102,8 @@ let swarm.matrix.maxRequestSize = 31457280; swarm.matrix.registrationTokenFile = "/etc/matrix/register.token"; swarm.matrix.gui.enable = false; + swarm.matrix.package = pkgs.emptyDirectory; + swarm.matrix.gui.package = pkgs.emptyDirectory; swarm.matrix.sso.clientSecretFile = "/etc/matrix/oidc.secret"; }; From 0002c15dc36e874a8988a44cf35091c44735e6c7 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 18:46:19 +0200 Subject: [PATCH 02/10] swarm: move grafana's package to deploy, beside the rest of its host knobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same rule as the matrix pair: which build a service runs is a decision of the host that runs it, so `package` follows `enable` out of the namespace every hive must agree on byte for byte. Grafana already had a `deploy.grafana` block holding the datasources, the plugin list and the socket directory, so this is the package joining options it belongs with rather than a new namespace. Prose: the swarm-side header listed "its package" among what stays; the deploy-side header listed what the host decides without it. Both edited, which is the fix — a substitution on the first alone would have left the second silently incomplete. `swarm-grafana.nix:219` was on the sweep list and is NOT edited: it says what stays above is what the service is to every hive, and that stays true once the package leaves. The stale half was the sentence after it. Refs #3772. --- nix/host-modules/deploy.nix | 4 ++++ nix/host-modules/swarm-grafana.nix | 36 +++++++++++++++--------------- nix/module-eval.nix | 1 + 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index 2dfe4c15..db784892 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -349,6 +349,10 @@ in [ "services" "hyperhive" "swarm" "matrix" "gui" "package" ] [ "services" "hyperhive" "deploy" "matrix" "gui" "package" ] ) + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "grafana" "package" ] + [ "services" "hyperhive" "deploy" "grafana" "package" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/swarm-grafana.nix b/nix/host-modules/swarm-grafana.nix index 58e7fce0..08ebc799 100644 --- a/nix/host-modules/swarm-grafana.nix +++ b/nix/host-modules/swarm-grafana.nix @@ -111,20 +111,13 @@ 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. - # What stays here is what the service IS to every hive: its package, 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 — - # where its datasources point, which plugins are in its store path, and - # the socket directory it shares with nginx — is below, under - # `deploy.grafana`. + # What stays 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 below, + # under `deploy.grafana`. options.services.hyperhive.swarm.grafana = { - package = lib.mkOption { - type = lib.types.package; - default = pkgs.grafana; - defaultText = lib.literalExpression "pkgs.grafana"; - description = "Grafana package to run."; - }; - machine = lib.mkOption { type = lib.types.str; readOnly = true; @@ -217,15 +210,22 @@ in }; # What stays above is what the service IS to every hive. What the host - # running it decides is here: 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. + # 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 # scope of what it ADDRESSES, not the fact that it is a URL: both stores # bind loopback, so these can only ever mean "on this host". options.services.hyperhive.deploy.grafana = { + package = lib.mkOption { + type = lib.types.package; + default = pkgs.grafana; + defaultText = lib.literalExpression "pkgs.grafana"; + description = "Grafana package to run."; + }; + socketDir = lib.mkOption { type = lib.types.str; default = "/run/swarm-grafana"; @@ -624,7 +624,7 @@ in services.grafana = { enable = true; - package = cfg.package; + package = deployCfg.grafana.package; # Passed through unconditionally, empty default included. # Upstream distinguishes `null` from `[ ]`, and both differences diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 109cc52f..f33e3dbf 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -160,6 +160,7 @@ let swarm.grafana.datasourceUrl = "http://127.0.0.1:19999"; swarm.grafana.logsDatasourceUrl = "http://127.0.0.1:19998"; swarm.grafana.plugins = [ ]; + swarm.grafana.package = pkgs.emptyDirectory; }; # The first slice to leave options on BOTH sides of the split, so the From e091add37a5d6b4b962ab520378016e50e9a1b1b Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 18:53:43 +0200 Subject: [PATCH 03/10] swarm: move both metric stores' package to deploy, and cover their shims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same rule as matrix and grafana: which build a service runs is a decision of the host that runs it. Both stores already had a `deploy.` option for retention, so the package joins something rather than opening a namespace. The prose in both modules claimed the package as part of "what the store IS from any hive's point of view" — a client hive needs the domain and the port to reach a store, never the build it runs. deploy.nix's own comment made the same claim about the pair and is corrected with them. Separately, and the reason this commit adds a fixture rather than a line: NEITHER STORE HAD AN OLD-PATH FIXTURE AT ALL. `swarm.victorialogs.` and `swarm.victoriametrics.` had zero hits in module-eval.nix, so the `enable` shims from the first slice and both `retentionPeriod` shims have been uncovered since they landed — the suite would have gone green with any of them deleted. That is precisely what the wireguard fixture's own comment warns about: a missing shim reads as a clean tree and breaks every existing operator config. `storesOldPath` therefore sets all six old paths, not just the two this commit moves. The case reads the package the CONTAINER renders rather than the option, so a shim that resolves but stops reaching the module fails too. Refs #3772. --- nix/host-modules/deploy.nix | 13 +++++++++-- nix/host-modules/swarm-victorialogs.nix | 23 ++++++++++--------- nix/host-modules/swarm-victoriametrics.nix | 23 ++++++++++--------- nix/module-eval.nix | 26 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index db784892..d8e818b8 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -259,8 +259,9 @@ in # Retention is read only where the container is defined, so it is a # decision of the host running the store rather than something the swarm - # agrees on. The two stores keep everything else — package, domain, port - # — in `swarm.*`, because a client hive needs those to reach them. + # agrees on. The two stores keep their domain and port in `swarm.*`, + # because a client hive needs those to reach them; `package` followed + # `enable` and `retentionPeriod` across in a later slice. (lib.mkRenamedOptionModule [ "services" "hyperhive" "swarm" "victoriametrics" "retentionPeriod" ] [ "services" "hyperhive" "deploy" "victoriametrics" "retentionPeriod" ] @@ -353,6 +354,14 @@ in [ "services" "hyperhive" "swarm" "grafana" "package" ] [ "services" "hyperhive" "deploy" "grafana" "package" ] ) + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "victorialogs" "package" ] + [ "services" "hyperhive" "deploy" "victorialogs" "package" ] + ) + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "victoriametrics" "package" ] + [ "services" "hyperhive" "deploy" "victoriametrics" "package" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/swarm-victorialogs.nix b/nix/host-modules/swarm-victorialogs.nix index 4709e3eb..ddead0d2 100644 --- a/nix/host-modules/swarm-victorialogs.nix +++ b/nix/host-modules/swarm-victorialogs.nix @@ -56,17 +56,11 @@ let ''; in { - # What stays here is what the store IS from any hive's point of view: its - # package, the name it answers on, the port. `enable` and `retentionPeriod` - # are decisions of the host that runs it and live under `deploy.*`. + # What stays here is what the store IS from any hive's point of view: the + # name it answers on and the port. `enable`, `package` and + # `retentionPeriod` are decisions of the host that runs it and live under + # `deploy.*`. options.services.hyperhive.swarm.victorialogs = { - package = lib.mkOption { - type = lib.types.package; - default = pkgs.victorialogs; - defaultText = lib.literalExpression "pkgs.victorialogs"; - description = "VictoriaLogs package to run."; - }; - machine = lib.mkOption { type = lib.types.str; readOnly = true; @@ -116,6 +110,13 @@ in # and a hive that is a *client* of the log store never consults it. That # makes it a `deploy.*` value by the same rule as the seal on the secret # store — options on the auto-deployed service itself. + options.services.hyperhive.deploy.victorialogs.package = lib.mkOption { + type = lib.types.package; + default = pkgs.victorialogs; + defaultText = lib.literalExpression "pkgs.victorialogs"; + description = "VictoriaLogs package to run."; + }; + options.services.hyperhive.deploy.victorialogs.retentionPeriod = lib.mkOption { type = lib.types.str; default = "30d"; @@ -259,7 +260,7 @@ in services.victorialogs = { enable = true; - package = cfg.package; + package = deployCfg.victorialogs.package; # ⚠️ PINNED TO LOOPBACK for the same reason the metrics store is, # and it matters more here: upstream's default listens on every diff --git a/nix/host-modules/swarm-victoriametrics.nix b/nix/host-modules/swarm-victoriametrics.nix index 7e080d97..604e9d3d 100644 --- a/nix/host-modules/swarm-victoriametrics.nix +++ b/nix/host-modules/swarm-victoriametrics.nix @@ -30,17 +30,11 @@ let domainBase = if swarmDomain == null then "invalid" else swarmDomain; in { - # What stays here is what the store IS from any hive's point of view: its - # package, the name it answers on, the port. `enable` and `retentionPeriod` - # are decisions of the host that runs it and live under `deploy.*`. + # What stays here is what the store IS from any hive's point of view: the + # name it answers on and the port. `enable`, `package` and + # `retentionPeriod` are decisions of the host that runs it and live under + # `deploy.*`. options.services.hyperhive.swarm.victoriametrics = { - package = lib.mkOption { - type = lib.types.package; - default = pkgs.victoriametrics; - defaultText = lib.literalExpression "pkgs.victoriametrics"; - description = "VictoriaMetrics package to run."; - }; - machine = lib.mkOption { type = lib.types.str; readOnly = true; @@ -79,6 +73,13 @@ in # Retention is a property of the store this host runs, not something the # swarm has to agree on: it is read only where the container is defined, # and a hive that is a *client* of the metrics store never consults it. + options.services.hyperhive.deploy.victoriametrics.package = lib.mkOption { + type = lib.types.package; + default = pkgs.victoriametrics; + defaultText = lib.literalExpression "pkgs.victoriametrics"; + description = "VictoriaMetrics package to run."; + }; + options.services.hyperhive.deploy.victoriametrics.retentionPeriod = lib.mkOption { type = lib.types.str; default = "5y"; @@ -213,7 +214,7 @@ in services.victoriametrics = { enable = true; - package = cfg.package; + package = deployCfg.victoriametrics.package; retentionPeriod = deployCfg.victoriametrics.retentionPeriod; # ⚠️ PINNED TO LOOPBACK, and this is a correction rather than a diff --git a/nix/module-eval.nix b/nix/module-eval.nix index f33e3dbf..5f098c53 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -154,6 +154,20 @@ let swarm.controller.queue.clientSecretFile = "/run/secrets/ctrl-queue.secret"; }; + # 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" + # failure ./module-eval.nix's wireguard fixture was written to catch. All six + # old paths are set, so dropping any single entry fails the eval. + storesOldPath = hive { + swarm.victoriametrics.enable = true; + swarm.victorialogs.enable = true; + swarm.victoriametrics.retentionPeriod = "3d"; + swarm.victorialogs.retentionPeriod = "5d"; + swarm.victoriametrics.package = pkgs.emptyDirectory; + swarm.victorialogs.package = pkgs.emptyDirectory; + }; + grafanaOldPath = hive { deploy.grafana.enable = true; swarm.grafana.socketDir = "/run/test-grafana-sock"; @@ -476,6 +490,18 @@ 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 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. + name = "a config written against the pre-rename store paths still picks the operator's package"; + ok = + storesOldPath.containers.swarm-victorialogs.config.services.victorialogs.package + == pkgs.emptyDirectory + && + storesOldPath.containers.swarm-victoriametrics.config.services.victoriametrics.package + == pkgs.emptyDirectory; + } + { name = "a bootstrap token on a host that runs no store grants nothing"; ok = !(baoGrantNoStore.systemd.services ? swarm-bao-bootstrap-dir); } From 190763b3a28784783be87ba334c591573b49ad36 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 19:29:04 +0200 Subject: [PATCH 04/10] swarm: move the swarm UI's package to deploy, emptying swarm.ui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- flake.nix | 2 +- nix/host-modules/deploy.nix | 7 ++++++ nix/host-modules/swarm-ui.nix | 40 +++++++++++++++++------------------ nix/module-eval.nix | 35 +++++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/flake.nix b/flake.nix index cefc5a2f..65763031 100644 --- a/flake.nix +++ b/flake.nix @@ -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 = diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index d8e818b8..034c0f28 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -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 diff --git a/nix/host-modules/swarm-ui.nix b/nix/host-modules/swarm-ui.nix index dd389558..c7c7aac1 100644 --- a/nix/host-modules/swarm-ui.nix +++ b/nix/host-modules/swarm-ui.nix @@ -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 diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 5f098c53..80bd56d5 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -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..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. From 0fe0df8d63a22b1e66cf6068f1b00f6f66f29579 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 19:43:29 +0200 Subject: [PATCH 05/10] swarm: move the queue's responder package to deploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `swarm.nats.authPackage` is the callout responder's build — a host decision like every other package in this slice — so it joins the `deploy.nats` block that already holds `enable`, the seed paths and the auto-mint toggle. Its one reader is the `ExecStart` in this file. Three comments moved with it, and none of them survived the move unedited: - The "deliberately NO `package` option" note now says *anywhere* — it argued against a `package` under `swarm.nats`, and after the move a reader's next question is why there isn't one under `deploy.nats` either, where every other service's build now lives. - The description's "see the note above" stopped resolving: the note is ~80 lines up and in a different option block, so it names `options.services.hyperhive.swarm.nats` explicitly. - The split-rationale comment listed "the responder package" among what the queue IS to every hive. It's what the host running it decides, so it moved to that half of the sentence. `flake.nix` writes this option (`lib.mkDefault`), so the writer is repointed too — three of those left, all in the same block. The eval fixture gains the old path rather than a new case: the shim is what a dropped rename would break, and `natsOldPath` already fails the eval if any single entry goes missing. --- flake.nix | 2 +- nix/host-modules/deploy.nix | 4 +++ nix/host-modules/swarm-nats.nix | 51 ++++++++++++++++++--------------- nix/module-eval.nix | 3 +- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/flake.nix b/flake.nix index 65763031..2fbdd495 100644 --- a/flake.nix +++ b/flake.nix @@ -151,7 +151,7 @@ services.hyperhive.deploy.swarm-ui.package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-ui; - services.hyperhive.swarm.nats.authPackage = + services.hyperhive.deploy.nats.authPackage = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth; services.hyperhive.swarm.authelia.bridgePackage = diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index 034c0f28..45d0836f 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -369,6 +369,10 @@ in [ "services" "hyperhive" "swarm" "ui" "package" ] [ "services" "hyperhive" "deploy" "swarm-ui" "package" ] ) + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "nats" "authPackage" ] + [ "services" "hyperhive" "deploy" "nats" "authPackage" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/swarm-nats.nix b/nix/host-modules/swarm-nats.nix index 031eeedc..9acd2e56 100644 --- a/nix/host-modules/swarm-nats.nix +++ b/nix/host-modules/swarm-nats.nix @@ -217,12 +217,14 @@ in # ./deploy.nix. What stays here is what the queue IS: its domain, # ports, accounts and callout wiring. - # ⚠️ Deliberately NO `package` option, unlike this module's siblings. - # `services.nats` upstream does not expose one — it resolves - # `pkgs.nats-server` itself — so an option here would either be - # ignored or need an overlay to mean anything, and an option that - # does not control what it names is worse than its absence. Pin the - # build with `nixpkgs.overlays` if you need to. + # ⚠️ Deliberately NO `package` option for the NATS server, anywhere — + # not here and not under `deploy.nats` either, where every other + # service's build now lives. `services.nats` upstream does not expose + # one; it resolves `pkgs.nats-server` itself, so an option would be + # ignored or need an overlay to mean anything, and an option that does + # not control what it names is worse than its absence. Pin the build + # with `nixpkgs.overlays` if you need to. (`deploy.nats.authPackage` + # is a different thing: the callout responder, which IS ours.) port = lib.mkOption { type = lib.types.port; @@ -284,6 +286,19 @@ in ''; }; + }; + + # What stays above is what the queue IS to every hive: the ports it answers + # on and the client id it is registered under. What the host running it + # decides is below — which responder build it runs, whether it mints its own + # keypairs, and where the seeds sit. `enable` already lives in ./deploy.nix, + # which also carries the renames. + # + # ⚠️ The two PUBLIC keys move with their seeds rather than staying: peers + # receive the user key over the wire when they connect (docs/swarm/secrets.md), + # they never configure it, and splitting a keypair across two namespaces is + # worse than either placement. + options.services.hyperhive.deploy.nats = { authPackage = lib.mkOption { type = lib.types.package; defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-nats-auth"; @@ -291,25 +306,15 @@ in The auth-callout responder package. ⚠️ Named `authPackage`, not `package`, on purpose: this module - deliberately has **no** `package` option for the server itself - (see the note above — upstream's `services.nats` resolves - `pkgs.nats-server` on its own), so a bare `package` here would - read as "the NATS package" and mean something else entirely. + deliberately has **no** `package` option for the NATS server + itself, because upstream's `services.nats` resolves + `pkgs.nats-server` on its own — see the note above + `options.services.hyperhive.swarm.nats`. A bare `package` here + would read as "the NATS package" and mean something else + entirely. ''; }; - }; - # What stays above is what the queue IS to every hive: the ports it answers - # on, the client id it is registered under, the responder package. The - # callout identity below is what the host running it decides — whether it - # mints its own keypairs, and where the seeds sit. `enable` already lives in - # ./deploy.nix, which also carries the renames. - # - # ⚠️ The two PUBLIC keys move with their seeds rather than staying: peers - # receive the user key over the wire when they connect (docs/swarm/secrets.md), - # they never configure it, and splitting a keypair across two namespaces is - # worse than either placement. - options.services.hyperhive.deploy.nats = { autoGenerateCallout = lib.mkOption { type = lib.types.bool; default = false; @@ -708,7 +713,7 @@ in wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = lib.concatStringsSep " " [ - "${cfg.authPackage}/bin/swarm-nats-auth" + "${deployCfg.nats.authPackage}/bin/swarm-nats-auth" "--nats-url nats://127.0.0.1:${toString cfg.port}" "--user-seed-file \${CREDENTIALS_DIRECTORY}/callout-user.seed" "--issuer-seed-file \${CREDENTIALS_DIRECTORY}/issuer.seed" diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 80bd56d5..1463606e 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -110,7 +110,7 @@ let # The queue's callout identity, fourth split slice. `autoGenerateCallout` is # left FALSE on purpose: that is what makes the seed paths the thing deciding # `responderConfigured`, so the assertion below is about the seeds rather - # than about the auto-mint branch. All five old paths are defined, so + # than about the auto-mint branch. All six old paths are defined, so # dropping any single shim entry fails the eval, not just the arms read. natsOldPath = hive { deploy.nats.enable = true; @@ -119,6 +119,7 @@ let swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutUserSeedFile = "/run/secrets/nats-user.seed"; swarm.nats.calloutIssuerSeedFile = "/run/secrets/nats-issuer.seed"; + swarm.nats.authPackage = pkgs.emptyDirectory; }; # Seventh split slice. Only `usersFile` has a rename entry: the other two From e061e4b446b2c1068866f0c679508743141e6b5e Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 20:06:34 +0200 Subject: [PATCH 06/10] module-eval: cover the last nats shim the nats fixture missed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `natsOldPath` exists to prove every old `swarm.nats.*` path still resolves through its rename shim, and it defined six of the seven — `enable` was spelled the NEW way (`deploy.nats.enable`), so nothing in the suite ever exercised that shim. Deleting it would have gone unnoticed. The comment above the fixture claimed more than the fixture delivered: "dropping any single shim entry fails the eval" reads as a guarantee over all seven. Closing the gap rather than narrowing the sentence, so the claim becomes true instead of merely careful. `autheliaOldPath` keeps `deploy.nats.enable` deliberately — it needs the queue on, it is not an old-path test for nats — and the gate controls both spellings so a future edit cannot silently swap them. Found by asking the fixture whether a planned mutation could fail before spending an eval on it: no fixture defined the old path, so the arm was guaranteed to survive. A grep answered in a second what a mutation would have taken ~6 minutes to report. --- nix/module-eval.nix | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 1463606e..7e9dd190 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -110,10 +110,12 @@ let # The queue's callout identity, fourth split slice. `autoGenerateCallout` is # left FALSE on purpose: that is what makes the seed paths the thing deciding # `responderConfigured`, so the assertion below is about the seeds rather - # than about the auto-mint branch. All six old paths are defined, so - # dropping any single shim entry fails the eval, not just the arms read. + # than about the auto-mint branch. Every one of the seven old paths is + # defined — `enable` included, which is why it is spelled the old way here + # while the fixture below uses the new one — so dropping any single nats + # shim fails the eval, not just the arms read. natsOldPath = hive { - deploy.nats.enable = true; + swarm.nats.enable = true; swarm.nats.autoGenerateCallout = false; swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; From 889a46729bb7b5eb8b7c44c1afadddd3b9f90f0a Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 20:20:41 +0200 Subject: [PATCH 07/10] swarm: move both authelia packages to deploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. --- flake.nix | 2 +- nix/host-modules/deploy.nix | 8 +++ nix/host-modules/swarm-authelia.nix | 78 +++++++++++++-------------- nix/host-modules/swarm-controller.nix | 2 +- nix/module-eval.nix | 10 ++-- 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/flake.nix b/flake.nix index 2fbdd495..2d0b2f46 100644 --- a/flake.nix +++ b/flake.nix @@ -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 = diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index 45d0836f..b1c36f90 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -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 diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 0c0e7e30..a2309257 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -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: diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index 85d9bee6..c9a939e5 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -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; }; diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 7e9dd190..71598d6a 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -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"; From cc82badd56526da194dd7887dae415bd71268815 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 20:33:53 +0200 Subject: [PATCH 08/10] swarm: move the controller's two packages to deploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `package` (the daemon build) and `swarmctlPackage` (the operator CLI installed beside it) are host decisions, so they join the `deploy.swarm-controller` block that already holds `enable`, the socket path and the three local-disk credentials. ⚠️ The target namespace is spelled out in the shim comment because getting it wrong here does not fail. `deploy.hive-controller` also exists — it is hive-c0re's, carrying `tls.*` and `statusPublish.*`, and is referenced from four modules. A rename pointing at it would land on a live, unrelated option and evaluate cleanly. The module already warned about this above its own deploy block; the warning now sits next to the entries it governs too. `swarm-ui.nix`'s package description cited `swarm.controller.package` as the precedent for "wired from flake.nix rather than defaulted to pkgs". That was true when written and stops being true here, so it moves with it — it lives in a module this slice already finished, which is exactly where a per-module sweep stops looking. These were the last two `services.hyperhive.swarm.*` writers in flake.nix; that file now has none. Riding along, and not a defect fix: `controllerOldPath` spelled `enable` the NEW way while every other entry used the old one, so six of the seven controller shims were exercised and the seventh was not. It is named for testing old paths, so it now uses the old path throughout. Unlike the nats fixture there was no comment claiming otherwise — this is coverage the fixture was already shaped to provide. --- flake.nix | 4 +- nix/host-modules/deploy.nix | 11 +++++ nix/host-modules/swarm-controller.nix | 63 ++++++++++++++------------- nix/host-modules/swarm-ui.nix | 2 +- nix/module-eval.nix | 6 ++- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/flake.nix b/flake.nix index 2d0b2f46..9e6d2ad4 100644 --- a/flake.nix +++ b/flake.nix @@ -142,10 +142,10 @@ agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel; managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel; }; - services.hyperhive.swarm.controller.package = + services.hyperhive.deploy.swarm-controller.package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-controller; - services.hyperhive.swarm.controller.swarmctlPackage = + services.hyperhive.deploy.swarm-controller.swarmctlPackage = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl; services.hyperhive.deploy.swarm-ui.package = diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index b1c36f90..45b1eade 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -381,6 +381,17 @@ in [ "services" "hyperhive" "swarm" "authelia" "bridgePackage" ] [ "services" "hyperhive" "deploy" "authelia" "bridgePackage" ] ) + # ⚠️ `swarm-controller`, not `hive-controller` — see the note in + # ./swarm-controller.nix. Both namespaces exist, so the wrong target here + # lands on a live option in hive-c0re's tree instead of failing. + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "controller" "package" ] + [ "services" "hyperhive" "deploy" "swarm-controller" "package" ] + ) + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "controller" "swarmctlPackage" ] + [ "services" "hyperhive" "deploy" "swarm-controller" "swarmctlPackage" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index c9a939e5..29d445ec 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -196,7 +196,7 @@ let # config back to it — and to get it wrong the day one of them changes. swarmctlConfigured = pkgs.symlinkJoin { name = "swarmctl-configured"; - paths = [ cfg.swarmctlPackage ]; + paths = [ deployCfg.swarm-controller.swarmctlPackage ]; nativeBuildInputs = [ pkgs.makeWrapper ]; postBuild = '' wrapProgram $out/bin/swarmctl ${ @@ -243,30 +243,8 @@ in # asserts it, and that was never an exception to "not derived from # services.hyperhive.enable": that mode says "this box is the whole # deployment", which answers the topology question outright, where - # `enable` alone never can. What stays here is what the daemon IS. - - package = lib.mkOption { - type = lib.types.package; - defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-controller"; - description = '' - swarm-controller package. Wired by default from this flake's own - package set (see `flake.nix`); override to run a different build. - ''; - }; - - swarmctlPackage = lib.mkOption { - type = lib.types.package; - defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarmctl"; - description = '' - `swarmctl` package — the swarm operator's CLI, installed on this - host alongside the daemon and wrapped with the paths it needs. - - A separate option from `package` rather than a second binary in - the same derivation: the CLI runs as root and acts directly, - the daemon runs unprivileged and serves a socket, and pinning one - without the other is a thing an operator may legitimately want. - ''; - }; + # `enable` alone never can. Both packages moved to that same + # `deploy.swarm-controller` block. What stays here is what the daemon IS. staleAfterSeconds = lib.mkOption { type = lib.types.ints.positive; @@ -371,16 +349,39 @@ in }; # What stays above is what the controller IS to the swarm: the quick-links - # it publishes, the client id it is registered under, how stale a snapshot - # may read, and its packages. What the host running it decides is here — - # where its socket lives, and the three credentials it reads off local - # disk. `enable` already lives in ./deploy.nix, which also carries the - # renames. + # it publishes, the client id it is registered under, and how stale a + # snapshot may read. What the host running it decides is here — which two + # builds it runs, where its socket lives, and the three credentials it reads + # off local disk. `enable` already lives in ./deploy.nix, which also carries + # the renames. # # ⚠️ `deploy.swarm-controller`, NOT `deploy.hive-controller`. Both exist: # the latter is hive-c0re's namespace, where `tls.*` lives. A definition on # the wrong one lands on a live, unrelated option rather than failing. options.services.hyperhive.deploy.swarm-controller = { + package = lib.mkOption { + type = lib.types.package; + defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-controller"; + description = '' + swarm-controller package. Wired by default from this flake's own + package set (see `flake.nix`); override to run a different build. + ''; + }; + + swarmctlPackage = lib.mkOption { + type = lib.types.package; + defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarmctl"; + description = '' + `swarmctl` package — the swarm operator's CLI, installed on this + host alongside the daemon and wrapped with the paths it needs. + + A separate option from `package` rather than a second binary in + the same derivation: the CLI runs as root and acts directly, + the daemon runs unprivileged and serves a socket, and pinning one + without the other is a thing an operator may legitimately want. + ''; + }; + socketPath = lib.mkOption { type = lib.types.str; default = "/run/swarm-controller/controller.sock"; @@ -591,7 +592,7 @@ in after = [ "network.target" ]; serviceConfig = { - ExecStart = "${cfg.package}/bin/swarm-controller"; + ExecStart = "${deployCfg.swarm-controller.package}/bin/swarm-controller"; # The two differ on purpose. The queue credential is # unconditional — the assertions above make its path a value that diff --git a/nix/host-modules/swarm-ui.nix b/nix/host-modules/swarm-ui.nix index c7c7aac1..f50b3eff 100644 --- a/nix/host-modules/swarm-ui.nix +++ b/nix/host-modules/swarm-ui.nix @@ -90,7 +90,7 @@ in 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 + `flake.nix`), the same way `deploy.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. diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 71598d6a..8e2e3ad7 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -153,8 +153,12 @@ let deploy.matrix.maxRequestSize = 99000000; }; + # `enable` is spelled the OLD way like everything else here, so all seven + # of the controller's shims are exercised rather than six. controllerOldPath = hive { - deploy.swarm-controller.enable = true; + swarm.controller.enable = true; + swarm.controller.package = pkgs.emptyDirectory; + swarm.controller.swarmctlPackage = pkgs.emptyDirectory; swarm.controller.socketPath = "/run/test-ctrl/ctrl.sock"; swarm.controller.forgeTokenFile = "/run/secrets/ctrl-forge.token"; swarm.controller.authBridgeUrl = "http://127.0.0.1:19097"; From ce4d5187a93c969271ad0251d8fa579660930a94 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 20:39:24 +0200 Subject: [PATCH 09/10] deploy: two comments still say a package stays under swarm.* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both are regressions from earlier commits in this same slice, not pre-existing drift: - the nats comment listed "the responder package" among what stays under `swarm.nats`; `authPackage` moved in a17286c4 - the matrix comment listed "its package" among what `swarm.matrix` keeps; both matrix packages moved in 1b2da54c Each move edited the prose in the module that declares the option — `swarm-nats.nix` carries the same "responder package" sentence and it was corrected there — but deploy.nix keeps its own one-paragraph description of every service, next to that service's shims. It is a per-module prose site that does not live in the module, so a sweep scoped to the module cannot reach it. The counts in both comments were checked and are correct as written: the matrix block has six shims and the nats block five. The `*.package` entries append to the separate block further down, which already has a comment saying what it is, so no count moved. Each fix is one clause. --- nix/host-modules/deploy.nix | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index 45b1eade..4c94d1ae 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -164,9 +164,10 @@ in ) # The queue's callout identity. What stays under `swarm.nats` is what the - # queue IS to every hive — the ports it answers on, the client id it is - # registered under, the responder package; these five are what the host - # running it decides. + # queue IS to every hive — the ports it answers on and the client id it is + # registered under; these five are what the host running it decides. The + # responder package is one of them now, further down with the other + # `*.package` moves. # # ⚠️ The two PUBLIC keys move with their seeds. A public key looks # swarm-wide, and the user key genuinely is published to every client that @@ -227,9 +228,10 @@ in ) # The homeserver, split the same way. `swarm.matrix` keeps what it IS to - # every hive — its package, the name it answers to, the ports and URLs it - # is reached on, the client id it is registered under. These six are what - # the host running it decides. ⚠️ `enable` is NOT here: it was renamed + # every hive — the name it answers to, the ports and URLs it is reached + # on, the client id it is registered under. These six are what the host + # running it decides; its two packages moved as well, further down with + # the other `*.package` moves. ⚠️ `enable` is NOT here: it was renamed # earlier and its shim lives in ./hive-matrix.nix, so that file is the one # to check before assuming this table is the whole matrix story. (lib.mkRenamedOptionModule From df18d3d4d714d0276b882787db2b68d33e70c11e Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 7 Sep 2026 20:43:51 +0200 Subject: [PATCH 10/10] =?UTF-8?q?forge:=20move=20the=20forgejo=20package?= =?UTF-8?q?=20to=20deploy=20=E2=80=94=20slice=2010=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last of the twelve. `swarm.forge.package` becomes `deploy.forgejo.package`, beside the `behindGateway` / `mirrors` / host-secret options that were already there. ⚠️ `deploy.forgejo`, not `deploy.forge`. The forge joins the controller and the UI as a mover where the two sides simply do not share a name, so the shim comment says which one. Four readers, and one of them is why an anchored grep was the wrong instrument: `:678` reads `cfg.package.data`, a sub-attribute, which `cfg.package}` and `cfg.package/` both miss. Three copies of the same sentence claimed the package stays under `swarm.forge` — `deploy.nix`, this module's own `deploy.forgejo` header, and `docs/integrations/forge.md`. The docs one is outside `nix/` entirely. A namespace split gets described wherever the split is explained, which is not only where the option is declared. `forgeOldPath` gains the old path, and its comment's count of the host-side options goes five → six with it. --- docs/integrations/forge.md | 5 ++- nix/host-modules/deploy.nix | 13 +++++-- nix/host-modules/hive-forge/default.nix | 50 ++++++++++++------------- nix/module-eval.nix | 5 ++- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/docs/integrations/forge.md b/docs/integrations/forge.md index d249f914..546bc764 100644 --- a/docs/integrations/forge.md +++ b/docs/integrations/forge.md @@ -3,9 +3,10 @@ Private Forgejo instance running in a nixos-container, used as the swarm's persistent code-collaboration surface (issues, PRs, reviews, attachments). Configured via `services.hyperhive.swarm.forge.*` (what the -forge IS to every hive — its package, names, ports, URLs) plus +forge IS to every hive — names, ports, URLs) plus `services.hyperhive.deploy.forgejo.*` (what the host running it decides — -how it's served, what it mirrors, where its host-local secrets sit). +which build it runs, how it's served, what it mirrors, where its +host-local secrets sit). Container shape, ROOT_URL / sub-domain routing, and operator-vs-in-cluster URL handling live in [`docs/networking/gateway.md`](../networking/gateway.md); this file owns the diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index 4c94d1ae..0d491143 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -196,9 +196,10 @@ in ) # The rest of the forge split. What stays under `swarm.forge` is what the - # forge IS from any hive's point of view — its package, the names and ports - # it answers on, the URLs it advertises, the client id it is registered - # under; these five are what the host running it decides. `sso` splits + # forge IS from any hive's point of view — the names and ports it answers + # on, the URLs it advertises, the client id it is registered under; these + # five are what the host running it decides. Its package moved as well, + # further down with the other `*.package` moves. `sso` splits # because its two halves are different facts: the client id must match the # entry in authelia's register, the secret is a path on this machine. # @@ -394,6 +395,12 @@ in [ "services" "hyperhive" "swarm" "controller" "swarmctlPackage" ] [ "services" "hyperhive" "deploy" "swarm-controller" "swarmctlPackage" ] ) + # ⚠️ `deploy.forgejo`, not `deploy.forge` — the deploy-side name differs + # from the swarm-side one here, as it does for the controller and the UI. + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "forge" "package" ] + [ "services" "hyperhive" "deploy" "forgejo" "package" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index a171553f..3562fbd5 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -232,21 +232,6 @@ in ''; }; - package = lib.mkOption { - type = lib.types.package; - default = pkgs.forgejo; - defaultText = lib.literalExpression "pkgs.forgejo"; - description = '' - Forgejo package to run inside the container. Defaults to - `pkgs.forgejo` (the latest release line) rather than the - nixpkgs-module default of `pkgs.forgejo-lts`, because LTS - lags far behind on schema and the DB easily ends up "newer - than the binary" if the operator ever ran a non-LTS forgejo - against the same state dir. Override to `pkgs.forgejo-lts` - if you actively want the slower release train. - ''; - }; - rootUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; @@ -294,13 +279,28 @@ in }; }; - # What stays above is what the forge IS from any hive's point of view: its - # package, the names and ports it answers on, the URLs it advertises, and - # the client id it is registered under. What lives here is what the host - # running it decides — how it is served, what it mirrors, and where its - # host-local secrets sit. Same rule as ./swarm-victorialogs.nix, and the - # renames are in ./deploy.nix with the rest. + # What stays above is what the forge IS from any hive's point of view: the + # names and ports it answers on, the URLs it advertises, and the client id + # it is registered under. What lives here is what the host running it + # decides — which build it runs, how it is served, what it mirrors, and + # where its host-local secrets sit. Same rule as ./swarm-victorialogs.nix, + # and the renames are in ./deploy.nix with the rest. options.services.hyperhive.deploy.forgejo = { + package = lib.mkOption { + type = lib.types.package; + default = pkgs.forgejo; + defaultText = lib.literalExpression "pkgs.forgejo"; + description = '' + Forgejo package to run inside the container. Defaults to + `pkgs.forgejo` (the latest release line) rather than the + nixpkgs-module default of `pkgs.forgejo-lts`, because LTS + lags far behind on schema and the DB easily ends up "newer + than the binary" if the operator ever ran a non-LTS forgejo + against the same state dir. Override to `pkgs.forgejo-lts` + if you actively want the slower release train. + ''; + }; + behindGateway = lib.mkOption { type = lib.types.bool; default = config.services.hyperhive.enable; @@ -675,7 +675,7 @@ in # the nix store — no separate hive-forge container rebuild needed, # and no persistent-state directory involved. staticRootWithTheme = pkgs.runCommand "forgejo-static-with-theme" { } '' - cp -r --no-preserve=mode,ownership ${cfg.package.data}/. $out/ + cp -r --no-preserve=mode,ownership ${deployCfg.forgejo.package.data}/. $out/ mkdir -p $out/public/assets/css cp ${./theme-catppuccin-vibec0re.css} \ $out/public/assets/css/theme-catppuccin-vibec0re.css @@ -747,7 +747,7 @@ in services.forgejo = { enable = true; - package = cfg.package; + package = deployCfg.forgejo.package; database.type = "sqlite3"; lfs.enable = true; settings = { @@ -1036,7 +1036,7 @@ in FORGEJO_CUSTOM = "/var/lib/forgejo/custom"; }; path = [ - cfg.package + deployCfg.forgejo.package pkgs.coreutils pkgs.gnugrep ]; @@ -1126,7 +1126,7 @@ in FORGEJO_CUSTOM = "/var/lib/forgejo/custom"; }; path = [ - cfg.package + deployCfg.forgejo.package pkgs.coreutils pkgs.gnugrep ]; diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 8e2e3ad7..53f2d2fc 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -72,11 +72,12 @@ let swarm.wireguard.privateKeyFile = "/etc/wireguard/hive.key"; }; - # Same shape for the forge, which SPLIT rather than moving whole: the five + # Same shape for the forge, which SPLIT rather than moving whole: the six # host-side options are set here through their pre-rename paths while the - # rest of `swarm.forge` stays put. All five are defined so that dropping any + # rest of `swarm.forge` stays put. All six are defined so that dropping any # single shim entry fails the eval, not just the two the assertion reads. forgeOldPath = hive { + swarm.forge.package = pkgs.emptyDirectory; swarm.forge.behindGateway = true; swarm.forge.openFirewall = true; swarm.forge.hostSwarmControllerTokenFile = "/etc/forge/sc.token";