From 6caf1774168e79e99d8986f976e7decf311aa9eb Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 13 Aug 2026 14:36:17 +0200 Subject: [PATCH] refactor(3202): matrix declares its own vhost, dns name and SPA map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the matrix sub-domain vhost out of the gateway's vhosts.nix, its `address=` rule out of dnsmasq.nix, and the Accept-header `$matrix_spa_target` map out of the gateway's appendHttpConfig — all three into hive-matrix.nix. The map is the one that had no business being where it was: it exists solely for the SPA fallback in the vhost's `/` location, and `appendHttpConfig` is a `lines` option, so a module can contribute to it without the gateway assembling it. The `.well-known/matrix/*` delegation deliberately stays on the hive's own vhost. The spec requires it at the SERVER NAME, which is the hive domain: that is the hive answering "where is my homeserver", not the homeserver answering for itself. Moving it would have been the obvious symmetric thing and it would have been wrong. --- nix/host-modules/hive-gateway/default.nix | 3 +- nix/host-modules/hive-gateway/dnsmasq.nix | 12 ---- nix/host-modules/hive-gateway/vhosts.nix | 69 +------------------ nix/host-modules/hive-matrix.nix | 80 +++++++++++++++++++++++ 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/nix/host-modules/hive-gateway/default.nix b/nix/host-modules/hive-gateway/default.nix index 0b9deea5..5ef0ab9b 100644 --- a/nix/host-modules/hive-gateway/default.nix +++ b/nix/host-modules/hive-gateway/default.nix @@ -347,7 +347,7 @@ in recommendedTlsSettings = true; recommendedGzipSettings = true; recommendedOptimisation = true; - inherit (nginxTree) appendHttpConfig virtualHosts; + inherit (nginxTree) virtualHosts; }; # ⚠️ NO `SupplementaryGroups = [ "hive-core" ]` on nginx, and its @@ -379,7 +379,6 @@ in lib cfg networkCfg - matrixCfg uiCfg hyperhiveDomain ; diff --git a/nix/host-modules/hive-gateway/dnsmasq.nix b/nix/host-modules/hive-gateway/dnsmasq.nix index 80ce77c8..bf0d6766 100644 --- a/nix/host-modules/hive-gateway/dnsmasq.nix +++ b/nix/host-modules/hive-gateway/dnsmasq.nix @@ -9,7 +9,6 @@ lib, cfg, # services.hyperhive.gateway networkCfg, - matrixCfg, uiCfg, hyperhiveDomain, }: @@ -54,20 +53,9 @@ # Hive authoritative records — answer queries for the hive domain # + its sub-domains with the bridge IP, where nginx is reachable # from every container netns. - # - # The matrix entry is redundant in the common case where - # `matrix.gatewayHost` is a sub-domain of `hyperhive.domain` — - # dnsmasq's `//` 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 ( - matrixCfg.enable && matrixCfg.gatewayHost != null - ) "/${matrixCfg.gatewayHost}/${networkCfg.bridgeIp}" # The swarm UI's name is the swarm APEX by default — a sibling of # the three above, not a child of anything this resolver already # answers for, so the `//` rule does not cover it. diff --git a/nix/host-modules/hive-gateway/vhosts.nix b/nix/host-modules/hive-gateway/vhosts.nix index d5d15a4d..6f6c79a0 100644 --- a/nix/host-modules/hive-gateway/vhosts.nix +++ b/nix/host-modules/hive-gateway/vhosts.nix @@ -3,7 +3,7 @@ # and authelia sub-domain vhosts, and the Accept-header SPA map for the # matrix GUI. Pure function — called from ./default.nix with the # outer-scope config values as arguments; returns -# `{ virtualHosts, appendHttpConfig }`. +# `{ virtualHosts }`. { lib, cfg, # services.hyperhive.gateway @@ -145,61 +145,6 @@ let }; }; - # Matrix sub-domain vhost. `server_name = matrixCfg.gatewayHost`. - # `/_matrix/*` → tuwunel (CORS *, 50M body cap, 1h long-poll - # timeout). `/` serves fluffychat or 404 if GUI off. nginx - # longer-prefix-wins puts `/_matrix/` ahead of `/`. See - # `docs/gateway.md`. Empty attrset when matrix has no gateway host. - matrixVhost = lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) { - "${matrixCfg.gatewayHost}" = (vhostTlsFor matrixCfg.gatewayHost) // { - listen = vhostListen; - extraConfig = securityHeaders; - locations = { - "/_matrix/" = { - proxyPass = "http://127.0.0.1:${toString matrixCfg.httpPort}"; - proxyWebsockets = true; - extraConfig = '' - proxy_buffering off; - client_max_body_size 50M; - proxy_read_timeout 1h; - proxy_send_timeout 1h; - ${securityHeaders} - add_header Access-Control-Allow-Origin *; - ''; - }; - } - // lib.optionalAttrs (matrixCfg.gui.enable) ( - { - # fluffychat at sub-domain root, SPA-fallback via - # the Accept-header `$matrix_spa_target` map. - "/" = { - alias = "${matrixCfg.gui.package}/"; - extraConfig = '' - try_files $uri $uri/ $matrix_spa_target =404; - ''; - }; - } - // { - # FluffyChat boot-config pre-fill so the client's - # `.well-known/matrix/client` lookup hits the - # right delegation endpoint. `domain` is required, so - # this is always present. - "= /config.json" = { - extraConfig = '' - default_type application/json; - return 200 '{"defaultHomeserver":"${hyperhiveDomain}"}'; - ''; - }; - } - ) - // lib.optionalAttrs (!matrixCfg.gui.enable) { - "/" = { - return = "404"; - }; - }; - }; - }; - # `/matrix/*` → 301 → `matrix./$1` (legacy deep-link # shim during the fluffychat sub-domain move). See `docs/gateway.md`. matrixRedirectLocations = @@ -385,17 +330,6 @@ let }; in { - # Accept-header SPA map for the matrix GUI only (see docs/gateway.md - # "SPA fallback"): text/html → index.html, else a sentinel so - # try_files falls through to 404. The dashboard doesn't use an - # Accept-header map — it routes by path (see dashboardProxyLocation). - appendHttpConfig = lib.optionalString (matrixCfg.enable && matrixCfg.gui.enable) '' - map $http_accept $matrix_spa_target { - default "/__matrix_spa_no_html_fallback"; - "~*text/html" "/index.html"; - } - ''; - virtualHosts = { # `tlsFor "_"`, not a separate binding: the default server is a # vhost named `_`, and a name that is not a swarm service domain @@ -435,6 +369,5 @@ in }; } - // matrixVhost // swarmUiVhost; } diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index 89780930..4a7e9d5c 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -9,6 +9,7 @@ let networkCfg = config.services.hyperhive.network; tlsCfg = config.services.hyperhive.tls; gatewayCfg = config.services.hyperhive.gateway; + hyperhiveDomain = config.services.hyperhive.domain; # Same runtime→build-time bridge hive-ci and hive-forge already cross: # binds the hive trust bundle (which folds in the swarm root) into the @@ -396,6 +397,85 @@ in }; config = lib.mkIf cfg.enable { + # Matrix's own gateway surface: the sub-domain vhost, the name the + # hive resolver answers for, and the Accept-header map that vhost's + # SPA fallback reads. All three are matrix knowledge and none of + # them is the gateway's business. + # + # `gatewayHost = null` means matrix is reachable directly rather + # than fronted, so there is no name to claim and no vhost to serve — + # every clause below carries that guard. + services.hyperhive.gateway.localNames = lib.optional (cfg.gatewayHost != null) cfg.gatewayHost; + + # Accept-header SPA map, used only by the `/` location below (see + # docs/gateway.md "SPA fallback"): text/html → index.html, else a + # sentinel so `try_files` falls through to 404. `appendHttpConfig` + # is a `lines` option, so this merges with anything else the host + # contributes instead of replacing it. + # + # The dashboard needs no equivalent — it routes by path. + services.nginx.appendHttpConfig = lib.optionalString cfg.gui.enable '' + map $http_accept $matrix_spa_target { + default "/__matrix_spa_no_html_fallback"; + "~*text/html" "/index.html"; + } + ''; + + # `server_name = gatewayHost`. `/_matrix/*` → tuwunel (CORS `*`, 50M + # body cap, 1h long-poll timeout). `/` serves fluffychat, or 404 + # with the GUI off. nginx's longest-prefix rule puts `/_matrix/` + # ahead of `/` with no ordering needed. + # + # ⚠️ The `.well-known/matrix/*` delegation is deliberately NOT here. + # It stays on the hive's own vhost because the spec requires it to + # be served at the *server name*, which is the hive domain — it is + # the hive answering "where is my homeserver", not the homeserver + # answering for itself. + services.nginx.virtualHosts = lib.optionalAttrs (cfg.gatewayHost != null) { + "${cfg.gatewayHost}" = (gatewayCfg.lib.tlsFor cfg.gatewayHost) // { + listen = gatewayCfg.lib.listen; + extraConfig = gatewayCfg.lib.securityHeaders; + locations = { + "/_matrix/" = { + proxyPass = "http://127.0.0.1:${toString cfg.httpPort}"; + proxyWebsockets = true; + extraConfig = '' + proxy_buffering off; + client_max_body_size 50M; + proxy_read_timeout 1h; + proxy_send_timeout 1h; + ${gatewayCfg.lib.securityHeaders} + add_header Access-Control-Allow-Origin *; + ''; + }; + } + // lib.optionalAttrs cfg.gui.enable { + # fluffychat at sub-domain root, SPA-fallback via the + # Accept-header `$matrix_spa_target` map above. + "/" = { + alias = "${cfg.gui.package}/"; + extraConfig = '' + try_files $uri $uri/ $matrix_spa_target =404; + ''; + }; + # FluffyChat boot-config pre-fill so the client's + # `.well-known/matrix/client` lookup hits the right delegation + # endpoint. `domain` is required, so this is always present. + "= /config.json" = { + extraConfig = '' + default_type application/json; + return 200 '{"defaultHomeserver":"${hyperhiveDomain}"}'; + ''; + }; + } + // lib.optionalAttrs (!cfg.gui.enable) { + "/" = { + return = "404"; + }; + }; + }; + }; + # `serverName` is irrevocably embedded in user/room IDs; it derives # from `services.hyperhive.domain` (required, asserted in # hive-network.nix) when not set explicitly, so no separate