Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
10 changed files with 59 additions and 86 deletions

View file

@ -12,7 +12,7 @@ You rarely switch it on yourself. `gateway.enable` defaults to off, and every mo
| URL | upstream | declared by, when | | URL | upstream | declared by, when |
| --- | --- | --- | | --- | --- | --- |
| `<swarm>/` | swarm-ui dist (static), behind an authelia subrequest | `swarm-ui.nix`, `deploy.swarm-controller.enable` | | `<swarm>/` | swarm-ui dist (static), behind an authelia subrequest | `swarm-ui.nix`, `deploy.swarm-ui.enable` |
| `auth.<swarm>/` | authelia (`9091`) | `swarm-authelia.nix`, `deploy.authelia.enable` | | `auth.<swarm>/` | authelia (`9091`) | `swarm-authelia.nix`, `deploy.authelia.enable` |
| `auth.<swarm>/bridge/*` | swarm-authelia-bridge (`9092`), prefix stripped; answers swarm-controller's client only | `swarm-authelia.nix`, `deploy.authelia.enable` | | `auth.<swarm>/bridge/*` | swarm-authelia-bridge (`9092`), prefix stripped; answers swarm-controller's client only | `swarm-authelia.nix`, `deploy.authelia.enable` |
| `forge.<swarm>/` | forgejo (`3000`) | `hive-forge/`, `deploy.forgejo.enable` | | `forge.<swarm>/` | forgejo (`3000`) | `hive-forge/`, `deploy.forgejo.enable` |

View file

@ -334,11 +334,10 @@ Where each credential lives and who reads it:
## Enabling ## Enabling
```nix ```nix
services.hyperhive.deploy.swarm-controller.enable = true; # the UI comes with it services.hyperhive.deploy.swarm-ui.enable = true; # defaults to deploy.swarm-controller.enable
``` ```
The UI has no switch of its own (setting `deploy.swarm-ui.enable` or Derived from the controller rather than from `allSwarmServices`:
`swarm.ui.enable` has no effect, and warns at evaluation), and doesn't follow `allSwarmServices`:
the UI is a view onto the controller's state and reaches it over that the UI is a view onto the controller's state and reaches it over that
daemon's socket, so the host that runs the controller is the host that daemon's 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. can serve the UI. A hive that merely _uses_ a swarm has nothing to serve.

View file

