From c945f3829a62025b9432a57b1d87a2c93d7b2aa8 Mon Sep 17 00:00:00 2001 From: bigbes Date: Wed, 22 Jul 2026 04:12:18 +0300 Subject: [PATCH] docs: add SourceHut integration section to the spec.sr.ht design Records the config-driven wiring (Recipe B: pure Go, chrome copied from compare.sr.ht), the canonical [spec.sr.ht] keys, and the shared keys read in place. Documents why skipping GraphQL federation is safe: api.sr.ht federates every .sr.ht config section with no allow-list, but updateSchema skips services that fail to serve /query, and the refresh is SIGHUP-driven rather than a ticker. --- docs/DESIGN.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 96f8b3019fbb6f90ec5e01be7a1d271b634b0564..ab547839608397d071eeb30311b1095a8b0562dc 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -294,6 +294,87 @@ consume this: `spec_search`, `spec_read`, `spec_propose`, `spec_comment`, Genuinely net-new: proposals, prose diff, review UI, agent tokens, frontmatter lifecycle, projects/aggregation. +## SourceHut integration + +**Recipe B** from the `sourcehut-custom-service` model: pure Go, API side on +`core-go`, web chrome reimplemented (`core-go` has no HTML templating — the +Jinja chrome lives only in Python `core.sr.ht`). compare.sr.ht already did +exactly this, so its nav template, cookie→identity handler, and SCSS entry are +the thing to copy rather than rederive. + +Integration is entirely config + nginx + DNS. **No upstream source is modified.** + +### Our section + +The section name **must** be the literal `spec.sr.ht` — the `.sr.ht` suffix is +what puts us in the nav `network` list (`core.sr.ht/srht/app/flask.py::_network`) +and what other services look us up by. + +```ini +[spec.sr.ht] +origin=https://spec.srht.bigb.es +connection-string=postgresql://specsrht@localhost/spec.sr.ht?sslmode=disable +; Bare repos, service-owned: /~/ +repos=/var/lib/spec +; Materialized checkouts + bleve project indexes. Pure cache; safe to delete. +cache=/var/cache/spec +static-dir=/usr/share/sourcehut/spec.sr.ht/static +;bind-address=127.0.0.1:5091 +migrate-on-upgrade=yes +``` + +Canonical key names only — `origin`, `connection-string`, `migrate-on-upgrade` +are read by the shared accessors (`config.GetOrigin`, `server.WithDefaultMiddleware`). +`repos` mirrors dolt.sr.ht/git.sr.ht; `static-dir` and `bind-address` are the +local house convention already used by both siblings. `cache` is ours. + +### Shared keys we read in place (never duplicate) + +| Key | Used for | +|---|---| +| `[sr.ht] network-key` | Fernet-decrypt `sr.ht.unified-login.v1`; mint Internal auth tokens | +| `[sr.ht] owner-name` / `owner-email` | **`config.GetOwner` panics if missing**; also the committer identity on merges | +| `[sr.ht] site-name` / `environment` | nav brand; non-`production` shows the dev banner | +| `[sr.ht] internal-ipnet` | this host must fall inside it or internal GraphQL calls are rejected | +| `[webhooks] private-key` | **`crypto.InitCrypto` fatally requires it even though v1 emits no webhooks** | +| `[meta.sr.ht] origin` | login/logout redirects, profile fetch, PAT validation | +| `[git.sr.ht] repos` / `api-origin` | only for read-only mounts of `docs/` dirs in existing git.sr.ht repos | + +### Wiring checklist + +1. **Config** — the `[spec.sr.ht] origin=` line must be visible to **every other + service's** config, not just ours; each service builds its own nav + independently. One shared `/etc/sr.ht/config.ini` makes this automatic — + then **restart the other services** so they pick up the switcher entry. +2. **DNS** — `spec.srht.bigb.es` must be under the shared cookie domain + (`*.srht.bigb.es`), or the unified-login cookie is never sent to us and every + viewer looks anonymous. +3. **nginx** — plain `proxy_pass http://127.0.0.1:5091`, modeled on + `contrib/compare.sr.ht.conf`. Note `client_max_body_size` needs raising if + attachments are allowed. +4. **internal-ipnet** — same prerequisite as both siblings. +5. **Migrations** — `specsrht-migrate`, a brant wrapper, copied from + `doltsrht-migrate`. + +### GraphQL federation: deliberately not in v1 + +Bots use REST + MCP; a gqlgen schema buys nothing for the loop this service +exists to serve. dolt.sr.ht made the same call. + +**Verified that opting out is safe.** `api.sr.ht` federates *every* config +section ending in `.sr.ht` — it does not consult an allow-list, so our section +does get added as a federated service pointed at `origin + "/query"`, which we +will not serve. `updateSchema` (`api.sr.ht/main.go`) handles this explicitly: +services that are offline or have an invalid schema are logged and **skipped**, +and `BuildSchema` runs over only the healthy ones. So the effect is one +`Unable to update service "spec.sr.ht"` log line, not a broken gateway. + +It is also not a hot loop: schema refresh is **SIGHUP-driven, not on a ticker** +(the goroutine selects on `signalChan`), so the failing fetch happens at startup +and on explicit reload only. dolt.sr.ht and compare.sr.ht already run with this +exact property today. Should we ever want federation, adding `api-origin=` and +serving `/query` is the whole change. + ## Phases **Phase 0 — de-risk gate.** Two spikes, both must pass before anything else is @@ -331,6 +412,10 @@ notifications, vector search over the project index. - **Project URL namespace.** `~user/+project` distinguishes projects from spaces (`~user/space`) in one character; alternatives are `/projects/~user/name` or reusing hub.sr.ht's own namespace. -- **Port.** compare.sr.ht is on 5090, dolt.sr.ht on 5306–5308. 5091 is free. +- **Port.** compare.sr.ht is on 5090, dolt.sr.ht on 5306–5308. 5091 is free and + is what the config block above assumes. +- **MCP transport.** Streamable HTTP on the same chi router (`/mcp`) keeps it to + one listener and one nginx block; a second port is only needed if MCP ends up + wanting different timeouts than the web UI. - **LICENSE.** Unchosen, same as compare.sr.ht. SourceHut's own services are AGPL/GPL.