From 3b387125e2c11356f6eb18b0e93a99d4638e3daa Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 21:30:29 +0200 Subject: [PATCH] fix(ci): run the container as the invoking user, not root Docker on the dev boxes is rootful, so without `--user` every byte the build writes into the bind-mounted repo is owned by root and the user needs `sudo` to delete their own artifacts. This is not hypothetical: `export/` in a working tree held 227 root-owned paths (149 MB) from earlier runs, and the `sylpheed.db` regen in the workspace CLAUDE.md writes straight into /work, so it lands root-owned every time. The catch is that the daemon creates a named volume root-owned, so a `--user` container cannot write /cargo or /target at all. So take ownership of both volumes first -- once, and only when it is actually wrong, since a recursive chown across a ~36 GB target volume is not something to repeat per invocation. Both are sampled, not just one, because an older run can leave them drifted. Volume names become overridable (SYLPH_CI_CARGO_VOL / SYLPH_CI_TARGET_VOL), which is what let the chown path be tested without touching the real caches. Placed above the corpus-mount block so it does not collide with #53. Measured, not assumed: * fresh root-owned volumes -> chowns once, then writes as uid 1000 * second run -> no chown, correctly cached * `cargo check -p sylpheed-ppc` through the runner -> passes, exit 0 * a file touched in /work -> owned fabi:fabi, removable without sudo Co-Authored-By: Claude Opus 5 (1M context) --- docker/ci/run | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docker/ci/run b/docker/ci/run index bd7c007c..39973bce 100755 --- a/docker/ci/run +++ b/docker/ci/run @@ -21,18 +21,51 @@ IMAGE="${SYLPH_CI_IMAGE:-sylph-ci:local}" CPUS="${SYLPH_CI_CPUS:-6}" MEM_GB="${SYLPH_CI_MEM_GB:-7}" +CARGO_VOL="${SYLPH_CI_CARGO_VOL:-sylph-ci-cargo}" +TARGET_VOL="${SYLPH_CI_TARGET_VOL:-sylph-ci-target}" + +# 🔴 Docker on the dev boxes is ROOTFUL, so without `--user` every byte the build +# writes into the bind-mounted repo is owned by root — and the user then needs +# `sudo` to delete their own artifacts. The regen command in the workspace +# `CLAUDE.md` writes `sylpheed.db` straight into /work, so it lands root-owned, +# and a stray root-owned file is exactly what survived the last cleanup and had +# to be sudo'd away. +# +# The catch is that the daemon creates a named volume root-owned, so a `--user` +# container cannot write /cargo or /target at all. Take ownership once — and +# only when it is actually wrong, because a recursive chown across a ~36 GB +# target volume is not something to repeat on every invocation. +RUN_UID="$(id -u)" +RUN_GID="$(id -g)" + +docker volume create "$CARGO_VOL" >/dev/null +docker volume create "$TARGET_VOL" >/dev/null + +# Both volumes, not just one: they are chowned together but can drift apart if an +# older root-owned run created only one of them. +vol_owner="$(docker run --rm -v "$CARGO_VOL:/cargo" -v "$TARGET_VOL:/target" "$IMAGE" \ + stat -c %u /cargo /target 2>/dev/null | sort -u | tr '\n' ' ' || echo unknown)" +if [ "$vol_owner" != "$RUN_UID " ]; then + echo "docker/ci/run: chowning the cargo/target volumes to $RUN_UID:$RUN_GID (one-off)" >&2 + docker run --rm \ + -v "$CARGO_VOL:/cargo" -v "$TARGET_VOL:/target" \ + "$IMAGE" chown -R "$RUN_UID:$RUN_GID" /cargo /target +fi + args=( --rm --cpus "$CPUS" --memory "${MEM_GB}g" --memory-swap "${MEM_GB}g" --pids-limit 2048 + # Run as the invoking user so build output in /work is owned by them, not root. + --user "$RUN_UID:$RUN_GID" -v "$REPO:/work" # Named volumes, not bind mounts: the host tree keeps a 32 GB `target/` from # earlier host-side builds, and mixing the two produces rebuilds that look # like cache misses and are actually two toolchains fighting over one directory. - -v sylph-ci-cargo:/cargo -e CARGO_HOME=/cargo - -v sylph-ci-target:/target -e CARGO_TARGET_DIR=/target + -v "$CARGO_VOL:/cargo" -e CARGO_HOME=/cargo + -v "$TARGET_VOL:/target" -e CARGO_TARGET_DIR=/target -w /work ) -- 2.49.1