# `checks.module-eval-forge-placement` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). # # Where the forge runs. A swarm has one, on the host with # `deploy.forgejo.enable`; every other hive is a client of it, and the parts # of a split deployment that must not assume every host runs a forge # (the OIDC client, the controller's token, the CI runner) have to hold. { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) hive runGroup ; bare = hive { }; allLocal = hive { deploy.singleHostSwarm = true; }; servicesHere = hive { deploy.allSwarmServices = true; }; forgeHere = hive { deploy.forgejo.enable = true; }; # The shared services here, the forge somewhere else. Every derivation in # ../host-modules/swarm-required-services.nix is `mkDefault`, so this stays # expressible — and it is also the authelia-without-forge host the OIDC # client case needs. servicesForgeElsewhere = hive { deploy.allSwarmServices = true; deploy.forgejo.enable = false; }; # The forge without authelia: the other half of the split. forgeNoAuthelia = hive { deploy.forgejo.enable = true; deploy.forgejo.sso.clientSecretFile = "/var/lib/forgejo-oidc/by-hand.secret"; }; # The runner alone: no hive, no forge, no store, its store identity placed # by hand. ciAlone = hive { deploy.hive-controller.enable = false; deploy.ci-runner.enable = true; deploy.bao.ciRunnerClientCertFile = "/etc/pki/bao-ci-runner.pem"; deploy.bao.ciRunnerClientKeyFile = "/etc/pki/bao-ci-runner-key.pem"; }; ciNoIdentity = hive { deploy.hive-controller.enable = false; deploy.ci-runner.enable = true; }; # A host config that still names the runner under the forge. ciUnderForge = hive { deploy.forgejo.ci.enable = true; }; runsForge = m: m.services.hyperhive.deploy.forgejo.enable && m.containers ? hive-forge; forgeClientIds = m: map (c: c.id) ( lib.filter ( c: c.id == m.services.hyperhive.swarm.forge.sso.clientId ) m.services.hyperhive.swarm.authelia.oidc.clients ); # Matched on the option the message names, same reasoning as # ./grafana.nix's `grafanaRefusedFor`. refusedOver = m: needle: lib.any (a: !a.assertion && lib.hasInfix needle a.message) m.assertions; tokenFile = m: m.services.hyperhive.deploy.swarm-controller.forgeTokenFile; forgePath = "/var/lib/hyperhive-forge/swarm-controller.token"; forgeSettings = forgeHere.containers.hive-forge.config.services.forgejo.settings; cases = [ { # The absence the whole option exists for: a second forge in a swarm # is a split brain nobody notices, so a hive that has not been told it # is the forge's host runs none of its surface. name = "a hive that is not the forge's host runs no forge"; ok = !bare.services.hyperhive.deploy.forgejo.enable && !(bare.containers ? hive-forge) && !(bare.systemd.services ? hive-forge-swarm-controller-token) && !(lib.elem "forge.t.local" bare.services.hyperhive.gateway.localNames) && !(refusedOver bare "deploy.forgejo.sso.clientSecretFile"); } { name = "hosting the swarm's shared services runs the forge"; ok = runsForge servicesHere; } { name = "the all-local mode runs the forge"; ok = runsForge allLocal && lib.elem "forge.t.local" allLocal.services.hyperhive.gateway.localNames; } { name = "an explicit deploy.forgejo.enable runs the forge on its own"; ok = runsForge forgeHere && !forgeHere.services.hyperhive.deploy.allSwarmServices; } { # `mkDefault`, not a plain assignment: the forge stays placeable on a # host of its own. `nats` is the control, so the case cannot pass on a # fixture where nothing came on. name = "placing the forge elsewhere survives the switch that would enable it"; ok = !(servicesForgeElsewhere.containers ? hive-forge) && servicesForgeElsewhere.services.hyperhive.deploy.nats.enable; } { # A client is a row in authelia's config, so it is declared where # authelia runs. With the forge's module gated, registering it from # there would leave a split swarm's forge unknown to its IdP. name = "the forge's OIDC client is registered wherever authelia runs, and only there"; ok = forgeClientIds servicesForgeElsewhere == [ "forgejo" ] && forgeClientIds allLocal == [ "forgejo" ] && forgeClientIds forgeNoAuthelia == [ ]; } { name = "the forge's OIDC callback is the one forgejo sends"; ok = servicesForgeElsewhere.services.hyperhive.swarm.forge.sso.redirectUri == "https://forge.t.local/user/oauth2/authelia/callback"; } { # The runner reaches the forge by name and reads its token with its own # certificate, so nothing about the host it runs on is asked. name = "the CI runner runs on a host with neither the forge nor a hive"; ok = ciAlone.containers ? swarm-ci-runner && !(ciAlone.containers ? hive-forge) && !ciAlone.services.hyperhive.deploy.hive-controller.enable && !(refusedOver ciAlone "ci-runner") && ciAlone.systemd.services.swarm-bao-ci-runner.environment.BAO_CLIENT_CERT == "/etc/pki/bao-ci-runner.pem"; } { # Without the identity the token never arrives; the control is the # placed pair above. name = "the CI runner is refused without its store identity"; ok = refusedOver ciNoIdentity "deploy.bao.ciRunnerClientCertFile"; } { # A removed option evaluates to an assertion naming the replacement. name = "a CI option under deploy.forgejo names its replacement under deploy.ci-runner"; ok = refusedOver ciUnderForge "services.hyperhive.deploy.ci-runner.enable instead"; } { # Nothing writes the delivery path away from the forge, and a # `LoadCredential=` naming a missing path is fatal to the unit. name = "the controller's forge token defaults to the delivery path only where the forge runs"; ok = tokenFile bare == null && tokenFile servicesForgeElsewhere == null && tokenFile forgeHere == forgePath && tokenFile allLocal == forgePath; } { # The only thing that makes a human's forge account: nothing else # creates one. name = "a first authelia login creates the forge account, named by preferred_username"; ok = let o = forgeSettings.oauth2_client; in o.ENABLE_AUTO_REGISTRATION == true && o.USERNAME == "preferred_username"; } { # `auto` would give an SSO user whatever local account carries their # name — an agent's, or `core`'s. name = "an SSO login adopts an existing forge account only with that account's password"; ok = forgeSettings.oauth2_client.ACCOUNT_LINKING == "login"; } { # Both halves: local sign-up stays off, and nothing turns on the one # setting forgejo's auto-registration does check. name = "local sign-up stays off without blocking the SSO registration"; ok = forgeSettings.service.DISABLE_REGISTRATION == true && !(forgeSettings.service.ALLOW_ONLY_INTERNAL_REGISTRATION or false); } ]; in runGroup "forge-placement" cases