Files
pi-devbox/entrypoint.sh
T
pi c1154f1fa6
Publish Docker Image / resolve-versions (push) Successful in 5s
Publish Docker Image / base-decide (push) Successful in 12s
Publish Docker Image / build-base (push) Successful in 45m47s
Publish Docker Image / smoke (push) Successful in 8m18s
Publish Docker Image / build-variant (push) Successful in 22m41s
Publish Docker Image / update-description (push) Failing after 9s
Publish Docker Image / promote-base-latest (push) Successful in 14s
v1.0.0: decouple from opencode-devbox
Self-contained build chain — own Dockerfile.base + Dockerfile.variant
+ entrypoint scripts + rootfs + CI pipeline. Previously v0.79.0 and
earlier were thin re-brands of opencode-devbox's pi-only variant
(joakimp/pi-devbox:base-pi-only built by opencode-devbox CI).

Architectural changes:
- Replace 5-line Dockerfile shim with full base+variant pair.
- Adapt CI workflow from opencode-devbox/docker-publish-split.yml,
  simplified to a single variant. Includes content-addressed base hash,
  PI_VERSION concrete-resolution to defeat registry-buildcache footgun,
  crane-based base-latest promotion, and the c6f9d11 smoke-test gate.
- pi-devbox releases no longer require rebuilding opencode-devbox first.

Base image additions:
- pandoc, graphviz, imagemagick, yq — broadly useful, ~260 MB total.
- tldr (tealdeer) — Rust port replaces Node tldr global, saves 135 MB.
- /etc/tmux.conf with base-index 0 + pane-base-index 0 — required for
  the planned :latest-studio variant; pi-studio hard-codes :0.0 target.

Smoke test:
- New checks for pandoc, graphviz, imagemagick, yq, tldr, tmux config,
  /tmp/sshcm directory.
- Image-size measurement now sums docker history layers (the prior
  inspect --format='{{.Size}}' returned only the variant-unique layer
  with the new base/variant split, understating by 2+ GB).
- Threshold 2850 → 3500 MB to absorb base additions + arch margin.

Image size:
- Local arm64 build: 3.20 GB. ~390 MB up from prior pi-only equivalent.
- Will tighten threshold once amd64 actuals settle in CI.

Pre-1.0 history preserved at tag pre-v1.0.0-decouple-backup.

Future work:
- v1.1.0: :latest-studio variant (adds pi-studio).
- v1.2.0: :latest-studio-tex variant (adds texlive-xetex for PDF).
- opencode-devbox v2.0.0 will retire INSTALL_PI / pi-only paths.
2026-06-10 01:14:07 +02:00

