Watch
0
0
Fork
You've already forked hyperhive
0

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:
atlas 2026-10-13 08:19:35 +02:00 • committed by mara
commit f9954b91b4
10 changed files with 83 additions and 59 deletions

View file

@ -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.
'';
};
};

View file

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

View file

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

View file

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

View file

@ -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.
'';

View file

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