Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
Author SHA1 Message Date
atlas
df18d3d4d7 forge: move the forgejo package to deploy — slice 10 complete
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.
2026-09-07 20:46:38 +02:00
atlas
ce4d5187a9 deploy: two comments still say a package stays under swarm.*
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.
2026-09-07 20:46:38 +02:00
atlas
cc82badd56 swarm: move the controller's two packages to deploy
`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.
2026-09-07 20:46:38 +02:00
atlas
889a46729b 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.
2026-09-07 20:46:38 +02:00
atlas
e061e4b446 module-eval: cover the last nats shim the nats fixture missed
`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.
2026-09-07 20:46:38 +02:00
atlas
0fe0df8d63 swarm: move the queue's responder package to deploy
`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.
2026-09-07 20:46:38 +02:00
atlas
190763b3a2 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.
2026-09-07 20:46:38 +02:00
atlas
e091add37a swarm: move both metric stores' package to deploy, and cover their shims
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.<store>`
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.
2026-09-07 20:46:38 +02:00
atlas
0002c15dc3 swarm: move grafana's package to deploy, beside the rest of its host knobs
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.
2026-09-07 20:46:37 +02:00
atlas
d6f23ff371 swarm: move the matrix packages to deploy, where their enable already lives
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.
2026-09-07 20:46:37 +02:00
14 changed files with 401 additions and 253 deletions

View file

