nix: run swarm-ui wherever swarm-controller runs
Remove deploy.swarm-ui.enable. Every reader (the swarm-ui vhost, the authelia operator rule for the swarm apex, hive-network's anyServiceEnabled) now reads deploy.swarm-controller.enable, and the controller's webhook public URL is gated on the swarm domain alone, since the vhost it names exists wherever the daemon does. Both spellings of the old switch (deploy.swarm-ui.enable and the already-renamed swarm.ui.enable) become mkRemovedOptionModule entries, so a host that still sets either fails evaluation with a message naming the controller toggle instead of silently changing. Ruling (mara, #3742): "swarm-ui always runs wherever controller runs". Part of #3742
This commit is contained in:
parent
70c13aee7a
commit
f9954b91b4
10 changed files with 83 additions and 59 deletions
|
|
@ -28,9 +28,13 @@
|
|||
# spread across the service modules, so all of them have a single home
|
||||
# and a single file to delete when the deprecation window closes — the
|
||||
# shape ./swarm-peers-removed.nix already uses.
|
||||
{ lib, config, ... }:
|
||||
{ lib, ... }:
|
||||
let
|
||||
deployCfg = config.services.hyperhive.deploy;
|
||||
# Shared by the two removed spellings of the swarm UI's own switch below.
|
||||
swarmUiSwitchRemoved = ''
|
||||
swarm-ui now runs wherever swarm-controller runs. Remove this
|
||||
definition; services.hyperhive.deploy.swarm-controller.enable decides it.
|
||||
'';
|
||||
|
||||
# Shared by the two removed spellings of the status-publishing secret below.
|
||||
statusSecretRemoved = ''
|
||||
|
|
@ -94,10 +98,20 @@ in
|
|||
be deleted.
|
||||
''
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "ui" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-ui" "enable" ]
|
||||
)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"swarm"
|
||||
"ui"
|
||||
"enable"
|
||||
] swarmUiSwitchRemoved)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"deploy"
|
||||
"swarm-ui"
|
||||
"enable"
|
||||
] swarmUiSwitchRemoved)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "authelia" "enable" ]
|
||||
[ "services" "hyperhive" "deploy" "authelia" "enable" ]
|
||||
|
|
@ -436,9 +450,8 @@ in
|
|||
[ "services" "hyperhive" "swarm" "victoriametrics" "package" ]
|
||||
[ "services" "hyperhive" "deploy" "victoriametrics" "package" ]
|
||||
)
|
||||
# With this one `services.hyperhive.swarm.ui` holds nothing at all — its
|
||||
# two options are both renamed, so the namespace exists only as this
|
||||
# entry and the `enable` one above it.
|
||||
# With this one `services.hyperhive.swarm.ui` holds nothing at all: its
|
||||
# `enable` is removed above and its `package` is renamed here.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "hyperhive" "swarm" "ui" "package" ]
|
||||
[ "services" "hyperhive" "deploy" "swarm-ui" "package" ]
|
||||
|
|
@ -688,23 +701,11 @@ in
|
|||
{option}`services.hyperhive.deploy.hive-controller.enable`: a swarm
|
||||
has one controller, so running it is a decision about this host
|
||||
rather than about whether it runs a hive.
|
||||
'';
|
||||
};
|
||||
|
||||
swarm-ui.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = deployCfg.swarm-controller.enable;
|
||||
defaultText = lib.literalExpression "services.hyperhive.deploy.swarm-controller.enable";
|
||||
example = true;
|
||||
description = ''
|
||||
Serve the swarm UI from this host.
|
||||
|
||||
Derived from
|
||||
{option}`services.hyperhive.deploy.swarm-controller.enable`: the
|
||||
UI is a view onto the controller's state and reaches it over that
|
||||
daemon's unix socket, so the host that runs the controller is the
|
||||
host that can serve the UI. A hive that merely *uses* a swarm has
|
||||
nothing to serve here.
|
||||
Also serves the swarm UI from this host. The UI is a view onto the
|
||||
controller's state and reaches it over that daemon's unix socket,
|
||||
so the host that runs the controller is the only host that can
|
||||
serve it.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -623,7 +623,7 @@ in
|
|||
# swarm's host was the missing one, and every swarm-controller
|
||||
# delivery was refused for it.
|
||||
#
|
||||
# Not gated on `deploy.swarm-ui.enable`: in a spread deployment the
|
||||
# Not gated on `deploy.swarm-controller.enable`: in a spread deployment the
|
||||
# forge and the swarm host are different machines, so that
|
||||
# per-host flag is false exactly where this entry is needed.
|
||||
webhook.ALLOWED_HOST_LIST = lib.concatStringsSep "," (
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ let
|
|||
lib.any lib.id [
|
||||
d.hive-controller.enable
|
||||
d.swarm-controller.enable
|
||||
d.swarm-ui.enable
|
||||
d.forgejo.enable
|
||||
d.ci-runner.enable
|
||||
d.matrix.enable
|
||||
|
|
|
|||
|
|
@ -1107,7 +1107,7 @@ in
|
|||
[ metricsRule ]
|
||||
# Also guarded on the domain being set: without it a null
|
||||
# apex would render a rule matching the string "null".
|
||||
++ lib.optional (deployCfg.swarm-ui.enable && swarmDomain != null) {
|
||||
++ lib.optional (deployCfg.swarm-controller.enable && swarmDomain != null) {
|
||||
domain = swarmDomain;
|
||||
subject = [ "group:${operatorGroup}" ];
|
||||
policy = "one_factor";
|
||||
|
|
|
|||
|
|
@ -204,9 +204,9 @@ let
|
|||
};
|
||||
|
||||
# How the forge must address this controller to deliver a swarm-wide
|
||||
# webhook. Gated on the swarm UI being served *here*, because that module
|
||||
# is what declares the vhost and the `/webhook/forge/` location inside it:
|
||||
# absent, nothing outside this host can reach the endpoint.
|
||||
# webhook. ./swarm-ui.nix declares the vhost and the `/webhook/forge/`
|
||||
# location inside it on every host that runs this daemon, so the only gate
|
||||
# left is a swarm domain to name it by.
|
||||
#
|
||||
# 🔑 The gate is the point, not a detail. The daemon registers hooks only
|
||||
# when this is set, and a hook whose `target_url` nothing answers is worse
|
||||
|
|
@ -216,17 +216,15 @@ let
|
|||
# ⚠️ Correct ONLY while the vhost and this daemon share a host, and they do
|
||||
# by construction: the UI's `/api/` location proxies
|
||||
# `http://unix:<socketPath>`, a path that resolves nowhere else, and this
|
||||
# daemon binds no TCP address at all (see the header). `deploy.swarm-ui`
|
||||
# therefore is not a guess about *some* host publishing the endpoint — it is
|
||||
# the flag that declares that vhost, on the box holding the socket.
|
||||
# daemon binds no TCP address at all (see the header).
|
||||
#
|
||||
# ⇒ The day this daemon grows a TCP listener so another host can front it,
|
||||
# THIS LINE SILENTLY STOPS BEING RIGHT: the env goes unset on the controller
|
||||
# host, registration quietly does not happen, and nothing errors. That is
|
||||
# THIS LINE SILENTLY STOPS BEING RIGHT: hooks get registered against a vhost
|
||||
# that may no longer be the one fronting the daemon, and nothing errors. That is
|
||||
# the moment to add an explicit `publicUrl` option — not before, because
|
||||
# until then there is exactly one derivable answer and an option would only
|
||||
# be a second place to get it wrong.
|
||||
webhookEnv = lib.optionalAttrs (deployCfg.swarm-ui.enable && swarmDomain != null) {
|
||||
webhookEnv = lib.optionalAttrs (swarmDomain != null) {
|
||||
SWARM_CONTROLLER_PUBLIC_URL = "https://${swarmDomain}";
|
||||
};
|
||||
|
||||
|
|
@ -469,8 +467,8 @@ in
|
|||
`services.hyperhive.swarm.bao.ui.domain`, and the Matrix entry
|
||||
from `services.hyperhive.swarm.matrix.gatewayHost` when it is set,
|
||||
so they are present whichever host runs each service.
|
||||
`swarm-ui.nix` contributes its own API-docs entry wherever the
|
||||
swarm UI is enabled on this host.
|
||||
`swarm-ui.nix` contributes its own API-docs entry, since the
|
||||
swarm UI runs wherever the controller does.
|
||||
|
||||
Read only on the host that runs the controller.
|
||||
'';
|
||||
|
|
|
|||
|
|
@ -66,10 +66,10 @@ let
|
|||
apex = if swarmCfg.domain == null then "swarm.invalid" else swarmCfg.domain;
|
||||
in
|
||||
{
|
||||
# `enable` is `services.hyperhive.deploy.swarm-ui.enable` — see
|
||||
# ./deploy.nix, where it derives from the controller's own deploy
|
||||
# toggle for the same reason. There is no `domain` option: the UI answers
|
||||
# on the swarm apex, which the swarm already declares once.
|
||||
# No `enable` of its own: the UI is on wherever
|
||||
# `services.hyperhive.deploy.swarm-controller.enable` is, because it reaches
|
||||
# that daemon over its unix socket. There is no `domain` option: the UI
|
||||
# answers on the swarm apex, which the swarm already declares once.
|
||||
imports = [
|
||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "ui" "domain" ] ''
|
||||
The swarm UI answers on services.hyperhive.swarm.domain and nothing
|
||||
|
|
@ -83,9 +83,9 @@ in
|
|||
'')
|
||||
];
|
||||
|
||||
# ⚠️ `services.hyperhive.swarm.ui` is not a namespace. Its two options,
|
||||
# `enable` and `package`, live under `deploy.swarm-ui`, and the `swarm.ui`
|
||||
# paths survive purely as renames in ./deploy.nix.
|
||||
# ⚠️ `services.hyperhive.swarm.ui` is not a namespace. Its `package` lives
|
||||
# under `deploy.swarm-ui`, and the `swarm.ui` paths survive purely as
|
||||
# rename/removal entries in ./deploy.nix.
|
||||
options.services.hyperhive.deploy.swarm-ui.package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
|
||||
|
|
@ -102,7 +102,7 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
config = lib.mkIf deployCfg.swarm-ui.enable {
|
||||
config = lib.mkIf deployCfg.swarm-controller.enable {
|
||||
assertions = [
|
||||
{
|
||||
# The `_` default server already answers for the hive domain
|
||||
|
|
|
|||
|
|
@ -631,11 +631,11 @@ let
|
|||
ok = !(bare.containers ? swarm-otel) && !(centralToggleOff.containers ? swarm-otel);
|
||||
}
|
||||
{
|
||||
# `deploy.swarm-ui.enable`, which defaults to the controller's toggle —
|
||||
# derived from a sibling deployment decision, still not from the hive
|
||||
# toggle. `t.local` is the fixtures' swarm domain, which is the apex the
|
||||
# UI claims; the arm below is what proves this vhost renders at all.
|
||||
name = "the swarm UI vhost is absent unless this host is told to serve it";
|
||||
# The swarm UI follows `deploy.swarm-controller.enable` — a sibling
|
||||
# deployment decision, not the hive toggle. `t.local` is the fixtures'
|
||||
# swarm domain, which is the apex the UI claims; the arm below is what
|
||||
# proves this vhost renders at all.
|
||||
name = "the swarm UI vhost is absent unless this host runs the swarm controller";
|
||||
ok =
|
||||
!(bare.services.nginx.virtualHosts ? "t.local")
|
||||
&& !(centralToggleOff.services.nginx.virtualHosts ? "t.local");
|
||||
|
|
|
|||
|
|
@ -70,13 +70,19 @@ let
|
|||
network.exposeHostPorts = [ 5432 ];
|
||||
};
|
||||
|
||||
# The swarm UI where its own toggle is on — the control the absence arm
|
||||
# The swarm UI on the controller's host — the control the absence arm
|
||||
# needs, since nothing else in this suite renders this vhost and an arm
|
||||
# saying "it is not there" would hold just as well if it were never there.
|
||||
# Package stubbed per this file's header: the vhost roots at it.
|
||||
#
|
||||
# Also sets both removed UI switches to `false`: the vhost must render
|
||||
# regardless, and each must fail evaluation through its assertion. Neither
|
||||
# is read anywhere else, so they change nothing the other arms see.
|
||||
swarmUiHere = hive {
|
||||
deploy.swarm-ui.enable = true;
|
||||
deploy.swarm-controller.enable = true;
|
||||
deploy.swarm-ui.package = pkgs.emptyDirectory;
|
||||
deploy.swarm-ui.enable = false;
|
||||
swarm.ui.enable = false;
|
||||
};
|
||||
|
||||
# The swarm's shared services hosted HERE without the all-local mode — a
|
||||
|
|
@ -224,7 +230,7 @@ let
|
|||
name = "hosting the swarm's shared services does not make a hive the swarm's control plane";
|
||||
ok =
|
||||
!swarmServicesHere.services.hyperhive.deploy.swarm-controller.enable
|
||||
&& !swarmServicesHere.services.hyperhive.deploy.swarm-ui.enable;
|
||||
&& !(swarmServicesHere.services.nginx.virtualHosts ? "t.local");
|
||||
}
|
||||
{
|
||||
# Control for the arm above, and the one place this slice is not inert:
|
||||
|
|
@ -235,9 +241,29 @@ let
|
|||
ok = builtins.elem 5432 (bridgePortsOrNone exposedPortsNoHive);
|
||||
}
|
||||
{
|
||||
name = "the swarm UI claims the swarm apex where this host serves it";
|
||||
name = "the swarm UI claims the swarm apex wherever this host runs the swarm controller";
|
||||
ok = swarmUiHere.services.nginx.virtualHosts ? "t.local";
|
||||
}
|
||||
{
|
||||
# Control: `swarmServicesHere` sets neither removed switch.
|
||||
name = "a definition of a removed swarm UI switch fails evaluation, naming the option and the controller";
|
||||
ok =
|
||||
let
|
||||
refused =
|
||||
m: path:
|
||||
lib.any (
|
||||
a:
|
||||
!a.assertion
|
||||
&& lib.hasInfix "services.hyperhive.${path}" a.message
|
||||
&& lib.hasInfix "swarm-ui now runs wherever swarm-controller runs" a.message
|
||||
) m.assertions;
|
||||
paths = [
|
||||
"deploy.swarm-ui.enable"
|
||||
"swarm.ui.enable"
|
||||
];
|
||||
in
|
||||
lib.all (refused swarmUiHere) paths && !(lib.any (refused swarmServicesHere) paths);
|
||||
}
|
||||
{
|
||||
# The swarm-services toggle enables them explicitly, from its own
|
||||
# module rather than from any of their defaults.
|
||||
|
|
|
|||
Loading…
Reference in a new issue