@ -28,23 +28,9 @@
# spread across the service modules, so all of them have a single home # 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 # and a single file to delete when the deprecation window closes — the
# shape ./swarm-peers-removed.nix already uses. # shape ./swarm-peers-removed.nix already uses.
{ config, lib, ... }: { lib, config, ... }:
let let
# The swarm UI's own switch, in both spellings. Accepted and ignored: a deployCfg = config.services.hyperhive.deploy;
# definition only produces a warning, so a host that still sets one keeps
# evaluating. `null` is "not set".
swarmUiSwitchIgnored = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
visible = false;
description = ''
No effect. swarm-ui runs wherever swarm-controller runs.
'';
};
swarmUiSwitchWarning =
path: value:
lib.optional (value != null)
"services.hyperhive.${path} no longer has any effect: swarm-ui now runs wherever swarm-controller runs. Remove this setting; services.hyperhive.deploy.swarm-controller.enable decides it.";
# Shared by the two removed spellings of the status-publishing secret below. # Shared by the two removed spellings of the status-publishing secret below.
statusSecretRemoved = '' statusSecretRemoved = ''
@ -108,6 +94,10 @@ in
be deleted. be deleted.
'' ''
) )
(lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "ui" "enable" ]
[ "services" "hyperhive" "deploy" "swarm-ui" "enable" ]
)
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "authelia" "enable" ] [ "services" "hyperhive" "swarm" "authelia" "enable" ]
[ "services" "hyperhive" "deploy" "authelia" "enable" ] [ "services" "hyperhive" "deploy" "authelia" "enable" ]
@ -446,8 +436,9 @@ in
[ "services" "hyperhive" "swarm" "victoriametrics" "package" ] [ "services" "hyperhive" "swarm" "victoriametrics" "package" ]
[ "services" "hyperhive" "deploy" "victoriametrics" "package" ] [ "services" "hyperhive" "deploy" "victoriametrics" "package" ]
) )
# With this one `services.hyperhive.swarm.ui` holds nothing at all: its # With this one `services.hyperhive.swarm.ui` holds nothing at all — its
# `enable` is an ignored switch (declared below) and its `package` is renamed here. # two options are both renamed, so the namespace exists only as this
# entry and the `enable` one above it.
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "ui" "package" ] [ "services" "hyperhive" "swarm" "ui" "package" ]
[ "services" "hyperhive" "deploy" "swarm-ui" "package" ] [ "services" "hyperhive" "deploy" "swarm-ui" "package" ]
@ -486,14 +477,7 @@ in
# ⚠️ Only `deploy.forgejo.enable` is declared here. The rest of # ⚠️ Only `deploy.forgejo.enable` is declared here. The rest of
# `deploy.forgejo` is in ./hive-forge/default.nix: the knobs live with the # `deploy.forgejo` is in ./hive-forge/default.nix: the knobs live with the
# module that reads them; this file stays the registry of toggles. # module that reads them; this file stays the registry of toggles.
options.services.hyperhive.swarm.ui.enable = swarmUiSwitchIgnored;
config.warnings =
swarmUiSwitchWarning "deploy.swarm-ui.enable" config.services.hyperhive.deploy.swarm-ui.enable
++ swarmUiSwitchWarning "swarm.ui.enable" config.services.hyperhive.swarm.ui.enable;
options.services.hyperhive.deploy = { options.services.hyperhive.deploy = {
swarm-ui.enable = swarmUiSwitchIgnored;
forgejo.enable = lib.mkOption { forgejo.enable = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
@ -704,11 +688,23 @@ in
{option}`services.hyperhive.deploy.hive-controller.enable`: a swarm {option}`services.hyperhive.deploy.hive-controller.enable`: a swarm
has one controller, so running it is a decision about this host has one controller, so running it is a decision about this host
rather than about whether it runs a hive. rather than about whether it runs a hive.
'';
};
Also serves the swarm UI from this host. The UI is a view onto the swarm-ui.enable = lib.mkOption {
controller's state and reaches it over that daemon's unix socket, type = lib.types.bool;
so the host that runs the controller is the only host that can default = deployCfg.swarm-controller.enable;
serve it. 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.
''; '';
}; };
}; };

View file

