Files
pi-devbox/entrypoint-user.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

145 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ── SSH ControlMaster socket dir ────────────────────────────────
# Companion to /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf in the
# base image — that file declares ControlPath=/tmp/sshcm/%r@%h:%p; this
# creates the directory with the right permissions on every container
# start. /tmp is per-container so the dir doesn't survive recreation;
# baking it into a Dockerfile layer would be wrong.
# Mode 700 is required — OpenSSH refuses to use a ControlPath dir that
# others can write to.
mkdir -p /tmp/sshcm
chmod 700 /tmp/sshcm
# ── LAN access: generic host-OS-agnostic reachability helper ────────
# On VM-backed hosts (macOS OrbStack / Docker Desktop) the container can't
# reach the host's directly-attached LAN peers by default; this generates a
# writable ~/.ssh-local/config that uses the host as an SSH jump. On native
# Linux (LAN reachable directly) it is a no-op. Controlled by DEVBOX_LAN_ACCESS
# (auto|jump|off) + HOST_SSH_USER. Always non-fatal. See the script header.
if [ -r /usr/local/lib/pi-devbox/setup-lan-access.sh ]; then
bash /usr/local/lib/pi-devbox/setup-lan-access.sh || true
fi
# ── Shell defaults: copy baked files from /etc/skel-devbox/ if absent
# Respects host bind-mounts and user customizations — existing files
# are never overwritten. To restore defaults: rm ~/.bash_aliases (or
# .inputrc) and recreate the container, or cp from /etc/skel-devbox/
# directly.
SKEL_DIR="/etc/skel-devbox"
if [ -d "$SKEL_DIR" ]; then
for f in .bash_aliases .inputrc; do
if [ -f "$SKEL_DIR/$f" ] && [ ! -e "$HOME/$f" ]; then
cp "$SKEL_DIR/$f" "$HOME/$f"
fi
done
fi
# ── MemPalace: initialize palace for the workspace if mempalace is installed
# Creates the palace directory structure on first run. Idempotent — skips
# if palace already exists, so upgrades from older versions preserve
# existing data. `--yes` auto-accepts detected entities so the init is
# non-interactive.
if command -v mempalace &>/dev/null && [ -d /workspace ]; then
PALACE_DIR="${HOME}/.mempalace"
if [ ! -d "$PALACE_DIR/palace" ]; then
echo "Initializing MemPalace for workspace (non-interactive)..."
# </dev/null: mempalace init has an interactive "Mine this directory
# now? [Y/n]" prompt that --yes does not auto-answer in all paths.
# Without redirected stdin, the process blocks here forever when run
# from `docker run -it` (the TTY keeps stdin open). EOF on stdin
# makes the prompt fall through to its default (skip).
mempalace init --yes /workspace </dev/null >/dev/null 2>&1 || true
fi
fi
# ── 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
# ── pi: deploy toolkit + extensions + mempalace bridge ─────────────
# pi is always installed in pi-devbox; no INSTALL_PI guard needed.
# Each install.sh is idempotent and backs up real files before linking,
# so re-running across container restarts is safe.
#
# Order: pi-toolkit first (creates ~/.pi/agent/keybindings.json symlink
# and writes the AWS env loader), then pi-extensions (symlinks our
# extensions), then settings.json bootstrap from the toolkit template,
# then the mempalace bridge symlink (one-liner; mempalace-toolkit's
# install_skill is intentionally skipped to avoid racing with skillset
# auto-deploy below).
if command -v pi &>/dev/null; then
if [ -d /opt/pi-toolkit ]; then
(cd /opt/pi-toolkit && ./install.sh --yes) || \
echo "WARN: pi-toolkit install.sh failed (continuing)"
fi
if [ -d /opt/pi-extensions ]; then
(cd /opt/pi-extensions && ./install.sh --yes) || \
echo "WARN: pi-extensions install.sh failed (continuing)"
fi
# Bootstrap settings.json from template if absent (pi rewrites this
# file at runtime — lastChangelogVersion, etc — so we can't symlink it).
if [ ! -f "$HOME/.pi/agent/settings.json" ] && \
[ -f /opt/pi-toolkit/settings.example.json ]; then
cp /opt/pi-toolkit/settings.example.json "$HOME/.pi/agent/settings.json"
fi
# pi↔mempalace MCP bridge — single extension symlink.
if [ -f /opt/mempalace-toolkit/extensions/pi/mempalace.ts ] && \
command -v mempalace &>/dev/null && \
[ ! -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then
ln -sf /opt/mempalace-toolkit/extensions/pi/mempalace.ts \
"$HOME/.pi/agent/extensions/mempalace.ts"
fi
# pi-fork (fork tool) + pi-observational-memory (recall tool).
# These are pi packages (not symlink-style extensions): they're cloned to
# /opt with node_modules baked at BUILD time, then registered here via
# `pi install <local-path>`. A local-path install is instant + in-place
# (pi loads the extension directly from /opt) + idempotent (no duplicate
# package entry on re-run), and stores a relative path that resolves into
# the image-layer /opt so it survives volume recreate. The fork/recall
# tools register on the NEXT pi start (extensions bind at startup). Guard
# on settings.json so we only install once per volume.
for _pkg in /opt/pi-fork /opt/pi-observational-memory; do
[ -d "$_pkg" ] || continue
_name=$(basename "$_pkg")
if ! grep -q "$_name" "$HOME/.pi/agent/settings.json" 2>/dev/null; then
pi install "$_pkg" >/dev/null 2>&1 || \
echo "WARN: pi install $_name failed (continuing)"
fi
done
fi
# ── Skillset: deploy skills/instructions from mounted skillset repo ──
# When the skillset repo is mounted (at $HOME/skillset or /workspace/skillset),
# run the deploy script to create relative symlinks for skills and instructions.
# This ensures skills resolve correctly inside the container regardless of
# where the repo lives on the host. Idempotent — second run is a no-op.
#
# Detection order:
# 1. SKILLSET_CONTAINER_PATH env var (explicit, for non-standard layouts)
# 2. $HOME/skillset (dedicated volume mount via SKILLSET_PATH in compose)
# 3. /workspace/skillset (skillset is directly inside workspace root)
SKILLSET_DEPLOY=""
if [ -n "${SKILLSET_CONTAINER_PATH:-}" ] && [ -x "${SKILLSET_CONTAINER_PATH}/deploy-skills.sh" ]; then
SKILLSET_DEPLOY="${SKILLSET_CONTAINER_PATH}/deploy-skills.sh"
elif [ -x "$HOME/skillset/deploy-skills.sh" ]; then
SKILLSET_DEPLOY="$HOME/skillset/deploy-skills.sh"
elif [ -x /workspace/skillset/deploy-skills.sh ]; then
SKILLSET_DEPLOY="/workspace/skillset/deploy-skills.sh"
fi
if [ -n "$SKILLSET_DEPLOY" ]; then
"$SKILLSET_DEPLOY" --bootstrap --prune-stale >/dev/null 2>&1 || true
fi
# ── Execute command ──────────────────────────────────────────────────
exec "$@"