fix: run ci/build.sh container as invoking user, not root #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/build-sh-user-perms"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Root cause
docker runinci/build.shhad no--userflag, socargo(and thefinal
cp -astep) ran as root inside the container. Because thecontainer's
/cargo-targetis a bind-adjacent copy source and${REPO_ROOT}is bind-mounted straight from the host, the final step:wrote onto the host checkout's
target/directory as root, leaving itroot-owned after every local run of
ci/build.sh. Any later host-sidecargocommand against that checkout then fails with a permission erroruntil someone manually
chownstarget/back. This has already hitbreadmon, breadshot, breadclip, and breadpad.
Why a bare
--userflag isn't sufficient on its ownThe obvious fix — add
--user "$(id -u):$(id -g)"— isn't enough byitself, because the cargo registry/git/target caches were named docker
volumes mounted at
/root/.cargo/registry,/root/.cargo/git, and/cargo-target:/rootis0750 root:rootin the base Arch image, so a non-root,non-root-group user can't even traverse into it —
/root/.cargo/registryis unreachable regardless of that subdirectory's own permissions.
localvolume driver initializes a brand-new named volume asroot:root 0755and does not chown it to match--user, so eventhe top-level
/cargo-targetmount (outside/root) has the sameproblem for a fresh volume.
I verified both failure modes and the fix locally with
podman(using--userns=keep-id, which is needed under rootless podman to reproduceplain rootful docker's 1:1 host/container uid mapping — this sandbox's
podman is rootless with subuid remapping by default, so without that flag
the test isn't representative of the real self-hosted docker runner):
--user:touch /root/.cargo/registry/testfile→Permission denied.--user: writes to the registry/git/target caches and thefinal
cp -ainto/workspace/targetall succeed, and every resultingfile lands owned by the invoking host user, not root.
Fix
under
/root/...to real host directories under${BREAD_CI_CACHE_ROOT:-$HOME/.cache/bread-ci}, bind-mounted in(inherits the invoking user's ownership since
mkdir -pruns on thehost before the container starts).
CARGO_HOMEat the new registry/git mount and keepCARGO_TARGET_DIRpointed at the new target mount.--user "$(id -u):$(id -g)"and-e HOME=/tmptodocker run.No chown-based cleanup step was added — this prevents the root-owned
files from being written in the first place rather than fixing them up
after the fact.
Note for whoever runs this on the actual CI/dev hosts: this switches the
cache location away from the old
bread-ci-cargo-registry/bread-ci-cargo-git/bread-ci-<product>-targetnamed docker volumesentirely, so there's no stale root-owned volume content to worry about —
the new host cache dirs start empty and get populated fresh on first run
under the new user. The old named volumes are simply orphaned and can be
docker volume rm'd whenever convenient; nothing depends on them anymore.docker run had no --user flag, so cargo built as root inside the container. The final `cp -a /cargo-target/. /workspace/target/` step then wrote onto the host bind-mounted repo as root, leaving target/ root-owned on the host after every local run — breaking any later host-side cargo command until someone manually chowned it back. This has bitten breadmon, breadshot, breadclip, and breadpad before. Adding a bare --user flag isn't enough on its own: the cargo registry/git/target caches were named docker volumes mounted under /root/.cargo/... and /cargo-target. /root is 0750 root:root in the base image, so a non-root user can't even traverse into it, and dockerd initializes fresh named volumes as root:root 0755 regardless of --user, so the top-level /cargo-target mount has the same problem. Switch those caches to host directories under ${BREAD_CI_CACHE_ROOT:-$HOME/.cache/bread-ci} (real bind mounts, not named volumes), point CARGO_HOME/CARGO_TARGET_DIR at them, and add --user "$(id -u):$(id -g)". Verified the failure mode and the fix with podman --userns=keep-id locally (rootless podman needs that flag to reproduce plain rootful docker's 1:1 uid mapping): the old /root/.cargo mount rejects writes even with --user set, while the new bind-mount layout lets the container write everywhere it needs to and leaves every resulting file owned by the invoking user on the host. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013r8iu6nnSMfZLirp82jaNnPull request closed