@ -3,9 +3,10 @@
Private Forgejo instance running in a nixos-container, used as the Private Forgejo instance running in a nixos-container, used as the
swarm's persistent code-collaboration surface (issues, PRs, reviews, swarm's persistent code-collaboration surface (issues, PRs, reviews,
attachments). Configured via `services.hyperhive.swarm.forge.*` (what the 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 — `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 Container
shape, ROOT_URL / sub-domain routing, and operator-vs-in-cluster URL 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 handling live in [`docs/networking/gateway.md`](../networking/gateway.md); this file owns the

View file

@ -625,7 +625,7 @@ existing in the page DOM.
A static matrix web client (default `pkgs.fluffychat-web` rebuilt A static matrix web client (default `pkgs.fluffychat-web` rebuilt
with `--base-href /matrix/`, swappable via 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 nginx container at `/matrix/` when
`services.hyperhive.deploy.matrix.gui.enable` is on (defaults to `services.hyperhive.deploy.matrix.gui.enable` is on (defaults to
`matrix.enable`). c0re signals availability via the `matrix.enable`). c0re signals availability via the

View file

@ -142,19 +142,19 @@
agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel; agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel;
managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel; managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel;
}; };
services.hyperhive.swarm.controller.package = services.hyperhive.deploy.swarm-controller.package =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-controller; self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-controller;
services.hyperhive.swarm.controller.swarmctlPackage = services.hyperhive.deploy.swarm-controller.swarmctlPackage =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl; self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl;
services.hyperhive.swarm.ui.package = services.hyperhive.deploy.swarm-ui.package =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-ui; self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-ui;
services.hyperhive.swarm.nats.authPackage = services.hyperhive.deploy.nats.authPackage =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth; self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-nats-auth;
services.hyperhive.swarm.authelia.bridgePackage = services.hyperhive.deploy.authelia.bridgePackage =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge; self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge;
services.hyperhive.gateway.swaggerUiTheme = services.hyperhive.gateway.swaggerUiTheme =

View file

@ -164,9 +164,10 @@ in
) )
# The queue's callout identity. What stays under `swarm.nats` is what the # 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 # queue IS to every hive — the ports it answers on and the client id it is
# registered under, the responder package; these five are what the host # registered under; these five are what the host running it decides. The
# running it decides. # 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 # ⚠️ 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 # swarm-wide, and the user key genuinely is published to every client that
@ -195,9 +196,10 @@ in
) )
# The rest of the forge split. What stays under `swarm.forge` is what the # 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 # forge IS from any hive's point of view — the names and ports it answers
# it answers on, the URLs it advertises, the client id it is registered # on, the URLs it advertises, the client id it is registered under; these
# under; these five are what the host running it decides. `sso` splits # 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 # 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. # entry in authelia's register, the secret is a path on this machine.
# #
@ -227,9 +229,10 @@ in
) )
# The homeserver, split the same way. `swarm.matrix` keeps what it IS to # 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 # every hive — the name it answers to, the ports and URLs it is reached
# is reached on, the client id it is registered under. These six are what # on, the client id it is registered under. These six are what the host
# the host running it decides. ⚠️ `enable` is NOT here: it was renamed # 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 # 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. # to check before assuming this table is the whole matrix story.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
@ -259,8 +262,9 @@ in
# Retention is read only where the container is defined, so it is a # 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 # decision of the host running the store rather than something the swarm
# agrees on. The two stores keep everything else — package, domain, port # agrees on. The two stores keep their domain and port in `swarm.*`,
# — in `swarm.*`, because a client hive needs those to reach them. # because a client hive needs those to reach them; `package` followed
# `enable` and `retentionPeriod` across in a later slice.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "victoriametrics" "retentionPeriod" ] [ "services" "hyperhive" "swarm" "victoriametrics" "retentionPeriod" ]
[ "services" "hyperhive" "deploy" "victoriametrics" "retentionPeriod" ] [ "services" "hyperhive" "deploy" "victoriametrics" "retentionPeriod" ]
@ -335,6 +339,68 @@ in
[ "services" "hyperhive" "swarm" "wireguard" "persistentKeepalive" ] [ "services" "hyperhive" "swarm" "wireguard" "persistentKeepalive" ]
[ "services" "hyperhive" "deploy" "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" ]
)
(lib.mkRenamedOptionModule
[ "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" ]
)
# 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" ]
)
(lib.mkRenamedOptionModule
[ "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" ]
)
# ⚠️ `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`, 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 # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the

View file

@ -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 { rootUrl = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
@ -294,13 +279,28 @@ in
}; };
}; };
# What stays above is what the forge IS from any hive's point of view: its # What stays above is what the forge IS from any hive's point of view: the
# package, the names and ports it answers on, the URLs it advertises, and # names and ports it answers on, the URLs it advertises, and the client id
# the client id it is registered under. What lives here is what the host # it is registered under. What lives here is what the host running it
# running it decides — how it is served, what it mirrors, and where its # decides — which build it runs, how it is served, what it mirrors, and
# host-local secrets sit. Same rule as ./swarm-victorialogs.nix, and the # where its host-local secrets sit. Same rule as ./swarm-victorialogs.nix,
# renames are in ./deploy.nix with the rest. # and the renames are in ./deploy.nix with the rest.
options.services.hyperhive.deploy.forgejo = { 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 { behindGateway = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = config.services.hyperhive.enable; default = config.services.hyperhive.enable;
@ -675,7 +675,7 @@ in
# the nix store — no separate hive-forge container rebuild needed, # the nix store — no separate hive-forge container rebuild needed,
# and no persistent-state directory involved. # and no persistent-state directory involved.
staticRootWithTheme = pkgs.runCommand "forgejo-static-with-theme" { } '' 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 mkdir -p $out/public/assets/css
cp ${./theme-catppuccin-vibec0re.css} \ cp ${./theme-catppuccin-vibec0re.css} \
$out/public/assets/css/theme-catppuccin-vibec0re.css $out/public/assets/css/theme-catppuccin-vibec0re.css
@ -747,7 +747,7 @@ in
services.forgejo = { services.forgejo = {
enable = true; enable = true;
package = cfg.package; package = deployCfg.forgejo.package;
database.type = "sqlite3"; database.type = "sqlite3";
lfs.enable = true; lfs.enable = true;
settings = { settings = {
@ -1036,7 +1036,7 @@ in
FORGEJO_CUSTOM = "/var/lib/forgejo/custom"; FORGEJO_CUSTOM = "/var/lib/forgejo/custom";
}; };
path = [ path = [
cfg.package deployCfg.forgejo.package
pkgs.coreutils pkgs.coreutils
pkgs.gnugrep pkgs.gnugrep
]; ];
@ -1126,7 +1126,7 @@ in
FORGEJO_CUSTOM = "/var/lib/forgejo/custom"; FORGEJO_CUSTOM = "/var/lib/forgejo/custom";
}; };
path = [ path = [
cfg.package deployCfg.forgejo.package
pkgs.coreutils pkgs.coreutils
pkgs.gnugrep pkgs.gnugrep
]; ];

View file

@ -172,8 +172,9 @@ in
[ "services" "hyperhive" "swarm" "matrix" ] [ "services" "hyperhive" "swarm" "matrix" ]
) )
# "does THIS host run it" is a per-host decision, and `swarm.*` is the # "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 # namespace every host in the swarm agrees on. What stays under
# settings genuinely are swarm-wide and stay where they are. # `swarm.matrix` is the homeserver's identity as every hive sees it —
# its server name, its addresses, its federation policy.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "enable" ] [ "services" "hyperhive" "swarm" "matrix" "enable" ]
[ "services" "hyperhive" "deploy" "matrix" "enable" ] [ "services" "hyperhive" "deploy" "matrix" "enable" ]
@ -191,17 +192,6 @@ in
]; ];
options.services.hyperhive.swarm.matrix = { 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 { serverName = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; 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.<swarm-domain>` 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 `<base href "/">`,
no sub-path gymnastics needed.
'';
};
};
# This homeserver always delegates login to the swarm's authelia, as # This homeserver always delegates login to the swarm's authelia, as
# an OIDC relying party — matrix SSO (`m.login.sso`), offered # an OIDC relying party — matrix SSO (`m.login.sso`), offered
# alongside password login. No toggle: a homeserver in a swarm is a # 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: # 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, # the name it answers to, the ports and URLs it is reached on, and the client
# and the client id it is registered under. What lives here is what the host # id it is registered under. What lives here is what the host running it
# running it decides — whether it is exposed, which peers it trusts, how large # decides — which build it runs, whether it is exposed, which peers it trusts,
# a request it accepts, and where its host-local secrets sit. Same rule as # 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 # ./swarm-victorialogs.nix; `enable` already lives in ./deploy.nix, which also
# carries the renames. # carries the renames.
options.services.hyperhive.deploy.matrix = { 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 { openFirewall = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
@ -509,11 +490,28 @@ in
off, the dashboard's `M4TR1X →` tab is hidden. See off, the dashboard's `M4TR1X →` tab is hidden. See
`docs/networking/gateway.md` for the discovery flow that lets clients `docs/networking/gateway.md` for the discovery flow that lets clients
auto-find the sub-domain. The client build itself is 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. 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.<swarm-domain>` 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 `<base href "/">`,
no sub-path gymnastics needed.
'';
};
sso.clientSecretFile = lib.mkOption { sso.clientSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
@ -634,7 +632,7 @@ in
# fluffychat at sub-domain root, SPA-fallback via the # fluffychat at sub-domain root, SPA-fallback via the
# Accept-header `$matrix_spa_target` map above. # Accept-header `$matrix_spa_target` map above.
"/" = { "/" = {
alias = "${cfg.gui.package}/"; alias = "${deployCfg.matrix.gui.package}/";
extraConfig = '' extraConfig = ''
try_files $uri $uri/ $matrix_spa_target =404; try_files $uri $uri/ $matrix_spa_target =404;
''; '';
@ -999,7 +997,7 @@ in
services.matrix-tuwunel = { services.matrix-tuwunel = {
enable = true; enable = true;
package = cfg.package; package = deployCfg.matrix.package;
settings.global = { settings.global = {
server_name = effectiveServerName; server_name = effectiveServerName;
# `address` + `port` are upstream `listOf` — wrap singles. # `address` + `port` are upstream `listOf` — wrap singles.
@ -1084,7 +1082,7 @@ in
"oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}" "oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}"
]; ];
environment.systemPackages = [ cfg.package ]; environment.systemPackages = [ deployCfg.matrix.package ];
}; };
}; };

View file

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

View file

@ -28,7 +28,7 @@ let
autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable { autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable {
# The CONFIGURED authelia, not whatever is on PATH: the argon2 # The CONFIGURED authelia, not whatever is on PATH: the argon2
# parameters baked into a hash have to match the verifier's. # parameters baked into a hash have to match the verifier's.
SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.package}/bin/authelia"; SWARMCTL_AUTHELIA_BIN = "${deployCfg.authelia.package}/bin/authelia";
SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile; SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile;
}; };
@ -196,7 +196,7 @@ let
# config back to it — and to get it wrong the day one of them changes. # config back to it — and to get it wrong the day one of them changes.
swarmctlConfigured = pkgs.symlinkJoin { swarmctlConfigured = pkgs.symlinkJoin {
name = "swarmctl-configured"; name = "swarmctl-configured";
paths = [ cfg.swarmctlPackage ]; paths = [ deployCfg.swarm-controller.swarmctlPackage ];
nativeBuildInputs = [ pkgs.makeWrapper ]; nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = '' postBuild = ''
wrapProgram $out/bin/swarmctl ${ wrapProgram $out/bin/swarmctl ${
@ -243,30 +243,8 @@ in
# asserts it, and that was never an exception to "not derived from # asserts it, and that was never an exception to "not derived from
# services.hyperhive.enable": that mode says "this box is the whole # services.hyperhive.enable": that mode says "this box is the whole
# deployment", which answers the topology question outright, where # deployment", which answers the topology question outright, where
# `enable` alone never can. What stays here is what the daemon IS. # `enable` alone never can. Both packages moved to that same
# `deploy.swarm-controller` block. 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.
'';
};
staleAfterSeconds = lib.mkOption { staleAfterSeconds = lib.mkOption {
type = lib.types.ints.positive; type = lib.types.ints.positive;
@ -371,16 +349,39 @@ in
}; };
# What stays above is what the controller IS to the swarm: the quick-links # 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 # it publishes, the client id it is registered under, and how stale a
# may read, and its packages. What the host running it decides is here — # snapshot may read. What the host running it decides is here — which two
# where its socket lives, and the three credentials it reads off local # builds it runs, where its socket lives, and the three credentials it reads
# disk. `enable` already lives in ./deploy.nix, which also carries the # off local disk. `enable` already lives in ./deploy.nix, which also carries
# renames. # the renames.
# #
# ⚠️ `deploy.swarm-controller`, NOT `deploy.hive-controller`. Both exist: # ⚠️ `deploy.swarm-controller`, NOT `deploy.hive-controller`. Both exist:
# the latter is hive-c0re's namespace, where `tls.*` lives. A definition on # 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. # the wrong one lands on a live, unrelated option rather than failing.
options.services.hyperhive.deploy.swarm-controller = { 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 { socketPath = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "/run/swarm-controller/controller.sock"; default = "/run/swarm-controller/controller.sock";
@ -591,7 +592,7 @@ in
after = [ "network.target" ]; after = [ "network.target" ];
serviceConfig = { serviceConfig = {
ExecStart = "${cfg.package}/bin/swarm-controller"; ExecStart = "${deployCfg.swarm-controller.package}/bin/swarm-controller";
# The two differ on purpose. The queue credential is # The two differ on purpose. The queue credential is
# unconditional — the assertions above make its path a value that # unconditional — the assertions above make its path a value that

View file

@ -111,20 +111,13 @@ in
# `enable` moved to `services.hyperhive.deploy.grafana.enable` — see # `enable` moved to `services.hyperhive.deploy.grafana.enable` — see
# ./deploy.nix. Whether this host runs the swarm's Grafana is a # ./deploy.nix. Whether this host runs the swarm's Grafana is a
# deployment decision, and `swarm.*` has to be identical on every host. # 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 # What stays here is what the service IS to every hive: the name it is
# name it is served under, the port its `/metrics` is re-served on, and # served under, the port its `/metrics` is re-served on, and the OIDC
# the OIDC client it is registered as. What the host running it decides — # client it is registered as. What the host running it decides — which
# where its datasources point, which plugins are in its store path, and # build it runs, where its datasources point, which plugins are in its
# the socket directory it shares with nginx — is below, under # store path, and the socket directory it shares with nginx — is below,
# `deploy.grafana`. # under `deploy.grafana`.
options.services.hyperhive.swarm.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 { machine = lib.mkOption {
type = lib.types.str; type = lib.types.str;
readOnly = true; readOnly = true;
@ -217,15 +210,22 @@ in
}; };
# What stays above is what the service IS to every hive. What the host # 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 # running it decides is here: which build it runs, where its datasources
# in its store path, and the directory it shares a socket with nginx # point, which plugins sit in its store path, and the directory it shares a
# through. `enable` already lives in ./deploy.nix, which also carries the # socket with nginx through. `enable` already lives in ./deploy.nix, which
# renames. # also carries the renames.
# #
# ⚠️ Both datasource URLs are wiring and still move. A URL's scope is the # ⚠️ 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 # 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". # bind loopback, so these can only ever mean "on this host".
options.services.hyperhive.deploy.grafana = { 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 { socketDir = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "/run/swarm-grafana"; default = "/run/swarm-grafana";
@ -624,7 +624,7 @@ in
services.grafana = { services.grafana = {
enable = true; enable = true;
package = cfg.package; package = deployCfg.grafana.package;
# Passed through unconditionally, empty default included. # Passed through unconditionally, empty default included.
# Upstream distinguishes `null` from `[ ]`, and both differences # Upstream distinguishes `null` from `[ ]`, and both differences

View file

@ -217,12 +217,14 @@ in
# ./deploy.nix. What stays here is what the queue IS: its domain, # ./deploy.nix. What stays here is what the queue IS: its domain,
# ports, accounts and callout wiring. # ports, accounts and callout wiring.
# ⚠️ Deliberately NO `package` option, unlike this module's siblings. # ⚠️ Deliberately NO `package` option for the NATS server, anywhere —
# `services.nats` upstream does not expose one — it resolves # not here and not under `deploy.nats` either, where every other
# `pkgs.nats-server` itself — so an option here would either be # service's build now lives. `services.nats` upstream does not expose
# ignored or need an overlay to mean anything, and an option that # one; it resolves `pkgs.nats-server` itself, so an option would be
# does not control what it names is worse than its absence. Pin the # ignored or need an overlay to mean anything, and an option that does
# build with `nixpkgs.overlays` if you need to. # 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 { port = lib.mkOption {
type = lib.types.port; 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 { authPackage = lib.mkOption {
type = lib.types.package; type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-nats-auth"; defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-nats-auth";
@ -291,25 +306,15 @@ in
The auth-callout responder package. The auth-callout responder package.
⚠️ Named `authPackage`, not `package`, on purpose: this module ⚠️ Named `authPackage`, not `package`, on purpose: this module
deliberately has **no** `package` option for the server itself deliberately has **no** `package` option for the NATS server
(see the note above — upstream's `services.nats` resolves itself, because upstream's `services.nats` resolves
`pkgs.nats-server` on its own), so a bare `package` here would `pkgs.nats-server` on its own — see the note above
read as "the NATS package" and mean something else entirely. `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 { autoGenerateCallout = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
@ -708,7 +713,7 @@ in
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
ExecStart = lib.concatStringsSep " " [ 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}" "--nats-url nats://127.0.0.1:${toString cfg.port}"
"--user-seed-file \${CREDENTIALS_DIRECTORY}/callout-user.seed" "--user-seed-file \${CREDENTIALS_DIRECTORY}/callout-user.seed"
"--issuer-seed-file \${CREDENTIALS_DIRECTORY}/issuer.seed" "--issuer-seed-file \${CREDENTIALS_DIRECTORY}/issuer.seed"

View file

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

View file

@ -56,17 +56,11 @@ let
''; '';
in in
{ {
# What stays here is what the store IS from any hive's point of view: its # What stays here is what the store IS from any hive's point of view: the
# package, the name it answers on, the port. `enable` and `retentionPeriod` # name it answers on and the port. `enable`, `package` and
# are decisions of the host that runs it and live under `deploy.*`. # `retentionPeriod` are decisions of the host that runs it and live under
# `deploy.*`.
options.services.hyperhive.swarm.victorialogs = { 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 { machine = lib.mkOption {
type = lib.types.str; type = lib.types.str;
readOnly = true; readOnly = true;
@ -116,6 +110,13 @@ in
# and a hive that is a *client* of the log store never consults it. That # 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 # makes it a `deploy.*` value by the same rule as the seal on the secret
# store — options on the auto-deployed service itself. # 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 { options.services.hyperhive.deploy.victorialogs.retentionPeriod = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "30d"; default = "30d";
@ -259,7 +260,7 @@ in
services.victorialogs = { services.victorialogs = {
enable = true; enable = true;
package = cfg.package; package = deployCfg.victorialogs.package;
# ⚠️ PINNED TO LOOPBACK for the same reason the metrics store is, # ⚠️ PINNED TO LOOPBACK for the same reason the metrics store is,
# and it matters more here: upstream's default listens on every # and it matters more here: upstream's default listens on every

View file

@ -30,17 +30,11 @@ let
domainBase = if swarmDomain == null then "invalid" else swarmDomain; domainBase = if swarmDomain == null then "invalid" else swarmDomain;
in in
{ {
# What stays here is what the store IS from any hive's point of view: its # What stays here is what the store IS from any hive's point of view: the
# package, the name it answers on, the port. `enable` and `retentionPeriod` # name it answers on and the port. `enable`, `package` and
# are decisions of the host that runs it and live under `deploy.*`. # `retentionPeriod` are decisions of the host that runs it and live under
# `deploy.*`.
options.services.hyperhive.swarm.victoriametrics = { 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 { machine = lib.mkOption {
type = lib.types.str; type = lib.types.str;
readOnly = true; readOnly = true;
@ -79,6 +73,13 @@ in
# Retention is a property of the store this host runs, not something the # 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, # 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. # 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 { options.services.hyperhive.deploy.victoriametrics.retentionPeriod = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "5y"; default = "5y";
@ -213,7 +214,7 @@ in
services.victoriametrics = { services.victoriametrics = {
enable = true; enable = true;
package = cfg.package; package = deployCfg.victoriametrics.package;
retentionPeriod = deployCfg.victoriametrics.retentionPeriod; retentionPeriod = deployCfg.victoriametrics.retentionPeriod;
# ⚠️ PINNED TO LOOPBACK, and this is a correction rather than a # ⚠️ PINNED TO LOOPBACK, and this is a correction rather than a

View file

@ -16,7 +16,7 @@
# #
# ⚠️ **This evaluates, it does not execute** — a command line, a request or # ⚠️ **This evaluates, it does not execute** — a command line, a request or
# a certificate needs something that *runs* it. A case needing a rendered # 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, pkgs,
lib, lib,
@ -72,11 +72,12 @@ let
swarm.wireguard.privateKeyFile = "/etc/wireguard/hive.key"; 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 # 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. # single shim entry fails the eval, not just the two the assertion reads.
forgeOldPath = hive { forgeOldPath = hive {
swarm.forge.package = pkgs.emptyDirectory;
swarm.forge.behindGateway = true; swarm.forge.behindGateway = true;
swarm.forge.openFirewall = true; swarm.forge.openFirewall = true;
swarm.forge.hostSwarmControllerTokenFile = "/etc/forge/sc.token"; swarm.forge.hostSwarmControllerTokenFile = "/etc/forge/sc.token";
@ -89,11 +90,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 # 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 # the opposite of its default so the definition has to actually land, and both
# so that dropping any single shim entry fails the eval, not just the two the # packages, stubbed to a derivation neither option defaults to for the same
# assertion reads. # reason. All eight so that dropping any single shim entry fails the eval, not
# just the ones the assertions read.
matrixOldPath = hive { matrixOldPath = hive {
deploy.matrix.enable = true; deploy.matrix.enable = true;
swarm.matrix.openFirewall = true; swarm.matrix.openFirewall = true;
@ -101,26 +103,33 @@ let
swarm.matrix.maxRequestSize = 31457280; swarm.matrix.maxRequestSize = 31457280;
swarm.matrix.registrationTokenFile = "/etc/matrix/register.token"; swarm.matrix.registrationTokenFile = "/etc/matrix/register.token";
swarm.matrix.gui.enable = false; swarm.matrix.gui.enable = false;
swarm.matrix.package = pkgs.emptyDirectory;
swarm.matrix.gui.package = pkgs.emptyDirectory;
swarm.matrix.sso.clientSecretFile = "/etc/matrix/oidc.secret"; swarm.matrix.sso.clientSecretFile = "/etc/matrix/oidc.secret";
}; };
# The queue's callout identity, fourth split slice. `autoGenerateCallout` is # The queue's callout identity, fourth split slice. `autoGenerateCallout` is
# left FALSE on purpose: that is what makes the seed paths the thing deciding # left FALSE on purpose: that is what makes the seed paths the thing deciding
# `responderConfigured`, so the assertion below is about the seeds rather # `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. Every one of the seven old paths is
# dropping any single shim entry fails the eval, not just the arms read. # 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 { natsOldPath = hive {
deploy.nats.enable = true; swarm.nats.enable = true;
swarm.nats.autoGenerateCallout = false; swarm.nats.autoGenerateCallout = false;
swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
swarm.nats.calloutUserSeedFile = "/run/secrets/nats-user.seed"; swarm.nats.calloutUserSeedFile = "/run/secrets/nats-user.seed";
swarm.nats.calloutIssuerSeedFile = "/run/secrets/nats-issuer.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 # Seventh split slice, plus slice 10's two authelia packages. Of slice 7's
# movers are `readOnly`, and a rename module contributes a definition, which # movers only `usersFile` has a rename entry — the other two are `readOnly`,
# a read-only option refuses — see ./host-modules/deploy.nix. So the two arms # and a rename module contributes a definition, which a read-only option
# refuses; see ./host-modules/deploy.nix. `package` and `bridgePackage` are
# ordinary options, so they do carry one. The two arms
# below have different jobs. `usersFile` tests the rename; the nats one tests # below have different jobs. `usersFile` tests the rename; the nats one tests
# that a reader repointed to the new namespace still renders the derived # that a reader repointed to the new namespace still renders the derived
# path, which is the failure this slice could actually have shipped — seven # path, which is the failure this slice could actually have shipped — seven
@ -129,6 +138,8 @@ let
deploy.authelia.enable = true; deploy.authelia.enable = true;
deploy.nats.enable = true; deploy.nats.enable = true;
swarm.authelia.usersFile = "/var/lib/test-authelia/users.yml"; swarm.authelia.usersFile = "/var/lib/test-authelia/users.yml";
swarm.authelia.package = pkgs.emptyDirectory;
swarm.authelia.bridgePackage = pkgs.emptyDirectory;
swarm.nats.autoGenerateCallout = false; swarm.nats.autoGenerateCallout = false;
swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
@ -143,20 +154,47 @@ let
deploy.matrix.maxRequestSize = 99000000; 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 { 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.socketPath = "/run/test-ctrl/ctrl.sock";
swarm.controller.forgeTokenFile = "/run/secrets/ctrl-forge.token"; swarm.controller.forgeTokenFile = "/run/secrets/ctrl-forge.token";
swarm.controller.authBridgeUrl = "http://127.0.0.1:19097"; swarm.controller.authBridgeUrl = "http://127.0.0.1:19097";
swarm.controller.queue.clientSecretFile = "/run/secrets/ctrl-queue.secret"; 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"
# 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 { grafanaOldPath = hive {
deploy.grafana.enable = true; deploy.grafana.enable = true;
swarm.grafana.socketDir = "/run/test-grafana-sock"; swarm.grafana.socketDir = "/run/test-grafana-sock";
swarm.grafana.datasourceUrl = "http://127.0.0.1:19999"; swarm.grafana.datasourceUrl = "http://127.0.0.1:19999";
swarm.grafana.logsDatasourceUrl = "http://127.0.0.1:19998"; swarm.grafana.logsDatasourceUrl = "http://127.0.0.1:19998";
swarm.grafana.plugins = [ ]; swarm.grafana.plugins = [ ];
swarm.grafana.package = pkgs.emptyDirectory;
}; };
# The first slice to leave options on BOTH sides of the split, so the # The first slice to leave options on BOTH sides of the split, so the
@ -472,6 +510,43 @@ let
# host-side half depends on: no store here, so no bind mount and no unit. # 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 # Without it a hive that merely names a token would drag the store's
# container config into its evaluation. # 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.
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"; name = "a bootstrap token on a host that runs no store grants nothing";
ok = !(baoGrantNoStore.systemd.services ? swarm-bao-bootstrap-dir); ok = !(baoGrantNoStore.systemd.services ? swarm-bao-bootstrap-dir);
} }