diff --git a/DOCKER_HUB.md b/DOCKER_HUB.md index e6ec49f..ca47429 100644 --- a/DOCKER_HUB.md +++ b/DOCKER_HUB.md @@ -103,6 +103,25 @@ opencode | `GIT_USER_NAME` | Git commit author name | | `GIT_USER_EMAIL` | Git commit author email | +### User ID Mapping + +The container runs as user `developer` (UID 1000 by default). If your host user has a different UID, file permission mismatches can occur on mounted volumes. + +The entrypoint automatically detects the owner of `/workspace` and adjusts the container user's UID/GID to match. You can also set it explicitly: + +| Variable | Description | Default | +|---|---|---| +| `USER_UID` | Container user UID | Auto-detect from `/workspace` owner | +| `USER_GID` | Container user GID | Auto-detect from `/workspace` owner | + +```bash +docker run -it --rm \ + -e USER_UID=$(id -u) \ + -e USER_GID=$(id -g) \ + -v ~/projects:/workspace \ + joakimp/opencode-devbox:latest +``` + ## Data Storage and Persistence Understanding what survives container restarts and what doesn't: diff --git a/Dockerfile b/Dockerfile index f8bf8db..5c06853 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ less \ vim-tiny \ sudo \ + gosu \ locales \ procps \ unzip \ @@ -103,9 +104,10 @@ RUN mkdir -p /workspace \ # ── Entrypoint ──────────────────────────────────────────────────────── COPY entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh +COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh +RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh -USER ${USER_NAME} +# Start as root — entrypoint adjusts UID/GID then drops to developer WORKDIR /workspace ENTRYPOINT ["entrypoint.sh"] diff --git a/README.md b/README.md index e8ed4a6..872ae00 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ docker compose exec devbox aws --version | `GIT_USER_EMAIL` | Git commit author email | — | | `WORKSPACE_PATH` | Host path to mount | `.` | | `SSH_KEY_PATH` | Host SSH key directory | `~/.ssh` | +| `USER_UID` | Override container user UID | Auto-detect from `/workspace` | +| `USER_GID` | Override container user GID | Auto-detect from `/workspace` | ### Custom opencode config diff --git a/entrypoint-user.sh b/entrypoint-user.sh new file mode 100644 index 0000000..73405ca --- /dev/null +++ b/entrypoint-user.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ── Git config defaults ────────────────────────────────────────────── +if [ -n "${GIT_USER_NAME:-}" ] && ! git config --global user.name &>/dev/null; then + git config --global user.name "$GIT_USER_NAME" +fi +if [ -n "${GIT_USER_EMAIL:-}" ] && ! git config --global user.email &>/dev/null; then + git config --global user.email "$GIT_USER_EMAIL" +fi + +# ── Generate opencode config from env vars if no config mounted ────── +CONFIG_DIR="$HOME/.config/opencode" +CONFIG_FILE="$CONFIG_DIR/opencode.json" + +if [ ! -f "$CONFIG_FILE" ] && [ -n "${OPENCODE_PROVIDER:-}" ]; then + echo "Generating opencode config for provider: $OPENCODE_PROVIDER" + mkdir -p "$CONFIG_DIR" + + case "$OPENCODE_PROVIDER" in + anthropic) + cat > "$CONFIG_FILE" < "$CONFIG_FILE" < "$CONFIG_FILE" < "$CONFIG_FILE" < auto-detect from /workspace > default (1000) +TARGET_UID="${USER_UID:-}" +TARGET_GID="${USER_GID:-}" + +# Auto-detect from /workspace owner if env vars not set +if [ -z "$TARGET_UID" ] && [ -d /workspace ]; then + WORKSPACE_UID=$(stat -c '%u' /workspace 2>/dev/null || stat -f '%u' /workspace 2>/dev/null) + WORKSPACE_GID=$(stat -c '%g' /workspace 2>/dev/null || stat -f '%g' /workspace 2>/dev/null) + # Only adjust if workspace is owned by a non-root user + if [ "$WORKSPACE_UID" != "0" ] && [ "$WORKSPACE_UID" != "$CURRENT_UID" ]; then + TARGET_UID="$WORKSPACE_UID" + TARGET_GID="${TARGET_GID:-$WORKSPACE_GID}" + fi +fi + +# Apply UID/GID changes if needed +if [ -n "$TARGET_GID" ] && [ "$TARGET_GID" != "$CURRENT_GID" ]; then + groupmod -g "$TARGET_GID" "$USER_NAME" 2>/dev/null || true + find /home/"$USER_NAME" -not -path "/home/$USER_NAME/.ssh/*" -group "$CURRENT_GID" -exec chgrp "$TARGET_GID" {} + 2>/dev/null || true +fi + +if [ -n "$TARGET_UID" ] && [ "$TARGET_UID" != "$CURRENT_UID" ]; then + usermod -u "$TARGET_UID" "$USER_NAME" 2>/dev/null || true + find /home/"$USER_NAME" -not -path "/home/$USER_NAME/.ssh/*" -user "$CURRENT_UID" -exec chown "$TARGET_UID" {} + 2>/dev/null || true + echo "Adjusted developer UID:GID to $TARGET_UID:${TARGET_GID:-$CURRENT_GID}" +fi + # ── SSH key permissions ────────────────────────────────────────────── # If SSH keys are mounted, fix permissions (bind mounts may have wrong perms) -if [ -d "$HOME/.ssh" ] && [ "$(ls -A "$HOME/.ssh" 2>/dev/null)" ]; then - chmod 700 "$HOME/.ssh" - find "$HOME/.ssh" -type f -name "id_*" ! -name "*.pub" -exec chmod 600 {} \; 2>/dev/null || true - find "$HOME/.ssh" -type f -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true - [ -f "$HOME/.ssh/known_hosts" ] && chmod 644 "$HOME/.ssh/known_hosts" - [ -f "$HOME/.ssh/config" ] && chmod 600 "$HOME/.ssh/config" +if [ -d "/home/$USER_NAME/.ssh" ] && [ "$(ls -A "/home/$USER_NAME/.ssh" 2>/dev/null)" ]; then + chmod 700 "/home/$USER_NAME/.ssh" + find "/home/$USER_NAME/.ssh" -type f -name "id_*" ! -name "*.pub" -exec chmod 600 {} \; 2>/dev/null || true + find "/home/$USER_NAME/.ssh" -type f -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true + [ -f "/home/$USER_NAME/.ssh/known_hosts" ] && chmod 644 "/home/$USER_NAME/.ssh/known_hosts" + [ -f "/home/$USER_NAME/.ssh/config" ] && chmod 600 "/home/$USER_NAME/.ssh/config" fi -# ── Git config defaults ────────────────────────────────────────────── -# Set git config from env vars if not already configured via mounted .gitconfig -if [ -n "${GIT_USER_NAME:-}" ] && ! git config --global user.name &>/dev/null; then - git config --global user.name "$GIT_USER_NAME" -fi -if [ -n "${GIT_USER_EMAIL:-}" ] && ! git config --global user.email &>/dev/null; then - git config --global user.email "$GIT_USER_EMAIL" -fi - -# ── Generate opencode config from env vars if no config mounted ────── -CONFIG_DIR="$HOME/.config/opencode" -CONFIG_FILE="$CONFIG_DIR/opencode.json" - -if [ ! -f "$CONFIG_FILE" ] && [ -n "${OPENCODE_PROVIDER:-}" ]; then - echo "Generating opencode config for provider: $OPENCODE_PROVIDER" - mkdir -p "$CONFIG_DIR" - - # Build provider-specific config - case "$OPENCODE_PROVIDER" in - anthropic) - cat > "$CONFIG_FILE" < "$CONFIG_FILE" < "$CONFIG_FILE" < "$CONFIG_FILE" <