From 585269b8a396daf627713e33ef50f41605f73998 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 30 Aug 2026 19:39:52 +0200 Subject: [PATCH 1/4] deploy: rename swarm.enableRequiredServices to deploy.allSwarmServices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both halves of the old name were wrong about the subject. The services are required of the SWARM, not of the host, and the option says whether THIS host runs them — so it described the wrong thing and sat in the namespace that has to be identical on every host. The new name is mara's own phrasing of what it means: "deploy all swarm level services on this host". mkRenamedOptionModule carries existing configs, read-side references included, so this warns rather than failing to evaluate. Three sites were not just the identifier: - local-defaults.nix set it inside `config.services.hyperhive.swarm = { … }`. It moves out as a path beside the other deploy.* setter rather than into a second `deploy = { … }` attrset — the warning that file already carries about `swarm` applies to any second definition of the same parent. - swarm-required-services.nix bound only `swarmCfg`, now unused; it binds and reads `deployCfg`. - Two comments in that file described a half-migrated state, where the switch asserted some `swarm.*.enable` toggles and some `deploy.*` ones. Every one of them has been `deploy.*` for several slices now. --- docs/swarm/services.md | 14 +++--- docs/swarm/ui.md | 2 +- nix/host-modules/deploy.nix | 23 +++++++--- nix/host-modules/local-defaults.nix | 12 +++-- nix/host-modules/swarm-authelia.nix | 4 +- nix/host-modules/swarm-required-services.nix | 46 +++++++++----------- nix/host-modules/swarm.nix | 4 +- 7 files changed, 58 insertions(+), 47 deletions(-) diff --git a/docs/swarm/services.md b/docs/swarm/services.md index 48b02b7f..dd72348e 100644 --- a/docs/swarm/services.md +++ b/docs/swarm/services.md @@ -7,11 +7,11 @@ optional ones live, and everything else derives: ```nix services.hyperhive.enableAllLocalDefaults = true; # everything on this box # or, for a dedicated services host with hives elsewhere: -services.hyperhive.swarm.enableRequiredServices = true; +services.hyperhive.deploy.allSwarmServices = true; ``` `enableAllLocalDefaults` is the all-on-one-box switch: it defaults both -`swarm.enableRequiredServices` (the shared services run here) and +`deploy.allSwarmServices` (the shared services run here) and `swarm.ca.autoConfigure` (the swarm CA is generated here). Each derived toggle can still be set on its own, so "all local except X" needs no further option. @@ -36,7 +36,7 @@ the shapes worth naming are the ones they produce: `enableAllLocalDefaults = true`. Setup is automatic apart from choosing a domain and creating the first user. - **Services on the swarm controller host.** - `swarm.enableRequiredServices = true` there; the required services + `deploy.allSwarmServices = true` there; the required services deploy together on that host, with hives elsewhere. - **Fully spread out.** One container / VM / machine per service, somewhere. @@ -65,7 +65,7 @@ provider, differentiated by roles and claims rather than by mechanism — there is one IdP and one auth path. - **`deploy.authelia`** — run the container here. Defaults from - `swarm.enableRequiredServices`. + `deploy.allSwarmServices`. - **`swarm.authelia.url`** — where clients are sent to authenticate. Present on **every** hive, defaulting to this host's own instance only when this module is the thing running it; otherwise `null`, and a hive @@ -98,7 +98,7 @@ one Grafana, in two containers at `metrics.` and `grafana.`. Two containers rather than one so Grafana can be restarted or broken without taking the time-series database with it. -Both follow `swarm.enableRequiredServices` like authelia and matrix, so +Both follow `deploy.allSwarmServices` like authelia and matrix, so the swarm's service host gets them with everything else. They derive together: a store with no UI is unreadable and a UI with no store is empty. To run one without the other, set it directly: @@ -133,7 +133,7 @@ the collector is the only intended writer. The swarm's service containers ship their journals to one VictoriaLogs at `logs.`, behind the same SSO as everything else. It follows -`swarm.enableRequiredServices` too, and the collector below is what writes +`deploy.allSwarmServices` too, and the collector below is what writes to it. **Reading them.** Open Grafana, pick **Explore**, and choose the @@ -160,7 +160,7 @@ and exports to `otel.endpoint`, doing both when both are configured. It also holds the upstream credential, which is why no hive and no agent needs one. -It follows `swarm.enableRequiredServices` like the services above, in a +It follows `deploy.allSwarmServices` like the services above, in a `swarm-otel` container. Its `swarm.otel.port` defaults to `4319` rather than OTLP's usual `4318`, which the hive tier already uses — swarm containers share the host's network namespace, so two collectors on one diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index 10bd72ee..67d5e3e4 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -12,7 +12,7 @@ answers for one host. This one is the view *across* hives. services.hyperhive.deploy.swarm-ui.enable = true; # defaults to deploy.swarm-controller.enable ``` -Derived from the controller rather than from `enableRequiredServices`: +Derived from the controller rather than from `allSwarmServices`: 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 can serve the UI. A hive that merely *uses* a swarm has nothing to serve. diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index ebc9f7fd..3d880c69 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -110,6 +110,15 @@ in [ "services" "hyperhive" "swarm" "victorialogs" "retentionPeriod" ] [ "services" "hyperhive" "deploy" "victorialogs" "retentionPeriod" ] ) + + # The switch over all of the above, and the name changes with the path + # because the old one described the wrong subject: those services are + # required of the SWARM, while the option says whether THIS host runs + # them. `allSwarmServices` is mara's own phrasing of what it means. + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "enableRequiredServices" ] + [ "services" "hyperhive" "deploy" "allSwarmServices" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the @@ -140,7 +149,7 @@ in Run the swarm's metrics store on this host. Derives from - {option}`services.hyperhive.swarm.enableRequiredServices` together + {option}`services.hyperhive.deploy.allSwarmServices` together with {option}`services.hyperhive.deploy.grafana.enable`: a store with no UI is unreadable and a UI with no store is empty, so there is no sensible deployment that takes one and not the other from @@ -155,7 +164,7 @@ in Run the swarm's log store on this host. Derives from - {option}`services.hyperhive.swarm.enableRequiredServices` for the + {option}`services.hyperhive.deploy.allSwarmServices` for the same reason as the metrics pair above: a hive that is not the service host is a *client* of this store, not a second one. ''; @@ -169,7 +178,7 @@ in Run the swarm's secret store in a `swarm-bao` container on this host. A swarm has one store and it has to exist somewhere, so this is asserted from - {option}`services.hyperhive.swarm.enableRequiredServices` + {option}`services.hyperhive.deploy.allSwarmServices` alongside the other once-per-swarm services. That assertion is a `mkDefault`, which is what keeps *where* the @@ -193,7 +202,7 @@ in example = true; description = '' Run the swarm's authelia in a `swarm-authelia` container on this - host. {option}`services.hyperhive.swarm.enableRequiredServices` + host. {option}`services.hyperhive.deploy.allSwarmServices` turns this on — a swarm has one SSO provider, and that says it lives here. @@ -212,7 +221,7 @@ in Run the **swarm's** telemetry collector on this host. Derives from - {option}`services.hyperhive.swarm.enableRequiredServices` with the + {option}`services.hyperhive.deploy.allSwarmServices` with the metrics pair it feeds: a swarm has one of these, and it belongs wherever the shared services live rather than on every hive. @@ -234,7 +243,7 @@ in `hive-matrix` container — on this host. Derives from - {option}`services.hyperhive.swarm.enableRequiredServices` with + {option}`services.hyperhive.deploy.allSwarmServices` with the other once-per-swarm services. Set it here directly to put the homeserver somewhere other than the host holding the rest. @@ -282,7 +291,7 @@ in Derived from {option}`services.hyperhive.deploy.swarm-controller.enable` rather than from - {option}`services.hyperhive.swarm.enableRequiredServices`: the UI + {option}`services.hyperhive.deploy.allSwarmServices`: 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 diff --git a/nix/host-modules/local-defaults.nix b/nix/host-modules/local-defaults.nix index 5034eee5..f025aff5 100644 --- a/nix/host-modules/local-defaults.nix +++ b/nix/host-modules/local-defaults.nix @@ -31,7 +31,7 @@ in Run the whole swarm on this host. Turning this on asserts the toggles that an all-on-one-box deployment implies: the swarm's shared services - (`services.hyperhive.swarm.enableRequiredServices`), the swarm + (`services.hyperhive.deploy.allSwarmServices`), the swarm CA (`services.hyperhive.swarm.ca.autoConfigure`), the swarm controller (`services.hyperhive.deploy.swarm-controller.enable`), and the host's `/etc/hosts` entries for the names this hive serves @@ -71,8 +71,14 @@ in # break every agent's access to the forge. config.services.hyperhive.gateway.localHostsEntry = lib.mkDefault cfg.enableAllLocalDefaults; + # Out of the `swarm` attrset below, because it is a `deploy.*` option now + # (./deploy.nix): "does THIS host run the swarm's services" is a per-host + # decision. Written as a path rather than folded into a second + # `config.services.hyperhive.deploy = { … }` attrset, for the same reason + # the ⚠️ below gives about `swarm`. + config.services.hyperhive.deploy.allSwarmServices = lib.mkDefault cfg.enableAllLocalDefaults; + config.services.hyperhive.swarm = { - enableRequiredServices = lib.mkDefault cfg.enableAllLocalDefaults; ca.autoConfigure = lib.mkDefault cfg.enableAllLocalDefaults; # The queue's auth-callout nkeys. Generating them is safe exactly # when one operator owns both the queue and its responder, which is @@ -113,7 +119,7 @@ in # option stays `default = false` precisely because running it is a # statement about swarm topology — but "this box is the whole # deployment" IS that statement, and it is the one shape where the - # answer isn't ambiguous. Deriving it from `enableRequiredServices` + # answer isn't ambiguous. Deriving it from `allSwarmServices` # instead would be wrong: a hive in a larger swarm can legitimately # want the shared services without being the host that controls them. # diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 4c8ba9e7..93a63e71 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -6,7 +6,7 @@ # - 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` asserts this module's `enable` +# `deploy.allSwarmServices` 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, @@ -992,7 +992,7 @@ in # # Gated on the collector's `enable` as well, and that second condition is # what makes the loopback address honest. Both services default from - # `swarm.enableRequiredServices` — but `mkDefault` is an invitation to + # `deploy.allSwarmServices` — but `mkDefault` is an invitation to # override, not a guarantee, so "they are on the same host" is a property # of the auto-deployed topology rather than of the module. Without this # gate, a host running authelia and no collector would declare a target diff --git a/nix/host-modules/swarm-required-services.nix b/nix/host-modules/swarm-required-services.nix index a3c649f5..46bbc5e4 100644 --- a/nix/host-modules/swarm-required-services.nix +++ b/nix/host-modules/swarm-required-services.nix @@ -4,20 +4,20 @@ # 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. +# Only the *optional* services derive. 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; + deployCfg = config.services.hyperhive.deploy; in { - options.services.hyperhive.swarm.enableRequiredServices = lib.mkOption { + options.services.hyperhive.deploy.allSwarmServices = lib.mkOption { type = lib.types.bool; default = false; example = true; @@ -28,9 +28,10 @@ in and log stores — have their toggle asserted from this, so a swarm's service host is declared in one place. - Those toggles live in two namespaces and the split is deliberate: - {option}`services.hyperhive.deploy.*` for "does THIS host run it", - `swarm.*.enable` for the ones not yet moved. See ./deploy.nix. + Every toggle it asserts is a {option}`services.hyperhive.deploy.*` + one, because "does THIS host run it" is a per-host decision — which + is the same reason this option is a `deploy.*` one itself. See + ./deploy.nix. 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, @@ -52,49 +53,44 @@ in # Everything derives under `deploy.*` now, because "does THIS host run # it" is a per-host decision and `swarm.*` has to be identical on every # host. Same switch, same rule, one attribute path. - config.services.hyperhive.deploy.matrix.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.matrix.enable = lib.mkDefault deployCfg.allSwarmServices; # The collector that feeds the pair above (note: no `swarm.` prefix, # this is ./otel.nix's existing per-hive option). - config.services.hyperhive.otel.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.otel.enable = lib.mkDefault deployCfg.allSwarmServices; - # The rest of the shared services, deriving from the same switch as the - # `swarm.*` ones above. They read differently only because "does THIS - # host run it" lives in `deploy.*` (./deploy.nix) — `swarm.*` has to be - # identical on every host, and these are exactly the values that must - # differ. + # The rest of the shared services, from the same switch and for the same + # reason. # # authelia: a swarm has one SSO provider, and this says it lives here. # With it off the hive is a *client* — `swarm.authelia.url` still points # at whoever runs it. - config.services.hyperhive.deploy.authelia.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.authelia.enable = lib.mkDefault deployCfg.allSwarmServices; # The queue. Same rule: once per swarm, optional. - config.services.hyperhive.deploy.nats.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.nats.enable = lib.mkDefault deployCfg.allSwarmServices; # The swarm collector that feeds the metrics pair, and the only tier # holding the upstream credential. ⚠️ NOT the per-hive collector below, # which every hive runs. - config.services.hyperhive.deploy.swarm-otel.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.swarm-otel.enable = lib.mkDefault deployCfg.allSwarmServices; # The metrics pair, deriving together on purpose: a store with no UI is # unreadable and a UI with no store is empty, so there is no sensible # deployment that takes one and not the other from this switch. An # operator who wants exactly one still sets it directly, which # `mkDefault` allows. - config.services.hyperhive.deploy.victoriametrics.enable = - lib.mkDefault swarmCfg.enableRequiredServices; - config.services.hyperhive.deploy.grafana.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.victoriametrics.enable = lib.mkDefault deployCfg.allSwarmServices; + config.services.hyperhive.deploy.grafana.enable = lib.mkDefault deployCfg.allSwarmServices; # The log store, from the same switch for the same reason as the rest: a # hive that is not the service host is a *client* of it, not a second one. - config.services.hyperhive.deploy.victorialogs.enable = - lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.victorialogs.enable = lib.mkDefault deployCfg.allSwarmServices; # The secret store. Once per swarm and optional, so it belongs to the # same switch: a hive that does not run it is a *client*, reading its # own secrets from whoever does. `mkDefault` is what keeps the store # placeable on a host of its own — it can be set directly here and # turned off wherever this switch happens to be on. - config.services.hyperhive.deploy.bao.enable = lib.mkDefault swarmCfg.enableRequiredServices; + config.services.hyperhive.deploy.bao.enable = lib.mkDefault deployCfg.allSwarmServices; } diff --git a/nix/host-modules/swarm.nix b/nix/host-modules/swarm.nix index 9bedc776..932c1540 100644 --- a/nix/host-modules/swarm.nix +++ b/nix/host-modules/swarm.nix @@ -80,7 +80,7 @@ let # ONE condition for all three of them, deliberately. # # 🩸 They were three independent conditions first, and that was wrong in - # a way only an eval gate finds: at the time `enableRequiredServices` + # a way only an eval gate finds: at the time `allSwarmServices` # turned on matrix and authelia but NOT nats, so an ordinary all-local # hive resolved authelia's two coordinates and not the queue URL. Two of # three set is exactly what the assertion below rejects, so every @@ -382,7 +382,7 @@ in ]; }; - # `enableRequiredServices` is declared in ./swarm-required-services.nix + # `allSwarmServices` 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. From 97a7b518ea28d5f42e3b3d16f0634632d1d7da61 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 30 Aug 2026 19:42:20 +0200 Subject: [PATCH 2/4] deploy: rename enableAllLocalDefaults to deploy.singleHostSwarm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same defect as the switch below it, one tier up: it sat at the TOP of `services.hyperhive`, a namespace that is meant to be everything about hyperhive rather than the settings of a single hive. Whether this box is the whole deployment is as per-host as a decision gets. The name follows mara's sentence for what it means — "everything in the swarm is running on this host" — rather than naming its mechanism. "Defaults" was doing no work: it is not a defaults toggle, it is a claim about where the swarm lives, and the pair now reads as the containment it already was, singleHostSwarm implying allSwarmServices plus this hive. One site was a setter rather than a reference: module-eval's `allLocal` fixture passes an attrset merged into `services.hyperhive`, so its key carries the path and had to become `deploy.singleHostSwarm`. A rename by bare identifier is right for the twelve prose mentions and wrong for exactly this one, which is worth knowing before the next rename. --- docs/swarm/ca.md | 2 +- docs/swarm/secrets.md | 2 +- docs/swarm/services.md | 6 +++--- nix/host-modules/deploy.nix | 10 ++++++++++ nix/host-modules/hive-gateway/options.nix | 2 +- nix/host-modules/hyperhive.nix | 2 +- nix/host-modules/local-defaults.nix | 18 +++++++++--------- nix/host-modules/swarm-ca.nix | 4 ++-- nix/host-modules/swarm-controller.nix | 6 +++--- nix/host-modules/swarm-nats.nix | 2 +- nix/host-modules/swarm-required-services.nix | 2 +- nix/host-modules/swarm.nix | 2 +- nix/module-eval.nix | 2 +- 13 files changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/swarm/ca.md b/docs/swarm/ca.md index 45b2c39c..19cd15eb 100644 --- a/docs/swarm/ca.md +++ b/docs/swarm/ca.md @@ -24,7 +24,7 @@ the root, so setting the swarm CA up is an operator action rather than something a host assumes. Turn it on for an all-on-one-host deployment and the hierarchy costs no configuration. -It defaults from `services.hyperhive.enableAllLocalDefaults`, the single +It defaults from `services.hyperhive.deploy.singleHostSwarm`, the single switch that says "this box is the whole deployment". **A hive given neither artifact keeps the self-signed CA it has always diff --git a/docs/swarm/secrets.md b/docs/swarm/secrets.md index d43012f9..5f25a132 100644 --- a/docs/swarm/secrets.md +++ b/docs/swarm/secrets.md @@ -86,7 +86,7 @@ scraped; otherwise no secret is needed and none is placed. ### Minting the queue's callout nkeys `nats.autoGenerateCallout` mints both keypairs on the host before the queue -starts. It is on by default only under `enableAllLocalDefaults` — the one +starts. It is on by default only under `singleHostSwarm` — the one topology where the queue, its responder and the operator are the same person. On every other topology, mint them yourself: diff --git a/docs/swarm/services.md b/docs/swarm/services.md index dd72348e..c2a540a0 100644 --- a/docs/swarm/services.md +++ b/docs/swarm/services.md @@ -5,12 +5,12 @@ matrix homeserver, the SSO provider, the CA. Two options say where the optional ones live, and everything else derives: ```nix -services.hyperhive.enableAllLocalDefaults = true; # everything on this box +services.hyperhive.deploy.singleHostSwarm = true; # everything on this box # or, for a dedicated services host with hives elsewhere: services.hyperhive.deploy.allSwarmServices = true; ``` -`enableAllLocalDefaults` is the all-on-one-box switch: it defaults both +`singleHostSwarm` is the all-on-one-box switch: it defaults both `deploy.allSwarmServices` (the shared services run here) and `swarm.ca.autoConfigure` (the swarm CA is generated here). Each derived toggle can still be set on its own, so "all local except X" needs no @@ -33,7 +33,7 @@ Those two options are what makes the difference between deployments, so the shapes worth naming are the ones they produce: - **All-local.** Everything on one machine: - `enableAllLocalDefaults = true`. Setup is automatic apart from + `singleHostSwarm = true`. Setup is automatic apart from choosing a domain and creating the first user. - **Services on the swarm controller host.** `deploy.allSwarmServices = true` there; the required services diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index 3d880c69..d7689dfd 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -119,6 +119,16 @@ in [ "services" "hyperhive" "swarm" "enableRequiredServices" ] [ "services" "hyperhive" "deploy" "allSwarmServices" ] ) + + # The mode above that one. It sat at the TOP of `services.hyperhive`, + # which is the same defect one tier up: that namespace is everything + # about hyperhive, not the settings of a single hive. The new name says + # what the mode asserts — the whole swarm runs on this host — instead of + # naming its mechanism. + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "enableAllLocalDefaults" ] + [ "services" "hyperhive" "deploy" "singleHostSwarm" ] + ) ]; # ⚠️ `deploy.forgejo` is declared in ./hive-ci.nix, not here, and it is the diff --git a/nix/host-modules/hive-gateway/options.nix b/nix/host-modules/hive-gateway/options.nix index f0199342..010c96b5 100644 --- a/nix/host-modules/hive-gateway/options.nix +++ b/nix/host-modules/hive-gateway/options.nix @@ -92,7 +92,7 @@ in DNS shouldn't have a stale `/etc/hosts` entry sticking around. Requires `services.hyperhive.domain` to be set. - `services.hyperhive.enableAllLocalDefaults` turns this on as + `services.hyperhive.deploy.singleHostSwarm` turns this on as part of saying "this box is the whole deployment": that mode means there is no real DNS for these names and the operator is browsing them from the host itself. Set it here explicitly to diff --git a/nix/host-modules/hyperhive.nix b/nix/host-modules/hyperhive.nix index 4cec58ce..b6d177d2 100644 --- a/nix/host-modules/hyperhive.nix +++ b/nix/host-modules/hyperhive.nix @@ -170,7 +170,7 @@ in ''; }; - # `enableAllLocalDefaults` is declared in ./local-defaults.nix, with + # `singleHostSwarm` 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. diff --git a/nix/host-modules/local-defaults.nix b/nix/host-modules/local-defaults.nix index f025aff5..528f86de 100644 --- a/nix/host-modules/local-defaults.nix +++ b/nix/host-modules/local-defaults.nix @@ -1,6 +1,6 @@ # The all-local deployment mode. # -# `enableAllLocalDefaults` is a *mode*, not a default other options read: +# `singleHostSwarm` 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". @@ -23,7 +23,7 @@ let cfg = config.services.hyperhive; in { - options.services.hyperhive.enableAllLocalDefaults = lib.mkOption { + options.services.hyperhive.deploy.singleHostSwarm = lib.mkOption { type = lib.types.bool; default = false; example = true; @@ -69,23 +69,23 @@ in # every agent at its own netns. That guard already existing is what # makes turning this on by default safe; without it this line would # break every agent's access to the forge. - config.services.hyperhive.gateway.localHostsEntry = lib.mkDefault cfg.enableAllLocalDefaults; + config.services.hyperhive.gateway.localHostsEntry = lib.mkDefault cfg.deploy.singleHostSwarm; # Out of the `swarm` attrset below, because it is a `deploy.*` option now # (./deploy.nix): "does THIS host run the swarm's services" is a per-host # decision. Written as a path rather than folded into a second # `config.services.hyperhive.deploy = { … }` attrset, for the same reason # the ⚠️ below gives about `swarm`. - config.services.hyperhive.deploy.allSwarmServices = lib.mkDefault cfg.enableAllLocalDefaults; + config.services.hyperhive.deploy.allSwarmServices = lib.mkDefault cfg.deploy.singleHostSwarm; config.services.hyperhive.swarm = { - ca.autoConfigure = lib.mkDefault cfg.enableAllLocalDefaults; + ca.autoConfigure = lib.mkDefault cfg.deploy.singleHostSwarm; # The queue's auth-callout nkeys. Generating them is safe exactly # when one operator owns both the queue and its responder, which is # what this mode asserts. On any other topology the seeds have to # reach whoever runs the responder, and minting them here would move # that hand-off somewhere less visible rather than removing it. - nats.autoGenerateCallout = lib.mkDefault cfg.enableAllLocalDefaults; + nats.autoGenerateCallout = lib.mkDefault cfg.deploy.singleHostSwarm; # The controller's queue coordinates. Co-location is what makes these # derivable at all — loopback only reaches the queue when the queue is # here, and the minted client secret only exists on the host authelia @@ -107,10 +107,10 @@ in # The *requirement* stays in `swarm-controller.nix` as an assertion: # needing a queue is the controller's own property in every topology, # and only the convenience is local. - controller.queue.natsUrl = lib.mkIf cfg.enableAllLocalDefaults ( + controller.queue.natsUrl = lib.mkIf cfg.deploy.singleHostSwarm ( lib.mkDefault "nats://127.0.0.1:${toString config.services.hyperhive.swarm.nats.port}" ); - controller.queue.clientSecretFile = lib.mkIf cfg.enableAllLocalDefaults ( + controller.queue.clientSecretFile = lib.mkIf cfg.deploy.singleHostSwarm ( lib.mkDefault "${config.services.hyperhive.swarm.authelia.hostClientSecretDir}/swarm-controller.secret" ); }; @@ -128,5 +128,5 @@ in # the per-host fact `swarm.*` may not carry. The ⚠️ collision note above # does not apply here — that one is about two definitions of `swarm` # itself, and this is a different top-level path. - config.services.hyperhive.deploy.swarm-controller.enable = lib.mkDefault cfg.enableAllLocalDefaults; + config.services.hyperhive.deploy.swarm-controller.enable = lib.mkDefault cfg.deploy.singleHostSwarm; } diff --git a/nix/host-modules/swarm-ca.nix b/nix/host-modules/swarm-ca.nix index d522ae57..f4d5c8cd 100644 --- a/nix/host-modules/swarm-ca.nix +++ b/nix/host-modules/swarm-ca.nix @@ -11,7 +11,7 @@ # Two provisioning modes, ONE structure — what differs is who puts the # artifacts on disk, never what the artifacts are: # -# - autoconfigured (`services.hyperhive.enableAllLocalDefaults`, or +# - autoconfigured (`services.hyperhive.deploy.singleHostSwarm`, or # `autoConfigure` set directly): the unit below generates the root # here on first boot. # - operator-provided (multi-host): the operator installs the root cert @@ -68,7 +68,7 @@ in 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. - `services.hyperhive.enableAllLocalDefaults` turns this on as + `services.hyperhive.deploy.singleHostSwarm` 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. diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index fbc5a3f3..b54686ba 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -240,7 +240,7 @@ in }; # `enable` moved to `services.hyperhive.deploy.swarm-controller.enable` — see - # ./deploy.nix. `services.hyperhive.enableAllLocalDefaults` still + # ./deploy.nix. `services.hyperhive.deploy.singleHostSwarm` still # asserts it, and that was never an exception to "not derived from # services.hyperhive.enable": that mode says "this box is the whole # deployment", which answers the topology question outright, where @@ -320,7 +320,7 @@ in Empty means unset, which the assertion below refuses — a controller with no queue is not a lighter controller. - `enableAllLocalDefaults` fills this in with loopback, because + `singleHostSwarm` fills this in with loopback, because that address is only correct when the queue is on this host: its container shares the host netns. That derivation lives with the mode rather than here, so this option describes itself @@ -354,7 +354,7 @@ in The controller cannot mint its own — minting happens inside authelia's state directory during its first boot — so away from that host the operator places the secret and names it here. - `enableAllLocalDefaults` points this at the minted file, which + `singleHostSwarm` points this at the minted file, which is exactly the case where one exists locally. Read by `LoadCredential`, so it needs to be readable by root at diff --git a/nix/host-modules/swarm-nats.nix b/nix/host-modules/swarm-nats.nix index 8e98cac2..e5266cb4 100644 --- a/nix/host-modules/swarm-nats.nix +++ b/nix/host-modules/swarm-nats.nix @@ -297,7 +297,7 @@ in Leave it off wherever the queue and its clients are not the same operator's problem: the seeds must reach whoever runs the responder, and minting them here only moves that distribution - somewhere less visible. `enableAllLocalDefaults` turns it on. + somewhere less visible. `singleHostSwarm` turns it on. ''; }; diff --git a/nix/host-modules/swarm-required-services.nix b/nix/host-modules/swarm-required-services.nix index 46bbc5e4..bc57d663 100644 --- a/nix/host-modules/swarm-required-services.nix +++ b/nix/host-modules/swarm-required-services.nix @@ -37,7 +37,7 @@ in 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 + `services.hyperhive.deploy.singleHostSwarm` 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. diff --git a/nix/host-modules/swarm.nix b/nix/host-modules/swarm.nix index 932c1540..2fd9d961 100644 --- a/nix/host-modules/swarm.nix +++ b/nix/host-modules/swarm.nix @@ -84,7 +84,7 @@ let # turned on matrix and authelia but NOT nats, so an ordinary all-local # hive resolved authelia's two coordinates and not the queue URL. Two of # three set is exactly what the assertion below rejects, so every - # `enableAllLocalDefaults` hive would have stopped evaluating. + # `singleHostSwarm` hive would have stopped evaluating. # # (The queue does derive from that switch now — ./swarm-required-services.nix # — so the original asymmetry is gone. The reasoning below is why the diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 67b8b5e5..ebc36bee 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -67,7 +67,7 @@ let ]; }).config; - allLocal = hive { enableAllLocalDefaults = true; }; + allLocal = hive { deploy.singleHostSwarm = true; }; bare = hive { }; withCi = hive { deploy.forgejo.ci.enable = true; }; From d7a471043bce22626f9d4d2e7c1867fd68423ee5 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 30 Aug 2026 19:57:49 +0200 Subject: [PATCH 3/4] docs/swarm: state the swarm-services rule once, not per service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mara on the PR: "remove the re stating of the 'follows allSwarmServices' from the docs everywhere. services md is the central place to explain what 'all swarm services' means (without listing them)". Four sections each restated that their service derives from the switch — authelia, the metrics pair, the log store, the swarm collector. The rule now lives once at the top of services.md, in the form that carries the information the four copies did: every optional once-per-swarm service takes its enable from it. The intro's enumeration goes with them. Naming the four services in prose is a hand-maintained list of the same kind, one that a fifth service does not update. That also makes an exception legible: swarm-ui's line saying it derives from the controller rather than from this switch now reads as a real difference instead of one paragraph disagreeing with four others. --- docs/swarm/services.md | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/docs/swarm/services.md b/docs/swarm/services.md index c2a540a0..24efda7b 100644 --- a/docs/swarm/services.md +++ b/docs/swarm/services.md @@ -1,8 +1,7 @@ # Swarm-wide services -Some things exist once per **swarm**, not once per hive: the forge, the -matrix homeserver, the SSO provider, the CA. Two options say where the -optional ones live, and everything else derives: +Some things exist once per **swarm** rather than once per hive. Two +options say where the optional ones live, and everything else derives: ```nix services.hyperhive.deploy.singleHostSwarm = true; # everything on this box @@ -10,11 +9,16 @@ services.hyperhive.deploy.singleHostSwarm = true; # everything on this box services.hyperhive.deploy.allSwarmServices = true; ``` -`singleHostSwarm` is the all-on-one-box switch: it defaults both -`deploy.allSwarmServices` (the shared services run here) and -`swarm.ca.autoConfigure` (the swarm CA is generated here). Each derived -toggle can still be set on its own, so "all local except X" needs no -further option. +**`deploy.allSwarmServices` is what "the swarm's shared services run +here" means: every once-per-swarm service that is *optional* takes its +`enable` from it.** That is the whole rule, stated once — the per-service +sections below do not repeat it, so a service that stops deriving is a +visible difference rather than one more paragraph saying the same thing. + +`singleHostSwarm` is the all-on-one-box switch above it: it defaults +both `deploy.allSwarmServices` and `swarm.ca.autoConfigure` (the swarm CA +is generated here). Each derived toggle can still be set on its own, +which wins, so "all local except X" needs no further option. **Both default to off**, and that is deliberate: a host cannot tell whether it is meant to be the swarm's service host, so this is an @@ -64,8 +68,7 @@ One authelia per swarm, in a `swarm-authelia` container, at provider, differentiated by roles and claims rather than by mechanism — there is one IdP and one auth path. -- **`deploy.authelia`** — run the container here. Defaults from - `deploy.allSwarmServices`. +- **`deploy.authelia`** — run the container here. - **`swarm.authelia.url`** — where clients are sent to authenticate. Present on **every** hive, defaulting to this host's own instance only when this module is the thing running it; otherwise `null`, and a hive @@ -98,10 +101,8 @@ one Grafana, in two containers at `metrics.` and `grafana.`. Two containers rather than one so Grafana can be restarted or broken without taking the time-series database with it. -Both follow `deploy.allSwarmServices` like authelia and matrix, so -the swarm's service host gets them with everything else. They derive -together: a store with no UI is unreadable and a UI with no store is -empty. To run one without the other, set it directly: +They derive together: a store with no UI is unreadable and a UI with no +store is empty. To run one without the other, set it directly: ```nix services.hyperhive.deploy.victoriametrics.enable = true; @@ -132,9 +133,8 @@ the collector is the only intended writer. ### Logs (VictoriaLogs) The swarm's service containers ship their journals to one VictoriaLogs at -`logs.`, behind the same SSO as everything else. It follows -`deploy.allSwarmServices` too, and the collector below is what writes -to it. +`logs.`, behind the same SSO as everything else. The +collector below is what writes to it. **Reading them.** Open Grafana, pick **Explore**, and choose the `VictoriaLogs` datasource — it is provisioned for you. Grafana's *Logs @@ -160,9 +160,8 @@ and exports to `otel.endpoint`, doing both when both are configured. It also holds the upstream credential, which is why no hive and no agent needs one. -It follows `deploy.allSwarmServices` like the services above, in a -`swarm-otel` container. Its `swarm.otel.port` defaults to `4319` rather -than OTLP's usual `4318`, which the hive tier already uses — swarm +It runs in a `swarm-otel` container. Its `swarm.otel.port` defaults to +`4319` rather than OTLP's usual `4318`, which the hive tier uses — swarm containers share the host's network namespace, so two collectors on one port is a coin toss at runtime rather than an error at build time. From 4bd5c51e2e9b2b475e330e5d98b8421c9f5c3ce2 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 30 Aug 2026 20:06:56 +0200 Subject: [PATCH 4/4] deploy: drop the per-option restatement of what allSwarmServices asserts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mara: "deploy.nix still says it everywhere". Seven option descriptions each told the reader that this service derives from the swarm-services switch — the same duplication just removed from docs/swarm/services.md, one layer over, and rendered into the generated options doc. Each keeps only what is true of that service and nothing else: the metrics store is paired with grafana, the log store has clients rather than second instances, the homeserver can be placed on its own host, a swarm has one SSO provider. The store's paragraph about where it runs being a separate question from that it runs survives, because that is about the store, not about the switch. swarm-ui keeps its positive derivation (the controller) and loses only the "rather than from allSwarmServices" half. Deliberately not touched, as a different question rather than an oversight: the *mode* option's description in local-defaults.nix, whose whole job is to name what it asserts; the module comments in swarm-authelia.nix / swarm.nix, which are read by someone editing that module rather than by an operator picking an option; and ui.md's contrast, which she has not ruled on. --- nix/host-modules/deploy.nix | 43 ++++++++++++------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix index d7689dfd..16b4ce8f 100644 --- a/nix/host-modules/deploy.nix +++ b/nix/host-modules/deploy.nix @@ -158,9 +158,8 @@ in description = '' Run the swarm's metrics store on this host. - Derives from - {option}`services.hyperhive.deploy.allSwarmServices` together - with {option}`services.hyperhive.deploy.grafana.enable`: a store + Paired with + {option}`services.hyperhive.deploy.grafana.enable`: a store with no UI is unreadable and a UI with no store is empty, so there is no sensible deployment that takes one and not the other from that switch. Set either directly to run exactly one. @@ -173,10 +172,8 @@ in description = '' Run the swarm's log store on this host. - Derives from - {option}`services.hyperhive.deploy.allSwarmServices` for the - same reason as the metrics pair above: a hive that is not the - service host is a *client* of this store, not a second one. + A hive that is not the service host is a *client* of this store, + not a second one. ''; }; @@ -186,14 +183,10 @@ in example = true; description = '' Run the swarm's secret store in a `swarm-bao` container on this - host. A swarm has one store and it has to exist somewhere, so - this is asserted from - {option}`services.hyperhive.deploy.allSwarmServices` - alongside the other once-per-swarm services. + host. A swarm has one store and it has to exist somewhere. - That assertion is a `mkDefault`, which is what keeps *where* the - store runs a separate question from *that* it runs: set this - directly to put the store on a host of its own, and clients + *Where* it runs is a separate question from *that* it runs: set + this directly to put the store on a host of its own, and clients still reach it by name at {option}`services.hyperhive.swarm.bao.domain` rather than at a local address. @@ -212,9 +205,7 @@ in example = true; description = '' Run the swarm's authelia in a `swarm-authelia` container on this - host. {option}`services.hyperhive.deploy.allSwarmServices` - turns this on — a swarm has one SSO provider, and that says it - lives here. + host. A swarm has one SSO provider, and this says it lives here. With it off, this hive is a *client*: {option}`services.hyperhive.swarm.authelia.url` still points at @@ -230,10 +221,8 @@ in description = '' Run the **swarm's** telemetry collector on this host. - Derives from - {option}`services.hyperhive.deploy.allSwarmServices` with the - metrics pair it feeds: a swarm has one of these, and it belongs - wherever the shared services live rather than on every hive. + A swarm has one of these, and it belongs wherever the shared + services live rather than on every hive. Named `swarm-otel` rather than `otel` because there are two collectors and the tier is the whole distinction: @@ -252,10 +241,8 @@ in Run the swarm's matrix homeserver — matrix-tuwunel, in a `hive-matrix` container — on this host. - Derives from - {option}`services.hyperhive.deploy.allSwarmServices` with - the other once-per-swarm services. Set it here directly to put - the homeserver somewhere other than the host holding the rest. + Set it directly to put the homeserver somewhere other than the + host holding the rest of the swarm's services. Client-side settings stay in {option}`services.hyperhive.swarm.matrix.*`, which every hive @@ -299,10 +286,8 @@ in Serve the swarm UI from this host. Derived from - {option}`services.hyperhive.deploy.swarm-controller.enable` rather - than from - {option}`services.hyperhive.deploy.allSwarmServices`: the UI - is a view onto the controller's state and reaches it over that + {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.