@ -623,7 +623,7 @@ in
# swarm's host was the missing one, and every swarm-controller # swarm's host was the missing one, and every swarm-controller
# delivery was refused for it. # delivery was refused for it.
# #
# Not gated on `deploy.swarm-controller.enable`: in a spread deployment the # Not gated on `deploy.swarm-ui.enable`: in a spread deployment the
# forge and the swarm host are different machines, so that # forge and the swarm host are different machines, so that
# per-host flag is false exactly where this entry is needed. # per-host flag is false exactly where this entry is needed.
webhook.ALLOWED_HOST_LIST = lib.concatStringsSep "," ( webhook.ALLOWED_HOST_LIST = lib.concatStringsSep "," (

View file

@ -40,6 +40,7 @@ let
lib.any lib.id [ lib.any lib.id [
d.hive-controller.enable d.hive-controller.enable
d.swarm-controller.enable d.swarm-controller.enable
d.swarm-ui.enable
d.forgejo.enable d.forgejo.enable
d.ci-runner.enable d.ci-runner.enable
d.matrix.enable d.matrix.enable

View file

@ -1107,7 +1107,7 @@ in
[ metricsRule ] [ metricsRule ]
# Also guarded on the domain being set: without it a null # Also guarded on the domain being set: without it a null
# apex would render a rule matching the string "null". # apex would render a rule matching the string "null".
++ lib.optional (deployCfg.swarm-controller.enable && swarmDomain != null) { ++ lib.optional (deployCfg.swarm-ui.enable && swarmDomain != null) {
domain = swarmDomain; domain = swarmDomain;
subject = [ "group:${operatorGroup}" ]; subject = [ "group:${operatorGroup}" ];
policy = "one_factor"; policy = "one_factor";

View file

@ -204,9 +204,9 @@ let
}; };
# How the forge must address this controller to deliver a swarm-wide # How the forge must address this controller to deliver a swarm-wide
# webhook. ./swarm-ui.nix declares the vhost and the `/webhook/forge/` # webhook. Gated on the swarm UI being served *here*, because that module
# location inside it on every host that runs this daemon, so the only gate # is what declares the vhost and the `/webhook/forge/` location inside it:
# left is a swarm domain to name it by. # absent, nothing outside this host can reach the endpoint.
# #
# 🔑 The gate is the point, not a detail. The daemon registers hooks only # 🔑 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 # when this is set, and a hook whose `target_url` nothing answers is worse
@ -216,15 +216,17 @@ let
# ⚠️ Correct ONLY while the vhost and this daemon share a host, and they do # ⚠️ Correct ONLY while the vhost and this daemon share a host, and they do
# by construction: the UI's `/api/` location proxies # by construction: the UI's `/api/` location proxies
# `http://unix:<socketPath>`, a path that resolves nowhere else, and this # `http://unix:<socketPath>`, a path that resolves nowhere else, and this
# daemon binds no TCP address at all (see the header). # 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.
# #
# ⇒ The day this daemon grows a TCP listener so another host can front it, # ⇒ The day this daemon grows a TCP listener so another host can front it,
# THIS LINE SILENTLY STOPS BEING RIGHT: hooks get registered against a vhost # THIS LINE SILENTLY STOPS BEING RIGHT: the env goes unset on the controller
# that may no longer be the one fronting the daemon, and nothing errors. That is # host, registration quietly does not happen, and nothing errors. That is
# the moment to add an explicit `publicUrl` option — not before, because # the moment to add an explicit `publicUrl` option — not before, because
# until then there is exactly one derivable answer and an option would only # until then there is exactly one derivable answer and an option would only
# be a second place to get it wrong. # be a second place to get it wrong.
webhookEnv = lib.optionalAttrs (swarmDomain != null) { webhookEnv = lib.optionalAttrs (deployCfg.swarm-ui.enable && swarmDomain != null) {
SWARM_CONTROLLER_PUBLIC_URL = "https://${swarmDomain}"; SWARM_CONTROLLER_PUBLIC_URL = "https://${swarmDomain}";
}; };
@ -467,8 +469,8 @@ in
`services.hyperhive.swarm.bao.ui.domain`, and the Matrix entry `services.hyperhive.swarm.bao.ui.domain`, and the Matrix entry
from `services.hyperhive.swarm.matrix.gatewayHost` when it is set, from `services.hyperhive.swarm.matrix.gatewayHost` when it is set,
so they are present whichever host runs each service. so they are present whichever host runs each service.
`swarm-ui.nix` contributes its own API-docs entry, since the `swarm-ui.nix` contributes its own API-docs entry wherever the
swarm UI runs wherever the controller does. swarm UI is enabled on this host.
Read only on the host that runs the controller. 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; apex = if swarmCfg.domain == null then "swarm.invalid" else swarmCfg.domain;
in in
{ {
# No `enable` of its own: the UI is on wherever # `enable` is `services.hyperhive.deploy.swarm-ui.enable` — see
# `services.hyperhive.deploy.swarm-controller.enable` is, because it reaches # ./deploy.nix, where it derives from the controller's own deploy
# that daemon over its unix socket. There is no `domain` option: the UI # toggle for the same reason. There is no `domain` option: the UI answers
# answers on the swarm apex, which the swarm already declares once. # on the swarm apex, which the swarm already declares once.
imports = [ imports = [
(lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "ui" "domain" ] '' (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "ui" "domain" ] ''
The swarm UI answers on services.hyperhive.swarm.domain and nothing 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 `package` lives # ⚠️ `services.hyperhive.swarm.ui` is not a namespace. Its two options,
# under `deploy.swarm-ui`, and the `swarm.ui` paths survive purely as # `enable` and `package`, live under `deploy.swarm-ui`, and the `swarm.ui`
# rename/removal entries in ./deploy.nix. # paths survive purely as renames in ./deploy.nix.
options.services.hyperhive.deploy.swarm-ui.package = lib.mkOption { options.services.hyperhive.deploy.swarm-ui.package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui"; defaultText = lib.literalExpression "hyperhive.packages.\${system}.swarm-ui";
@ -102,7 +102,7 @@ in
''; '';
}; };
config = lib.mkIf deployCfg.swarm-controller.enable { config = lib.mkIf deployCfg.swarm-ui.enable {
assertions = [ assertions = [
{ {
# The `_` default server already answers for the hive domain # The `_` default server already answers for the hive domain

View file

@ -631,11 +631,11 @@ let
ok = !(bare.containers ? swarm-otel) && !(centralToggleOff.containers ? swarm-otel); ok = !(bare.containers ? swarm-otel) && !(centralToggleOff.containers ? swarm-otel);
} }
{ {
# The swarm UI follows `deploy.swarm-controller.enable` — a sibling # `deploy.swarm-ui.enable`, which defaults to the controller's toggle —
# deployment decision, not the hive toggle. `t.local` is the fixtures' # derived from a sibling deployment decision, still not from the hive
# swarm domain, which is the apex the UI claims; the arm below is what # toggle. `t.local` is the fixtures' swarm domain, which is the apex the
# proves this vhost renders at all. # 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"; name = "the swarm UI vhost is absent unless this host is told to serve it";
ok = ok =
!(bare.services.nginx.virtualHosts ? "t.local") !(bare.services.nginx.virtualHosts ? "t.local")
&& !(centralToggleOff.services.nginx.virtualHosts ? "t.local"); && !(centralToggleOff.services.nginx.virtualHosts ? "t.local");

View file

@ -70,19 +70,13 @@ let
network.exposeHostPorts = [ 5432 ]; network.exposeHostPorts = [ 5432 ];
}; };
# The swarm UI on the controller's host — the control the absence arm # The swarm UI where its own toggle is on — the control the absence arm
# needs, since nothing else in this suite renders this vhost and an 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. # 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. # Package stubbed per this file's header: the vhost roots at it.
#
# Also sets both ignored UI switches to `false`: the vhost must render
# regardless, and each must evaluate and warn. Neither
# is read anywhere else, so they change nothing the other arms see.
swarmUiHere = hive { swarmUiHere = hive {
deploy.swarm-controller.enable = true; deploy.swarm-ui.enable = true;
deploy.swarm-ui.package = pkgs.emptyDirectory; 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 # The swarm's shared services hosted HERE without the all-local mode — a
@ -230,7 +224,7 @@ let
name = "hosting the swarm's shared services does not make a hive the swarm's control plane"; name = "hosting the swarm's shared services does not make a hive the swarm's control plane";
ok = ok =
!swarmServicesHere.services.hyperhive.deploy.swarm-controller.enable !swarmServicesHere.services.hyperhive.deploy.swarm-controller.enable
&& !(swarmServicesHere.services.nginx.virtualHosts ? "t.local"); && !swarmServicesHere.services.hyperhive.deploy.swarm-ui.enable;
} }
{ {
# Control for the arm above, and the one place this slice is not inert: # Control for the arm above, and the one place this slice is not inert:
@ -241,28 +235,9 @@ let
ok = builtins.elem 5432 (bridgePortsOrNone exposedPortsNoHive); ok = builtins.elem 5432 (bridgePortsOrNone exposedPortsNoHive);
} }
{ {
name = "the swarm UI claims the swarm apex wherever this host runs the swarm controller"; name = "the swarm UI claims the swarm apex where this host serves it";
ok = swarmUiHere.services.nginx.virtualHosts ? "t.local"; ok = swarmUiHere.services.nginx.virtualHosts ? "t.local";
} }
{
# Control: `swarmServicesHere` sets neither ignored switch.
name = "a definition of an ignored swarm UI switch evaluates and warns, naming the option and the controller";
ok =
let
warned =
m: path:
lib.any (
w:
lib.hasInfix "services.hyperhive.${path} no longer has any effect" w
&& lib.hasInfix "swarm-ui now runs wherever swarm-controller runs" w
) m.warnings;
paths = [
"deploy.swarm-ui.enable"
"swarm.ui.enable"
];
in
lib.all (warned swarmUiHere) paths && !(lib.any (warned swarmServicesHere) paths);
}
{ {
# The swarm-services toggle enables them explicitly, from its own # The swarm-services toggle enables them explicitly, from its own
# module rather than from any of their defaults. # module rather than from any of their defaults.