a8f5b35d2a
1e4c238fixed 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 at1e4c238-> 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.
80 lines
3.6 KiB
Bash
Executable File
80 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# studio-expose — make a container-loopback pi-studio server reachable
|
|
# through a published Docker port.
|
|
#
|
|
# WHY THIS EXISTS
|
|
# 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 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:<port>, 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
|
|
# reach the container's network interface (and the host port you publish)
|
|
# can connect. Studio's tokenized URL is the only auth. Mitigate by
|
|
# publishing the host port on localhost only:
|
|
# ports: ["127.0.0.1:${STUDIO_PORT}:${STUDIO_PORT}"]
|
|
# and use `ssh -L` for remote access. Bridge nothing you don't intend to.
|
|
#
|
|
# USAGE
|
|
# studio-expose [PORT] # bridge PORT (default: $STUDIO_PORT or 8765)
|
|
# studio-expose --help
|
|
#
|
|
# Typically: inside a pi session run `/studio --no-browser --port 8765`,
|
|
# then in a container shell run `studio-expose` (or set STUDIO_EXPOSE=1 in
|
|
# compose to auto-start it on container boot — see entrypoint-user.sh).
|
|
#
|
|
# Runs in the foreground; Ctrl-C to stop. The entrypoint auto-start path
|
|
# runs it backgrounded.
|
|
set -euo pipefail
|
|
|
|
PORT="${1:-${STUDIO_PORT:-8765}}"
|
|
|
|
if [ "$PORT" = "--help" ] || [ "$PORT" = "-h" ]; then
|
|
sed -n '2,35p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit 0
|
|
fi
|
|
|
|
case "$PORT" in
|
|
''|*[!0-9]*) echo "studio-expose: invalid port '$PORT'" >&2; exit 2 ;;
|
|
esac
|
|
|
|
if ! command -v socat >/dev/null 2>&1; then
|
|
echo "studio-expose: socat not found in PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Container's primary egress IPv4. In Docker the container hostname resolves
|
|
# to its eth0 address, so `hostname -I` lists it; we take the first
|
|
# non-loopback IPv4. We must bind this specific address rather than 0.0.0.0
|
|
# — binding 0.0.0.0 would collide with Studio's own 127.0.0.1:PORT listener
|
|
# (0.0.0.0 includes loopback) and fail with EADDRINUSE. `ip route get` is a
|
|
# fallback only when iproute2 happens to be present (not in the base image).
|
|
BIND_IP="$(hostname -I 2>/dev/null | tr ' ' '\n' \
|
|
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | grep -vE '^127\.' | head -n1)"
|
|
if [ -z "${BIND_IP:-}" ] && command -v ip >/dev/null 2>&1; then
|
|
BIND_IP="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')"
|
|
fi
|
|
[ -n "${BIND_IP:-}" ] || BIND_IP="$(hostname -i 2>/dev/null | awk '{print $1}')"
|
|
if [ -z "${BIND_IP:-}" ]; then
|
|
echo "studio-expose: could not determine container egress IP" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "studio-expose: bridging ${BIND_IP}:${PORT} -> 127.0.0.1:${PORT}"
|
|
echo "studio-expose: open the tokenized URL pi-studio printed; if the host"
|
|
echo "studio-expose: publishes ${PORT}, reach it at http://127.0.0.1:${PORT}/?token=..."
|
|
echo "studio-expose: (remote host: ssh -L ${PORT}:127.0.0.1:${PORT} user@host)"
|
|
|
|
# fork: one child per connection (handles concurrent + long-lived WebSocket
|
|
# connections). reuseaddr: survive quick restarts. Studio need not be up yet
|
|
# — connections simply fail until `/studio --port ${PORT}` is running.
|
|
exec socat "TCP-LISTEN:${PORT},bind=${BIND_IP},fork,reuseaddr" "TCP:127.0.0.1:${PORT}"
|