123 lines
5.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
USER_NAME="developer"
CURRENT_UID=$(id -u "$USER_NAME")
CURRENT_GID=$(id -g "$USER_NAME")
# ── UID/GID adjustment ───────────────────────────────────────────────
# Priority per dimension: env var > auto-detect from /workspace > no-op
# UID and GID are detected independently so a GID-only mismatch (e.g. host
# user has UID 1000 but primary group at GID 1001) is still corrected.
TARGET_UID="${USER_UID:-}"
TARGET_GID="${USER_GID:-}"
if [ -d /workspace ]; then
WORKSPACE_UID=$(stat -c '%u' /workspace 2>/dev/null || stat -f '%u' /workspace 2>/dev/null || echo "")
WORKSPACE_GID=$(stat -c '%g' /workspace 2>/dev/null || stat -f '%g' /workspace 2>/dev/null || echo "")
# Adopt workspace UID if env var not set and workspace is non-root-owned
if [ -z "$TARGET_UID" ] && [ -n "$WORKSPACE_UID" ] && [ "$WORKSPACE_UID" != "0" ] && [ "$WORKSPACE_UID" != "$CURRENT_UID" ]; then
TARGET_UID="$WORKSPACE_UID"
fi
# Adopt workspace GID if env var not set and workspace group differs
if [ -z "$TARGET_GID" ] && [ -n "$WORKSPACE_GID" ] && [ "$WORKSPACE_GID" != "0" ] && [ "$WORKSPACE_GID" != "$CURRENT_GID" ]; then
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
echo "Adjusted developer GID to $TARGET_GID"
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 to $TARGET_UID"
fi
# ── SSH key permissions ──────────────────────────────────────────────
# If SSH keys are mounted, fix permissions (skip if read-only mount)
if [ -d "/home/$USER_NAME/.ssh" ] && [ "$(ls -A "/home/$USER_NAME/.ssh" 2>/dev/null)" ]; then
if touch "/home/$USER_NAME/.ssh/.perm_test" 2>/dev/null; then
rm -f "/home/$USER_NAME/.ssh/.perm_test"
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
fi
# ── Fix ownership of named volume mount points ──────────────────────
# Named volumes are created as root on first use. Fix ownership so the
# developer user can write to them.
FINAL_UID="${TARGET_UID:-$CURRENT_UID}"
FINAL_GID="${TARGET_GID:-$CURRENT_GID}"
# First, fix parent dirs that Docker auto-creates as root:root when it
# materializes nested mount points (e.g. mounting a volume at
# .local/state/opencode creates .local/state as root). Non-recursive —
# we only need the dir node itself; children are handled below or were
# created by the user.
for parent in \
/home/"$USER_NAME"/.local \
/home/"$USER_NAME"/.local/share \
/home/"$USER_NAME"/.local/state \
/home/"$USER_NAME"/.cache \
/home/"$USER_NAME"/.config; do
if [ -d "$parent" ] && [ "$(stat -c '%u' "$parent" 2>/dev/null)" != "$FINAL_UID" ]; then
chown "$FINAL_UID":"$FINAL_GID" "$parent" 2>/dev/null || true
fi
done
for dir in \
/home/"$USER_NAME"/.local/share/opencode \
/home/"$USER_NAME"/.local/state/opencode \
/home/"$USER_NAME"/.local/share/uv \
/home/"$USER_NAME"/.local/share/zoxide \
/home/"$USER_NAME"/.local/share/nvim \
/home/"$USER_NAME"/.mempalace \
/home/"$USER_NAME"/.cache/bash \
/home/"$USER_NAME"/.cache/chroma \
/home/"$USER_NAME"/.rustup \
/home/"$USER_NAME"/.cargo \
/home/"$USER_NAME"/.vscode-server \
/home/"$USER_NAME"/.config/opencode \
/home/"$USER_NAME"/.config/nvim \
/home/"$USER_NAME"/.pi \
/home/"$USER_NAME"/.ssh-local \
/home/"$USER_NAME"/.agents/skills; do
[ -d "$dir" ] || continue
# Sentinel-file fast path: on volumes with thousands of files (nvim
# plugins, palace data) the recursive chown used to cost multiple
# seconds on every container start even when ownership was already
# correct. Now we write a sentinel after a successful chown and skip
# the walk when the sentinel matches the target UID:GID.
#
# If USER_UID changes between runs (user switches hosts, different
# workspace owner), the sentinel won't match and the full chown runs.
sentinel="$dir/.devbox-owner"
expected="$FINAL_UID:$FINAL_GID"
if [ -f "$sentinel" ] && [ "$(cat "$sentinel" 2>/dev/null)" = "$expected" ]; then
continue
fi
# Recursive chown needed. Only do it when the top-level differs too
# (covers the common case of fresh root-owned named volumes).
if [ "$(stat -c '%u' "$dir" 2>/dev/null)" != "$FINAL_UID" ]; then
chown -R "$FINAL_UID":"$FINAL_GID" "$dir" 2>/dev/null || true
fi
# Write sentinel so subsequent starts skip the recursive walk.
# Suppress errors — a read-only mount would fail here, but that would
# already have failed above on the chown itself.
echo "$expected" > "$sentinel" 2>/dev/null || true
done
# ── Drop to developer user for remaining setup ──────────────────────
exec gosu "$USER_NAME" /usr/local/bin/entrypoint-user.sh "$@"