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.