diff --git a/docs/phase-1-exposure-runbook.md b/docs/phase-1-exposure-runbook.md index 309825d..2d1390a 100644 --- a/docs/phase-1-exposure-runbook.md +++ b/docs/phase-1-exposure-runbook.md @@ -75,6 +75,35 @@ Two implications people miss: reviewable — a committed compose file — where duplicates are visible. That, not username hygiene in `.env`, is the discipline this design actually asks of you. +### 1.3 "Then why Pangolin at all, if the feeder uses SSH?" + +Because they are not alternatives — they carry different traffic, and neither substitutes for the other. + +| | Pangolin/newt (HTTPS) | SSH + rsync | +| --- | --- | --- | +| Carries | the **MCP tool surface**: `search`, `add_drawer`, `diary_write`, `kg_*` — every live tool call | **transcript files only**, once per session or cron run | +| Used by | the pi extension, opencode `type:remote`, any MCP client | the feeder, internally (`bin/mempalace-pi-session:677-680`) | +| Needed because | clients need one stable URL, reachable from wherever they are | `mempalace_mine` expands its source path **server-side**, so the server can only mine files on its own disk | + +HTTPS alone is a palace you can query but cannot feed. SSH alone is files shipped with no live query API. +The rsync is not a transport preference; it is a workaround for *where `mine` resolves paths*. + +**Could SSH replace Pangolin?** Partly, and it is worth being honest about it: +`ssh -L 8765:172.17.0.1:8765 synlig` yields a working local MCP endpoint with no public HTTPS at all. +Three reasons this runbook does not do that: + +1. **Direction.** synlig dials *out* through newt. That we reached for a dial-out tunnel rather than a + port-forward is itself the evidence that inbound was not available — a corporate host does not accept + connections from a phone on a foreign network. +2. **MCP clients want a durable URL**, not a per-session forwarded port. opencode `type:remote` takes a + URL; a forward that drops takes the tools down mid-session. +3. The forward must be up on **every device before every session**. Pangolin is up once. + +**The weak point, stated plainly.** The rsync runs *client → synlig*, so it needs synlig's SSH reachable +**from the client**. Were that already true everywhere, no tunnel would be needed for MCP either. So the +honest expectation after Phase 1 is: **query and write from anywhere, mine only from devices that can +reach synlig's SSH** (corporate network / VPN / LAN). See §4 for the change that would remove that limit. + --- ## 2. The bind trap, in full @@ -131,7 +160,7 @@ cat ~/.mempalace/server/f5d849287f6d73f0141b29d7/token Directory name is `sha256(realpath(palace))[:24]` — it changes if the palace path ever moves. Store via the `.env.age` flow, 0600 (RFC §6.2). -### 3.3 newt on synlig +### 3.3 newt on synlig — ✅ done 2026-08-12 (installed, connected to Pangolin) synlig runs Docker (Gitea Actions runner + digikam) but **no tunnel client** — runbook §4.2. Pangolin on nyvaken cannot dial in; synlig must dial out. Add a `newt` container with the credentials Pangolin issues @@ -144,6 +173,18 @@ host, before touching DNS. > synlig has 7.8 GiB shared with a CI runner (runbook §1). newt is small, but do not colocate anything > else here casually. +**Confirm next, now that newt is up.** "Connected to Pangolin" proves newt reached *nyvaken* — a different +claim from newt reaching *the palace*, and the two fail independently: + +```sh +# from inside newt's namespace, not from the host +docker exec wget -qO- http://172.17.0.1:8765/healthz # expect ok +``` + +If that hangs or refuses while the Pangolin dashboard shows the site online, the tunnel is fine and the +*target* is wrong — look at the resource's upstream address (§3.5), not at newt. Note this check needs +§3.1 done first: if `mempalace-serve` is not running yet, it fails for that reason alone. + ### 3.4 DNS at the web hotel One CNAME: `mempalace` → **the same target your existing Pangolin resources use** (nyvaken's public @@ -174,10 +215,39 @@ The 401 check matters as much as the 200: it is the only evidence that the thing internet is not open. Then, and only then, Phase 1 client flip — **one machine first** (RFC §8), and remember opencode containers need the §4.1 sidecar merge before their `.env` takes effect. +### 3.7 Flipping a client: set three variables, or none + +⚠ **`MEMPALACE_REMOTE_URL` on its own does not degrade to local feeding — it stops feeding.** `auto` mode +switches to `remote` the moment the URL is set, and remote mode then refuses to run without an SSH target: + +```sh +auto) if [[ -n "$REMOTE_URL" ]]; then MODE="remote"; else MODE="local"; fi ;; # :286 +... +command -v rsync >/dev/null 2>&1 || { echo "error: rsync not found ..."; exit 3; } # :297 +if [[ -z "$SSH_TARGET" ]]; then + echo "error: MEMPALACE_PI_SSH_TARGET unset (needed for --mode remote)" >&2; exit 1 # :298-300 +fi +``` + +That exit happens **before anything is staged or filed**, and a cron-driven feeder will simply start +failing — the loudest symptom is silence, which is the hardest kind to notice. Two safe orders: + +- **Both paths at once:** set `MEMPALACE_REMOTE_URL`, `MEMPALACE_REMOTE_TOKEN` **and** + `MEMPALACE_PI_SSH_TARGET` (plus `MEMPALACE_PI_DEVICE`, §1.2) in the same edit. +- **HTTPS first, mining later:** set the URL and token, and pin the feeder to `--mode local` until the SSH + target exists. Tools then read/write the shared palace while transcripts keep landing in the local one. + +Either way, run the feeder once by hand and read its exit code before trusting the timer. This is +precisely the failure "one machine first" is meant to contain. + --- ## 4. Still open +- **Feeding without SSH — the upstream ask that would close §1.3's gap.** Have the feeder send *content* + over MCP (`add_drawer` / `diary_write`, which it already calls) instead of asking the server to mine a + path it must first rsync there. HTTPS would then be genuinely sufficient and mining would work from any + network. Until then, mining is limited to devices that can reach synlig's SSH. - **Per-device tokens** — Phase 4. Until then `origin_device` is advisory (§1.1). - **§7.6 diary dedup** must be settled *before* the first §4.4 join; replay duplicates every entry. - **§7.2**: never run `mempalace sync` against the shared palace. Doubly true now that the pi/opencode diff --git a/docs/synlig-primary-runbook.md b/docs/synlig-primary-runbook.md index 629933e..b559507 100644 --- a/docs/synlig-primary-runbook.md +++ b/docs/synlig-primary-runbook.md @@ -129,16 +129,20 @@ standing up a network-reachable service while you were asleep was not mine to de ## 4. Tomorrow, in order > **2026-08-12: items 1–2 and 5 now have their own runbook —** -> [`phase-1-exposure-runbook.md`](./phase-1-exposure-runbook.md). Pangolin on nyvaken is updated (done). -> newt is still missing on synlig. That doc also records why per-device Pangolin users are the wrong layer, -> and an additional loopback finding: a loopback bind does not merely 403, it also silently starts the -> server with **no token at all** (auto-minting is gated on the bind being non-loopback). +> [`phase-1-exposure-runbook.md`](./phase-1-exposure-runbook.md). Pangolin on nyvaken is updated (done), +> and **newt is now installed on synlig and connected to Pangolin (done 2026-08-12)** — so the blocker is +> now item 3, the one `sudo`. That doc also records why per-device Pangolin users are the wrong layer, why +> the HTTPS tunnel and the feeder's SSH path are **not** redundant (§1.3), the client-flip variable trap +> (§3.7), and an additional loopback finding: a loopback bind does not merely 403, it also silently starts +> the server with **no token at all** (auto-minting is gated on the bind being non-loopback). 1. **Pangolin update on nyvaken** (yours). ✅ done 2026-08-12. -2. **⚠️ synlig has no tunnel client.** `docker ps` shows only the Gitea runner and digikam — no `newt`. - Pangolin on nyvaken cannot reach synlig by itself; synlig needs a newt container (or an equivalent - outbound tunnel) added. Easy to miss because Pangolin will look healthy on its own side. If newt runs - in Docker here, the docker0 bind above is already correct for it. +2. ~~**⚠️ synlig has no tunnel client.**~~ ✅ **done 2026-08-12** — newt installed and connected to Pangolin. + (Kept for the reasoning: `docker ps` showed only the Gitea runner and digikam. Pangolin on nyvaken + cannot reach synlig by itself; synlig had to dial out. Easy to miss because Pangolin looks healthy on + its own side — which is also why "connected" is not yet proof it can reach the palace: verify + `172.17.0.1:8765/healthz` from *inside* newt's namespace, exposure runbook §3.3.) Since newt runs in + Docker here, the docker0 bind above is already correct for it. 3. **One sudo, then start** (the unit is already staged; just drop the suffix): ```sh sudo loginctl enable-linger ecsjper