Peering: admin can enable/disable it from the running instance #134

Closed
opened 2026-08-07 01:08:16 +00:00 by Cordy · 1 comment
Owner

Decided with Nikola (2026-08-07). Today peering needs peering.statePath set in config.json before the admin panel does anything — a deploy-time prerequisite with no in-app way to turn the feature on. That is why "generate key" 404s on the dogfood.

The design

An admin turns peering on and off from the running instance, entirely in the UI.

  1. Default peering.statePath to /data/.cairn/peering.json, so the peering subsystem is always mounted and the admin panel always functions. The state file is created lazily and is empty until keys/peers are added — no behavioural change beyond the panel working.

  2. An enable/disable switch at the top of the Peering panel, bound to the existing runtime peeringEnabled setting. Off by default. When off: the panel still lets an admin configure peers (issue keys, register), so they can set everything up before going live, but no transfers flow and the end-user send surface is hidden. Flipping it on makes peering live immediately — no restart.

  3. Gate the end-user send surface on peeringEnabled. The feat.peerSend probe (GET /api/v1/peering/peers) and the send/transfers endpoints return unavailable when peering is off, so "Send to peer" and the Transfers nav only appear once an admin has turned peering on. Sending from an instance with peering off is refused for the same reason receiving is.

Why this shape

statePath is filesystem plumbing; it should not double as the feature's on/off switch. peeringEnabled (already in the settings store, already the transport's runtime gate) becomes the single, admin-controlled master switch, surfaced where an admin looks to configure peering. Separates "where state lives" (config) from "is the feature on" (runtime, self-service).

Supersedes the "graceful not-enabled" notice added in #131/v0.4.5 for the normal case — with statePath defaulted, the admin API is always mounted, so that 404 notice only remains as defence for an operator who deliberately sets statePath: "".

Follow-up already noted: #129 (warn on group-only receivers lists without an IdP admin client).

Decided with Nikola (2026-08-07). Today peering needs `peering.statePath` set in `config.json` before the admin panel does anything — a deploy-time prerequisite with no in-app way to turn the feature on. That is why "generate key" 404s on the dogfood. ## The design **An admin turns peering on and off from the running instance, entirely in the UI.** 1. **Default `peering.statePath`** to `/data/.cairn/peering.json`, so the peering subsystem is always mounted and the admin panel always functions. The state file is created lazily and is empty until keys/peers are added — no behavioural change beyond the panel working. 2. **An enable/disable switch at the top of the Peering panel**, bound to the existing runtime `peeringEnabled` setting. Off by default. When off: the panel still lets an admin configure peers (issue keys, register), so they can set everything up *before* going live, but no transfers flow and the end-user send surface is hidden. Flipping it on makes peering live immediately — no restart. 3. **Gate the end-user send surface on `peeringEnabled`.** The `feat.peerSend` probe (`GET /api/v1/peering/peers`) and the send/transfers endpoints return unavailable when peering is off, so "Send to peer" and the Transfers nav only appear once an admin has turned peering on. Sending from an instance with peering off is refused for the same reason receiving is. ## Why this shape `statePath` is filesystem plumbing; it should not double as the feature's on/off switch. `peeringEnabled` (already in the settings store, already the transport's runtime gate) becomes the single, admin-controlled master switch, surfaced where an admin looks to configure peering. Separates "where state lives" (config) from "is the feature on" (runtime, self-service). Supersedes the "graceful not-enabled" notice added in #131/v0.4.5 for the normal case — with statePath defaulted, the admin API is always mounted, so that 404 notice only remains as defence for an operator who deliberately sets `statePath: ""`. Follow-up already noted: #129 (warn on group-only receivers lists without an IdP admin client).
Cordy referenced this issue from a commit 2026-08-07 01:11:26 +00:00
Cordy closed this issue 2026-08-07 01:20:57 +00:00
Author
Owner

