refactor(3202): the forge declares its own vhost and dns name

Moves `forgeVhost` out of the gateway's vhosts.nix and the forge's
`address=` rule out of dnsmasq.nix, into nix/host-modules/hive-forge —
the module that already owns everything else about the forge.

The gateway keeps what is gateway knowledge (the listen set, which
issuer covers a name, the header block) and loses the last reason it
had to read `swarm.forge` at all: `forgeCfg` is gone from both files
and from the module's `let`.

Both halves stay gated on `behindGateway` — with it off the operator
fronts forgejo themselves, so this hive must neither claim the vhost nor
answer DNS for the name.
This commit is contained in:
atlas 2026-08-13 12:45:47 +02:00
commit d60a0585d6
4 changed files with 40 additions and 36 deletions

View file

@ -397,6 +397,39 @@ in
};
config = lib.mkIf config.services.hyperhive.enable {
# This service's own gateway surface: the vhost that fronts it and
# the name the hive resolver answers for. Declared here rather than
# in the gateway so the forge's public face lives with the forge —
# the gateway supplies the primitives (`lib.listen`, `lib.tlsFor`,
# `lib.securityHeaders`) and never needs to know this service by
# name.
#
# Both halves are gated on `behindGateway`: with it off the operator
# fronts forgejo themselves, so this hive must neither claim the
# vhost nor answer DNS for it.
services.hyperhive.gateway.localNames = lib.optional cfg.behindGateway cfg.domain;
# `server_name = forge.domain`, proxies all `/` → forgejo. Tuned for
# git: `client_max_body_size 1G`, `proxy_read_timeout 1h` (multi-GB
# clones). SSH stays direct on `forge.sshPort`. See
# `docs/gateway.md`.
services.nginx.virtualHosts = lib.optionalAttrs cfg.behindGateway {
"${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
listen = gatewayCfg.lib.listen;
extraConfig = gatewayCfg.lib.securityHeaders;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString cfg.httpPort}/";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
client_max_body_size 1G;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
'';
};
};
};
assertions = [
{
# Fail at EVAL, not at boot. The alternative failure is a login

View file

@ -25,7 +25,6 @@ let
autheliaCfg = config.services.hyperhive.swarm.authelia;
uiCfg = config.services.hyperhive.swarm.ui;
controllerCfg = config.services.hyperhive.swarm.controller;
forgeCfg = config.services.hyperhive.swarm.forge;
networkCfg = config.services.hyperhive.network;
# Dashboard SPA dist, static-served by nginx.
@ -87,7 +86,6 @@ let
inherit
lib
cfg
forgeCfg
matrixCfg
autheliaCfg
uiCfg
@ -374,7 +372,6 @@ in
lib
cfg
networkCfg
forgeCfg
matrixCfg
autheliaCfg
uiCfg

View file

@ -9,7 +9,6 @@
lib,
cfg, # services.hyperhive.gateway
networkCfg,
forgeCfg,
matrixCfg,
autheliaCfg,
uiCfg,
@ -57,17 +56,16 @@
# + its sub-domains with the bridge IP, where nginx is reachable
# from every container netns.
#
# The forge / matrix entries are redundant in the common case
# where `forge.domain` / `matrix.gatewayHost` are sub-domains of
# `hyperhive.domain` — dnsmasq's `/<domain>/` rule already matches
# sub-domains. Kept explicit because operators can override either
# to a cross-domain hostname (e.g. `forge.domain =
# "git.example.com"`); listing them explicitly keeps that case
# routed without needing an extra config block.
# The matrix entry is redundant in the common case where
# `matrix.gatewayHost` is a sub-domain of `hyperhive.domain` —
# dnsmasq's `/<domain>/` rule already matches sub-domains. Kept
# explicit because an operator can override it to a cross-domain
# hostname (e.g. `git.example.com` for the forge); listing such a
# name explicitly keeps that case routed without an extra config
# block.
address = [
"/${hyperhiveDomain}/${networkCfg.bridgeIp}"
]
++ lib.optional ((forgeCfg.behindGateway or false)) "/${forgeCfg.domain}/${networkCfg.bridgeIp}"
++ lib.optional (
matrixCfg.enable && matrixCfg.gatewayHost != null
) "/${matrixCfg.gatewayHost}/${networkCfg.bridgeIp}"

View file

@ -7,7 +7,6 @@
{
lib,
cfg, # services.hyperhive.gateway
forgeCfg,
matrixCfg,
autheliaCfg, # services.hyperhive.swarm.authelia
uiCfg, # services.hyperhive.swarm.ui
@ -37,28 +36,6 @@ let
publicPort = cfg.httpsPort;
publicPortSuffix = if publicPort == 443 then "" else ":${toString publicPort}";
# Forge sub-domain vhost. `server_name = forge.domain`, proxies
# all `/` → forgejo. Tuned for git: `client_max_body_size 1G`,
# `proxy_read_timeout 1h` (multi-GB clones). SSH stays direct on
# `forge.sshPort`. See `docs/gateway.md`. Empty attrset when the
# forge isn't behind the gateway.
forgeVhost = lib.optionalAttrs (forgeCfg.behindGateway or false) {
"${forgeCfg.domain}" = (vhostTlsFor forgeCfg.domain) // {
listen = vhostListen;
extraConfig = securityHeaders;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString forgeCfg.httpPort}/";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
client_max_body_size 1G;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
'';
};
};
};
# Authelia sub-domain vhost. `server_name = authelia.domain`, all of
# `/` → authelia. Empty attrset unless THIS host runs the container:
# every hive knows the swarm's `authelia.url`, but only the one
@ -504,7 +481,6 @@ in
'';
};
}
// forgeVhost
// autheliaVhost
// matrixVhost
// swarmUiVhost;