v_2-0_local-projects #1
@ -37,9 +37,18 @@ RUN ARCH=$(uname -m) && \
|
||||
rm -rf /tmp/gs-install
|
||||
|
||||
# --- the user Claude runs as ---
|
||||
# We create this user so the home folder is owned by the same ID the launcher
|
||||
# runs as. Without it, the container couldn't write to its own home.
|
||||
RUN useradd -m -s /bin/bash -u 1001 coder
|
||||
# We create 'coder' with the *host* user's UID/GID (passed in by the launcher).
|
||||
# This is what makes the bind-mounted project at /code writable: the files there
|
||||
# keep the host's ownership, so the container can only write them if it runs as
|
||||
# that same ID. It also means files Claude creates come out owned by you on the
|
||||
# host — not root or some stray ID. The launcher rebuilds this image if the host
|
||||
# ID ever changes (it reads the labels below to notice), so the default here is
|
||||
# just a placeholder for a from-scratch build.
|
||||
ARG HOST_UID=1000
|
||||
ARG HOST_GID=1000
|
||||
RUN if ! getent group "${HOST_GID}" >/dev/null; then groupadd -g "${HOST_GID}" coder; fi && \
|
||||
useradd -m -s /bin/bash -u "${HOST_UID}" -g "${HOST_GID}" coder
|
||||
LABEL safeclaude.uid="${HOST_UID}" safeclaude.gid="${HOST_GID}"
|
||||
|
||||
# Claude installs itself into one of these folders, so add them to PATH.
|
||||
ENV PATH="/home/coder/.local/bin:/home/coder/.claude/bin:$PATH"
|
||||
|
||||
26
safeclaude
26
safeclaude
@ -65,10 +65,25 @@ context_hash() {
|
||||
} | sha256sum | cut -c1-12
|
||||
}
|
||||
|
||||
# Build the shared base image, baking in the host user's UID/GID so the container
|
||||
# can write the bind-mounted project (see Dockerfile.base). We rebuild if the
|
||||
# image is missing, or if its baked-in IDs don't match the current host user —
|
||||
# e.g. a different user runs safeclaude, or the repo moved to another machine.
|
||||
ensure_base() {
|
||||
docker image inspect "$BASE_IMAGE" &>/dev/null && return 0
|
||||
echo "[safeclaude] building base image $BASE_IMAGE ..." >&2
|
||||
docker build -t "$BASE_IMAGE" -f "$SCRIPT_DIR/Dockerfile.base" "$SCRIPT_DIR" >&2
|
||||
local want_uid want_gid
|
||||
want_uid="$(id -u)"; want_gid="$(id -g)"
|
||||
if docker image inspect "$BASE_IMAGE" &>/dev/null; then
|
||||
local have_uid have_gid
|
||||
have_uid="$(docker image inspect --format '{{index .Config.Labels "safeclaude.uid"}}' "$BASE_IMAGE" 2>/dev/null || true)"
|
||||
have_gid="$(docker image inspect --format '{{index .Config.Labels "safeclaude.gid"}}' "$BASE_IMAGE" 2>/dev/null || true)"
|
||||
[ "$have_uid" = "$want_uid" ] && [ "$have_gid" = "$want_gid" ] && return 0
|
||||
echo "[safeclaude] base image user ${have_uid:-?}:${have_gid:-?} != host $want_uid:$want_gid — rebuilding ..." >&2
|
||||
else
|
||||
echo "[safeclaude] building base image $BASE_IMAGE ..." >&2
|
||||
fi
|
||||
docker build -t "$BASE_IMAGE" \
|
||||
--build-arg HOST_UID="$want_uid" --build-arg HOST_GID="$want_gid" \
|
||||
-f "$SCRIPT_DIR/Dockerfile.base" "$SCRIPT_DIR" >&2
|
||||
}
|
||||
|
||||
# Build the project's container if we haven't already (force=1 rebuilds it
|
||||
@ -110,8 +125,11 @@ cmd_run() {
|
||||
- To add setup that runs at container startup: ask the developer to add a script to .safeclaude/hooks/.
|
||||
- /home/coder persists between runs. Edits under /code are real and shared with the host. Everything else is discarded when the container exits."
|
||||
|
||||
# Run as the host user so writes to the mounted /code (and its cache/) land with
|
||||
# the right ownership. The base image creates this exact UID/GID as 'coder', so
|
||||
# HOME and the home volume resolve correctly. Still non-root, still locked down.
|
||||
exec docker run --rm "${tty_args[@]}" \
|
||||
--user 1001:1001 \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--cap-drop ALL \
|
||||
--security-opt no-new-privileges:true \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
|
||||
Reference in New Issue
Block a user