feat(#1843): static-serve the dashboard via the gateway, hive-c0re API-only
nginx proxied `<hive>/` straight to hive-c0re:7000, and hive-c0re served the dashboard dist itself via `tower_http::ServeDir` (from `HIVE_STATIC_DIR` baked into its service env). So a frontend-only change rebuilt the hive-c0re unit and restarted the core daemon — every operator session dropped its SSE stream for a pure CSS/JS change. The gateway nginx now static-serves the dashboard dist directly; hive-c0re's dashboard router is API-only. The split uses the Accept-header SPA fallback (the same `map $http_accept` pattern the matrix/agent vhosts already use), so no backend prefix has to be enumerated: a browser navigation (Accept: text/html) whose path is not an on-disk asset gets the SPA index.html; everything else (every /api route, the bare action/mutation routes, the two SSE streams, the knowledge webhook — all Accept != text/html) falls through `try_files` to the `@c0re` named location and is reverse-proxied to hive-c0re. A new c0re route needs no gateway change. - hive-c0re.nix: expose the themed dist as a new internal read-only option `services.hyperhive.c0re.servedFrontend`; drop `HIVE_STATIC_DIR` from the service env (the router no longer serves files). - hive-gateway.nix: read that option in host-module scope (dashboardDist), static-serve `dashboard/` with the Accept-header `try_files ... @c0re` split; `@c0re` carries `proxy_buffering off` + a 1d read timeout for the SSE streams and a duplicated auth_basic block (named locations do not inherit it). The dashboard map is unconditional; the matrix map stays gated on the matrix GUI. - dashboard.rs: drop the ServeDir fallback + the HIVE_STATIC_DIR resolution; the router 404s unmatched paths (the gateway only proxies non-static requests). - hive-c0re/Cargo.toml: drop the now-unused tower-http dependency. - docs/gateway.md: document the dashboard static split + the `@c0re` fall-through. The store path is reachable inside the gateway nspawn container (shared /nix/store), mirroring how HIVE_AGENT_FRONTEND_DIR already exposes the per-agent UIs. The gateway and c0re changes must land together (atomic cutover) or the dashboard 404s — this needs a watched gateway + c0re rebuild.
This commit is contained in:
parent
1caf978004
commit
4db8a8cd3d
6 changed files with 68 additions and 58 deletions
|
|
@ -6,7 +6,7 @@ Single nginx in front of every hyperhive web surface. Container `hive-gateway`,
|
|||
|
||||
| URL | vhost | upstream | source |
|
||||
| --- | --- | --- | --- |
|
||||
| `<hive>/` | `_` (catch-all) | hive-c0re dashboard (`7000`) | always |
|
||||
| `<hive>/` | `_` (catch-all) | dashboard dist (static, from `servedFrontend`) + API/SSE/actions → hive-c0re (`7000`) via `@c0re` | always |
|
||||
| `<hive>/agent/<name>/` | `_` | per-agent harness (UDS or TCP) | `agents.conf` (runtime-generated) |
|
||||
| `<hive>/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null` |
|
||||
| `<hive>/matrix/` (deprecated) | `_` | 301 → `matrix.<hive>/` | `matrix.gui.enable` |
|
||||
|
|
@ -31,12 +31,17 @@ Federation peers fetch `.well-known/matrix/server` → `{"m.server":"matrix.<hiv
|
|||
|
||||
## 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:
|
||||
The `<hive>` catch-all (operator dashboard), the per-agent UIs, and the `matrix.<hive>` vhost all serve a flutter/SPA bundle. 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
|
||||
- a non-navigation request that isn't an on-disk asset must NOT get HTML with the wrong content-type
|
||||
|
||||
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.
|
||||
Solution: an `nginx http`-context `map $http_accept $<name>_spa_target { ... }` keyed on the request's Accept header. Browser navigations (`Accept: text/html,...`) get `index.html`; everything else (`Accept: image/*`, `*/*`, `application/json`, `text/event-stream`, …) gets a sentinel nonexistent path, so `try_files $uri $<name>_spa_target <final>` falls through to `<final>`. No extension allowlist, no `if` block, no regex heuristics.
|
||||
|
||||
The two vhosts differ only in `<final>`:
|
||||
|
||||
- **matrix / per-agent static assets** → `=404` (a missing asset is just missing).
|
||||
- **dashboard** → `@c0re` (a named location that reverse-proxies to hive-c0re `7000`). The dashboard's dynamic surface — every `/api/*`, the two SSE streams, the ~20 bare action/mutation routes (`/approve/{id}`, `/kill/{name}`, `/op-send`, …), and `/webhook/knowledge` — is all `Accept != text/html`, so it lands on `@c0re` automatically, **without enumerating a single backend prefix**. This is what lets the gateway static-serve the dashboard dist (from the `servedFrontend` nix-store path) while hive-c0re stays API-only — so a frontend-only change no longer rebuilds + restarts the core daemon. `@c0re` carries `proxy_buffering off` + a 1d read timeout (for the SSE streams) and a duplicated `auth_basic` block (named locations don't inherit it). Follow-up #1846 will move every backend route under `/api/`, collapsing this to a trivial `/api/* → c0re, else static` split.
|
||||
|
||||
## Local dev (`localHostsEntry`)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue