Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
14 changed files with 253 additions and 401 deletions

View file

@ -3,10 +3,9 @@
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 — names, ports, URLs) plus forge IS to every hive — its package, names, ports, URLs) plus
`services.hyperhive.deploy.forgejo.*` (what the host running it decides — `services.hyperhive.deploy.forgejo.*` (what the host running it decides —
which build it runs, how it's served, what it mirrors, where its how it's served, what it mirrors, where its host-local secrets sit).
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.deploy.matrix.gui.package`) served by the hive-gateway `services.hyperhive.swarm.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.deploy.swarm-controller.package = services.hyperhive.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.deploy.swarm-controller.swarmctlPackage = services.hyperhive.swarm.controller.swarmctlPackage =
lib.mkDefault lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl; self.packages.${pkgs.stdenv.hostPlatform.system}.swarmctl;
services.hyperhive.deploy.swarm-ui.package = services.hyperhive.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.deploy.nats.authPackage = services.hyperhive.swarm.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.deploy.authelia.bridgePackage = services.hyperhive.swarm.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,10 +164,9 @@ 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 and the client id it is # queue IS to every hive — the ports it answers on, the client id it is
# registered under; these five are what the host running it decides. The # registered under, the responder package; these five are what the host
# responder package is one of them now, further down with the other # running it decides.
# `*.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
@ -196,10 +195,9 @@ 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 — the names and ports it answers # forge IS from any hive's point of view — its package, the names and ports
# on, the URLs it advertises, the client id it is registered under; these # it answers on, the URLs it advertises, the client id it is registered
# five are what the host running it decides. Its package moved as well, # under; these five are what the host running it decides. `sso` splits
# 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.
# #
@ -229,10 +227,9 @@ 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 — the name it answers to, the ports and URLs it is reached # every hive — its package, the name it answers to, the ports and URLs it
# on, the client id it is registered under. These six are what the host # is reached on, the client id it is registered under. These six are what
# running it decides; its two packages moved as well, further down with # the host running it decides. ⚠️ `enable` is NOT here: it was renamed
# 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
@ -262,9 +259,8 @@ 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 their domain and port in `swarm.*`, # agrees on. The two stores keep everything else — package, domain, port
# because a client hive needs those to reach them; `package` followed # — in `swarm.*`, because a client hive needs those to reach them.
# `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" ]
@ -339,68 +335,6 @@ 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,6 +232,21 @@ 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;
@ -279,28 +294,13 @@ in
}; };
}; };
# What stays above is what the forge IS from any hive's point of view: the # What stays above is what the forge IS from any hive's point of view: its
# names and ports it answers on, the URLs it advertises, and the client id # package, the names and ports it answers on, the URLs it advertises, and
# it is registered under. What lives here is what the host running it # the client id it is registered under. What lives here is what the host
# decides — which build it runs, how it is served, what it mirrors, and # running it decides — how it is served, what it mirrors, and where its
# where its host-local secrets sit. Same rule as ./swarm-victorialogs.nix, # host-local secrets sit. Same rule as ./swarm-victorialogs.nix, and the
# and the renames are in ./deploy.nix with the rest. # 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 ${deployCfg.forgejo.package.data}/. $out/ cp -r --no-preserve=mode,ownership ${cfg.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 = deployCfg.forgejo.package; package = cfg.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 = [
deployCfg.forgejo.package cfg.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 = [
deployCfg.forgejo.package cfg.package
pkgs.coreutils pkgs.coreutils
pkgs.gnugrep pkgs.gnugrep
]; ];

View file

