refactor(gateway): extract the _ vhost agent + dashboard location groups

Step 3 of the hive-gateway.nix vhost cleanup. Lift the /agent/ catch-all
(+ its two internal error-page targets) and the catch-all dashboard proxy
out of the inline //-chain into agentLocations / dashboardProxyLocation
bindings. The default _ server's locations now read as a flat composition
of named groups — matrixRedirectLocations // wellKnownLocations //
agentLocations // dashboardProxyLocation // <auth optionalAttrs> — instead
of a deep nested literal. The auth-401 group stays inline (a self-contained
lib.optionalAttrs already).

Pure readability refactor, eval-identical: generated virtualHosts toJSON is
byte-identical before/after (8888 bytes, diff empty).
This commit is contained in:
atlas 2026-06-06 15:47:23 +02:00 committed by mara
commit 36e5e19d8e

View file

@ -643,9 +643,9 @@ in
};
# `_` (default) server location groups, lifted out of the inline
# `//`-chain so the two matrix groups (each with its own `let`)
# read on their own. Composed into the `_` vhost's `locations`
# below alongside the still-inline agent/dashboard/auth groups.
# `//`-chain so each conditional group reads on its own. Composed
# into the `_` vhost's `locations` below alongside the still-inline
# auth-401 group (a self-contained `lib.optionalAttrs`).
# `<hive>/matrix/*` → 301 → `matrix.<hive>/$1` (legacy deep-link
# shim during the fluffychat sub-domain move). See `docs/gateway.md`.
@ -707,6 +707,68 @@ in
};
}
);
# `/agent/` catch-all 404 + the two internal error-page targets it
# points at. Per-agent `location /agent/<name>/` blocks live in the
# runtime-generated `/run/hive-state/agents.conf` (included via
# `extraConfig` on the vhost); nginx longest-prefix-match makes a
# real `/agent/<name>/` beat this catch-all. `internal` keeps the
# error pages reachable only through nginx's error handling.
agentLocations = {
"/agent/" = {
extraConfig = ''
error_page 404 = /__hive_agent_not_found;
return 404;
'';
};
"= /__hive_agent_not_found" = {
extraConfig = ''
internal;
alias ${agentErrorPagesDir}/not-found.html;
default_type text/html;
'';
};
"= /__hive_agent_unreachable" = {
extraConfig = ''
internal;
alias ${agentErrorPagesDir}/unreachable.html;
default_type text/html;
'';
};
};
# Everything else proxies to hive-c0re. Upgrade headers stay set so
# SSE (`/dashboard/stream`, `/events/stream`) + websocket
# (`/screen/ws`) endpoints keep working transparently. When auth is
# enabled, nginx's built-in `auth_basic` validates against the
# bind-mounted htpasswd; the `=401` error_page points at the
# internal unauthorized page (the auth-only location below).
dashboardProxyLocation = {
"/" = {
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
proxy_read_timeout 1d;
${lib.optionalString cfg.auth.enable ''
auth_basic "${cfg.auth.realm}";
# htpasswd file lives in the gateway state dir,
# already bind-mounted read-only at /run/hive-state/.
# Host path: /var/lib/hyperhive/gateway/gateway.htpasswd
auth_basic_user_file /run/hive-state/gateway.htpasswd;
# Serve a custom page when credentials are missing or wrong.
# `=401` forces the final status to remain 401 so browsers
# still present the login dialog on first visit; users who
# dismiss the dialog see a page explaining how to add users
# with `hivectl gateway create-user`.
# The exact-match location below beats `location /` in nginx's
# prefix ordering, so the internal subrequest does not loop back
# through auth_basic.
error_page 401 =401 /__hive_auth_unauthorized;
''}
'';
};
};
in
{
system.stateVersion = "26.05";
@ -830,78 +892,8 @@ in
locations =
matrixRedirectLocations
// wellKnownLocations
//
# `/agent/` catch-all: hits when an operator
# requests `/agent/<unknown>/...`. Without this the
# request falls through to `/` (c0re dashboard) and
# returns 404 with no useful context. Custom 404
# page instead. Per-agent `location /agent/<name>/`
# blocks live in `/run/hive-state/agents.conf` —
# nginx picks them up via the `include` in
# `extraConfig` below; the catch-all only matches
# names that aren't in that file (nginx longest-
# prefix-match: `/agent/atlas/` beats `/agent/`).
{
"/agent/" = {
extraConfig = ''
error_page 404 = /__hive_agent_not_found;
return 404;
'';
};
# Internal static-file locations the error_page
# directives above point at. `internal` keeps
# operators from hitting the file directly (only
# nginx's error-handling can reach it); `alias`
# serves the exact file regardless of request URI.
"= /__hive_agent_not_found" = {
extraConfig = ''
internal;
alias ${agentErrorPagesDir}/not-found.html;
default_type text/html;
'';
};
"= /__hive_agent_unreachable" = {
extraConfig = ''
internal;
alias ${agentErrorPagesDir}/unreachable.html;
default_type text/html;
'';
};
}
// {
# Everything else proxies to hive-c0re. Upgrade
# headers stay set so SSE (`/dashboard/stream`,
# `/events/stream`) + websocket (`/screen/ws`)
# endpoints keep working transparently.
# When auth is enabled, nginx's built-in `auth_basic`
# validates credentials against the htpasswd file
# bind-mounted at `/run/gateway-auth/`. No extra
# service or host-side daemon required.
"/" = {
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
proxyWebsockets = true;
extraConfig = ''
proxy_buffering off;
proxy_read_timeout 1d;
${lib.optionalString cfg.auth.enable ''
auth_basic "${cfg.auth.realm}";
# htpasswd file lives in the gateway state dir,
# already bind-mounted read-only at /run/hive-state/.
# Host path: /var/lib/hyperhive/gateway/gateway.htpasswd
auth_basic_user_file /run/hive-state/gateway.htpasswd;
# Serve a custom page when credentials are missing or wrong.
# `=401` forces the final status to remain 401 so browsers
# still present the login dialog on first visit; users who
# dismiss the dialog see a page explaining how to add users
# with `hivectl gateway create-user`.
# The exact-match location below beats `location /` in nginx's
# prefix ordering, so the internal subrequest does not loop back
# through auth_basic.
error_page 401 =401 /__hive_auth_unauthorized;
''}
'';
};
}
// agentLocations
// dashboardProxyLocation
// lib.optionalAttrs cfg.auth.enable {
# Internal-only target for the 401 error_page above.
# `internal` prevents direct client access; `alias` serves