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>
77 lines
3.3 KiB
Bash
Executable File
77 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Run a command against this repository inside the capped CI container.
|
|
#
|
|
# docker/ci/run cargo check --workspace --all-targets
|
|
# docker/ci/run cargo clippy --workspace --all-targets --keep-going -- -D warnings
|
|
# docker/ci/run bash -c 'cargo fmt --all; cargo fmt --all -- --check | grep -c "^Diff in"'
|
|
#
|
|
# 🔴 EVERY HEAVY COMMAND GOES THROUGH HERE. A bare `cargo build` on the host is
|
|
# unbounded: `CARGO_BUILD_JOBS` caps codegen units, not rustc's own threads, not
|
|
# the linker, not the test harness. A full-parallel build has OOM-crashed this
|
|
# box, and unbounded host runs have frozen it since. The container is the cap
|
|
# that actually holds.
|
|
#
|
|
# Half the machine, no swap. `--memory-swap` equal to `--memory` means a build
|
|
# that would swap is killed instead, which is a fast failure rather than an
|
|
# hour of thrashing that takes the desktop with it.
|
|
set -euo pipefail
|
|
|
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
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 "$CARGO_VOL:/cargo" -e CARGO_HOME=/cargo
|
|
-v "$TARGET_VOL:/target" -e CARGO_TARGET_DIR=/target
|
|
-w /work
|
|
)
|
|
|
|
# The disc, read-only, when a disc-backed test or the exporter needs it.
|
|
DISC="${SYLPHEED_DISC:-$REPO/../sylph_extract}"
|
|
[ -d "$DISC" ] && args+=(-v "$DISC:/disc:ro" -e SYLPHEED_DISC=/disc)
|
|
|
|
exec docker run "${args[@]}" "$IMAGE" "$@"
|