diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 941a257c0e..de000fe978 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -77,7 +77,7 @@ RUN curl -Lo /usr/local/bin/kubectl https://dl.k8s.io/release/v${VERSION_KUBECTL RUN apt update \ - && apt install tmux dnsutils telnet iputils-ping jq certbot nethogs nload vim -y \ + && apt install tmux dnsutils telnet iputils-ping jq certbot nethogs nload vim socat -y \ && apt clean -y RUN curl -Lo /usr/local/bin/kind https://kind.sigs.k8s.io/dl/v${VERSION_KIND}/kind-linux-amd64 \ && chmod +x /usr/local/bin/kind @@ -201,6 +201,77 @@ RUN curl -sS https://downloads.1password.com/linux/keys/1password.asc | \ RUN mkdir -p /home/vscode/.vscode-server/data/Machine/ && echo '{"workbench.colorTheme": "VS Code Dark"}' > /home/vscode/.vscode-server/data/Machine/settings.json +# The apt `code` package installs the desktop (Electron) binary at /usr/bin/code. +# In these headless serve-web/tunnel sessions there is no X display, so running +# `code ` in the terminal crashes ("Missing X server or $DISPLAY"). +# Drop a wrapper on $HOME/.local/bin (first on the interactive PATH, see .zshrc/.bashrc +# below) that forwards to the serve-web/remote-cli shim, which opens files in a running +# VS Code window over its $VSCODE_IPC_HOOK_CLI socket. The wrapper picks the newest shim +# so it survives VS Code updates, and routes to the newest live window socket so it keeps +# working in long-lived shells (e.g. tmux) whose captured socket has gone stale (see the +# inline comments). Only affects interactive shells; build-time `code --install-extension` +# above still resolves to /usr/bin/code. +RUN mkdir -p /home/vscode/.local/bin \ + && printf '%s\n' \ + '#!/usr/bin/env sh' \ + '# Route `code` to the VS Code serve-web/remote CLI shim instead of the GUI desktop' \ + '# binary (/usr/bin/code), which has no display in this container and crashes.' \ + '' \ + '# 0. tunnel/serve-web are full-CLI subcommands the remote-cli shim does not implement' \ + '# (it would treat "tunnel" as a filename to open) and they need no window socket.' \ + '# Hand them to the real /usr/bin/code, which dispatches them to its bundled CLI and' \ + '# runs fine headless. Use the absolute path -- calling `code` here would recurse' \ + '# into this wrapper.' \ + 'case "$1" in' \ + ' tunnel|serve-web) exec /usr/bin/code "$@" ;;' \ + 'esac' \ + '' \ + '# 1. Locate the remote CLI shim. There is one per installed VS Code build and the' \ + '# path embeds a build hash, so pick the newest to survive VS Code updates. The' \ + '# -x check turns a found-but-not-executable match into the friendly error below' \ + '# instead of an opaque `exec: Permission denied`.' \ + 'shim=$(ls -t "$HOME"/.vscode/cli/serve-web/*/bin/remote-cli/code \' \ + ' "$HOME"/.vscode-server/bin/*/bin/remote-cli/code \' \ + ' "$HOME"/.vscode-server/cli/servers/*/server/bin/remote-cli/code \' \ + ' 2>/dev/null | head -n1)' \ + 'if [ -z "$shim" ] || [ ! -x "$shim" ]; then' \ + ' echo "code: no usable VS Code remote CLI shim found; is a serve-web/remote session running?" >&2' \ + ' exit 1' \ + 'fi' \ + '' \ + '# 2. Choose the IPC socket of the window to open files in. $VSCODE_IPC_HOOK_CLI is' \ + '# captured when the shell starts, so a long-lived shell (e.g. a tmux server that' \ + '# outlives a VS Code reconnect) keeps pointing at an OLD window. That old socket' \ + '# often still accepts connections, so files silently open in a window you can no' \ + '# longer see. VS Code creates one socket per window/connection under /tmp, so the' \ + '# newest is your current window: prefer the newest socket that still accepts a' \ + '# connection, falling back to older ones. If none is reachable, keep whatever the' \ + '# environment provided so socket-less subcommands (code --version, etc.) still work.' \ + '# LIMITATION: "newest socket" is the current window only with a single window/tab' \ + '# open. With multiple windows, or after an extension-host reload, files may open in' \ + '# another window -- serve-web exposes no API to target a specific window, so this is' \ + '# the best heuristic available.' \ + 'ipc_live() {' \ + ' [ -S "$1" ] || return 1' \ + ' # Best-effort connect probe to skip sockets whose window has gone away. socat is' \ + ' # optional here: if it is not installed, treat an existing socket as usable. The' \ + ' # timeout bounds the connect so a pathological socket can never wedge `code`.' \ + ' command -v socat >/dev/null 2>&1 || return 0' \ + ' timeout 2 socat -u OPEN:/dev/null UNIX-CONNECT:"$1" >/dev/null 2>&1' \ + '}' \ + 'for sock in $(ls -t /tmp/vscode-ipc-*.sock 2>/dev/null); do' \ + ' if ipc_live "$sock"; then' \ + ' VSCODE_IPC_HOOK_CLI="$sock"' \ + ' export VSCODE_IPC_HOOK_CLI' \ + ' break' \ + ' fi' \ + 'done' \ + '' \ + '# 3. Hand off to the shim, preserving every argument and its exit status.' \ + 'exec "$shim" "$@"' \ + > /home/vscode/.local/bin/code \ + && chmod +x /home/vscode/.local/bin/code + # Add backup warning to .zshrc for vscode user RUN echo '\n# === Backup & Git Reminder ===' | tee -a /home/vscode/.zshrc && \ echo 'echo -e "\e[1;31m⚠️ WARNING: No backups are configured. You are responsible for any data loss.\e[0m"' | tee -a /home/vscode/.zshrc && \