From 5d52b79a0737311e63df3014170df49b4b52d757 Mon Sep 17 00:00:00 2001 From: indifferentketchup Date: Tue, 19 May 2026 13:14:21 +0000 Subject: [PATCH] =?UTF-8?q?v1.10.2:=20booterm=20runtime=20on=20bookworm-sl?= =?UTF-8?q?im=20(glibc),=20su-exec=20=E2=86=92=20gosu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switched the booterm runtime + proddeps stages from node:20-alpine (musl) to node:20-bookworm-slim (glibc) so host-installed glibc binaries (Claude Code, opencode, nvm node) run inside the container when invoked from the terminal pane. node-pty's native .node has to be compiled in the same libc env as the runtime, so both stages flip together; the TypeScript-only builder stage stays on alpine. su-exec is alpine-only; Debian replacement is gosu — swapped in both the runtime apt install and the tmux default-command. uid/gid 1000 collision with the bookworm `node` user handled via userdel/groupdel before groupadd/useradd. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/booterm/Dockerfile | 35 ++++++++++++++++++++++++++--------- apps/booterm/tmux.conf | 10 ++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/apps/booterm/Dockerfile b/apps/booterm/Dockerfile index a82f928..788805a 100644 --- a/apps/booterm/Dockerfile +++ b/apps/booterm/Dockerfile @@ -15,28 +15,45 @@ COPY apps/booterm ./apps/booterm RUN pnpm --filter=@boocode/booterm build # ---- Prod-deps stage: hoisted, native built via npm rebuild ---- -FROM node:20-alpine AS proddeps +# v1.10.2: switched to bookworm-slim (glibc) so node-pty's native .node is +# compiled against the same libc as the runtime stage. A musl-built .node +# won't dlopen in a glibc node binary, so both stages must match. +FROM node:20-bookworm-slim AS proddeps ENV COREPACK_DEFAULT_TO_LATEST=0 RUN corepack enable && corepack prepare pnpm@10.15.1 --activate -RUN apk add --no-cache python3 make g++ +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 make g++ ca-certificates \ + && rm -rf /var/lib/apt/lists/* WORKDIR /prod COPY apps/booterm/package.json ./package.json RUN pnpm install --prod --config.node-linker=hoisted --config.strict-peer-dependencies=false # pnpm 10 ignores build scripts; force compile with npm directly. -# node-gyp is bundled with npm in the node:20-alpine image. +# node-gyp is bundled with npm in the node:20-bookworm-slim image. RUN cd node_modules/node-pty && npm run install # Sanity check — fail the build if the artifact still isn't there RUN test -f node_modules/node-pty/build/Release/pty.node && echo "pty.node OK" || (echo "pty.node MISSING" && exit 1) # ---- Runtime ---- -FROM node:20-alpine AS runtime -RUN apk add --no-cache tmux libstdc++ bash su-exec shadow -# v1.10.1: terminal shells inside tmux drop privs to samkintop via su-exec. +# v1.10.2: switched from node:20-alpine (musl) to node:20-bookworm-slim (glibc) +# so glibc-linked binaries from /home/samkintop (Claude Code, opencode, the +# host's nvm node) run inside the container when invoked from the terminal +# pane. Side-effect: su-exec is alpine-only — Debian replacement is gosu. +FROM node:20-bookworm-slim AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + tmux bash gosu ca-certificates procps \ + && rm -rf /var/lib/apt/lists/* # Mirror uid/gid 1000:1000 from the host so the bind-mounted /home/samkintop # (added in docker-compose) is owned by the user from the container's view. -RUN deluser --remove-home node 2>/dev/null; delgroup node 2>/dev/null; \ - addgroup -g 1000 samkintop && \ - adduser -D -u 1000 -G samkintop -s /bin/bash samkintop +# bookworm-slim ships a `node` user at 1000 — wipe whatever sits on uid/gid +# 1000 first, then create samkintop fresh. +RUN if id -u 1000 >/dev/null 2>&1; then \ + userdel -r "$(id -un 1000)" 2>/dev/null || true; \ + fi; \ + if getent group 1000 >/dev/null 2>&1; then \ + groupdel "$(getent group 1000 | cut -d: -f1)" 2>/dev/null || true; \ + fi; \ + groupadd -g 1000 samkintop && \ + useradd -m -u 1000 -g 1000 -s /bin/bash samkintop WORKDIR /app COPY --from=builder /build/apps/booterm/dist ./dist COPY --from=proddeps /prod/package.json ./package.json diff --git a/apps/booterm/tmux.conf b/apps/booterm/tmux.conf index bf8cfd6..34457b9 100644 --- a/apps/booterm/tmux.conf +++ b/apps/booterm/tmux.conf @@ -7,7 +7,9 @@ set -g destroy-unattached off # v1.10.1: shells drop privs to samkintop (uid 1000) so the terminal runs in # the user's environment, not root. `env HOME=… USER=…` is required because -# su-exec only changes uid/gid — it leaves env intact, and tmux server runs -# as root so HOME would otherwise be /root. bash -l then sources samkintop's -# ~/.profile / ~/.bashrc to pick up PATH (nvm, ~/.local/bin, ~/.opencode/bin). -set -g default-command "su-exec samkintop:samkintop env HOME=/home/samkintop USER=samkintop SHELL=/bin/bash bash -l" +# gosu (and su-exec before it) only changes uid/gid — it leaves env intact, +# and the tmux server runs as root so HOME would otherwise be /root. bash -l +# then sources samkintop's ~/.profile / ~/.bashrc to pick up PATH (nvm, +# ~/.local/bin, ~/.opencode/bin). +# v1.10.2: su-exec → gosu (alpine → debian; functionally identical). +set -g default-command "gosu samkintop:samkintop env HOME=/home/samkintop USER=samkintop SHELL=/bin/bash bash -l"