{ pkgs, lib, config, ... }: let cfg = config.services.hyperhive.swarm.forge; gatewayCfg = config.services.hyperhive.gateway; hyperhiveDomain = config.services.hyperhive.domain; swarmDomain = config.services.hyperhive.swarm.domain; tlsCfg = config.services.hyperhive.tls; # Forgejo's name for the login source. A constant, not an option: it # is the key this module's own idempotency check looks up, so making # it configurable would buy nothing and add a way for the lookup and # the row to disagree. ssoSourceName = "authelia"; # `url` is the half of the authelia module that exists on EVERY hive — # null when no SSO provider is configured anywhere, which the # assertion below turns into an eval failure rather than a discovery # request to `null/.well-known/...`. autheliaCfg = config.services.hyperhive.swarm.authelia; autheliaUrl = autheliaCfg.url; autheliaDiscoveryUrl = "${toString autheliaUrl}/.well-known/openid-configuration"; # The all-local case: this host runs BOTH the forge and the swarm's # authelia, so the secret can be moved without an operator. The other # two cases (swarm side via swarmctl, remote hive) leave # `clientSecretFile` to be set explicitly — see docs/swarm/. ssoLocal = cfg.sso.enable && autheliaCfg.enable; # Where the plaintext lands inside the forge container. Under # /var/lib rather than /run: the forge may start before the delivery # unit on a later boot, and a secret that evaporates on reboot turns a # working login into an intermittent one. forgeSecretPath = "/var/lib/forgejo-oidc/${cfg.sso.clientId}.secret"; # The swarm-controller's forge account. Same name as # `swarm-controller.nix`'s existing `queueClientId` — the account is # provisioned to *match* that identity, not invented independently — # "swarm controller having one identity with stuff derived from it is # the right shape" was the swarm-level design call this account # follows. A constant, not an option: # nothing here makes the name configurable without also updating # `swarm-controller.nix`'s own constant, so a shared option would # invite the two to drift rather than prevent it. swarmControllerForgeUser = "swarm-controller"; # Where the minted token lands inside the forge container — under # forgejo's own state dir for the same "survives a reboot" reason as # `forgeSecretPath` above. swarmControllerTokenPath = "/var/lib/forgejo/swarm-controller-token"; # Forgejo's OAuth2 callback shape. Built from the SAME `ssoSourceName` # the registration uses, so the redirect URI authelia is told to allow # and the one forgejo will actually send cannot drift apart — a # mismatch there is a rejected login with no error text worth reading. ssoRedirectUri = "${effectiveRootUrl}user/oauth2/${ssoSourceName}/callback"; caTrust = import ../lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; }; # ROOT_URL forgejo advertises in clone links + outbound URLs. When # served behind the gateway, `cfg.domain` doubles as both the # forgejo `DOMAIN` setting AND the gateway vhost server-name, so # ROOT_URL just uses it directly. The gateway always terminates TLS # (self-signed is the implicit floor when neither `tls.certDir` nor # ACME is configured), so behind the gateway the forge is always # advertised over `https` on `httpsPort` — the canonical 443 elides # the port suffix. When direct (`behindGateway = false`), keep the # host:httpPort shape so direct browser access still produces correct # links. Operators can still override via `cfg.rootUrl` for bespoke # shapes. defaultRootUrl = if cfg.behindGateway then let portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; in "https://${cfg.domain}${portSuffix}/" else "http://${cfg.domain}:${toString cfg.httpPort}/"; effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; # When CI is enabled, the runner needs `actions/checkout` resolvable # without external DNS (hive-ci shares the host netns, so a host-resolver # 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.swarm.forge.ci.enable; actionCheckoutMirror = { upstream = "https://github.com/actions/checkout"; dest = "actions/checkout"; }; # Auto-append the actions/checkout mirror only when CI is on AND the # operator hasn't already declared that dest themselves (else CI-on + # an explicit `actions/checkout` entry would duplicate it). effectiveMirrors = cfg.mirrors ++ lib.optional ( ciEnabled && !(lib.any (m: m.dest == actionCheckoutMirror.dest) cfg.mirrors) ) actionCheckoutMirror; in { # Private Forgejo in a `hive-forge` nixos-container, shared host # netns. Agents reach it at `forge.` via the gateway. State # at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/` survives # restart. See `docs/gateway.md::hive-forge container shape`. # External Forgejo/Gitea/Codeberg-compatible forges (beyond the mandatory # internal one) are entirely dashboard-provisioned — no nix config here. # An operator manually creates a token on the external forge (however # that forge lets them) and pastes name + base URL + token into the # dashboard's FORGES tab; hive-c0re just persists it to # `/forge-