1d1283f942
Re-point the re-brand at the new pi-only variant instead of with-pi, so pi-devbox stays a lean pi-focused image (no opencode) while the pi install logic still lives in one place upstream. This keeps pi-devbox meaningfully distinct from opencode-devbox:latest-with-pi. - Dockerfile: BASE_IMAGE default -> joakimp/opencode-devbox:latest-pi-only. - smoke-test.sh: size threshold 2900 -> 2750 MB (pi-only = with-pi minus opencode's ~145 MB binary). - Docs (README/AGENTS/DOCKER_HUB/CHANGELOG/docker-compose): drop the 'also contains opencode' notes; describe pi-only basis and the distinction from with-pi. Publish ordering unchanged: release opencode-devbox first so latest-pi-only carries the target pi version, then tag here (smoke asserts pi --version).
139 lines
6.3 KiB
Bash
Executable File
139 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# smoke-test.sh — basic sanity checks for the pi-devbox image
|
|
#
|
|
# Usage: ./scripts/smoke-test.sh <image>
|
|
#
|
|
# Verifies:
|
|
# - pi binary present and returns a version
|
|
# - pi-toolkit cloned at /opt/pi-toolkit
|
|
# - pi-extensions cloned at /opt/pi-extensions
|
|
# - entrypoint deploys pi-toolkit keybindings symlink
|
|
# - entrypoint deploys ≥4 extensions
|
|
# - mempalace bridge symlink present
|
|
# - settings.json bootstrapped
|
|
# - image size within threshold
|
|
|
|
set -euo pipefail
|
|
|
|
IMAGE="${1:?usage: $0 <image>}"
|
|
PASS=0; FAIL=0
|
|
# Since the refactor to FROM opencode-devbox:latest-pi-only, this image equals
|
|
# the pi-only variant (pi + companions + fork/recall node_modules, NO opencode),
|
|
# so the threshold tracks pi-only's (2750 MB), not the old standalone 2200 MB.
|
|
SIZE_THRESHOLD_MB=2750
|
|
|
|
run() {
|
|
local label="$1"; local cmd="$2"
|
|
if docker run --rm --entrypoint="" "$IMAGE" sh -c "$cmd" >/dev/null 2>&1; then
|
|
printf " ✅ %s\n" "$label"; PASS=$((PASS+1))
|
|
else
|
|
printf " ❌ %s\n" "$label"; FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
|
|
# Stricter version of `run` that also asserts an expected substring in stdout.
|
|
# Used for catching the "image bytes silently identical to previous release"
|
|
# class of regression (Docker layer cache hit on `npm install -g <pkg>` because
|
|
# the bare command string is identical across builds, even when `latest` would
|
|
# resolve differently). Discovered 2026-05-23 — every pi-devbox release v0.74.0
|
|
# through v0.75.5 had been shipping the same image bytes.
|
|
run_expect() {
|
|
local label="$1"; local cmd="$2"; local expect="$3"
|
|
local out
|
|
out=$(docker run --rm --entrypoint="" "$IMAGE" sh -c "$cmd" 2>&1) || true
|
|
if echo "$out" | grep -Fq "$expect"; then
|
|
printf " ✅ %s (got %s)\n" "$label" "$expect"; PASS=$((PASS+1))
|
|
else
|
|
printf " ❌ %s — expected substring %q, got: %s\n" "$label" "$expect" "$out"; FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
|
|
echo "=== pi-devbox smoke test: $IMAGE ==="
|
|
echo ""
|
|
|
|
# ── Basic binary checks ───────────────────────────────────────────────
|
|
echo "── Binaries ──"
|
|
if [ -n "${EXPECTED_PI_VERSION:-}" ]; then
|
|
run_expect "pi version matches build arg" "pi --version" "$EXPECTED_PI_VERSION"
|
|
else
|
|
run "pi" "pi --version"
|
|
fi
|
|
run "node" "node --version"
|
|
run "git" "git --version"
|
|
run "aws" "aws --version"
|
|
run "uv" "uv --version"
|
|
run "nvim" "nvim --version"
|
|
run "mempalace-mcp" "mempalace-mcp --help"
|
|
|
|
# ── Repo clones ───────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "── Repo clones ──"
|
|
run "pi-toolkit clone" "test -d /opt/pi-toolkit && git -C /opt/pi-toolkit rev-parse --short HEAD"
|
|
run "pi-extensions clone" "test -d /opt/pi-extensions && git -C /opt/pi-extensions rev-parse --short HEAD"
|
|
# pi-fork (fork tool) + pi-observational-memory (recall tool) — inherited from
|
|
# the pi-only base, cloned to /opt with node_modules baked at build time.
|
|
run "pi-fork clone + node_modules" \
|
|
"test -f /opt/pi-fork/package.json && test -d /opt/pi-fork/node_modules"
|
|
run "pi-observational-memory clone + node_modules" \
|
|
"test -f /opt/pi-observational-memory/package.json && test -d /opt/pi-observational-memory/node_modules"
|
|
|
|
# ── Runtime deployment (needs entrypoint to run) ──────────────────────
|
|
echo ""
|
|
echo "── Runtime deployment ──"
|
|
# Spin up a long-running container WITHOUT overriding the entrypoint, so
|
|
# the baked entrypoint chain (entrypoint.sh → entrypoint-user.sh) runs and
|
|
# deploys pi-toolkit + pi-extensions to ~/.pi/agent/. Override CMD to
|
|
# tail -f /dev/null so the container stays alive while we docker-exec.
|
|
CID=$(docker run -d --rm "$IMAGE" tail -f /dev/null)
|
|
cleanup() { docker rm -f "$CID" >/dev/null 2>&1 || true; }
|
|
trap cleanup EXIT
|
|
|
|
# Wait for entrypoint-user.sh to finish deploying pi-toolkit + extensions
|
|
for i in $(seq 1 30); do
|
|
if docker exec "$CID" test -L /home/developer/.pi/agent/keybindings.json 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
exec_test() {
|
|
local label="$1"; local cmd="$2"
|
|
if docker exec -u developer "$CID" sh -c "$cmd" >/dev/null 2>&1; then
|
|
printf " ✅ %s\n" "$label"; PASS=$((PASS+1))
|
|
else
|
|
printf " ❌ %s\n" "$label"; FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
|
|
exec_test "keybindings.json (pi-toolkit)" 'test -L $HOME/.pi/agent/keybindings.json && echo ok'
|
|
exec_test "extensions ≥ 4 (pi-extensions)" 'count=$(ls -1 $HOME/.pi/agent/extensions/*.ts 2>/dev/null | wc -l); [ $count -ge 4 ] && echo "$count extensions"'
|
|
exec_test "mempalace.ts bridge" 'test -L $HOME/.pi/agent/extensions/mempalace.ts && echo ok'
|
|
exec_test "settings.json bootstrapped" 'test -f $HOME/.pi/agent/settings.json && echo ok'
|
|
|
|
# pi-fork + pi-observational-memory are registered by entrypoint-user.sh via
|
|
# `pi install /opt/<pkg>`, which runs slightly after the keybindings marker.
|
|
for i in $(seq 1 15); do
|
|
if docker exec "$CID" grep -q pi-observational-memory \
|
|
/home/developer/.pi/agent/settings.json 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
exec_test "pi-fork registered (fork tool)" 'grep -q pi-fork $HOME/.pi/agent/settings.json && echo ok'
|
|
exec_test "pi-observational-memory registered (recall tool)" 'grep -q pi-observational-memory $HOME/.pi/agent/settings.json && echo ok'
|
|
|
|
# ── Image size ────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "── Image size ──"
|
|
SIZE_MB=$(docker image inspect "$IMAGE" --format='{{.Size}}' | awk '{printf "%d", $1/1048576}')
|
|
if [ "$SIZE_MB" -le "$SIZE_THRESHOLD_MB" ]; then
|
|
printf " ✅ size: %d MB (threshold %d MB)\n" "$SIZE_MB" "$SIZE_THRESHOLD_MB"; PASS=$((PASS+1))
|
|
else
|
|
printf " ❌ size: %d MB exceeds threshold %d MB\n" "$SIZE_MB" "$SIZE_THRESHOLD_MB"; FAIL=$((FAIL+1))
|
|
fi
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
|
|
[ "$FAIL" -eq 0 ]
|