From 36e5e19d8e61822eaeb9b0be74ed18e2407ae435 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 6 Jun 2026 15:47:23 +0200 Subject: [PATCH] refactor(gateway): extract the _ vhost agent + dashboard location groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 // — 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). --- nix/modules/hive-gateway.nix | 142 +++++++++++++++++------------------ 1 file changed, 67 insertions(+), 75 deletions(-) diff --git a/nix/modules/hive-gateway.nix b/nix/modules/hive-gateway.nix index 7ce91da8..219785e7 100644 --- a/nix/modules/hive-gateway.nix +++ b/nix/modules/hive-gateway.nix @@ -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`). # `/matrix/*` → 301 → `matrix./$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//` 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//` 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//...`. Without this the - # request falls through to `/` (c0re dashboard) and - # returns 404 with no useful context. Custom 404 - # page instead. Per-agent `location /agent//` - # 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