mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
/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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9ZSTEenc8soSu9bTi8Xru
97 lines
5.1 KiB
Bash
Executable File
97 lines
5.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# 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
|
|
# directory, a restored backup - the Docker daemon creates it owned by root,
|
|
# and an unprivileged server cannot then create its own state directory. The
|
|
# result is a container that restarts forever on:
|
|
#
|
|
# Failed to start web server: EACCES: permission denied, mkdir '/home/<user>/.codeman'
|
|
#
|
|
# Running this as root and dropping afterwards removes that failure mode without
|
|
# 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
|
|
|
|
# Honour an explicit `user:` in Compose: when the container was not started as
|
|
# root there is nothing to correct and no privilege to drop.
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
exec "$@"
|
|
fi
|
|
|
|
: "${PUID:=1000}"
|
|
: "${PGID:=1000}"
|
|
|
|
for target in "${HOME:-}" "${CODEMAN_CASES_PATH:-}"; do
|
|
[ -n "$target" ] && [ -d "$target" ] || continue
|
|
owner=$(stat -c '%u:%g' "$target")
|
|
[ "$owner" = "${PUID}:${PGID}" ] && continue
|
|
|
|
# Only ever correct a directory the DAEMON created (root-owned, because
|
|
# neither PUID nor PGID existed yet when it materialised the missing bind
|
|
# source). Anything else - a host tree that legitimately belongs to some
|
|
# OTHER account, such as an existing CODEMAN_CASES_PATH the README already
|
|
# allows pointing at a normal project directory - is not this container's
|
|
# to reassign; recursively chowning it on every mismatch silently rewrote
|
|
# a credentials tree or a projects directory to PUID:PGID with one log
|
|
# line to explain it. Refuse instead, the same way Start-Codeman.sh already
|
|
# refuses to touch a root-owned appdata directory it did not expect.
|
|
if [ "${owner%%:*}" != '0' ]; then
|
|
printf 'entrypoint: %s is owned by %s, which is neither root nor PUID:PGID (%s:%s).\n' \
|
|
"$target" "$owner" "$PUID" "$PGID" >&2
|
|
printf 'entrypoint: refusing to change ownership of a directory this container did not create.\n' >&2
|
|
printf 'entrypoint: either chown it on the host, or set PUID/PGID to match its current owner.\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Deliberately not fatal for a root-owned directory. A bind mount backed by
|
|
# NFS, CIFS or a rootless daemon can refuse chown while still being
|
|
# perfectly writable, and those deployments must keep working. A warning is
|
|
# more useful than a container that will not start.
|
|
if chown -R "${PUID}:${PGID}" "$target" 2>/dev/null; then
|
|
printf 'entrypoint: corrected ownership of %s to %s:%s\n' "$target" "$PUID" "$PGID"
|
|
else
|
|
printf 'entrypoint: warning: cannot change ownership of %s to %s:%s\n' \
|
|
"$target" "$PUID" "$PGID" >&2
|
|
printf 'entrypoint: warning: continuing; set the ownership on the host if startup fails\n' >&2
|
|
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, -)
|
|
[ -n "$supplementary" ] || supplementary="$PGID"
|
|
|
|
# --bounding-set -all: with the reuid/regid drop above, CapPrm/CapEff are
|
|
# already empty, but the bounding set otherwise still lists everything
|
|
# cap_add granted (visible as a nonzero CapBnd even post-drop). no-new-privileges
|
|
# already makes that moot - nothing can regain a capability outside the
|
|
# bounding set - but clearing it too is free and matches what "drops to
|
|
# PUID:PGID" actually promises.
|
|
exec setpriv --reuid "$PUID" --regid "$PGID" --groups "$supplementary" --bounding-set -all "$@"
|