@ -172,9 +172,8 @@ 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. What stays under # namespace every host in the swarm agrees on. The rest of the matrix
# `swarm.matrix` is the homeserver's identity as every hive sees it — # settings genuinely are swarm-wide and stay where they are.
# 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" ]
@ -192,6 +191,17 @@ 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;
@ -328,6 +338,26 @@ 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
@ -368,24 +398,13 @@ 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:
# the name it answers to, the ports and URLs it is reached on, and the client # its package, the name it answers to, the ports and URLs it is reached on,
# id it is registered under. What lives here is what the host running it # and the client id it is registered under. What lives here is what the host
# decides — which build it runs, whether it is exposed, which peers it trusts, # running it decides — whether it is exposed, which peers it trusts, how large
# how large a request it accepts, and where its host-local secrets sit. Same rule as # 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;
@ -490,28 +509,11 @@ 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
`deploy.matrix.gui.package` — which client, as opposed to whether `swarm.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;
@ -632,7 +634,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 = "${deployCfg.matrix.gui.package}/"; alias = "${cfg.gui.package}/";
extraConfig = '' extraConfig = ''
try_files $uri $uri/ $matrix_spa_target =404; try_files $uri $uri/ $matrix_spa_target =404;
''; '';
@ -997,7 +999,7 @@ in
services.matrix-tuwunel = { services.matrix-tuwunel = {
enable = true; enable = true;
package = deployCfg.matrix.package; package = cfg.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.
@ -1082,7 +1084,7 @@ in
"oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}" "oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}"
]; ];
environment.systemPackages = [ deployCfg.matrix.package ]; environment.systemPackages = [ cfg.package ];
}; };
}; };

View file

@ -320,12 +320,22 @@ let
''; '';
in in
{ {
# `enable` and both packages moved to `services.hyperhive.deploy.authelia` # `enable` moved to `services.hyperhive.deploy.authelia.enable` — see
# — see ./deploy.nix. Whether this host runs the swarm's SSO provider, and # ./deploy.nix. Whether this host runs the swarm's SSO provider is a
# which build it runs, are deployment decisions; what stays here is what # deployment decision; what stays here is what authelia IS, including
# authelia IS, including `url` and the OIDC client registry every hive needs # `url` and the OIDC client registry every hive needs as a *client*
# as a *client* whether or not it runs the container. # 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;
@ -689,6 +699,17 @@ 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;
@ -729,37 +750,16 @@ 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 — which two builds it # What the host running the container decides is here, and all three of
# runs, and three filesystem paths that only exist on the machine running # these are the same kind of thing — a filesystem path that only exists on
# `swarm-authelia`. A hive that does not run it has nothing at any of those # the machine that runs `swarm-authelia`. A hive that does not run it has
# paths. `enable` already lives in ./deploy.nix, which also carries the # nothing at any of them. `enable` already lives in ./deploy.nix, which
# renames. # also carries the 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 = [ deployCfg.authelia.package ]; environment.systemPackages = [ cfg.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" ];
# `deployCfg.authelia.package` is here for its CLI, not its # `cfg.package` is here for its CLI, not its daemon: the
# daemon: the client secrets are minted with `authelia crypto # client secrets are minted with `authelia crypto hash
# hash generate`, which is the only way to produce a digest in # 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
deployCfg.authelia.package cfg.package
]; ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
@ -1216,7 +1216,7 @@ in
"${unitName}.service" "${unitName}.service"
]; ];
serviceConfig = { serviceConfig = {
ExecStart = "${deployCfg.authelia.bridgePackage}/bin/swarm-authelia-bridge"; ExecStart = "${cfg.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 = "${deployCfg.authelia.package}/bin/authelia"; SWARM_AUTHELIA_BRIDGE_AUTHELIA_BIN = "${cfg.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 = deployCfg.authelia.package; package = cfg.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 = "${deployCfg.authelia.package}/bin/authelia"; SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.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 = [ deployCfg.swarm-controller.swarmctlPackage ]; paths = [ cfg.swarmctlPackage ];
nativeBuildInputs = [ pkgs.makeWrapper ]; nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = '' postBuild = ''
wrapProgram $out/bin/swarmctl ${ wrapProgram $out/bin/swarmctl ${
@ -243,8 +243,30 @@ 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. Both packages moved to that same # `enable` alone never can. What stays here is what the daemon IS.
# `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;
@ -349,39 +371,16 @@ 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, and how stale a # it publishes, the client id it is registered under, how stale a snapshot
# snapshot may read. What the host running it decides is here — which two # may read, and its packages. What the host running it decides is here —
# builds it runs, where its socket lives, and the three credentials it reads # where its socket lives, and the three credentials it reads off local
# off local disk. `enable` already lives in ./deploy.nix, which also carries # disk. `enable` already lives in ./deploy.nix, which also carries the
# the renames. # 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";
@ -592,7 +591,7 @@ in
after = [ "network.target" ]; after = [ "network.target" ];
serviceConfig = { serviceConfig = {
ExecStart = "${deployCfg.swarm-controller.package}/bin/swarm-controller"; ExecStart = "${cfg.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,13 +111,20 @@ 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: the name it is # What stays here is what the service IS to every hive: its package, the
# served under, the port its `/metrics` is re-served on, and the OIDC # name it is served under, the port its `/metrics` is re-served on, and
# client it is registered as. What the host running it decides — which # the OIDC client it is registered as. What the host running it decides —
# build it runs, where its datasources point, which plugins are in its # where its datasources point, which plugins are in its store path, and
# store path, and the socket directory it shares with nginx — is below, # the socket directory it shares with nginx — is below, under
# under `deploy.grafana`. # `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;
@ -210,22 +217,15 @@ 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: which build it runs, where its datasources # running it decides is here: where its datasources point, which plugins sit
# point, which plugins sit in its store path, and the directory it shares a # in its store path, and the directory it shares a socket with nginx
# socket with nginx through. `enable` already lives in ./deploy.nix, which # through. `enable` already lives in ./deploy.nix, which also carries the
# also carries the renames. # 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 = deployCfg.grafana.package; package = cfg.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,14 +217,12 @@ 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 for the NATS server, anywhere — # ⚠️ Deliberately NO `package` option, unlike this module's siblings.
# not here and not under `deploy.nats` either, where every other # `services.nats` upstream does not expose one — it resolves
# service's build now lives. `services.nats` upstream does not expose # `pkgs.nats-server` itself — so an option here would either be
# one; it resolves `pkgs.nats-server` itself, so an option would be # ignored or need an overlay to mean anything, and an option that
# ignored or need an overlay to mean anything, and an option that does # does not control what it names is worse than its absence. Pin the
# not control what it names is worse than its absence. Pin the build # build with `nixpkgs.overlays` if you need to.
# 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;
@ -286,19 +284,6 @@ 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";
@ -306,15 +291,25 @@ 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 NATS server deliberately has **no** `package` option for the server itself
itself, because upstream's `services.nats` resolves (see the note above — upstream's `services.nats` resolves
`pkgs.nats-server` on its own — see the note above `pkgs.nats-server` on its own), so a bare `package` here would
`options.services.hyperhive.swarm.nats`. A bare `package` here read as "the NATS package" and mean something else entirely.
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;
@ -713,7 +708,7 @@ in
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
ExecStart = lib.concatStringsSep " " [ ExecStart = lib.concatStringsSep " " [
"${deployCfg.nats.authPackage}/bin/swarm-nats-auth" "${cfg.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,6 +13,7 @@
... ...
}: }:
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;
@ -21,11 +22,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 `deployCfg.swarm-ui.package`'s own. # ever served a themed `colors.css` in place of `cfg.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 `deployCfg.swarm-ui.package` (unlike hive-c0re's `servedFrontend`, which # serving `cfg.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
@ -78,23 +79,22 @@ in
'') '')
]; ];
# ⚠️ `services.hyperhive.swarm.ui` no longer exists as a namespace. `enable` options.services.hyperhive.swarm.ui = {
# moved in an earlier slice and `package` in this one, and those were its package = lib.mkOption {
# only two options — so both paths survive purely as renames in ./deploy.nix. type = lib.types.package;
options.services.hyperhive.deploy.swarm-ui.package = lib.mkOption { defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
type = lib.types.package; description = ''
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui"; Static build of the swarm UI. nginx serves this store path
description = '' directly — there is no server-side component beyond the
Static build of the swarm UI. nginx serves this store path controller's own API.
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 `deploy.swarm-controller.package` is. There `flake.nix`), the same way `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 = "${deployCfg.swarm-ui.package}"; root = "${cfg.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 `deployCfg.swarm-ui.package`'s own static root, rather # this one file from `cfg.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,11 +56,17 @@ let
''; '';
in in
{ {
# What stays here is what the store IS from any hive's point of view: the # What stays here is what the store IS from any hive's point of view: its
# name it answers on and the port. `enable`, `package` and # package, the name it answers on, the port. `enable` and `retentionPeriod`
# `retentionPeriod` are decisions of the host that runs it and live under # are decisions of the host that runs it and live under `deploy.*`.
# `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;
@ -110,13 +116,6 @@ 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";
@ -260,7 +259,7 @@ in
services.victorialogs = { services.victorialogs = {
enable = true; enable = true;
package = deployCfg.victorialogs.package; package = cfg.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,11 +30,17 @@ 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: the # What stays here is what the store IS from any hive's point of view: its
# name it answers on and the port. `enable`, `package` and # package, the name it answers on, the port. `enable` and `retentionPeriod`
# `retentionPeriod` are decisions of the host that runs it and live under # are decisions of the host that runs it and live under `deploy.*`.
# `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;
@ -73,13 +79,6 @@ 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";
@ -214,7 +213,7 @@ in
services.victoriametrics = { services.victoriametrics = {
enable = true; enable = true;
package = deployCfg.victoriametrics.package; package = cfg.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 (`deploy.swarm-ui.package = pkgs.emptyDirectory`). # file must stub what it drags in (`swarm.ui.package = pkgs.emptyDirectory`).
{ {
pkgs, pkgs,
lib, lib,
@ -72,12 +72,11 @@ 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 six # Same shape for the forge, which SPLIT rather than moving whole: the five
# 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 six are defined so that dropping any # rest of `swarm.forge` stays put. All five 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";
@ -90,12 +89,11 @@ let
]; ];
}; };
# The homeserver's turn to split. All eight host-side options are set through # The homeserver's turn to split. All six 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, and both # the opposite of its default so the definition has to actually land. All six
# packages, stubbed to a derivation neither option defaults to for the same # so that dropping any single shim entry fails the eval, not just the two the
# reason. All eight so that dropping any single shim entry fails the eval, not # assertion reads.
# 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;
@ -103,33 +101,26 @@ 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. Every one of the seven old paths is # than about the auto-mint branch. All five old paths are defined, so
# defined — `enable` included, which is why it is spelled the old way here # dropping any single shim entry fails the eval, not just the arms read.
# 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 {
swarm.nats.enable = true; deploy.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, plus slice 10's two authelia packages. Of slice 7's # Seventh split slice. Only `usersFile` has a rename entry: the other two
# movers only `usersFile` has a rename entry — the other two are `readOnly`, # movers are `readOnly`, and a rename module contributes a definition, which
# and a rename module contributes a definition, which a read-only option # a read-only option refuses — see ./host-modules/deploy.nix. So the two arms
# 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
@ -138,8 +129,6 @@ 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";
@ -154,47 +143,20 @@ 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 {
swarm.controller.enable = true; deploy.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
@ -510,43 +472,6 @@ 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);
} }