diff --git a/CHANGELOG.md b/CHANGELOG.md index f3757ad..76207ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,32 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). --- +## Unreleased + +Docs only: this entry changes no pin. + +### Fixed + +- **README "Using pi-studio" said pi-studio has no bind flag.** It has had + `/studio --listen-all` (binds `0.0.0.0`) since pi-studio 0.9.60, so the + section now lists it as path **C**, next to host networking (A) and the + `studio-expose` bridge (B). It also warns not to combine B and C on one port. + With `STUDIO_EXPOSE=1` the bridge holds the egress IP on `$STUDIO_PORT` + from boot, and `/studio --listen-all --port 8765` then fails with + `listen EADDRINUSE: address already in use 0.0.0.0:8765` (measured on + tor-ms22, v1.11.2). +- **The remote recipe described a pi-studio 0.9 URL.** Since 0.10 the printed + URL carries `studioTabState` and `hostedWorkspace` as well as the token, and + a token-only URL is answered `409`. The recipe now says to open the whole + URL, and names the copy hazard: the URL wraps, a side panel's text can land + inside it, and Studio answers *"This link looks damaged…"*. It also says + Studio must run on the **published** port (only the laptop's side of + `ssh -L` is free to choose) and that `--port` does not move a running server + (`/studio --stop` first). Two more additions: `--watch ` for a rendered, + read-only preview, and an on-host `curl` that tells "chain up" (`403`) from + "Studio down or on another port" (empty reply). A remote session hit exactly + that: Studio was started on `18765` while only `8765` is published. + ## v1.11.2 — 2026-10-14 **v1.11.1 was tagged and never published.** Run 724 failed one smoke assertion diff --git a/README.md b/README.md index 8751ff3..894638e 100644 --- a/README.md +++ b/README.md @@ -198,12 +198,11 @@ Inside a pi session in the container: ### Reaching the UI from your browser (the container caveat) -pi-studio **hard-binds its server to `127.0.0.1` inside the container** -(`index.ts`: `.listen(port, "127.0.0.1")`) and serves a tokenized URL. -There is no `--host`/bind flag. This matters for a container: a plain +By default pi-studio **binds its server to `127.0.0.1` inside the +container** and serves a tokenized URL. This matters for a container: a plain `docker run -p 8765:8765` publish forwards to the container's *external* -interface, **not** its loopback, so it will not reach Studio. Two paths -work: +interface, **not** its loopback, so it will not reach a default Studio. +Three paths work (A and B keep Studio itself on loopback; C does not): **A. Host networking (simplest — OrbStack / single-host, no bridge).** Run the container with host networking so the container's loopback is the @@ -215,8 +214,9 @@ services: network_mode: host # container 127.0.0.1 == host 127.0.0.1 ``` -Then `http://127.0.0.1:8765/?token=…` works in a browser on the Docker -host. This is the most secure option (Studio never leaves loopback). Note: +Then the URL Studio prints (`http://127.0.0.1:8765/?token=…`) works in a +browser on the Docker host. This is the most secure option (Studio never +leaves loopback). Note: host networking changes `host.docker.internal` semantics, so weigh it against the LAN-jump SSH feature if you use that. @@ -253,6 +253,19 @@ studio-expose & # bridges $STUDIO_PORT (default 8765); --help for detai > its tokenized URL is the only auth. Keep the host-side publish on > `127.0.0.1:` and use `ssh -L` for remote access. Default is **off**. +**C. `/studio --listen-all` (pi-studio ≥ 0.9.60, no bridge).** pi-studio has +its own opt-in flag: `/studio --no-browser --listen-all --port 8765` binds +`0.0.0.0`, so the published port reaches it without `socat`. The URL it +prints still says `127.0.0.1`, so it works verbatim through a same-port +publish. Keep the publish on `127.0.0.1:` as in B: anyone who can reach the +port and has the full URL controls Studio, including running prompts and +reading or writing files, and there is no TLS. + +> **Use B or C on a port, not both.** With `STUDIO_EXPOSE=1` the bridge holds +> the container's egress IP on `$STUDIO_PORT` from boot, and `--listen-all` on +> that same port fails with `EADDRINUSE` (`0.0.0.0` includes that address). +> If the bridge is running, start Studio *without* `--listen-all`. + ### Remote host (SSH / mosh) When the Docker host is remote, keep Studio on localhost and forward the @@ -298,20 +311,38 @@ Assuming the compose file publishes `127.0.0.1:8765:8765` (see method B): TUI, not a shell command: ``` /studio --no-browser --port 8765 + /studio --no-browser --port 8765 --watch notes.md # or: a rendered, read-only preview of a file /studio --status # reprint the URL anytime ``` - Copy the `http://…:8765/?token=` it prints. **This** is where - the real token comes from — not `studio-expose`. + **This** is where the real token comes from — not `studio-expose`. + The port must be the **published** one (`8765` here): the tunnel, the + publish and the bridge all lead to it, and Studio on any other port is + unreachable from outside. `--port` does not move a running server (Studio + only warns), so run `/studio --stop` first to change it. 3. **On your laptop** — open the ssh port-forward alongside mosh: ```bash - ssh -L 8765:127.0.0.1:8765 user@docker-host + ssh -N -o ExitOnForwardFailure=yes -L 8765:127.0.0.1:8765 user@docker-host ``` -4. **In your laptop browser** — open `http://127.0.0.1:8765/?token=` - (keep the port and token verbatim; only the host part is `127.0.0.1`). + Only the **left** port is yours to choose. If `8765` is already taken on + the laptop (for example by the laptop's own devbox publish), use + `-L 18765:127.0.0.1:8765` and change only the port in the URL. +4. **In your laptop browser** — open the **whole** URL Studio printed. It + already says `127.0.0.1`, and it carries more than the token + (`token`, `studioTabState`, `hostedWorkspace` since pi-studio 0.10, or + `docPath`/`watchFile`/… for `--watch`). A URL cut down to just the token + is answered `409`. + The URL is long and wraps across TUI lines; a side panel next to it (such + as the pi-atelier sidebar) can end up inside the copied text, and Studio + then answers *"This link looks damaged…"*. Rejoin the lines without the + panel text, or widen the terminal and copy it again. > **Order check:** nothing listens on the container's `127.0.0.1:8765` > until step 2 runs. If the browser can't connect, verify Studio is up -> (`/studio --status`) and the bridge is running (`ps aux | grep socat`). +> **on the published port** (`/studio --status`) and the bridge is running +> (`ps aux | grep socat`). On the Docker host, +> `curl -s http://127.0.0.1:8765/` answers `403` when the whole chain is up; +> an empty reply means the bridge has nothing behind it, i.e. Studio is down +> or on another port. > PDF export (`/studio-pdf`, `studio_export_pdf`) uses **`typst`**, shipped in > the base image as the pandoc PDF engine (`pandoc --pdf-engine=typst`). For