From c179daf8695d0f3daa1b190b30a1a82481264178 Mon Sep 17 00:00:00 2001 From: Devvyn <22340871+opticon454@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:28:23 +0800 Subject: [PATCH] fix(docker): re-assert /opt/codeman-cli ownership every start, not just at build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /opt/codeman-cli is chowned to PUID:PGID once, at image build time, from the PUID/PGID build args. That bake only happens when the image is actually rebuilt (`docker compose up --build`, which Start-Codeman.sh always does) — a deployment that runs the compose file directly instead (Unraid's Compose Manager, a native systemd unit, any plain `docker compose up`/`restart`) can change PUID/PGID in .env and restart without ever rebuilding. The container then runs as the NEW uid via entrypoint's setpriv (Linux needs no /etc/passwd entry to setuid to an arbitrary number) while the CLI directory is still owned by the OLD one baked into the image layer — silently breaking the self-update-a-CLI- in-place fix that directory exists for. Unlike HOME/CODEMAN_CASES_PATH, this one is pure image content Codeman itself populated, never host data that might legitimately belong to someone else, so there is no ownership to be careful about — it is always correct for it to be owned by whoever the container is about to run as. Re-assert it unconditionally on every start. Verified live: built an image with PUID=99/PGID=100, ran it with PUID=1234/PGID=4321 (no rebuild, simulating a changed .env restarted directly), confirmed /opt/codeman-cli ends up 1234:4321-owned and is genuinely writable by the running process. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01R9ZSTEenc8soSu9bTi8Xru --- docker/entrypoint.sh | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a8f2d00c..be4912e4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,5 +1,6 @@ #!/bin/sh -# Corrects the ownership of the host bind mounts, then drops to PUID:PGID. +# Corrects ownership - host bind mounts, and the image-baked CLI prefix - +# then drops to PUID:PGID. # # Compose binds CODEMAN_APPDATA_PATH and CODEMAN_CASES_PATH from the host. When # either path does not exist yet - a first run, a cleared application-data @@ -10,7 +11,10 @@ # Failed to start web server: EACCES: permission denied, mkdir '/home//.codeman' # # Running this as root and dropping afterwards removes that failure mode without -# leaving the server privileged. +# leaving the server privileged. The same root start also lets it re-assert +# /opt/codeman-cli's ownership on every start, not just at image build time - +# see the comment at that chown below for why that matters for anyone who +# runs the compose file directly rather than through Start-Codeman.sh. set -eu @@ -58,6 +62,26 @@ for target in "${HOME:-}" "${CODEMAN_CASES_PATH:-}"; do fi done +# /opt/codeman-cli (the four agent CLIs) is chowned to PUID:PGID once, at +# image BUILD time, from the PUID/PGID build args - server.Dockerfile's own +# comment on that RUN step explains why it lives in its own prefix rather than +# /usr/local. Unlike HOME/CODEMAN_CASES_PATH above, that bake happens only +# when the image is actually rebuilt (`docker compose up --build`, which +# Start-Codeman.sh always does) - a deployment that instead runs the compose +# file directly (Unraid's Compose Manager, a native Debian systemd unit, any +# `docker compose up`/`restart` with no --build) can change PUID/PGID in .env +# and restart without ever rebuilding, at which point the container runs as +# the NEW uid while the CLI directory is still owned by the OLD one baked into +# the image layer - silently breaking the very "self-update a CLI in place" +# fix this directory exists for. Re-assert it here, every start, unconditionally: +# unlike the host bind mounts above, this is pure image content Codeman itself +# populated, never host data that might legitimately belong to someone else, +# so there is no ownership to be careful about - it is always correct for it +# to be owned by whoever this container is about to run as. +if [ -d /opt/codeman-cli ] && [ "$(stat -c '%u:%g' /opt/codeman-cli)" != "${PUID}:${PGID}" ]; then + chown -R "${PUID}:${PGID}" /opt/codeman-cli +fi + # Preserve the supplementary groups Compose granted through group_add - that is # how the Docker socket stays reachable - while discarding root's own group. supplementary=$(id -G | tr ' ' '\n' | grep -vx 0 | paste -sd, -)