diff --git a/nix/modules/hive-ci.nix b/nix/modules/hive-ci.nix index abe82b3b..8d2f7093 100644 --- a/nix/modules/hive-ci.nix +++ b/nix/modules/hive-ci.nix @@ -7,6 +7,18 @@ let cfg = config.services.hyperhive.forge.ci; forgeCfg = config.services.hyperhive.forge; + gatewayCfg = config.services.hyperhive.gateway; + tlsCfg = config.services.hyperhive.tls; + + # Self-signed TLS is the gateway default (no operator cert / ACME). When + # active, forgejo's ROOT_URL is `https://forge.` and the leaf is + # signed by the host hive CA — so the runner's Node-based actions (e.g. + # `upload-artifact`, which POSTs to the ROOT_URL-derived artifact endpoint) + # reject the chain, since Node trusts only its bundled CA bundle, not the + # system store. Trust the hive CA explicitly via NODE_EXTRA_CA_CERTS below. + useSelfSigned = gatewayCfg.tls.certDir == null && !gatewayCfg.tls.acme.enable; + caHostPath = "${tlsCfg.stateDir}/ca.pem"; + caContainerPath = "/run/hive-ca/ca.pem"; # hive-c0re writes its own admin token here on first forge startup. # The token has read:admin + write:admin scopes — sufficient to call @@ -315,6 +327,15 @@ in }; }; + # Self-signed mode: the CA cert the bind-mount above sources is + # generated by the host `hive-tls-ca` service. Order the container after + # it so the bind source exists before nspawn sets the mount up (a + # condition-skipped/late CA would otherwise fail the container start). + systemd.services."container@hive-ci" = lib.mkIf useSelfSigned { + after = [ "hive-tls-ca.service" ]; + requires = [ "hive-tls-ca.service" ]; + }; + containers.hive-ci = { autoStart = true; ephemeral = false; @@ -329,6 +350,18 @@ in hostPath = "/run/hive-ci/runner-token"; isReadOnly = true; }; + } + # Self-signed mode: bind ONLY the public hive CA cert (never the + # `hive-tls` state dir — it holds the CA + leaf private keys) so the + # runner's Node actions can trust the gateway/forge self-signed leaf + # (see NODE_EXTRA_CA_CERTS in the container config). Source generated + # by the host `hive-tls-ca` service; the container@hive-ci ordering + # below guarantees it exists before this mount is set up. + // lib.optionalAttrs useSelfSigned { + ${caContainerPath} = { + hostPath = caHostPath; + isReadOnly = true; + }; }; config = @@ -408,6 +441,19 @@ in systemd.services."gitea-runner-hive" = { path = [ pkgs.nix ]; + # Trust the hive CA in Node-based actions. With self-signed TLS, + # forgejo's ROOT_URL is `https://forge.` (CA-signed leaf), + # so actions like `upload-artifact` — whose Node HTTP client uses + # Node's *bundled* CA bundle, not the system store — reject the + # chain with "unable to verify the first certificate". Pointing + # NODE_EXTRA_CA_CERTS at the bind-mounted CA adds it to Node's + # roots for every action, hive-wide. Inherited by the job + # processes the runner spawns (host execution mode). Only set in + # self-signed mode; with an operator cert / ACME the public CA + # already validates and the bind-mount is absent. + environment = lib.mkIf useSelfSigned { + NODE_EXTRA_CA_CERTS = caContainerPath; + }; # Gate runner start (and therefore job registration/claiming) on # the in-container nix daemon being reachable. After a hive-ci # restart the runner re-registers and immediately claims any