docs: scrub #NNN self-references (closes #719)
Last pass of the docs-from-code → docs/ epic (#708). Drops every attribution cookie from docs/ + README.md + CLAUDE.md so the source-tree files no longer reference the issue tracker. Issue threads + commit history retain the references — those are the canonical record. - README.md: drop #701 / #660×2 / #551 from matrix + display-name sections, rephrase to convey the semantics directly - CLAUDE.md: scrub 18 cookies from the file map (#655, #15, #784, #832, #444, #425, #361, #548, #598, #539, #544, #589, #701, #658, #280, #660, #551, #764, #772, #793, #14, #805) - docs/agent-hierarchy.md: drop #658 ×3 (per-agent user is the current shape, not a transition) - docs/conventions.md: drop #571 (replaced with a docs xref to persistence.md::matrix-avatar-sync) - docs/gateway.md: scrub vhost-map table cookies + Sub-domain rationale + Per-agent unix-socket upstream + Self-signed TLS + Firewall posture + HIVE_FORGE_URL + Per-agent error pages sections; drop the trailing 'Sequencing history' issue list + the 'Next-up' issue-link footnote - docs/matrix.md: scrub serverName/gatewayHost + Default-closed firewall + Provisioning flow + Initial rollout + Assertion rationale + fluffychat-web build fixes; drop the trailing 'Sequencing history' issue list - docs/network.md: drop 'Why ship before #14' #805 quote + Container shape #805 attribution + trailing 'Sequencing history' + Cross-references issue links; rename v2 column to 'after netns isolation' - docs/web-ui.md: drop #784 from Container row, replace with a docs xref to docs/gateway.md::Per-agent unix-socket upstream Only remaining #NNN in docs/ is the literal markdown-heading example in docs/forge.md (`#tag`, `#123`, `#!/bin/bash`) which demonstrates the renderer's behaviour — not an attribution cookie.
This commit is contained in:
parent
2a2d76d733
commit
6bdfe52386
8 changed files with 142 additions and 187 deletions
|
|
@ -7,15 +7,15 @@ 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>/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 |
|
||||
| `<hive>/agent/<name>/` | `_` | per-agent harness on `agent_web_port(name)` | `agentPortsFile` JSON |
|
||||
| `<hive>/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null` |
|
||||
| `<hive>/matrix/` (deprecated) | `_` | 301 → `matrix.<hive>/` | `matrix.gui.enable` |
|
||||
| `forge.<hive>/` | `forge.<hive>` | forgejo (`3000`) | `forge.behindGateway` |
|
||||
| `matrix.<hive>/_matrix/*` | `matrix.<hive>` | tuwunel (`8008`) | `matrix.gatewayHost != null` |
|
||||
| `matrix.<hive>/` | `matrix.<hive>` | fluffychat-web static | `matrix.gui.enable` |
|
||||
| `matrix.<hive>/config.json` | `matrix.<hive>` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` |
|
||||
|
||||
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.
|
||||
Per-agent UIs stay sub-path because they're hyperhive-internal and base-path-aware. 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)
|
||||
|
||||
|
|
@ -34,9 +34,9 @@ Federation peers fetch `.well-known/matrix/server` → `{"m.server":"matrix.<hiv
|
|||
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)
|
||||
- missing assets must surface as 404, not as HTML with 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. #686 + #729 thread for the design history.
|
||||
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.
|
||||
|
||||
## Local dev (`localHostsEntry`)
|
||||
|
||||
|
|
@ -50,14 +50,14 @@ Solution: an `nginx http`-context `map $http_accept $matrix_spa_target { ... }`
|
|||
|
||||
## 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.
|
||||
Operator decision: 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.
|
||||
- per-agent UIs are hyperhive-internal and 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.
|
||||
`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 — operators want control over the full shape, not a forced `<label>.<hive-domain>` pattern.
|
||||
|
||||
## Tuning knobs
|
||||
|
||||
|
|
@ -69,29 +69,28 @@ Per-vhost timeouts + body-size limits live in the location blocks:
|
|||
|
||||
SSH for forge stays direct on `cfg.sshPort` — separate listener protocol, not HTTP-over-nginx.
|
||||
|
||||
## Per-agent unix-socket upstream (#784)
|
||||
## Per-agent unix-socket upstream
|
||||
|
||||
Sub-agent `/agent/<name>/` upstreams flip from TCP loopback to a
|
||||
unix-domain socket as each agent opts in. The mechanism:
|
||||
|
||||
1. **Agent side** (`hyperhive.web.useUnixSocket = true` in
|
||||
`agent.nix`, #815). Sets `HIVE_WEB_SOCKET=/run/hive-agent/<name>/web.sock`
|
||||
`agent.nix`). Sets `HIVE_WEB_SOCKET=/run/hive-agent/<name>/web.sock`
|
||||
on the harness service env; `web_ui::serve` binds a `UnixListener`
|
||||
at that path instead of TCP.
|
||||
2. **Host side**. `hive-c0re` bind-mounts the per-agent subdir
|
||||
(`/run/hive-agent/<name>/`) into the agent's container (#813). Dir
|
||||
(`/run/hive-agent/<name>/`) into the agent's container. Dir
|
||||
bind, not file bind — file bind-mounts don't survive the
|
||||
harness's `unlink + bind(2)` cycle on socket replace. Per-agent
|
||||
subdir keeps each agent's container blind to siblings'
|
||||
sockets (mara on #800).
|
||||
subdir keeps each agent's container blind to siblings' sockets.
|
||||
3. **Marker gate**. After successful `bind_unix`, the harness drops
|
||||
`<dir>/.bound` next to the socket. c0re's `agent_sockets::write`
|
||||
filters its JSON map by marker presence — only agents whose
|
||||
harness has actually bound the socket appear there (#784 atlas
|
||||
gate). Without this filter, the gateway would `proxy_pass` to a
|
||||
non-existent socket for every sub-agent that hasn't opted in yet.
|
||||
4. **Gateway side** (#829). Reads `agent-sockets.json` at
|
||||
request-handling time and routes `/agent/<name>/` to
|
||||
harness has actually bound the socket appear there. Without this
|
||||
filter, the gateway would `proxy_pass` to a non-existent socket
|
||||
for every sub-agent that hasn't opted in yet.
|
||||
4. **Gateway side**. Reads `agent-sockets.json` at request-handling
|
||||
time and routes `/agent/<name>/` to
|
||||
`http://unix:/run/hive-agent/<name>/web.sock:/`. Whole
|
||||
`/run/hive-agent/` is bind-mounted read-only into the gateway
|
||||
container so it can reach every published socket.
|
||||
|
|
@ -103,7 +102,7 @@ one stat per agent per tick.
|
|||
|
||||
Transition: agents that haven't flipped `useUnixSocket = true` still
|
||||
appear in `agent-ports.json` (the legacy TCP map) and the gateway
|
||||
falls back to TCP for them. Step 4 of #784 will drop the TCP map +
|
||||
falls back to TCP for them. A future cleanup will drop the TCP map +
|
||||
the harness's TCP bind once every agent's flipped.
|
||||
|
||||
## Dashboard link shape (gateway vs direct)
|
||||
|
|
@ -122,22 +121,11 @@ tracked by `forge.behindGateway`); `external`-kind links are
|
|||
already absolute. See `docs/web-ui.md::Container row` for the
|
||||
frontend-side derivation.
|
||||
|
||||
## 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>/`.
|
||||
- #784 / #800 / #813 / #815 / #822 / #829 — sub-agent UI flips to unix-domain socket upstream, opt-in per agent.
|
||||
|
||||
Next-up tracked separately: #14 (container netns isolation), TLS (#594).
|
||||
|
||||
## Self-signed TLS (`selfSignedTls`)
|
||||
|
||||
On by default since #837. The gateway generates a self-signed RSA-4096 cert at first boot (10-year validity) and listens on `httpsPort` (default 443) on every vhost beside the plain-http `port` (default 80).
|
||||
On by default. The gateway generates a self-signed RSA-4096 cert at first boot (10-year validity) and listens on `httpsPort` (default 443) on every vhost beside the plain-http `port` (default 80).
|
||||
|
||||
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client cannot bootstrap (#837). For headless agent traffic and operator dashboard reach plain http is fine, so the http listen stays in parallel — operators can keep using `http://<hive>/` from the dashboard if they don't care about the cert prompt.
|
||||
**Why on by default**: matrix-dart-sdk (FluffyChat's SDK) hardcodes `https://<host>/.well-known/matrix/client` for homeserver discovery and refuses to fall back to plain http. Without TLS the browser client cannot bootstrap. For headless agent traffic and operator dashboard reach plain http is fine, so the http listen stays in parallel — operators can keep using `http://<hive>/` from the dashboard if they don't care about the cert prompt.
|
||||
|
||||
**Cert shape**: subject CN = bare hive domain; subjectAltName covers `<hive>` + wildcard `*.<hive>` so all current and future sub-domain vhosts (matrix, forge, ...) validate under the same cert. Stored at `/var/lib/hive-gateway/tls/{cert,key}.pem` inside the gateway container (`ephemeral = false`, so persisted across container restart).
|
||||
|
||||
|
|
@ -156,25 +144,24 @@ On by default since #837. The gateway generates a self-signed RSA-4096 cert at f
|
|||
`services.hyperhive.gateway.enable = false`**. With the gateway on
|
||||
(default), it's the sole external entry point and proxies to
|
||||
`127.0.0.1:<port>` internally — leaving the per-agent ports
|
||||
firewall-open would defeat the single-front-door story (closes
|
||||
#621).
|
||||
firewall-open would defeat the single-front-door story.
|
||||
|
||||
`services.hyperhive.gateway.openFirewall = true` opens `port` plus
|
||||
`httpsPort` when `selfSignedTls = true` (default). Operators who
|
||||
flip `selfSignedTls = false` to front the gateway with a real
|
||||
TLS-terminating reverse proxy on the host get only `port` opened.
|
||||
|
||||
Manager hashes into the same range since #753 (no more
|
||||
The manager hashes into the same port range as sub-agents (no
|
||||
"manager pinned at 8000" special case), so one range opening covers
|
||||
every container.
|
||||
|
||||
The dashboard port (`cfg.dashboardPort`, default 7000) is *not*
|
||||
listed in either case — since #652 it binds `127.0.0.1` only, so a
|
||||
firewall hole would be a no-op. Remote dashboard access flows
|
||||
through the gateway. Operators who opt out of the gateway lose
|
||||
external dashboard reach by design — the surface is privileged
|
||||
(approve / deny / destroy) and must not be exposed without a real
|
||||
reverse proxy in front.
|
||||
listed in either case — it binds `127.0.0.1` only, so a firewall
|
||||
hole would be a no-op. Remote dashboard access flows through the
|
||||
gateway. Operators who opt out of the gateway lose external
|
||||
dashboard reach by design — the surface is privileged (approve /
|
||||
deny / destroy) and must not be exposed without a real reverse
|
||||
proxy in front.
|
||||
|
||||
## `HIVE_FORGE_URL`: loopback for in-cluster, sub-domain for the operator
|
||||
|
||||
|
|
@ -182,14 +169,13 @@ Agents poll `HIVE_FORGE_URL` for Forgejo notifications + run all
|
|||
`hive-forge` calls against it. `hive-c0re.nix` pins this to
|
||||
`http://127.0.0.1:<forge.httpPort>` for the in-cluster path: every
|
||||
agent container shares the host's network namespace, so loopback
|
||||
reaches the forge container directly with no DNS lookup needed
|
||||
(closes #761).
|
||||
reaches the forge container directly with no DNS lookup needed.
|
||||
|
||||
The post-#754 sub-domain default (`forge.<hive-domain>`) is for
|
||||
**operator browsers + cross-host clients**, not in-cluster traffic.
|
||||
Using the sub-domain URL inside agent containers would fail every
|
||||
`hive-forge` invocation with "Name or service not known" — the
|
||||
agent's nspawn doesn't have DNS for the external hostname.
|
||||
The sub-domain default (`forge.<hive-domain>`) is for **operator
|
||||
browsers + cross-host clients**, not in-cluster traffic. Using the
|
||||
sub-domain URL inside agent containers would fail every `hive-forge`
|
||||
invocation with "Name or service not known" — the agent's nspawn
|
||||
doesn't have DNS for the external hostname.
|
||||
|
||||
## hive-forge container shape
|
||||
|
||||
|
|
@ -213,7 +199,7 @@ container.
|
|||
## Per-agent error pages
|
||||
|
||||
`/agent/<name>/` requests hit two failure modes; both get static
|
||||
HTML pages instead of nginx's default error chrome (#755):
|
||||
HTML pages instead of nginx's default error chrome:
|
||||
|
||||
- **Agent not found** (`/agent/<unknown>/...`) — name isn't in
|
||||
`agentPortsTable`. nginx's prefix match falls back to the bare
|
||||
|
|
@ -238,8 +224,8 @@ palette (`#1e1e2e` bg, `#cdd6f4` text, `#cba6f7` heading). No
|
|||
dependencies on the frontend dist — these pages render even when
|
||||
hive-c0re itself is down.
|
||||
|
||||
Scope is intentionally narrow per mara on #755: "only for routes
|
||||
already special cased in the nginx config". Other gateway routes
|
||||
Scope is intentionally narrow: only routes already special-cased in
|
||||
the nginx config get custom error pages. Other gateway routes
|
||||
(forge / matrix / fluffychat) get nginx defaults — extending the
|
||||
custom-error pattern there is a separate follow-up.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue