refactor(3202): matrix declares its own vhost, dns name and SPA map

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.
This commit is contained in:
atlas 2026-08-13 14:36:17 +02:00
commit 6caf177416
4 changed files with 82 additions and 82 deletions

View file

@ -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