diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index 673d7485..f79b0367 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -8,6 +8,22 @@ let cfg = config.services.hyperhive.forge; gatewayCfg = config.services.hyperhive.gateway; hyperhiveDomain = config.services.hyperhive.domain; + tlsCfg = config.services.hyperhive.tls; + + # Self-signed gateway TLS: forgejo (Go) validates outbound webhook + # deliveries (e.g. the config-PR webhook to https:///webhook/...) + # against its system cert store, which lacks the runtime-generated hive + # CA — so delivery fails with an x509 "unknown authority". Go has no + # additive trust env var (SSL_CERT_FILE *replaces* the default bundle), + # so bind the public CA in and hand forgejo a combined bundle (system + # CAs + hive CA) via SSL_CERT_FILE. Only active in self-signed mode; + # with an operator cert / ACME the public chain already validates and + # this whole block drops out. `gateway.useSelfSigned` is the single + # source of truth for the self-signed condition (no duplicated logic). + useSelfSigned = gatewayCfg.useSelfSigned; + caHostPath = "${tlsCfg.stateDir}/ca.pem"; + caContainerPath = "/run/hive-ca/ca.pem"; + forgeCaBundle = "/run/hive-forge-ca/ca-bundle.crt"; # ROOT_URL forgejo advertises in clone links + outbound URLs. When # served behind the gateway, `cfg.domain` doubles as both the @@ -303,6 +319,15 @@ in } ]; + # The hive CA cert is generated at runtime by the host `hive-tls-ca` + # service. Order the container after it (self-signed mode only) so the + # bind source exists before nspawn sets the CA mount up — a + # condition-skipped/late CA would otherwise fail the container start. + systemd.services."container@hive-forge" = lib.mkIf useSelfSigned { + after = [ "hive-tls-ca.service" ]; + requires = [ "hive-tls-ca.service" ]; + }; + containers.hive-forge = { autoStart = true; ephemeral = false; @@ -311,6 +336,17 @@ in # and agent containers (which also share host netns) reach it # via plain `localhost`. privateNetwork = false; + # Self-signed mode: bind ONLY the public hive CA cert (never the + # `hive-tls` state dir — it holds the CA + leaf private keys) so + # forgejo can trust the gateway's self-signed leaf for outbound + # webhook delivery. The combined bundle is assembled at container + # start by hive-forge-ca-bundle below. + bindMounts = lib.optionalAttrs useSelfSigned { + ${caContainerPath} = { + hostPath = caHostPath; + isReadOnly = true; + }; + }; config = { pkgs, ... }: let @@ -447,6 +483,36 @@ in "d /var/lib/forgejo/data/actions_artifacts 0750 forgejo forgejo - -" ]; + # Self-signed mode: assemble the combined TLS trust bundle + # (system CAs + the bind-mounted hive CA) forgejo's Go HTTP + # client validates outbound webhook deliveries against. Go's + # SSL_CERT_FILE *replaces* the default bundle, so we concatenate + # rather than point at the CA alone — otherwise mirror fetches + # from public hosts would lose their trust anchors. Runs before + # forgejo each boot; /run is tmpfs so the bundle is rebuilt from + # the current CA every start. + systemd.services.hive-forge-ca-bundle = lib.mkIf useSelfSigned { + description = "assemble forgejo TLS trust bundle (system CAs + hive CA)"; + wantedBy = [ "forgejo.service" ]; + before = [ "forgejo.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + SyslogIdentifier = "hive-forge-ca-bundle"; + }; + path = [ pkgs.coreutils ]; + script = '' + set -euo pipefail + install -d -m 0755 /run/hive-forge-ca + cat /etc/ssl/certs/ca-certificates.crt ${caContainerPath} \ + > ${forgeCaBundle} + chmod 0644 ${forgeCaBundle} + ''; + }; + # Point forgejo's Go TLS stack at the combined bundle so webhook + # delivery to the self-signed gateway validates. + systemd.services.forgejo.environment.SSL_CERT_FILE = lib.mkIf useSelfSigned forgeCaBundle; + # Ensure Forgejo has a usable GPG signing key so UI merges / CRUD # commits are signed instead of erroring "does not have a signing # key". This service (a) generates a key in forgejo's persistent