From 38c222fadd532491446cfa87d1fe60f75f4bf0f8 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 4 Aug 2026 22:48:37 +0200 Subject: [PATCH] feat(nix): move the forge host options under services.hyperhive.swarm Forgejo is a swarm-global service, so its operator-facing host options move to services.hyperhive.swarm.forge (and .swarm.forge.ci) as the first of the namespace consolidation. Existing hive configs keep evaluating: swarm-renames.nix maps every moved leaf with mkRenamedOptionModule, which also emits a deprecation warning naming both the old and new path, so an operator is told what to rename rather than discovering it from a failed eval. The per-agent hyperhive.forge.url does NOT move. It is a client pointer at whatever forge an agent talks to - it shares a word with the service and nothing else, and the two are already documented as separate option surfaces. Verified by evaluating the host module, since no Rust gate evaluates nix: setting the old paths and reading the new ones yields the values (httpPort 3999, ci.concurrency 7), and config.warnings carries the rename notice. --- nix/docs/default.nix | 3 +- nix/host-modules/default.nix | 1 + nix/host-modules/hive-c0re/default.nix | 2 +- nix/host-modules/hive-c0re/environment.nix | 8 ++-- nix/host-modules/hive-ci.nix | 12 ++--- nix/host-modules/hive-forge/default.nix | 18 ++++---- nix/host-modules/hive-gateway/default.nix | 6 +-- nix/host-modules/swarm-renames.nix | 52 ++++++++++++++++++++++ 8 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 nix/host-modules/swarm-renames.nix diff --git a/nix/docs/default.nix b/nix/docs/default.nix index fef90953..4f7a1144 100644 --- a/nix/docs/default.nix +++ b/nix/docs/default.nix @@ -153,7 +153,8 @@ let - [host options](host.md) — options exposed by `hyperhive.nixosModules.default` to operator host configurations - (`services.hyperhive.{enable,domain,c0re,forge,matrix,gateway}.*`). + (`services.hyperhive.{enable,domain,c0re,gateway}.*`, + `services.hyperhive.swarm.{forge,matrix}.*`). - [per-agent options](agent.md) — options declared in `nix/agent-modules/`, visible from every `agent.nix` (`hyperhive.model`, `hyperhive.allowedRecipients`, diff --git a/nix/host-modules/default.nix b/nix/host-modules/default.nix index 2b87a421..9a0ebcc7 100644 --- a/nix/host-modules/default.nix +++ b/nix/host-modules/default.nix @@ -12,6 +12,7 @@ { imports = [ ./hyperhive.nix + ./swarm-renames.nix ./hive-c0re ./hive-ci.nix ./hive-forge diff --git a/nix/host-modules/hive-c0re/default.nix b/nix/host-modules/hive-c0re/default.nix index 28937561..2cb40eea 100644 --- a/nix/host-modules/hive-c0re/default.nix +++ b/nix/host-modules/hive-c0re/default.nix @@ -31,7 +31,7 @@ let safeDirGitconfig = pkgs.writeText "hyperhive-safe-gitconfig" '' [safe] directory = * - [credential "http://${config.services.hyperhive.forge.domain}"] + [credential "http://${config.services.hyperhive.swarm.forge.domain}"] helper = hive-forge username = core [http] diff --git a/nix/host-modules/hive-c0re/environment.nix b/nix/host-modules/hive-c0re/environment.nix index 956a23af..0a86bbba 100644 --- a/nix/host-modules/hive-c0re/environment.nix +++ b/nix/host-modules/hive-c0re/environment.nix @@ -98,7 +98,7 @@ in # agents via meta.rs for their forge-notify client. The forge is # mandatory, so this is unconditional (the whole env block is already # gated on hyperhive being enabled). See `docs/gateway.md::HIVE_FORGE_URL`. - HIVE_FORGE_URL = "http://${config.services.hyperhive.forge.domain}"; + HIVE_FORGE_URL = "http://${config.services.hyperhive.swarm.forge.domain}"; } // lib.optionalAttrs @@ -147,17 +147,17 @@ in # dashboard doesn't need to learn the gateway is unconditional. HIVE_GATEWAY_ENABLED = "1"; } -// lib.optionalAttrs (config.services.hyperhive.forge.publicUrl != null) { +// lib.optionalAttrs (config.services.hyperhive.swarm.forge.publicUrl != null) { # Public URL of the forge, for the dashboard to build browser-facing # forge links from instead of guessing `:3000` (which # breaks the moment the operator's browser hostname isn't the forge # host, e.g. through the gateway or a reverse proxy). Sourced from - # `services.hyperhive.forge.publicUrl`, which itself defaults to the + # `services.hyperhive.swarm.forge.publicUrl`, which itself defaults to the # gateway vhost URL when `behindGateway = true` and `null` otherwise # — see that option's doc for the "hide, don't guess" rationale. # Absent here whenever `publicUrl` is `null`; the dashboard hides # forge links rather than emitting one it can't justify. - HIVE_FORGE_PUBLIC_URL = config.services.hyperhive.forge.publicUrl; + HIVE_FORGE_PUBLIC_URL = config.services.hyperhive.swarm.forge.publicUrl; } // lib.optionalAttrs diff --git a/nix/host-modules/hive-ci.nix b/nix/host-modules/hive-ci.nix index cd8e3a34..408e490a 100644 --- a/nix/host-modules/hive-ci.nix +++ b/nix/host-modules/hive-ci.nix @@ -5,8 +5,8 @@ ... }: let - cfg = config.services.hyperhive.forge.ci; - forgeCfg = config.services.hyperhive.forge; + cfg = config.services.hyperhive.swarm.forge.ci; + forgeCfg = config.services.hyperhive.swarm.forge; gatewayCfg = config.services.hyperhive.gateway; networkCfg = config.services.hyperhive.network; tlsCfg = config.services.hyperhive.tls; @@ -52,14 +52,14 @@ in # nspawn containers can't create the user-namespaces that nix sandboxing # requires. See docs/gotchas.md. - options.services.hyperhive.forge.ci = { + options.services.hyperhive.swarm.forge.ci = { enable = lib.mkOption { type = lib.types.bool; default = false; example = true; description = '' Run a Forgejo Actions runner in a `hive-ci` nixos-container. - Grouped under `services.hyperhive.forge` because the runner is + Grouped under `services.hyperhive.swarm.forge` because the runner is tightly coupled to the forge instance it registers against. Disabled by default; the internal forge it registers against is always present (mandatory), so enabling this is all that's needed. @@ -142,8 +142,8 @@ in { assertion = forgeCfg.behindGateway; message = '' - services.hyperhive.forge.ci.enable requires - services.hyperhive.forge.behindGateway = true. + services.hyperhive.swarm.forge.ci.enable requires + services.hyperhive.swarm.forge.behindGateway = true. The CI container runs with a private network namespace and reaches the forge through the gateway vhost on the bridge IP. Set behindGateway = true (it defaults to true alongside diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index ea1fe885..118f5022 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -5,7 +5,7 @@ ... }: let - cfg = config.services.hyperhive.forge; + cfg = config.services.hyperhive.swarm.forge; gatewayCfg = config.services.hyperhive.gateway; hyperhiveDomain = config.services.hyperhive.domain; tlsCfg = config.services.hyperhive.tls; @@ -53,7 +53,7 @@ let # blip otherwise reds every `actions/checkout@vN` fetch from # data.forgejo.org). Auto-append a pull-mirror of it and point # forgejo's DEFAULT_ACTIONS_URL at this instance so `uses:` resolves local. - ciEnabled = config.services.hyperhive.forge.ci.enable; + ciEnabled = config.services.hyperhive.swarm.forge.ci.enable; actionCheckoutMirror = { upstream = "https://github.com/actions/checkout"; dest = "actions/checkout"; @@ -86,7 +86,7 @@ in # meta flake + every agent's config repo (and the `internal/*` repos), # so there is no enable/disable toggle. It deploys whenever hyperhive # itself is enabled (`services.hyperhive.enable`). - options.services.hyperhive.forge = { + options.services.hyperhive.swarm.forge = { httpPort = lib.mkOption { type = lib.types.port; default = 3000; @@ -244,7 +244,7 @@ in **Breaking change**: this used to default to `true`. If you relied on the old default for external reach, add - `services.hyperhive.forge.openFirewall = true;` to your host + `services.hyperhive.swarm.forge.openFirewall = true;` to your host config before rebuilding. ''; }; @@ -283,7 +283,7 @@ in so a host-resolver blip leaves a *stale* mirror, never a hard failure on whatever reads it. - When `services.hyperhive.forge.ci.enable` is set, an + When `services.hyperhive.swarm.forge.ci.enable` is set, an `actions/checkout` mirror is auto-appended to this list and forgejo's `DEFAULT_ACTIONS_URL` is pointed at this instance, so CI `uses: actions/checkout@vN` steps resolve entirely on loopback with @@ -298,7 +298,7 @@ in { assertion = cfg.rootUrl == null || lib.hasSuffix "/" cfg.rootUrl; message = '' - services.hyperhive.forge.rootUrl must end with "/". forgejo's + services.hyperhive.swarm.forge.rootUrl must end with "/". forgejo's ROOT_URL contract requires a trailing slash for correct relative-link generation; without it forgejo emits URLs like `https://forge.example.com.user.id` instead of @@ -313,7 +313,7 @@ in # loud. assertion = cfg.domain != ""; message = '' - services.hyperhive.forge.domain = "" is rejected. The + services.hyperhive.swarm.forge.domain = "" is rejected. The rendered URLs would be invalid (nginx wildcard catch-all for an empty server_name, /etc/hosts rejects empty entries). Either leave at default (auto-derives to @@ -326,7 +326,7 @@ in # splits on the single slash to create the org + repo. assertion = lib.all (m: lib.length (lib.splitString "/" m.dest) == 2) effectiveMirrors; message = '' - Every services.hyperhive.forge.mirrors[].dest must be exactly + Every services.hyperhive.swarm.forge.mirrors[].dest must be exactly "/" (one slash). Got: ${lib.concatMapStringsSep ", " (m: m.dest) effectiveMirrors} ''; } @@ -344,7 +344,7 @@ in ]) ) effectiveMirrors; message = '' - services.hyperhive.forge.mirrors[].dest must not place a mirror + services.hyperhive.swarm.forge.mirrors[].dest must not place a mirror in a hive-c0re-managed org (config / shared / agents / core) — those are provisioned by hive-c0re and a mirror there would collide. Use a dedicated org (e.g. "actions/checkout"). diff --git a/nix/host-modules/hive-gateway/default.nix b/nix/host-modules/hive-gateway/default.nix index f813692e..327c27ce 100644 --- a/nix/host-modules/hive-gateway/default.nix +++ b/nix/host-modules/hive-gateway/default.nix @@ -16,7 +16,7 @@ let cfg = config.services.hyperhive.gateway; hyperhiveDomain = config.services.hyperhive.domain; matrixCfg = config.services.hyperhive.matrix; - forgeCfg = config.services.hyperhive.forge; + forgeCfg = config.services.hyperhive.swarm.forge; networkCfg = config.services.hyperhive.network; # Dashboard SPA dist, static-served by nginx. Read in OUTER scope so @@ -411,8 +411,8 @@ in networking.hosts = lib.mkIf cfg.localHostsEntry { "127.0.0.1" = lib.unique ( [ hyperhiveDomain ] - ++ lib.optional (config.services.hyperhive.forge.behindGateway or false - ) config.services.hyperhive.forge.domain + ++ lib.optional (config.services.hyperhive.swarm.forge.behindGateway or false + ) config.services.hyperhive.swarm.forge.domain ++ lib.optional (matrixCfg.enable && matrixCfg.gatewayHost != null) matrixCfg.gatewayHost ); }; diff --git a/nix/host-modules/swarm-renames.nix b/nix/host-modules/swarm-renames.nix new file mode 100644 index 00000000..6acd7749 --- /dev/null +++ b/nix/host-modules/swarm-renames.nix @@ -0,0 +1,52 @@ +# Backwards-compatibility aliases for options that moved under +# `services.hyperhive.swarm.*` when the swarm-global services were +# consolidated into one namespace. +# +# `mkRenamedOptionModule` maps the old path to the new one *and* emits a +# deprecation warning naming both, so an existing hive keeps evaluating and +# its operator is told exactly what to rename. That is the whole reason the +# move is safe to make in one commit: nothing has to be edited in lockstep. +# +# ⚠️ Only options that are genuinely **swarm-global** belong here. The +# per-agent `hyperhive.forge.url` (`nix/agent-modules/forge.nix`) is a +# client pointer at whatever forge an agent talks to — it shares a word with +# the service and nothing else, and it does not move. +{ lib, ... }: +let + # One entry per moved leaf. Written as a list rather than a fold over the + # option tree on purpose: a rename has to name both paths explicitly, and + # deriving them would make an accidental rename invisible in review. + renamed = old: new: lib.mkRenamedOptionModule old new; + + forgeOption = + name: + renamed [ "services" "hyperhive" "forge" name ] [ "services" "hyperhive" "swarm" "forge" name ]; + + forgeCiOption = + name: + renamed + [ "services" "hyperhive" "forge" "ci" name ] + [ "services" "hyperhive" "swarm" "forge" "ci" name ]; +in +{ + imports = + map forgeOption [ + "httpPort" + "sshPort" + "domain" + "publicUrl" + "package" + "behindGateway" + "rootUrl" + "openFirewall" + "mirrors" + ] + ++ map forgeCiOption [ + "enable" + "name" + "concurrency" + "labels" + "package" + "jobTimeout" + ]; +}