diff --git a/nix/modules/hive-gateway.nix b/nix/modules/hive-gateway.nix index aeaf6e72..d38e05de 100644 --- a/nix/modules/hive-gateway.nix +++ b/nix/modules/hive-gateway.nix @@ -326,48 +326,21 @@ in # (https://spec.matrix.org/v1.15/client-server-api/#getwell-knownmatrixclient). # No-op until the operator turns matrix on; until then # there's no homeserver to advertise. - lib.optionalAttrs (matrixCfg.enable && hyperhiveDomain != null) ( - let - # `.well-known/matrix/{client,server}` advertise where - # the actual matrix API lives. When `matrixCfg.gatewayHost` - # is set (default `matrix.`, #747), point - # at the sub-domain — no port suffix when the gateway - # is on the canonical port 80, transparent to clients - # (mara on #749:9609 sub-domain verdict, "not user- - # visible because the .well-known redirect routes - # clients through automatically"). When `gatewayHost` - # is unset (no hive-domain, or operator nulled it), - # fall back to the direct `host:port` shape — clients - # reach tuwunel without going through the gateway, - # no sub-domain delegation. - portSuffix = if cfg.port == 80 then "" else ":${toString cfg.port}"; - clientBaseUrl = - if matrixCfg.gatewayHost != null then - "http://${matrixCfg.gatewayHost}${portSuffix}" - else - "http://${hyperhiveDomain}:${toString matrixCfg.httpPort}"; - serverHostPort = - if matrixCfg.gatewayHost != null then - "${matrixCfg.gatewayHost}${portSuffix}" - else - "${hyperhiveDomain}:${toString matrixCfg.httpPort}"; - in - { - "= /.well-known/matrix/client" = { - extraConfig = '' - default_type application/json; - add_header Access-Control-Allow-Origin *; - return 200 '{"m.homeserver":{"base_url":"${clientBaseUrl}"}}'; - ''; - }; - "= /.well-known/matrix/server" = { - extraConfig = '' - default_type application/json; - return 200 '{"m.server":"${serverHostPort}"}'; - ''; - }; - } - ) + lib.optionalAttrs (matrixCfg.enable && hyperhiveDomain != null) { + "= /.well-known/matrix/client" = { + extraConfig = '' + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"m.homeserver":{"base_url":"http://${hyperhiveDomain}:${toString matrixCfg.httpPort}"}}'; + ''; + }; + "= /.well-known/matrix/server" = { + extraConfig = '' + default_type application/json; + return 200 '{"m.server":"${hyperhiveDomain}:${toString matrixCfg.httpPort}"}'; + ''; + }; + } // # Per-agent UIs (#15 v0). One `/agent//` # block per `: ` entry in @@ -469,61 +442,6 @@ in ''; }; }; - } - // - # Matrix homeserver vhost (#747, mara verdict on #749:9609 — - # sub-domain over sub-path for matrix; "not user-visible" - # because clients discover the sub-domain via the - # `.well-known/matrix/{client,server}` delegation served - # above on the bare hive-domain). - # - # `server { server_name = matrixCfg.gatewayHost; }` proxies - # `/_matrix/...` → `http://127.0.0.1:''${matrixCfg.httpPort}/_matrix/...`. - # Tuwunel listens on `:''${httpPort}` (default 8008); the - # gateway terminates on `:''${cfg.port}` (80) so external - # clients speak matrix over the canonical web port without - # operators having to open the tuwunel port through firewalls. - # - # `/` returns 404 — nothing else lives at the matrix vhost; - # the matrix client-server API is entirely under `/_matrix/`, - # and federation under `/_matrix/federation/...`. - # - # CORS `*` on the matrix vhost per the matrix spec — - # federation + client requests come from any origin. - # - # `client_max_body_size 50M` covers typical media uploads - # (matrix-spec media size cap default); operators with bigger - # uploads override via the matrix module's own cap when that - # lands. - # - # `proxy_read_timeout 1h` for long-poll `/sync`; the default - # 60s would abort `/sync?timeout=30000` legitimately when - # tuwunel's keepalive exceeds that. - lib.optionalAttrs (matrixCfg.enable && matrixCfg.gatewayHost != null) { - "${matrixCfg.gatewayHost}" = { - listen = [ - { - addr = "0.0.0.0"; - port = cfg.port; - } - ]; - 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; - add_header Access-Control-Allow-Origin *; - ''; - }; - "/" = { - return = "404"; - }; - }; - }; }; }; }; @@ -534,16 +452,16 @@ in }; # `/etc/hosts` entries for local dev: the bare hive domain plus - # any sub-domain modules (forge via #749/#754, matrix via #747) - # that are on. All map to `127.0.0.1` since the gateway shares - # host netns. Operators with real DNS leave `localHostsEntry = - # false`; this is the dev-loop shortcut for `http:///` - # + `http://forge./` + `http://matrix./` - # resolving locally. + # any sub-domain modules (forge, matrix-via-#751) that are on. + # All map to `127.0.0.1` since the gateway shares host netns. + # Operators with real DNS leave `localHostsEntry = false`; this + # is the dev-loop shortcut for `http:///` + + # `http://forge./` resolving locally. # - # `lib.unique` collapses any duplicate (e.g. if forge.domain - # happens to equal hyperhiveDomain or matrixCfg.gatewayHost) so - # `/etc/hosts` doesn't carry the same entry twice. + # Forge's `cfg.domain` may equal `hyperhiveDomain` (e.g. operator + # set `forge.domain = "darkest.space"` matching the hive domain) + # — `lib.unique` collapses the duplicate so `/etc/hosts` doesn't + # carry the same entry twice. networking.hosts = lib.mkIf (cfg.localHostsEntry && hyperhiveDomain != null) { "127.0.0.1" = lib.unique ( [ hyperhiveDomain ] @@ -551,7 +469,6 @@ in (config.services.hyperhive.forge.enable or false) && (config.services.hyperhive.forge.behindGateway or false) ) config.services.hyperhive.forge.domain - ++ lib.optional (matrixCfg.enable && matrixCfg.gatewayHost != null) matrixCfg.gatewayHost ); }; }; diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index 05b97925..907312aa 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -252,63 +252,6 @@ in ''; }; - gatewayHost = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = if hyperhiveDomain != null then "matrix.${hyperhiveDomain}" else null; - defaultText = lib.literalExpression '' - if services.hyperhive.domain != null then - "matrix.''${services.hyperhive.domain}" - else - null - ''; - example = "matrix.example.com"; - description = '' - Public hostname for the matrix homeserver behind the - hive-gateway nginx (#747, mara verdict on #749:9609 — sub-domain - over sub-path for matrix, but **not user-visible** because the - `.well-known/matrix/{client,server}` redirect routes clients - through automatically). - - When set + gateway is on, the gateway adds a `server { server_name - = gatewayHost; }` block that proxies `/_matrix/...` → - `http://127.0.0.1:''${httpPort}/_matrix/...`. The - `.well-known/matrix/{client,server}` endpoints (served by the - gateway at the bare hive-domain) then point at - `http(s)://''${gatewayHost}/` — matrix clients automatically - discover + follow that delegation. - - Defaults to `matrix.''${services.hyperhive.domain}` when the - hive-domain is set (idiomatic matrix-spec shape — `matrix` - labelled under the hive's bare server_name domain). Defaults to - `null` when the hive-domain is unset (gateway vhost not added; - clients reach tuwunel directly on `httpPort`). - - Set to a full hostname (`matrix.example.com`, - `homeserver.internal.lan`) for a bespoke vhost shape. Set to - `null` to disable the gateway vhost entirely (tuwunel stays - direct on `httpPort`). - - **server_name vs gatewayHost**: `serverName` is the matrix - identifier domain embedded in user/room IDs irrevocably (per - #660 default = bare hive-domain). `gatewayHost` is just where - the API listens behind nginx. The two are different — see the - matrix-spec server-discovery flow. - - **Federation port caveat**: the `.well-known/matrix/server` - delegation advertises `''${gatewayHost}` with no port suffix - when the gateway listens on 80. Per the matrix federation - spec, peers fall back to port 8448 when no explicit port is - present — but the gateway only listens on the configured - `services.hyperhive.gateway.port`. Cross-hive federation - therefore needs either: - - a DNS SRV record (`_matrix._tcp.''${gatewayHost}` → port 80), - - or `services.hyperhive.matrix.openFirewall = true` so peers - can reach tuwunel's federation port directly. - Hyperhive is mostly closed/internal, so this rarely bites in - practice — but flagging for the federation-curious operator. - ''; - }; - openFirewall = lib.mkOption { type = lib.types.bool; default = false; @@ -452,22 +395,6 @@ in stable hostname before enabling. ''; } - { - # Same footgun as forge.domain (#754): empty string renders - # `.` shaped garbage in both nginx server_name (treated - # as wildcard catch-all, surprising) and /etc/hosts (invalid - # entry). Argus 🟡 on #764 — fail loud here rather than ship - # the surprising behaviour. - assertion = cfg.gatewayHost == null || cfg.gatewayHost != ""; - message = '' - services.hyperhive.matrix.gatewayHost = "" is rejected. The - rendered URLs would be invalid (nginx wildcard catch-all for - an empty server_name, /etc/hosts rejects empty entries). - Use `null` to disable the gateway vhost entirely (tuwunel - stays direct on httpPort), or set a non-empty hostname like - "matrix.example.com" or "homeserver.internal". - ''; - } ]; # Generate the registration token at system activation time, BEFORE