refactor(#2427): extract shared hive-CA trust bind-mount helper

hive-ci and hive-forge both bind the runtime-generated hive CA cert
read-only and order their container@ unit after hive-tls-ca.service so
the bind source exists before nspawn sets the mount up — the same
bind-mount + ordering + rationale duplicated verbatim in two modules.

Extract that language-agnostic half into a pure helper,
nix/host-modules/lib/hive-ca-trust.nix, taking a container name and
returning { useSelfSigned, caContainerPath, bindMount, containerOrdering }.
The per-runtime consumption stays at each call site (hive-ci's additive
NODE_EXTRA_CA_CERTS, hive-forge's Go SSL_CERT_FILE concat). hive-ci folds
containerOrdering into its existing mkMerge alongside the TimeoutStartSec
bump.

The helper is a pure function, not a module: host-modules/default.nix is
an explicit aggregator (not a glob) and the docs eval imports that same
aggregator, so the lib/ file is never picked up as a module. A third
outbound-TLS-trusting container no longer means a third copy-paste.
This commit is contained in:
atlas 2026-07-15 20:06:43 +02:00 committed by mara
commit 6c4ef5f798
3 changed files with 92 additions and 49 deletions

View file

@ -18,11 +18,13 @@ let
# 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";
# this whole block drops out. The bind-mount + `container@` ordering
# that make the CA reachable are shared with hive-ci via the
# `hive-ca-trust` helper; only the Go SSL_CERT_FILE concat below is
# hive-forge-specific.
caTrust = import ../lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; };
useSelfSigned = caTrust.useSelfSigned;
caContainerPath = caTrust.caContainerPath;
forgeCaBundle = "/run/hive-forge-ca/ca-bundle.crt";
# ROOT_URL forgejo advertises in clone links + outbound URLs. When
@ -319,14 +321,10 @@ 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" ];
};
# `caTrust.containerOrdering` orders this unit after `hive-tls-ca.service`
# in self-signed mode (see the hive-ca-trust helper), so the CA bind
# source exists before nspawn sets the mount up.
systemd.services."container@hive-forge" = caTrust.containerOrdering;
containers.hive-forge = {
autoStart = true;
@ -336,17 +334,12 @@ 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;
};
};
# Self-signed mode: bind the public hive CA cert read-only so forgejo
# can trust the gateway's self-signed leaf for outbound webhook
# delivery (combined bundle assembled at container start by
# hive-forge-ca-bundle below). Shared bind-mount + ordering come from
# the hive-ca-trust helper.
bindMounts = caTrust.bindMount;
config =
{ pkgs, ... }:
let