From a8f5b35d2a0fd730e3467702e25eb70802f3ee0c Mon Sep 17 00:00:00 2001 From: Joakim Persson Date: Wed, 14 Oct 2026 19:27:42 +0200 Subject: [PATCH] docs: correct the "pi-studio has no bind flag" claim everywhere it was baked 1e4c238 fixed README; the same claim lived in three more places, all wrong since pi-studio 0.9.60 added /studio --listen-all: - rootfs/usr/local/bin/studio-expose header (and so its --help; the sed range grows 2,31p -> 2,35p so --help still ends at "runs it backgrounded.") - pi-devbox-environment skill section 7: --listen-all as a third path, plus the 0.10 whole-URL / 409 / "link looks damaged" notes, the published-port rule, --stop before --port, and the on-host curl 403-vs-empty-reply diagnostic - Dockerfile.variant STUDIO_PORT comment Bridge and --listen-all on one port: whichever starts second fails, measured both ways on tor-ms22 (v1.11.2): Studio "listen EADDRINUSE ... 0.0.0.0:8765" with the bridge up; studio-expose rc 1 on socat "bind(...): Address already in use" with a 0.0.0.0 listener up. README's warning now says both directions. Cost: studio-expose and the skill are in rootfs/, which base_tag hashes, so the next release rebuilds the base (recomputed with the CI recipe: base-6f3b20266de1 at 1e4c238 -> base-26d425cea6c2 now). Noted in the CHANGELOG Unreleased entry. No pin changes. lint-shell, dockerfile-directives and base-hash guards pass; check-doc-drift is red only for the unrelated pi-extensions 750b2a8 -> 0d8e417. --- CHANGELOG.md | 22 +++++++++++++----- Dockerfile.variant | 9 ++++---- README.md | 9 ++++---- rootfs/usr/local/bin/studio-expose | 18 +++++++++------ .../skills/pi-devbox-environment/SKILL.md | 23 +++++++++++++++---- 5 files changed, 56 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76207ce..eb7d143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,18 +13,28 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). ## Unreleased -Docs only: this entry changes no pin. +Docs and comments only: this entry changes no pin. Two of the files are +baked into the base image (`rootfs/usr/local/bin/studio-expose` and the +`pi-devbox-environment` skill), and `base_tag` hashes all of `rootfs/`, so +**the next release rebuilds the base** rather than reusing +`base-6f3b20266de1`. ### 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). + `studio-expose` bridge (B). It also warns not to combine B and C on one + port, because whichever starts second fails. With `STUDIO_EXPOSE=1`, + `/studio --listen-all --port 8765` fails with + `listen EADDRINUSE: address already in use 0.0.0.0:8765`. In the other + order, `studio-expose` exits 1 on socat's `bind(…): Address already in + use` (both measured on tor-ms22, v1.11.2). The same stale "no bind + flag" claim is corrected in the three other places that carried it: + the `studio-expose` header (and so its `--help`, whose `sed` range + grows from `2,31p` to `2,35p`), §7 of the baked `pi-devbox-environment` + skill (which also gains the whole-URL, published-port and `curl` notes + below), and the `STUDIO_PORT` comment in `Dockerfile.variant`. - **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 diff --git a/Dockerfile.variant b/Dockerfile.variant index 5280b5c..bb08257 100644 --- a/Dockerfile.variant +++ b/Dockerfile.variant @@ -676,10 +676,11 @@ RUN if [ "${INSTALL_STUDIO}" = "true" ]; then \ # STUDIO_PORT: advisory default consumed by docker-compose port publishing # and the recommended `/studio --no-browser --port "$STUDIO_PORT"` launch. -# Harmless in the non-studio variant. NOTE: pi-studio hard-binds the server -# to 127.0.0.1 inside the container (index.ts: .listen(port,"127.0.0.1")), -# so reaching it from a browser needs a loopback bridge or host networking — -# see the "Using pi-studio" section in README.md. +# Harmless in the non-studio variant. NOTE: by default pi-studio binds the +# server to 127.0.0.1 inside the container, so reaching it from a browser +# needs host networking, the studio-expose loopback bridge, or the opt-in +# `/studio --listen-all` (pi-studio >= 0.9.60; never together with the +# bridge on one port) — see the "Using pi-studio" section in README.md. ENV STUDIO_PORT=8765 # ── Optional: Go toolchain ─────────────────────────────────────────── diff --git a/README.md b/README.md index 894638e..22a8aee 100644 --- a/README.md +++ b/README.md @@ -261,10 +261,11 @@ 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`. +> **Use B or C on a port, not both.** Whichever starts second fails with +> `EADDRINUSE`: `0.0.0.0` includes the egress IP the bridge binds. With +> `STUDIO_EXPOSE=1` the bridge holds `$STUDIO_PORT` from boot, so +> `/studio --listen-all --port 8765` fails. If the bridge is running, start +> Studio *without* `--listen-all`. ### Remote host (SSH / mosh) diff --git a/rootfs/usr/local/bin/studio-expose b/rootfs/usr/local/bin/studio-expose index 6e3e00a..b22e8d6 100755 --- a/rootfs/usr/local/bin/studio-expose +++ b/rootfs/usr/local/bin/studio-expose @@ -3,13 +3,17 @@ # through a published Docker port. # # WHY THIS EXISTS -# pi-studio hard-binds its HTTP/WebSocket server to 127.0.0.1 inside the -# container (index.ts: `.listen(port, "127.0.0.1")`) and there is no -# --host / bind flag. A plain `docker run -p 8765:8765` forwards to the +# By default pi-studio binds its HTTP/WebSocket server to 127.0.0.1 inside +# the container. A plain `docker run -p 8765:8765` forwards to the # container's EXTERNAL interface (eth0), not its loopback, so it cannot -# reach Studio. This helper runs a socat TCP relay that listens on the -# container's egress IP and forwards to 127.0.0.1:, so a published -# port (and an `ssh -L` tunnel from your laptop) can reach Studio. +# reach a default Studio. This helper runs a socat TCP relay that listens +# on the container's egress IP and forwards to 127.0.0.1:, so a +# published port (and an `ssh -L` tunnel from your laptop) can reach Studio. +# +# The alternative (pi-studio >= 0.9.60) is `/studio --listen-all`, which +# binds 0.0.0.0 and needs no bridge. Use one or the other on a port, never +# both: whichever starts second fails with EADDRINUSE (0.0.0.0 includes +# the egress IP this relay binds). # # SECURITY # This intentionally exposes Studio beyond loopback — anything that can @@ -34,7 +38,7 @@ set -euo pipefail PORT="${1:-${STUDIO_PORT:-8765}}" if [ "$PORT" = "--help" ] || [ "$PORT" = "-h" ]; then - sed -n '2,31p' "$0" | sed 's/^# \{0,1\}//' + sed -n '2,35p' "$0" | sed 's/^# \{0,1\}//' exit 0 fi diff --git a/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md b/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md index 8e136b3..d3e1b71 100644 --- a/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md +++ b/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md @@ -356,8 +356,8 @@ window: 0". Leave the indexing alone in this environment. ## 7. pi-studio reachability (only in the `-studio` variant) Present only if `/opt/pi-studio` exists / the `studio_*` tools are in your tool -list. pi-studio **binds to `127.0.0.1` inside the container** with no host-bind -flag, so a plain `docker -p` publish can't reach it. Two supported paths: +list. By default pi-studio **binds to `127.0.0.1` inside the container**, so a +plain `docker -p` publish can't reach it. Three supported paths: - **Host networking** (`network_mode: host`): container loopback == host loopback; open the tokenized URL on the host. (Changes @@ -365,10 +365,25 @@ flag, so a plain `docker -p` publish can't reach it. Two supported paths: - **`studio-expose` bridge** (`STUDIO_EXPOSE=1` or run `studio-expose &`): a `socat` relay from the container's external interface to its loopback, so a published `127.0.0.1:PORT` + `ssh -L PORT:127.0.0.1:PORT host` reaches it. +- **`/studio --listen-all`** (pi-studio ≥ 0.9.60): binds `0.0.0.0`, so the + published port reaches it with no bridge; the printed URL still says + `127.0.0.1`. **Never on the same port as the bridge**: whichever starts + second fails with `EADDRINUSE`. `ss -ltnp | grep ":$STUDIO_PORT"` showing a + `socat` line means the bridge is up, so start Studio without `--listen-all`. The real auth token comes from the `/studio` slash command (`/studio --status` -to reprint), **not** from `studio-expose`. For Graphviz, use `dot-watch` → -PNG (Studio renders Mermaid natively and previews PNG, but not SVG/DOT). +to reprint), **not** from `studio-expose`. Hand over the **whole** printed +URL: since pi-studio 0.10 it also carries `studioTabState` + `hostedWorkspace` +(or `docPath`/`watchFile`/… for `--watch`), and a token-only URL is answered +`409`. The URL wraps across TUI lines; if a side panel's text lands inside the +copy, Studio answers *"This link looks damaged…"*. Studio must run on the +**published** port (only the laptop side of `ssh -L` is free to choose), and +`--port` on a running server only warns, so `/studio --stop` first. On the +Docker host, `curl -s http://127.0.0.1:$STUDIO_PORT/` answering `403` means +the chain is up; an empty reply means Studio is down or on another port. + +For Graphviz, use `dot-watch` → PNG (Studio renders Mermaid natively and +previews PNG, but not SVG/DOT). ## 8. MemPalace is the shared brain