Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion devBenches/devcontainer.test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Layer 1a adds developer tools on top of Layer 0:
- Zsh and oh-my-zsh with plugins
- PATH configuration for all dev tools

The Layer 3 user image adds the effective-user Corepack cache exercised by the
unprivileged pnpm checks in this harness.

## Quick Start

```bash
Expand All @@ -42,7 +45,7 @@ docker compose down

The `test.sh` script validates:
- ✅ Python development tools
- ✅ Node.js development tools
- ✅ Node.js development tools and unprivileged pnpm/Corepack operation
- ✅ Python package managers (uv)
- ✅ Spec-driven tools (`specify`, `openspec`)
- ✅ Worktree-mode Speckit bootstrap installation
Expand Down
3 changes: 3 additions & 0 deletions devBenches/devcontainer.test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ echo "=== Node.js Development ==="
test_tool_output "Node.js" "node --version"
test_tool_output "npm" "npm --version"
test_tool_output "yarn" "yarn --version"
test_tool_output "pnpm as unprivileged user" "pnpm --version"
test_tool "Corepack cache is user-owned and writable" \
"runtime_home=\$(getent passwd \"\$(id -u)\" | cut -d: -f6); cache=\"\$runtime_home/.cache/corepack\"; test -d \"\$cache\" && test -w \"\$cache\" && test \"\$(stat -c '%u' \"\$cache\")\" = \"\$(id -u)\""
test_tool "/usr/local/bin in PATH" "echo \$PATH | grep -q '/usr/local/bin'"

echo ""
Expand Down
11 changes: 10 additions & 1 deletion user-layer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ FROM ${BASE_IMAGE}
# Container version labels
LABEL layer="3"
LABEL layer.name="user-layer"
LABEL layer.version="1.2.0"
LABEL layer.version="1.2.1"
LABEL layer.description="User personalization layer"

# Build arguments
Expand All @@ -30,6 +30,15 @@ ARG EXTRA_CHOWN_DIRS=""

USER root

# Corepack's shared base-layer cache is root-owned. Route Corepack and its
# package-manager shims through a runtime wrapper that selects a writable cache
# from the effective user's passwd home, including for non-interactive calls.
COPY corepack-user-cache /usr/local/libexec/workbenches-corepack-user-cache
RUN chmod 0755 /usr/local/libexec/workbenches-corepack-user-cache && \
for command in corepack pnpm pnpx yarn yarnpkg; do \
ln -sfn /usr/local/libexec/workbenches-corepack-user-cache "/usr/local/bin/$command"; \
done

# ========================================
# USER CREATION
# ========================================
Expand Down
28 changes: 28 additions & 0 deletions user-layer/corepack-user-cache
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
set -eu

command_name=${0##*/}
case "$command_name" in
corepack|pnpm|pnpx|yarn|yarnpkg) ;;
*)
echo "Unsupported Corepack command: $command_name" >&2
exit 64
;;
esac

# Respect an explicit caller override. Replace only the shared image default,
# which is root-owned and therefore unusable by Layer 3 runtime users.
case "${COREPACK_HOME:-}" in
""|/opt/corepack)
runtime_home=$(getent passwd "$(id -u)" | cut -d: -f6)
if [ -z "$runtime_home" ]; then
echo "Could not determine the effective user's home directory" >&2
exit 1
fi
COREPACK_HOME="$runtime_home/.cache/corepack"
export COREPACK_HOME
;;
esac
Comment on lines +15 to +25

mkdir -p "$COREPACK_HOME"
exec "/usr/bin/$command_name" "$@"
Comment on lines +13 to +28

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by the final design at b837741 and current head 1b9311c: Layer 3 no longer sets COREPACK_HOME globally. The wrapper replaces the inherited /opt/corepack value using the effective UID passwd home and already emits a clear error when that home cannot be resolved.