Shipped in v0.4.6 (PR #135), live on the dogfood.

The problem this closes. Peering had two independent gates that had drifted apart. peering.statePath (deploy-time config) decided whether the subsystem was mounted at all — the admin panel, key issuance, peer registration. peeringEnabled (runtime setting) was meant to decide whether the transport answered. On the dogfood, statePath was unset, so the whole subsystem was absent: the admin panel's POST /api/v1/admin/peering/keys 404'd (root cause of the "generate key does nothing" / "Not Found" reports in #131), and there was no way to turn peering on from the running instance — it required a redeploy.

Resolution — mount always, answer on demand.

  • config.gopeering.statePath now defaults to /data/.cairn/peering.json. The subsystem is mounted on every instance, so the admin panel, key issuance and peer registration always work. peering.Open still creates no file until the first key/peer is registered, so an instance that never uses peering carries no state.
  • peering_send.goPeeringSendAPI gains Enabled func() bool. send / peers / transfers return 503 when it reports false (nil = ungated, so existing tests are untouched). Because the send UI feature-probes GET /api/v1/peering/peers, the 503 makes the user-facing send surface auto-hide until an admin turns peering on — no extra frontend gate needed. New test TestSendSurfaceUnavailableWhenPeeringOff asserts all three endpoints 503 when disabled.
  • main.go — wires the existing peerOn closure (settingsStore.Get().PeeringEnabled) into PeeringSendAPI.Enabled. The gRPC control plane and blob route already gate on peerOn, so an external peer hitting a disabled instance also gets 503.
  • index.html — a labelled enable/disable switch at the top of the Instance-peering panel, bound to serverSettings.peeringEnabled. Toggling re-PUTs the full settings object with the flag flipped through the existing PUT /api/v1/settings (admin-gated) — no new endpoint. A quiet notice explains what "off" means; i18n for en/de/fr/it.

Net UX. An admin opens Instance peering on any instance, sees the switch, and turns peering on or off live. Off → transport 503s, send UI hidden for everyone, but keys and peers can still be prepared. On → everything answers. No redeploy, no config edit, no secret edit.

Verified: gofmt/vet clean, full go test ./... green (incl. the new test), go build ./... clean, both JS blocks pass node --check on the branch; image cordy/cairn:v0.4.6 (sha256:e837d5d9…) built by release.yml and confirmed Running 1/1 as cairn-enc on the dogfood.

Shipped in **v0.4.6** (PR #135), live on the dogfood. **The problem this closes.** Peering had two independent gates that had drifted apart. `peering.statePath` (deploy-time config) decided whether the subsystem was *mounted* at all — the admin panel, key issuance, peer registration. `peeringEnabled` (runtime setting) was meant to decide whether the transport *answered*. On the dogfood, statePath was unset, so the whole subsystem was absent: the admin panel's `POST /api/v1/admin/peering/keys` 404'd (root cause of the "generate key does nothing" / "Not Found" reports in #131), and there was no way to turn peering on from the running instance — it required a redeploy. **Resolution — mount always, answer on demand.** - **config.go** — `peering.statePath` now defaults to `/data/.cairn/peering.json`. The subsystem is mounted on every instance, so the admin panel, key issuance and peer registration always work. `peering.Open` still creates no file until the first key/peer is registered, so an instance that never uses peering carries no state. - **peering_send.go** — `PeeringSendAPI` gains `Enabled func() bool`. `send` / `peers` / `transfers` return `503` when it reports false (`nil` = ungated, so existing tests are untouched). Because the send UI feature-probes `GET /api/v1/peering/peers`, the 503 makes the *user-facing* send surface auto-hide until an admin turns peering on — no extra frontend gate needed. New test `TestSendSurfaceUnavailableWhenPeeringOff` asserts all three endpoints 503 when disabled. - **main.go** — wires the existing `peerOn` closure (`settingsStore.Get().PeeringEnabled`) into `PeeringSendAPI.Enabled`. The gRPC control plane and blob route already gate on `peerOn`, so an external peer hitting a disabled instance also gets 503. - **index.html** — a labelled enable/disable switch at the top of the Instance-peering panel, bound to `serverSettings.peeringEnabled`. Toggling re-`PUT`s the full settings object with the flag flipped through the existing `PUT /api/v1/settings` (admin-gated) — **no new endpoint**. A quiet notice explains what "off" means; i18n for en/de/fr/it. **Net UX.** An admin opens Instance peering on any instance, sees the switch, and turns peering on or off live. Off → transport 503s, send UI hidden for everyone, but keys and peers can still be prepared. On → everything answers. No redeploy, no config edit, no secret edit. Verified: gofmt/vet clean, full `go test ./...` green (incl. the new test), `go build ./...` clean, both JS blocks pass `node --check` on the branch; image `cordy/cairn:v0.4.6` (`sha256:e837d5d9…`) built by `release.yml` and confirmed Running 1/1 as `cairn-enc` on the dogfood.
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#134
No description provided.