diff --git a/docs/scheduler/ci.md b/docs/scheduler/ci.md index d2762c66..5719856f 100644 --- a/docs/scheduler/ci.md +++ b/docs/scheduler/ci.md @@ -219,7 +219,7 @@ Set `actionsFromSelf = false` there to resolve actions from `data.forgejo.org` and seed no mirror. The option defaults to whether the forge's host also runs the swarm controller -(`services.hyperhive.deploy.swarm-controller.enable`): on when it does, off +(`services.hyperhive.deploy.swarm-controller.enable`; set by `glue-forge-controller.nix`): on when it does, off otherwise. Setting it `true` on a forge host without the controller leaves the actions unresolved, because nothing seeds the mirrors; evaluation warns about it. @@ -228,7 +228,7 @@ warns about it. The **swarm controller** seeds the mirror in its swarm-wide forge-objects pass (`swarm-controller/src/forge/objects.rs`), beside the orgs it ensures. -The nix module forwards the effective mirror list as +`glue-forge-controller.nix` forwards the effective mirror list as `SWARM_CONTROLLER_FORGE_MIRRORS` in the `swarm-controller` service environment (JSON-encoded `[{upstream, dest}]` list). It reads the list of the host the controller runs on: mirrors declared on any other host aren't diff --git a/nix/host-modules/default.nix b/nix/host-modules/default.nix index 99e988e6..aedbd81c 100644 --- a/nix/host-modules/default.nix +++ b/nix/host-modules/default.nix @@ -31,6 +31,7 @@ ./glue-bao-ui-oidc-client.nix ./glue-controller-bao-identity.nix ./glue-controller-oidc-client.nix + ./glue-forge-controller.nix ./glue-forge-oidc-client.nix ./glue-grafana-oidc-client.nix ./glue-hive-otel-authelia.nix diff --git a/nix/host-modules/glue-forge-controller.nix b/nix/host-modules/glue-forge-controller.nix new file mode 100644 index 00000000..ccce7ab1 --- /dev/null +++ b/nix/host-modules/glue-forge-controller.nix @@ -0,0 +1,29 @@ +# Glue: wire the forge and the swarm-controller together when one host runs both. +# +# ONE PAIRING PER FILE — forge ← swarm-controller, and nothing else. Deleting +# this leaves the controller seeding no pull-mirrors and the forge resolving +# workflow actions from `data.forgejo.org`. +# +# ⚠️ Gated on BOTH services being here: the mirrors reach a controller on this +# host only, and the forge's own `config` block hangs off +# `deploy.forgejo.enable`. The forge's `mirrors` warning covers the split case. +{ + lib, + config, + ... +}: +let + deployCfg = config.services.hyperhive.deploy; +in +{ + config = lib.mkIf (deployCfg.forgejo.enable && deployCfg.swarm-controller.enable) { + # The controller seeds the `actions/*` mirrors, so a forge beside one can + # resolve actions from itself. `mkDefault`: an operator's explicit value wins. + services.hyperhive.deploy.forgejo.actionsFromSelf = lib.mkDefault true; + + # JSON-encoded list of { upstream, dest }, seeded by the controller's + # forge-objects pass (`swarm-controller/src/forge/objects.rs`). + systemd.services.swarm-controller.environment.SWARM_CONTROLLER_FORGE_MIRRORS = + builtins.toJSON deployCfg.forgejo.effectiveMirrors; + }; +} diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index 754be86d..1c0c7d5c 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -219,8 +219,7 @@ in actionsFromSelf = lib.mkOption { type = lib.types.bool; - default = deployCfg.swarm-controller.enable; - defaultText = lib.literalExpression "config.services.hyperhive.deploy.swarm-controller.enable"; + default = false; example = false; description = '' Resolve a workflow's `uses: /@vN` against this forge @@ -230,8 +229,9 @@ in have something to resolve to. A CI job's actions then need no external DNS; only the mirrors' re-sync does. - Defaults to on when this host runs the swarm-controller, which seeds - the mirrors, and off otherwise. Setting it `true` on a host without + Defaults to on when this host also runs the swarm-controller, which + seeds the mirrors (set by `glue-forge-controller.nix`), and off + otherwise. Setting it `true` on a host without the controller leaves both actions unresolved: nothing seeds the mirrors, and the warning about unseeded mirrors fires. @@ -240,6 +240,16 @@ in ''; }; + effectiveMirrors = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + internal = true; + readOnly = true; + description = '' + {option}`mirrors` plus the `actions/*` mirrors {option}`actionsFromSelf` + adds; what `glue-forge-controller.nix` forwards to the controller. + ''; + }; + # The other half of the SSO pair: the client *id* is swarm-wide (it has # to match authelia's register), the secret is a path on this host. The # design note for the login source itself is with the id. @@ -1238,17 +1248,11 @@ in ]; }; - # Forward the declared pull-mirrors to the swarm-controller, which seeds - # them in its swarm-wide forge-objects pass - # (`swarm-controller/src/forge/objects.rs`), beside the orgs they live in. - # JSON-encoded list of { upstream, dest }; `[]` when nothing to seed. - # - # ⚠️ This host's `mirrors` reach a controller on THIS host only. A - # controller elsewhere reads its own host's list, so mirrors declared - # here would never be seeded; the warning below says so at eval rather - # than leaving it to be noticed as a missing repo. - systemd.services.swarm-controller.environment.SWARM_CONTROLLER_FORGE_MIRRORS = - lib.mkIf deployCfg.swarm-controller.enable (builtins.toJSON effectiveMirrors); + # The mirrors reach a controller on THIS host only (see + # ../glue-forge-controller.nix); a controller elsewhere reads its own + # host's list, so the warning below says so at eval rather than leaving + # it to be noticed as a missing repo. + services.hyperhive.deploy.forgejo.effectiveMirrors = effectiveMirrors; warnings = lib.optional (effectiveMirrors != [ ] && !deployCfg.swarm-controller.enable) '' services.hyperhive.deploy.forgejo.mirrors (or the actions/* mirrors deploy.forgejo.actionsFromSelf adds) is set on a host that does not run the diff --git a/nix/module-eval/swarm-services-switch.nix b/nix/module-eval/swarm-services-switch.nix index edbe577c..dcd92397 100644 --- a/nix/module-eval/swarm-services-switch.nix +++ b/nix/module-eval/swarm-services-switch.nix @@ -564,6 +564,13 @@ let !forgeOnly.services.hyperhive.deploy.forgejo.actionsFromSelf && actionsUrlOf forgeOnly == null && forgeOnly.warnings == [ ] + && !( + (forgeOnly.systemd.services.swarm-controller.environment or { }) ? SWARM_CONTROLLER_FORGE_MIRRORS + ) + && !( + (mirrorsNoController.systemd.services.swarm-controller.environment or { }) + ? SWARM_CONTROLLER_FORGE_MIRRORS + ) && forgeWithController.services.hyperhive.deploy.forgejo.actionsFromSelf && actionsUrlOf forgeWithController == "self" && lib.elem "actions/checkout" (map (m: m.dest) (mirrorsOf forgeWithController)); diff --git a/swarm-controller/src/forge/objects.rs b/swarm-controller/src/forge/objects.rs index ab349631..b33d4c3d 100644 --- a/swarm-controller/src/forge/objects.rs +++ b/swarm-controller/src/forge/objects.rs @@ -117,7 +117,7 @@ Do **not** push directly to `main` — agents have read-only access. "; /// The operator-declared pull-mirrors (nix `deploy.forgejo.mirrors`, plus the -/// `actions/*` entries `deploy.forgejo.actionsFromSelf` adds), JSON-encoded by `hive-forge/default.nix`. +/// `actions/*` entries `deploy.forgejo.actionsFromSelf` adds), JSON-encoded by `glue-forge-controller.nix`. /// Absent or empty means no mirrors. const MIRRORS_ENV: &str = "SWARM_CONTROLLER_FORGE_MIRRORS";