diff --git a/nix/host-modules/default.nix b/nix/host-modules/default.nix index 40015bad..e303ac84 100644 --- a/nix/host-modules/default.nix +++ b/nix/host-modules/default.nix @@ -12,6 +12,7 @@ { imports = [ ./hyperhive.nix + ./local-defaults.nix ./hive-c0re ./hive-ci.nix ./hive-forge @@ -27,5 +28,6 @@ ./swarm-snapshot-store.nix ./swarm-wireguard.nix ./swarm.nix + ./swarm-required-services.nix ]; } diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index f737c9fb..6e98114a 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -99,19 +99,16 @@ in options.services.hyperhive.swarm.matrix = { enable = lib.mkOption { type = lib.types.bool; - default = config.services.hyperhive.swarm.enableRequiredServices; - defaultText = lib.literalExpression "services.hyperhive.swarm.enableRequiredServices"; + default = false; description = '' Run hive-matrix — a private matrix-tuwunel homeserver (in a nixos-container) for hyperhive agents. Matrix is a swarm-wide service — one homeserver, not one per - hive — so this defaults from - `services.hyperhive.swarm.enableRequiredServices`, which says - the swarm's shared services live on this host. That is off by - default, so this is off by default, as before. Set it directly - to run the homeserver somewhere other than the host that holds - the rest of the swarm's services. + hive — so `services.hyperhive.swarm.enableRequiredServices` + turns this on as part of saying the swarm's shared services live + on this host. Set it here directly to run the homeserver + somewhere other than the host that holds the rest of them. ''; }; diff --git a/nix/host-modules/hyperhive.nix b/nix/host-modules/hyperhive.nix index d370aeab..c080e780 100644 --- a/nix/host-modules/hyperhive.nix +++ b/nix/host-modules/hyperhive.nix @@ -132,31 +132,9 @@ in ''; }; - # The one switch for "everything runs on this box". Every autoconf - # toggle in the tree defaults from it, so an all-local deployment is a - # single line rather than one line per service that grew a toggle. - options.services.hyperhive.enableAllLocalDefaults = lib.mkOption { - type = lib.types.bool; - default = false; - example = true; - description = '' - Run the whole swarm on this host: the swarm-wide services - (`services.hyperhive.swarm.enableRequiredServices`) and the swarm - CA (`services.hyperhive.swarm.ca.autoConfigure`) all default from - this, and anything autoconfigurable added later should too. - - **Off by default, and that is the load-bearing part.** A swarm's - services and its hives can live on different hosts, and a host has - no way to tell which ones it is meant to be — so this is an - operator saying "this is that box", never something inferred. - Turn it on for a dev box or a single-hive swarm and get a working - deployment with no further configuration; leave it off and every - swarm-level artifact is operator-provided. - - Each derived toggle can still be set explicitly to override this - one, so "all local except X" needs no new option. - ''; - }; + # `enableAllLocalDefaults` is declared in ./local-defaults.nix, with + # the values it asserts. It is a deployment mode rather than a setting + # this module's options read, so it lives with its consequences. # Whether this hive runs "ruthless" — with no root/manager agent at # all. Some hives don't want a root agent — see issue tracker diff --git a/nix/host-modules/local-defaults.nix b/nix/host-modules/local-defaults.nix new file mode 100644 index 00000000..d1d0e03d --- /dev/null +++ b/nix/host-modules/local-defaults.nix @@ -0,0 +1,58 @@ +# The all-local deployment mode. +# +# `enableAllLocalDefaults` is a *mode*, not a default other options read: +# it says "this box is the whole deployment" and then asserts the values +# that follow from that. mara, on the issue: it is "more of a deployment +# mode via settings set, less a default setting". +# +# That distinction is why the derivations live here as `mkDefault` in a +# `config` block rather than as `default =` inside each option. An option +# declares what IT is and what it is when nobody asks; a mode declares +# what a deployment shape implies. Written the other way round, every +# service option had to name a flag it has no relationship to, and the +# answer to "what does all-local turn on?" was spread across five files. +# +# Adding an autoconfigurable thing later means one line here — not a +# `default =` in the new module pointing back at this flag. +{ + lib, + config, + ... +}: +let + cfg = config.services.hyperhive; +in +{ + options.services.hyperhive.enableAllLocalDefaults = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Run the whole swarm on this host. Turning this on asserts the + swarm-level toggles that an all-on-one-box deployment implies: + the swarm's shared services + (`services.hyperhive.swarm.enableRequiredServices`) and the swarm + CA (`services.hyperhive.swarm.ca.autoConfigure`). + + **Off by default, and that is the load-bearing part.** A swarm's + services and its hives can live on different hosts, and a host has + no way to tell which ones it is meant to be — so this is an + operator saying "this is that box", never something inferred. + Turn it on for a dev box or a single-hive swarm and get a working + deployment with no further configuration; leave it off and every + swarm-level artifact is operator-provided. + + Each toggle it asserts can still be set explicitly, which wins — + so "all local except X" needs no new option. + ''; + }; + + # What the mode asserts. `mkDefault` (priority 1000) beats an option's + # own `default` (1500) and loses to any explicit definition, which is + # exactly the precedence a deployment mode wants: it fills in for an + # operator who hasn't spoken, and never argues with one who has. + config.services.hyperhive.swarm = { + enableRequiredServices = lib.mkDefault cfg.enableAllLocalDefaults; + ca.autoConfigure = lib.mkDefault cfg.enableAllLocalDefaults; + }; +} diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index f4dbcb9d..f4f2ec87 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -5,9 +5,9 @@ # # - the CLIENT pointer (`url`) exists on every hive, because a hive # that doesn't run authelia still has to know where to send people. -# - the CONTAINER only exists where the swarm's shared services live -# (`swarm.enableRequiredServices`, itself defaulting from -# `enableAllLocalDefaults`). A hive is a client by default. +# - the CONTAINER only exists where the swarm's shared services live. +# `swarm.enableRequiredServices` asserts this module's `enable` +# (see ./swarm-required-services.nix); a hive is a client by default. # # Operator and agents are both subjects of the same provider, # differentiated by roles/claims rather than by mechanism — there is one @@ -31,7 +31,6 @@ let cfg = config.services.hyperhive.swarm.authelia; hyperhiveCfg = config.services.hyperhive; - swarmCfg = config.services.hyperhive.swarm; hyperhiveDomain = hyperhiveCfg.domain; # Upstream's `services.authelia.instances.` derives the unit, @@ -51,14 +50,13 @@ in options.services.hyperhive.swarm.authelia = { enable = lib.mkOption { type = lib.types.bool; - default = swarmCfg.enableRequiredServices; - defaultText = lib.literalExpression "services.hyperhive.swarm.enableRequiredServices"; + default = false; example = true; description = '' Run the swarm's authelia in a `swarm-authelia` container on this - host. Defaults from - `services.hyperhive.swarm.enableRequiredServices` — a swarm has - one SSO provider, and this says it lives here. + host. `services.hyperhive.swarm.enableRequiredServices` turns + this on — a swarm has one SSO provider, and that says it lives + here. With it off, this hive is a *client*: `url` below still points at whoever runs it, and no container is created. diff --git a/nix/host-modules/swarm-ca.nix b/nix/host-modules/swarm-ca.nix index 5e7ed467..d0ae1dc7 100644 --- a/nix/host-modules/swarm-ca.nix +++ b/nix/host-modules/swarm-ca.nix @@ -52,18 +52,15 @@ in options.services.hyperhive.swarm.ca = { autoConfigure = lib.mkOption { type = lib.types.bool; - default = hyperhiveCfg.enableAllLocalDefaults; - defaultText = lib.literalExpression "services.hyperhive.enableAllLocalDefaults"; + default = false; example = true; description = '' Run the whole swarm CA on this one host: generate the swarm root when it is missing, and issue this hive's CA under it. - Defaults from `services.hyperhive.enableAllLocalDefaults`, the - all-on-one-box switch — which is off, so this is off, and the - paragraph below still describes what a hive does by default. - Set it directly to run the CA on a host that is not otherwise - all-local. + `services.hyperhive.enableAllLocalDefaults` turns this on as + part of the all-on-one-box mode. Set it here directly to run the + CA on a host that is not otherwise all-local. **Off by default, deliberately.** A swarm's services and its hives can live on different hosts, and this host has no way to diff --git a/nix/host-modules/swarm-required-services.nix b/nix/host-modules/swarm-required-services.nix new file mode 100644 index 00000000..94d82ba3 --- /dev/null +++ b/nix/host-modules/swarm-required-services.nix @@ -0,0 +1,51 @@ +# "The swarm-wide services run HERE." +# +# A swarm has one forge, one matrix, one SSO. This says this host is +# where they live, and asserts the per-service `enable`s that follow — +# the same mode-not-default shape as ./local-defaults.nix, one tier down. +# +# Only the *optional* services derive: matrix and authelia. The forge has +# no `enable` to assert, because it is not optional — it is the canonical +# store for the meta flake and every agent's config repo, so it deploys +# with hyperhive itself. +{ + lib, + config, + ... +}: +let + swarmCfg = config.services.hyperhive.swarm; +in +{ + options.services.hyperhive.swarm.enableRequiredServices = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = '' + Host the swarm's shared services on this hive. The services that + exist once per swarm rather than once per hive and are *optional* + — the matrix homeserver, the SSO provider — have their `enable` + asserted from this, so a swarm's service host is declared in one + place. + + The forge is swarm-wide too but has nothing to assert: it is the + canonical store for the meta flake and every agent's config repo, + so it deploys with hyperhive itself and is not optional. + + `services.hyperhive.enableAllLocalDefaults` turns this on as part + of the all-on-one-box mode. Set it directly to run the swarm's + services on a host that is not otherwise all-local — a dedicated + services box with hives elsewhere is exactly that shape. + + With it off, this hive is a *client* of those services: it still + configures how to reach them, it just doesn't run them. + ''; + }; + + # Same precedence reasoning as ./local-defaults.nix: fills in for an + # operator who hasn't spoken, yields to one who has. + config.services.hyperhive.swarm = { + matrix.enable = lib.mkDefault swarmCfg.enableRequiredServices; + authelia.enable = lib.mkDefault swarmCfg.enableRequiredServices; + }; +} diff --git a/nix/host-modules/swarm.nix b/nix/host-modules/swarm.nix index 6a374d3b..31b8c7a1 100644 --- a/nix/host-modules/swarm.nix +++ b/nix/host-modules/swarm.nix @@ -126,36 +126,10 @@ ''; }; - # "The swarm-wide services run HERE." A swarm has one forge, one - # matrix, one SSO — this says this host is where they live. The - # OPTIONAL ones (matrix, authelia) default their own enable from it, - # rather than the operator enabling them one at a time; the forge has - # no enable to derive because it is not optional. - options.services.hyperhive.swarm.enableRequiredServices = lib.mkOption { - type = lib.types.bool; - default = config.services.hyperhive.enableAllLocalDefaults; - defaultText = lib.literalExpression "services.hyperhive.enableAllLocalDefaults"; - example = true; - description = '' - Host the swarm's shared services on this hive. The services that - exist once per swarm rather than once per hive and are *optional* - — the matrix homeserver, the SSO provider — default their - `enable` from this, so a swarm's service host is declared in one - place. - - The forge is swarm-wide too but has no `enable` to derive: it is - the canonical store for the meta flake and every agent's config - repo, so it deploys with hyperhive itself and is not optional. - - Defaults from `services.hyperhive.enableAllLocalDefaults` (off), - which is the all-on-one-box switch. Set it directly to run the - swarm's services on a host that is not otherwise all-local — a - dedicated services box with hives elsewhere is exactly that shape. - - With it off, this hive is a *client* of those services: it still - configures how to reach them, it just doesn't run them. - ''; - }; + # `enableRequiredServices` is declared in ./swarm-required-services.nix + # together with the per-service `enable`s it asserts — it is a + # deployment-shape switch rather than swarm bookkeeping, so it lives + # with its consequences instead of here. options.services.hyperhive.swarm.snapshotStore = { address = lib.mkOption {