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
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

View file

@ -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

View file

@ -142,19 +142,19 @@
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.swarm.ui.package =
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 =
services.hyperhive.deploy.authelia.bridgePackage =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarm-authelia-bridge;
services.hyperhive.gateway.swaggerUiTheme =

View file

@ -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
@ -195,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.
#
@ -227,9 +229,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
@ -259,8 +262,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" ]
@ -335,6 +339,68 @@ 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" ]
)
(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

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 {
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
];

View file

@ -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.<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
# 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.<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 {
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 ];
};
};

View file

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

View file

@ -28,7 +28,7 @@ let
autheliaEnv = lib.optionalAttrs deployCfg.authelia.enable {
# The CONFIGURED authelia, not whatever is on PATH: the argon2
# parameters baked into a hash have to match the verifier's.
SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.package}/bin/authelia";
SWARMCTL_AUTHELIA_BIN = "${deployCfg.authelia.package}/bin/authelia";
SWARMCTL_AUTHELIA_USERS_FILE = deployCfg.authelia.hostUsersFile;
};
@ -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

View file

@ -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

View file

@ -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"

View file

@ -13,7 +13,6 @@
...
}:
let
cfg = config.services.hyperhive.swarm.ui;
deployCfg = config.services.hyperhive.deploy;
gatewayCfg = config.services.hyperhive.gateway;
autheliaCfg = config.services.hyperhive.swarm.authelia;
@ -22,11 +21,11 @@ let
# this UI was left out of that overlay entirely (an operator with a
# stylix-themed host saw the dashboard in their own colours but this
# UI still on the default Catppuccin palette), because nothing here
# ever served a themed `colors.css` in place of `cfg.package`'s own.
# ever served a themed `colors.css` in place of `deployCfg.swarm-ui.package`'s own.
# Detection + CSS-generation is shared (`./stylix-theme.nix`).
#
# No package-copy derivation: this vhost has exactly one location
# serving `cfg.package` (unlike hive-c0re's `servedFrontend`, which
# serving `deployCfg.swarm-ui.package` (unlike hive-c0re's `servedFrontend`, which
# backs both the dashboard root AND every per-agent gateway route, so
# a single swapped tree covers both) — an `= /static/colors.css`
# exact-match location overriding just that one file, same idiom
@ -79,8 +78,10 @@ in
'')
];
options.services.hyperhive.swarm.ui = {
package = lib.mkOption {
# ⚠️ `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 = ''
@ -89,13 +90,12 @@ 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.
'';
};
};
config = lib.mkIf (config.services.hyperhive.enable && deployCfg.swarm-ui.enable) {
assertions = [
@ -178,7 +178,7 @@ in
extraConfig = gatewayCfg.lib.securityHeaders;
locations = {
"/" = {
root = "${cfg.package}";
root = "${deployCfg.swarm-ui.package}";
extraConfig = ''
${swarmAuthRequest}
# SPA: any path the bundle routes client-side is served the
@ -189,7 +189,7 @@ in
}
// lib.optionalAttrs (stylixThemeColors != null) {
# Stylix theming (see the `let` block above): overrides just
# this one file from `cfg.package`'s own static root, rather
# this one file from `deployCfg.swarm-ui.package`'s own static root, rather
# than copying the whole tree to change one file inside it —
# nginx resolves the more specific `=` exact match over the
# `/` prefix root above, so this simply doesn't exist (falling

View file

@ -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

View file

@ -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

View file

@ -16,7 +16,7 @@
#
# ⚠️ **This evaluates, it does not execute** — a command line, a request or
# a certificate needs something that *runs* it. A case needing a rendered
# file must stub what it drags in (`swarm.ui.package = pkgs.emptyDirectory`).
# file must stub what it drags in (`deploy.swarm-ui.package = pkgs.emptyDirectory`).
{
pkgs,
lib,
@ -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";
@ -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
# 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,26 +103,33 @@ 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";
};
# 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
# 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";
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
# 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
@ -129,6 +138,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";
@ -143,20 +154,47 @@ 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";
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 {
deploy.grafana.enable = true;
swarm.grafana.socketDir = "/run/test-grafana-sock";
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
@ -472,6 +510,43 @@ let
# host-side half depends on: no store here, so no bind mount and no unit.
# Without it a hive that merely names a token would drag the store's
# container config into its evaluation.
# Reads the vhost's rendered `root`, not the option: the UI is served
# straight out of a store path, so a shim that resolves but stops
# reaching the module would leave nginx pointing at the default build.
name = "a config written against the pre-rename swarm-ui paths still serves the operator's build";
#
# ⚠️ `unsafeDiscardStringContext` on both sides, load-bearing rather than
# tidy: interpolating a derivation carries string *context*, and this
# suite renders its results into a `buildCommand` that may not reference
# store paths. Comparing the paths as plain text is the intent — the case
# asks "does nginx point HERE", not "depend on what it points at".
ok =
let
want = builtins.unsafeDiscardStringContext "${pkgs.emptyDirectory}";
# `or ""` is NOT enough: nginx's `locations.<l>.root` is `nullOr`, so
# a vhost that HAS the attribute set to null skips the default and
# reaches the coercion. Filter nulls, then discard context.
roots = map builtins.unsafeDiscardStringContext (
lib.filter (r: r != null) (
map (v: v.locations."/".root or null) (builtins.attrValues uiOldPath.services.nginx.virtualHosts)
)
);
in
builtins.elem want roots;
}
{
# Reads the package the CONTAINER renders, not the option: a shim that
# resolves but stops reaching the module would leave the store running
# nixpkgs' default while the operator's override read back fine.
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);
}