diff --git a/docs/swarm/services.md b/docs/swarm/services.md index 56764f15..4668c9a3 100644 --- a/docs/swarm/services.md +++ b/docs/swarm/services.md @@ -75,7 +75,7 @@ empty. To run one without the other, set it directly: ```nix services.hyperhive.swarm.victoriametrics.enable = true; -services.hyperhive.swarm.grafana.enable = false; +services.hyperhive.deploy.grafana = false; ``` ⚠️ **This starts a database that grows for as long as the swarm runs.** diff --git a/nix/host-modules/default.nix b/nix/host-modules/default.nix index da0225c9..8c180f4f 100644 --- a/nix/host-modules/default.nix +++ b/nix/host-modules/default.nix @@ -12,6 +12,7 @@ { imports = [ ./hyperhive.nix + ./deploy.nix ./local-defaults.nix ./hive-c0re ./hive-ci.nix diff --git a/nix/host-modules/deploy.nix b/nix/host-modules/deploy.nix new file mode 100644 index 00000000..7fdc5d73 --- /dev/null +++ b/nix/host-modules/deploy.nix @@ -0,0 +1,53 @@ +# "What does THIS host deploy?" +# +# Separated from `services.hyperhive.swarm.*` because those are two +# different kinds of fact and only one of them varies per machine: +# +# swarm.* — swarm-wide truth. The swarm's name, domain, hives, peers, +# CA, and where each service lives. **Identical on every +# host**, byte for byte; a hive needs all of it to be a +# *client* of the swarm. +# deploy.* — this machine's deployment decisions. Necessarily different +# on every host, because that is what a deployment is. +# +# The `enable` toggles used to live under `swarm.*`, which made the +# namespace that is supposed to be identical everywhere carry the one +# thing that must differ. +# +# Flat and named for the thing deployed — `deploy.forgejo`, not +# `deploy.swarmServices.forgejo`. Grouping by "swarm service" would +# re-encode the service-side taxonomy into a layer that does not care +# about it: from here, a host deploys forgejo, or a hive, or the lot, and +# what *kind* of thing each one is belongs to the service module. +# +# ⚠️ The renames below are deliberately in this one file rather than +# spread across the service modules, so the whole move has a single home +# and a single file to delete when the deprecation window closes — the +# shape ./swarm-peers-removed.nix already uses. +{ lib, ... }: +{ + imports = [ + # Same type, same meaning, new path — so a rename carries it exactly + # and existing configs keep evaluating with one warning naming both + # paths. Precedent: ./hive-forge/default.nix, ./hive-matrix.nix. + (lib.mkRenamedOptionModule + [ "services" "hyperhive" "swarm" "grafana" "enable" ] + [ "services" "hyperhive" "deploy" "grafana" ] + ) + ]; + + options.services.hyperhive.deploy = { + grafana = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Run the swarm's metrics UI on this host. + + Off by default and not derived from + {option}`services.hyperhive.enable`: a swarm has one Grafana, so + running it is a decision about this host rather than about + whether hyperhive is installed. + ''; + }; + }; +} diff --git a/nix/host-modules/swarm-grafana.nix b/nix/host-modules/swarm-grafana.nix index 1e9fde3f..919c2fa8 100644 --- a/nix/host-modules/swarm-grafana.nix +++ b/nix/host-modules/swarm-grafana.nix @@ -16,6 +16,7 @@ }: let cfg = config.services.hyperhive.swarm.grafana; + deployCfg = config.services.hyperhive.deploy; networkCfg = config.services.hyperhive.network; hyperhiveCfg = config.services.hyperhive; gatewayCfg = hyperhiveCfg.gateway; @@ -106,18 +107,12 @@ let in { + # `enable` moved to `services.hyperhive.deploy.grafana` — see + # ./deploy.nix. Whether this host runs the swarm's Grafana is a + # deployment decision, and `swarm.*` has to be identical on every host. + # What stays here is what the service IS: its package, domain, and + # wiring. options.services.hyperhive.swarm.grafana = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Run the swarm's metrics UI on this host. Off by default and not - derived from {option}`services.hyperhive.enable`: a swarm has one - Grafana, so enabling it is a decision about swarm topology rather - than about whether hyperhive is installed. - ''; - }; - package = lib.mkOption { type = lib.types.package; default = pkgs.grafana; @@ -312,8 +307,8 @@ in }; }; - config = lib.mkIf (hyperhiveCfg.enable && cfg.enable) { - # The gateway name and the quick-link, both inside `cfg.enable` — that + config = lib.mkIf (hyperhiveCfg.enable && deployCfg.grafana) { + # The gateway name and the quick-link, both inside `deploy.grafana` — that # guard is the load-bearing part. Every hive in a swarm may know this UI # exists, but only the host that RUNS it may claim the name; a client # hive declaring the vhost would answer for a service it does not have. diff --git a/nix/host-modules/swarm-required-services.nix b/nix/host-modules/swarm-required-services.nix index a40b7e56..dbaa26a0 100644 --- a/nix/host-modules/swarm-required-services.nix +++ b/nix/host-modules/swarm-required-services.nix @@ -69,7 +69,8 @@ in # takes one and not the other from this switch. An operator who wants # exactly one still sets it directly, which `mkDefault` allows. victoriametrics.enable = lib.mkDefault swarmCfg.enableRequiredServices; - grafana.enable = lib.mkDefault swarmCfg.enableRequiredServices; + # (grafana's half of the pair derives below — it lives in `deploy.*` + # now, which is a different attribute path, not a different rule.) # The log store, deriving from the same switch for the same reason — # and deliberately in the same commit as the collector pipeline that @@ -88,4 +89,10 @@ in # 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; + + # Grafana, the UI half of the metrics pair. Same derivation and the same + # reasoning as `victoriametrics.enable` above; it reads differently only + # because "does this host run it" now lives in `deploy.*` (./deploy.nix) + # rather than under `swarm.*`, which has to be identical on every host. + config.services.hyperhive.deploy.grafana = lib.mkDefault swarmCfg.enableRequiredServices; } diff --git a/nix/host-modules/swarm.nix b/nix/host-modules/swarm.nix index aad8691a..6437b9fd 100644 --- a/nix/host-modules/swarm.nix +++ b/nix/host-modules/swarm.nix @@ -23,6 +23,7 @@ let cfg = config.services.hyperhive; swarmCfg = cfg.swarm; + deployCfg = cfg.deploy; # Public hostnames of the swarm's own services, in declaration order. # `serviceDomains` below is this set sorted + deduplicated. @@ -57,7 +58,7 @@ let # metrics UI and store looked fine for as long as only people opened # them; the collector's exporter — same defect, no human in the loop — # failed every POST and dropped the samples. - ++ lib.optional swarmCfg.grafana.enable swarmCfg.grafana.domain + ++ lib.optional deployCfg.grafana swarmCfg.grafana.domain ++ lib.optional swarmCfg.victoriametrics.enable swarmCfg.victoriametrics.domain ++ lib.optional swarmCfg.otel.enable swarmCfg.otel.domain # VictoriaLogs' vhost is new (was previously unpublished entirely — see