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

Moves the authelia vhost out of the gateway's vhosts.nix and its
`address=` rule out of dnsmasq.nix, into swarm-authelia.nix.

Both land inside that module's existing `cfg.enable` guard, which is
the load-bearing part: every hive in a swarm knows `authelia.url`, but
only the host that RUNS the container may claim the name. A client hive
declaring the vhost would answer for a service it does not run, and
publishing the DNS record would point every agent on its bridge at that
wrong answer.

The kit grows a fourth member, `errorPages`, because the vhost aims its
502/503/504 at the gateway's styled sso-unavailable page. Republished
rather than imported per module: a service rendering its own would drift
from the rest of the gateway the first time the theme changed.
This commit is contained in:
atlas 2026-08-13 12:50:55 +02:00
commit 56ab6d26c1
6 changed files with 91 additions and 56 deletions

View file

@ -35,6 +35,7 @@
let
cfg = config.services.hyperhive.swarm.authelia;
hyperhiveCfg = config.services.hyperhive;
gatewayCfg = hyperhiveCfg.gateway;
hyperhiveDomain = hyperhiveCfg.domain;
swarmDomain = hyperhiveCfg.swarm.domain;
uiCfg = hyperhiveCfg.swarm.ui;
@ -402,6 +403,59 @@ in
};
config = lib.mkIf (hyperhiveCfg.enable && cfg.enable) {
# Authelia's own gateway surface: the vhost that fronts it and the
# name the hive resolver answers for. Both live here rather than in
# the gateway, and both are inside `cfg.enable` — that guard is the
# load-bearing part.
#
# ⚠️ Every hive in a swarm knows `authelia.url`, but only the host
# that RUNS the container may claim the name. A client hive
# declaring this vhost would answer for a service it does not run,
# and publishing the DNS record would point every agent on its
# bridge at that wrong answer.
services.hyperhive.gateway.localNames = [ cfg.domain ];
# `server_name = authelia.domain`, all of `/` → authelia.
#
# ⚠️ The server name must be exactly `cfg.domain`, not a near-miss:
# authelia validates `authelia_url ⊂ session cookie domain` at
# STARTUP, so a mismatch is a container that refuses to boot rather
# than a login that misbehaves.
#
# ⚠️ And deliberately NO `dashboardAuth` here. That block is the
# gateway's `auth_basic`; applying it to the SSO provider would put
# the login page behind the login mechanism it exists to replace.
services.nginx.virtualHosts."${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
listen = gatewayCfg.lib.listen;
extraConfig = gatewayCfg.lib.securityHeaders;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString cfg.port}/";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
# authelia decides by the ORIGINAL request, not by the hop it
# sees — the login redirect and the session cookie's domain
# both derive from these. Without them every request looks
# like it arrived at 127.0.0.1 over plain http.
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# A dead upstream here means "not bootstrapped" far more often
# than "misconfigured proxy", and a bare 502 says the opposite.
proxy_intercept_errors on;
error_page 502 503 504 = /__hive_sso_unavailable;
'';
};
locations."= /__hive_sso_unavailable" = {
extraConfig = ''
internal;
alias ${gatewayCfg.lib.errorPages.ssoUnavailable};
default_type text/html;
'';
};
};
containers.${cfg.machine} = {
autoStart = true;
ephemeral = false;