mara on PR #775: "this is too much docs in code - move bigger picture stuff to md files and put refs in code" New `docs/gateway.md` consolidates the gateway architecture story that was spreading across long inline comments in `hive-gateway.nix`, `hive-matrix.nix`, and `hive-forge.nix`: - vhost map (which URL serves what, which upstream, which option) - matrix discovery flow (.well-known → sub-domain delegation sequence) - Accept-header SPA fallback pattern (#686 / #729 design history) - local-dev `localHostsEntry` story - sub-domain rationale (mara verdict tracking) + when sub-path is right (hyperhive-internal apps) - per-vhost tuning knobs (forge LFS, matrix long-poll, agent SSE) - sequencing history (which PR added which routing piece) In-code comments in the two nix modules get trimmed to short refs into the doc — keeps the *why* in the markdown while the *what* stays alongside the code: - hive-gateway.nix: top-of-file comment, `agentPortsTable`, `appendHttpConfig`, every location block + vhost - hive-matrix.nix: `fluffychat-web-fixed`, `fluffychat-web-imaging`, the dart compile postInstall README.md gets a new row in the docs table pointing at gateway.md. Verified `nix eval` still resolves the same vhost + location layout after the comment trim — no behavioral change, just less in-code prose.
78 lines
5.5 KiB
Markdown
78 lines
5.5 KiB
Markdown
# hive-gateway
|
|
|
|
Single nginx in front of every hyperhive web surface. Container `hive-gateway`, shared host netns, system-config (not meta-flake managed). Configured via `services.hyperhive.gateway.*` + per-subsystem opt-in flags in `services.hyperhive.{forge,matrix,...}`.
|
|
|
|
## Vhost map
|
|
|
|
| URL | vhost | upstream | source |
|
|
| --- | --- | --- | --- |
|
|
| `<hive>/` | `_` (catch-all) | hive-c0re dashboard (`7000`) | always |
|
|
| `<hive>/agent/<name>/` | `_` | per-agent harness on `agent_web_port(name)` | `agentPortsFile` JSON, #15 |
|
|
| `<hive>/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null`, #660 / #747 |
|
|
| `<hive>/matrix/` (deprecated) | `_` | 301 → `matrix.<hive>/` | `matrix.gui.enable`, #772 |
|
|
| `forge.<hive>/` | `forge.<hive>` | forgejo (`3000`) | `forge.behindGateway`, #754 |
|
|
| `matrix.<hive>/_matrix/*` | `matrix.<hive>` | tuwunel (`8008`) | `matrix.gatewayHost != null`, #764 |
|
|
| `matrix.<hive>/` | `matrix.<hive>` | fluffychat-web static | `matrix.gui.enable`, #772 |
|
|
| `matrix.<hive>/config.json` | `matrix.<hive>` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null`, #736 |
|
|
|
|
Per-agent UIs stay sub-path because they're hyperhive-internal and base-path-aware (iris #731). External standard apps (forge / matrix) get sub-domains because their defaults work cleanly at sub-domain root + per-origin cookies / storage isolation matters.
|
|
|
|
## Discovery flow (matrix)
|
|
|
|
Operator points client at `<hive>`. Sequence:
|
|
|
|
1. Client fetches `http://<hive>/.well-known/matrix/client` → `{"m.homeserver":{"base_url":"http://matrix.<hive>"}}` (no port suffix when gateway listens on 80).
|
|
2. Client connects to `matrix.<hive>/_matrix/client/...`.
|
|
3. Gateway routes `/_matrix/*` → tuwunel at `127.0.0.1:8008`.
|
|
|
|
Federation peers fetch `.well-known/matrix/server` → `{"m.server":"matrix.<hive>"}` and connect to `matrix.<hive>:8448` per spec default. Gateway only listens on configured `port`; cross-hive federation needs either an SRV record (`_matrix._tcp.matrix.<hive>` → port 80) OR `matrix.openFirewall = true` so peers reach tuwunel's federation port directly. Hyperhive is mostly closed/internal, so this rarely bites.
|
|
|
|
## SPA fallback (Accept-header pattern)
|
|
|
|
The `<hive>` catch-all and the `matrix.<hive>` vhost both serve a flutter SPA (per-agent UI, fluffychat). Two requirements collide:
|
|
|
|
- hard-refresh on a sub-route must serve `index.html` (SPA's client-side router takes over after JS bootstrap)
|
|
- missing assets must surface as 404, not as HTML with wrong content-type (the original #643 bug)
|
|
|
|
Solution: an `nginx http`-context `map $http_accept $matrix_spa_target { ... }` keyed on the request's Accept header. Browser navigations (`Accept: text/html,...`) get `index.html`; asset fetches (`Accept: image/*`, `*/*`, etc.) get a sentinel nonexistent path → `try_files` falls through to `=404`. No extension allowlist, no `if` block, no regex heuristics. #686 + #729 thread for the design history.
|
|
|
|
## Local dev (`localHostsEntry`)
|
|
|
|
`services.hyperhive.gateway.localHostsEntry = true` adds entries to the host's `/etc/hosts`:
|
|
|
|
- `<hive-domain>` → `127.0.0.1`
|
|
- `forge.<hive>` → `127.0.0.1` (when forge.behindGateway)
|
|
- `matrix.<hive>` → `127.0.0.1` (when matrix.gatewayHost set)
|
|
|
|
`lib.unique` de-dupes if any sub-domain happens to equal another entry. Operators with real DNS leave it off.
|
|
|
|
## Sub-domain shape (rationale)
|
|
|
|
mara verdict at #749:9609 + #747:9722: sub-domain over sub-path for forge + matrix, sub-path for per-agent UIs.
|
|
|
|
- forgejo's default `ROOT_URL = http://<host>/` works without any `X-Forwarded-Prefix` gymnastics — sub-domain hosting is the canonical Forgejo deploy shape.
|
|
- matrix-spec deployments universally use `matrix.<server_name>` for the actual API listener — federation already expects this.
|
|
- per-agent UIs are hyperhive-internal; iris's #731 made them base-path-aware specifically for `/agent/<name>/`. Sub-domain per agent would multiply DNS + TLS-per-subdomain cost without per-app config wins.
|
|
- cookie / storage isolation: a future forge XSS can't reach the dashboard session because they're different origins.
|
|
|
|
`services.hyperhive.{forge.domain,matrix.gatewayHost}` take the full hostname (`forge.darkest.space`, `git.example.com`) rather than a label that gets concatenated with hive-domain — mara on #754:9684 wanted operator control over the full shape, not a forced `<label>.<hive-domain>` pattern.
|
|
|
|
## Tuning knobs
|
|
|
|
Per-vhost timeouts + body-size limits live in the location blocks:
|
|
|
|
- forge `/` (forgejo): `client_max_body_size 1G` (LFS), `proxy_read_timeout 1h` (multi-GB clones), `proxyWebsockets = true` (live-update endpoints).
|
|
- matrix `/_matrix/` (tuwunel): `client_max_body_size 50M` (media uploads), `proxy_read_timeout 1h` (long-poll `/sync`), CORS `*` (federation + cross-origin clients), `proxyWebsockets = true`.
|
|
- per-agent `/agent/<name>/`: `proxy_read_timeout 1d` (long-lived SSE / WebSocket dashboards), `proxyWebsockets = true`, `X-Forwarded-Prefix` set so the harness can build absolute URLs when relative isn't enough.
|
|
|
|
SSH for forge stays direct on `cfg.sshPort` — separate listener protocol, not HTTP-over-nginx.
|
|
|
|
## Sequencing history
|
|
|
|
- #15 v0 (per-agent routing, #740) — first sub-app behind the gateway, JSON port table from c0re.
|
|
- #686 / #729 — Accept-header SPA fallback pattern.
|
|
- #749 / #754 — forge to sub-domain (mara: sub-domain over sub-path).
|
|
- #747 / #764 — matrix sub-domain vhost + `.well-known` delegation.
|
|
- #772 / #775 — fluffychat hops from `<hive>/matrix/` to `matrix.<hive>/`.
|
|
|
|
Next-up tracked separately: #14 (container netns isolation), TLS (#594).
|