fix(ci): run the container as the invoking user, not root
All checks were successful
CI / Native — linux (pull_request) Successful in 2h12m2s
CI / WASM — Web (pull_request) Successful in 32m25s
CI / Formatting (pull_request) Successful in 2m7s

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) <noreply@anthropic.com>
This commit is contained in:
2026-09-18 21:30:29 +02:00
parent e7325577e4
commit 3b387125e2

View File

@@ -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
)