Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a538c405 | |||
| 08bb0c520e | |||
| e996b01542 | |||
| 03629cdac7 | |||
| 1d1283f942 | |||
| c139be326f |
@@ -9,6 +9,25 @@ WORKSPACE_PATH=~/projects
|
||||
# Path to SSH keys on host
|
||||
SSH_KEY_PATH=~/.ssh
|
||||
|
||||
# ── LAN access from the container (host-OS-agnostic) ─────────────────
|
||||
# On VM-backed hosts (macOS OrbStack / Docker Desktop) the container can't
|
||||
# reach the host's directly-attached LAN peers by default. The entrypoint
|
||||
# then sets up the host as an SSH jump (use the `dssh` alias). Reach the host
|
||||
# with `dssh host`; for named LAN peers put `ProxyJump host` overrides in a
|
||||
# host-owned ~/.config/devbox-shell/ssh-lan.conf (bind-mounted in) rather than
|
||||
# editing ~/.ssh/config. On native Linux Docker the LAN is reachable directly
|
||||
# and this is a no-op.
|
||||
# See the opencode-devbox README for the full walkthrough.
|
||||
#
|
||||
# DEVBOX_LAN_ACCESS: auto (default) | jump | off
|
||||
# DEVBOX_LAN_ACCESS=auto
|
||||
# HOST_SSH_USER: your username on the host (required for the jump). On first
|
||||
# start the entrypoint prints the public key to authorize on the host.
|
||||
# HOST_SSH_USER=
|
||||
# DEVBOX_LAN_AUTOJUMP_PRIVATE: 1 = ProxyJump any private (RFC1918) IP through
|
||||
# the host, so bare `dssh user@<ip>` works on whatever LAN you're roaming on.
|
||||
# DEVBOX_LAN_AUTOJUMP_PRIVATE=0
|
||||
|
||||
# ── Git Configuration ────────────────────────────────────────────────
|
||||
GIT_USER_NAME=
|
||||
GIT_USER_EMAIL=
|
||||
|
||||
@@ -33,14 +33,13 @@ jobs:
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
with: {driver-opts: network=host}
|
||||
|
||||
# Derive PI_VERSION from the tag (e.g. v0.75.5 -> 0.75.5; v0.75.5b -> 0.75.5).
|
||||
# MUST be passed as a build-arg so Docker's layer cache invalidates when pi
|
||||
# is bumped. Without this, the bare `npm install -g <pkg>` in the Dockerfile
|
||||
# produces an identical layer-hash across builds and the registry buildcache
|
||||
# silently reuses the layer from whatever pi version was current when the
|
||||
# cache was first populated. Discovered 2026-05-23 — every pi-devbox release
|
||||
# since v0.74.0 had been shipping the same image bytes (manifest digests
|
||||
# identical across v0.74.0..v0.75.5 on both arches).
|
||||
# Derive PI_VERSION from the tag (e.g. v0.78.0 -> 0.78.0; v0.78.0b -> 0.78.0).
|
||||
# Since the refactor to FROM opencode-devbox:latest-with-pi, this repo no
|
||||
# longer installs pi itself — pi comes from the base image. We still resolve
|
||||
# the tag version and feed it to the smoke test as EXPECTED_PI_VERSION: the
|
||||
# smoke asserts the inherited base actually carries this pi version, which
|
||||
# turns the version coupling into an enforced publish-ordering guard (it
|
||||
# fails loudly if latest-with-pi is stale relative to this tag).
|
||||
- name: Resolve PI_VERSION from tag
|
||||
id: resolve
|
||||
run: |
|
||||
@@ -58,8 +57,6 @@ jobs:
|
||||
push: false
|
||||
load: true
|
||||
tags: pi-devbox:smoke
|
||||
build-args: |
|
||||
PI_VERSION=${{ steps.resolve.outputs.pi_version }}
|
||||
|
||||
- name: Smoke test
|
||||
env:
|
||||
@@ -102,7 +99,8 @@ jobs:
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# See the smoke job for why this is required (cache-hit silent regression).
|
||||
# See the smoke job for why the tag version is resolved (now used only for
|
||||
# the base-freshness smoke guard; pi is no longer installed in this repo).
|
||||
- name: Resolve PI_VERSION from tag
|
||||
id: resolve
|
||||
run: |
|
||||
@@ -115,7 +113,6 @@ jobs:
|
||||
- name: Build and push (amd64 + arm64) — with retry
|
||||
shell: bash
|
||||
env:
|
||||
PI_VERSION: ${{ steps.resolve.outputs.pi_version }}
|
||||
TAGS: ${{ steps.tags.outputs.tags }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -125,24 +122,16 @@ jobs:
|
||||
while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}"
|
||||
# 3-attempt retry around `docker buildx build --push` for transient
|
||||
# registry-1.docker.io blips (rate limits, CDN flap, brief 5xx).
|
||||
# Does NOT mask deterministic failures: a true regression (e.g. the
|
||||
# cache-export 400 hit 2026-05-23..28) will fail all 3 attempts
|
||||
# identically and the job still fails — by design.
|
||||
# Registry cache disabled: buildkit's mode=max cache-export to
|
||||
# registry-1.docker.io reproducibly returns HTTP 400 on resumable-
|
||||
# upload PUT (Hub-CDN protocol mismatch with buildx 0.34.x, surfaced
|
||||
# ~2026-05-23). Diagnosed during opencode-devbox v1.15.12 manual
|
||||
# publish: image push works fine, only --cache-to fails. See
|
||||
# opencode-devbox CHANGELOG v1.15.12 Unreleased section for full
|
||||
# root-cause analysis. Re-enable when buildkit upstream resolves.
|
||||
# Single-stage Dockerfile + tiny diff (npm install pi only) means
|
||||
# build is fast even without cache (~30-60s).
|
||||
# The build itself is now trivial (FROM opencode-devbox:latest-with-pi
|
||||
# + an empty layer) so it is fast even without registry cache.
|
||||
# Registry cache stays disabled (buildkit mode=max cache-export hits a
|
||||
# reproducible HTTP 400 from Hub CDN since ~2026-05-23; image push is
|
||||
# unaffected). See opencode-devbox CHANGELOG v1.15.12.
|
||||
for attempt in 1 2 3; do
|
||||
echo "==> Build+push attempt ${attempt}/3"
|
||||
if docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--build-arg "PI_VERSION=${PI_VERSION}" \
|
||||
"${TAG_FLAGS[@]}" \
|
||||
.; then
|
||||
echo "==> Attempt ${attempt} succeeded"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# AGENTS.md — pi-devbox
|
||||
|
||||
Container image that adds pi coding-agent on top of the opencode-devbox base image.
|
||||
Container image that re-brands the opencode-devbox **pi-only** variant as a
|
||||
pi-focused image. As of 2026-06-03 it no longer installs pi itself.
|
||||
|
||||
## Repository layout
|
||||
|
||||
- `Dockerfile` — single-stage build, `FROM opencode-devbox:base-latest`, installs pi + companion repos
|
||||
- `Dockerfile` — thin re-brand: `FROM joakimp/pi-devbox:base-pi-only` (overridable via `BASE_IMAGE` arg). No install logic of its own — pi + companions are inherited from the pi-only build (built `INSTALL_OPENCODE=false`, so **no opencode** — that's the distinction from `opencode-devbox:latest-with-pi`). The `base-pi-only` tag is produced by opencode-devbox CI (from `opencode-devbox/Dockerfile.variant`) but published into THIS repo as an internal building-block tag. This refactor removed the install-logic duplication that used to drift against `opencode-devbox/Dockerfile.variant`.
|
||||
- `docker-compose.yml` — compose file for local use
|
||||
- `.env.example` — environment variable template
|
||||
- `scripts/smoke-test.sh` — sanity checks run by CI before pushing to Docker Hub
|
||||
@@ -13,12 +14,12 @@ Container image that adds pi coding-agent on top of the opencode-devbox base ima
|
||||
## Versioning scheme
|
||||
|
||||
- Tags follow the pi npm version: `v{pi_version}[letter]`
|
||||
- Bump `PI_VERSION` build-arg default in `Dockerfile` when cutting a new release
|
||||
- The image inherits pi from `base-pi-only`, so the **publish ordering matters**: rebuild opencode-devbox first so `joakimp/pi-devbox:base-pi-only` carries the target pi version, *then* tag this repo. The smoke test asserts `pi --version` matches the tag (`EXPECTED_PI_VERSION`) and fails loudly if the base is stale.
|
||||
- Docker Hub: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest`
|
||||
|
||||
## Release-day checklist
|
||||
|
||||
1. Bump `PI_VERSION` in `Dockerfile` (or leave as `latest` to pick up current)
|
||||
1. Ensure opencode-devbox has been released so `joakimp/pi-devbox:base-pi-only` carries the target pi version (and the fork/recall extensions). This is the hard prerequisite — the smoke guard enforces it.
|
||||
2. Update `CHANGELOG.md`: promote `Unreleased` → `vX.Y.Z — YYYY-MM-DD`
|
||||
3. Add fresh `## Unreleased` section
|
||||
4. Commit, tag `vX.Y.Z`, push tag → CI fires automatically
|
||||
@@ -38,27 +39,27 @@ per version. Don't try to derive notes from the npm registry metadata
|
||||
|
||||
## Key facts
|
||||
|
||||
- **Base image**: `joakimp/opencode-devbox:base-latest` — rebuilt whenever opencode-devbox cuts a new base
|
||||
- **pi binary**: baked at `/usr/bin/pi` (system npm prefix); `NPM_CONFIG_PREFIX=/home/developer/.pi/npm-global` at runtime so user-installed pi/packages land on the named volume
|
||||
- **Companion repos**: pi-toolkit and pi-extensions cloned to `/opt/` at build time; `entrypoint-user.sh` (inherited from base) deploys symlinks to `~/.pi/agent/` on container start
|
||||
- **MemPalace**: fully operational — inherited from base image; bridge extension deployed by entrypoint
|
||||
- **Base image**: `joakimp/pi-devbox:base-pi-only` — an internal building-block tag (produced by opencode-devbox CI from `Dockerfile.variant`, the single source of truth for the pi install + companions; published into this repo, not under opencode-devbox). Rebuilt whenever opencode-devbox releases. Not for end users — they pull `joakimp/pi-devbox:latest` or a `vX.Y.Z` tag.
|
||||
- **Inherited content**: pi (`/usr/bin/pi`), pi-toolkit, pi-extensions, pi-fork (`fork`), pi-observational-memory (`recall`), the mempalace bridge, the LAN-access helper, entrypoints, and all base dev tooling. The pi-only variant is built `INSTALL_OPENCODE=false`, so the image does **not** contain opencode.
|
||||
- **Companion repos**: cloned to `/opt/` by the pi-only build; `entrypoint-user.sh` (inherited) deploys/registers them on container start.
|
||||
- **MemPalace**: fully operational — inherited from base; bridge extension deployed by entrypoint.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Do NOT call `mempalace-toolkit/install.sh` in the Dockerfile — the base entrypoint handles it
|
||||
- `NPM_CONFIG_PREFIX=/usr` must be set per-RUN for any build-time `npm install -g` to keep baked binaries off the volume-shadowed path
|
||||
- The smoke test threshold is 2200 MB — update if the image legitimately grows past it
|
||||
- **PI_VERSION must be passed explicitly by CI as a concrete version** (derived from the git tag), not left as the `latest` default. The Dockerfile's bare `npm install -g @earendil-works/pi-coding-agent` (without `@${PI_VERSION}`) produces an identical layer-hash across builds; combined with registry buildcache (`cache-from`/`cache-to`) the layer gets reused even when `latest` would have resolved to a newer pi version. **All releases v0.74.0 → v0.75.5 silently shipped the same image bytes** because of this (verified via `docker manifest inspect` — identical digests across both arches and all four tags). Fixed in v0.75.5b: workflow now derives `PI_VERSION` from `${{ github.ref_name }}` and passes it as a build-arg; smoke-test asserts the resulting `pi --version` matches via `EXPECTED_PI_VERSION` env var. Same latent bug exists in opencode-devbox's `with-pi` variants but is masked there because `OPENCODE_VERSION` bumps invalidate downstream layers — will only manifest when cutting a `vN.N.Nb`-style opencode-version-unchanged release that only bumps pi.
|
||||
- This repo no longer installs pi or clones companion repos — do **not** re-add that logic here. Change it in `opencode-devbox/Dockerfile.variant` (the single source of truth) instead.
|
||||
- The smoke test threshold is 2750 MB (tracks the pi-only variant) — update if the image legitimately grows past it.
|
||||
- The CI still resolves the tag's pi version, but only to feed `EXPECTED_PI_VERSION` to the smoke base-freshness guard — it is no longer passed as a build-arg (nothing in the Dockerfile consumes it).
|
||||
- To pin a specific base build instead of tracking `base-pi-only`, override the `BASE_IMAGE` arg (a `base-pi-only-vX.Y.Z` tag or a digest).
|
||||
|
||||
## Documentation drift sweep
|
||||
|
||||
Before committing any non-trivial change, check that prose still matches code. Drift hotspots in this repo:
|
||||
|
||||
- `README.md` — quick-start examples, env-var table, base-image reference (must match `FROM` in `Dockerfile`).
|
||||
- `AGENTS.md` (this file) — `Key facts` block (pi binary path, `NPM_CONFIG_PREFIX`, base-image tag), smoke-test threshold number.
|
||||
- `README.md` — quick-start examples, env-var table, base-image reference (must match `FROM` in `Dockerfile`), "what's inside" (fork/recall; no opencode).
|
||||
- `AGENTS.md` (this file) — `Key facts` block (base-image tag, inherited content), smoke-test threshold number.
|
||||
- `CHANGELOG.md` — promote `Unreleased` only on tag, but record post-release fixes in a fresh `Unreleased` block.
|
||||
- `DOCKER_HUB.md` — hand-maintained slim Hub description; sync anything user-facing that changes (env vars, run command, base image).
|
||||
- `.env.example` — hand-updated, must match Dockerfile/entrypoint env vars.
|
||||
- `Dockerfile` `PI_VERSION` ARG default — if you intend to pin (rather than `latest`), bump it on release.
|
||||
- `.env.example` — hand-updated, must match Dockerfile/entrypoint env vars (including the inherited LAN-access knobs).
|
||||
- `Dockerfile` `BASE_IMAGE` ARG default — the pi-only tag this image tracks.
|
||||
|
||||
Quick triage: `git diff --name-only HEAD | xargs -I{} grep -l 'thing-you-changed' README.md AGENTS.md DOCKER_HUB.md CHANGELOG.md .env.example`.
|
||||
|
||||
+66
-1
@@ -8,7 +8,72 @@ Tags follow the pi npm version: `v{pi_version}[letter]` — bare tag for the fir
|
||||
|
||||
## Unreleased
|
||||
|
||||
_(no changes since v0.78.0)_
|
||||
_(no changes since v0.78.0c)_
|
||||
|
||||
## v0.78.0c — 2026-06-04
|
||||
|
||||
### Fixed / Added (inherited from the base via `FROM`)
|
||||
|
||||
LAN-access improvements made in opencode-devbox's `setup-lan-access.sh` (baked
|
||||
into the `base-pi-only` image, published by opencode-devbox v1.15.13d) flow
|
||||
through to pi-devbox automatically — no pi-devbox source change. Built `FROM`
|
||||
the rebuilt `joakimp/pi-devbox:base-pi-only` (digest `83b45335…`):
|
||||
|
||||
- **Fixed:** the generated `~/.ssh-local/config` had `Include ~/.ssh/config`
|
||||
scoped to the `host`/`mac` block, so `dssh <peer>` by name was ignored.
|
||||
- **Fixed:** read-only `~/.ssh/cm` ControlPath broke multiplexed hosts
|
||||
(`pmx-jh`, `proxmox*`, …); master sockets now use the writable sidecar.
|
||||
- **Added:** host-owned `~/.config/devbox-shell/ssh-lan.conf` for named-peer
|
||||
`ProxyJump host` overrides (Included before `~/.ssh/config`).
|
||||
- **Added:** `DEVBOX_LAN_AUTOJUMP_PRIVATE=1` — ProxyJump any RFC1918 IP through
|
||||
the host for roaming laptops.
|
||||
|
||||
## v0.78.0b — 2026-06-03
|
||||
|
||||
Container-level rebuild on pi `0.78.0` (unchanged): re-brands the pi-only build
|
||||
as a thin `FROM joakimp/pi-devbox:base-pi-only`, inheriting fork/recall and
|
||||
host-OS-agnostic LAN access. Letter-suffix release (pi version unchanged).
|
||||
|
||||
### Changed: refactored to re-brand the opencode-devbox `pi-only` variant
|
||||
|
||||
pi-devbox no longer installs pi itself. The `Dockerfile` is now a thin
|
||||
`FROM joakimp/pi-devbox:base-pi-only` (overridable via the `BASE_IMAGE`
|
||||
arg), inheriting pi + pi-toolkit + pi-extensions and all base tooling from the
|
||||
single source of truth. This eliminates the install-logic duplication that
|
||||
used to drift against `opencode-devbox/Dockerfile.variant`.
|
||||
|
||||
The pi-only artifact is **built** by opencode-devbox's CI (from
|
||||
`opencode-devbox/Dockerfile.variant` with `INSTALL_OPENCODE=false`) but is
|
||||
**published into this repo** as the internal building-block tag
|
||||
`joakimp/pi-devbox:base-pi-only` (+ `base-pi-only-vX.Y.Z`, where `vX.Y.Z` is
|
||||
the opencode-devbox release version). This supersedes the brief approach of
|
||||
publishing it as `opencode-devbox:latest-pi-only` — an "opencode-devbox" tag
|
||||
with no opencode in it confused users. `base-pi-only` is internal; end users
|
||||
pull `joakimp/pi-devbox:latest` or a `vX.Y.Z` tag.
|
||||
|
||||
The pi-only build uses `INSTALL_OPENCODE=false`, so this image
|
||||
stays lean and pi-focused — it does **not** carry opencode, and remains
|
||||
distinct from `opencode-devbox:latest-with-pi` (which has both).
|
||||
|
||||
### Added (inherited from the pi-only variant)
|
||||
|
||||
- **`fork` tool** (pi-fork) and **`recall` tool** (pi-observational-memory),
|
||||
baked into `/opt` with `node_modules` and registered at runtime.
|
||||
- **Host-OS-agnostic LAN access**: on VM-backed hosts (macOS OrbStack /
|
||||
Docker Desktop) the entrypoint sets up the host as an SSH jump to reach LAN
|
||||
peers (`dssh` alias; `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` env). No-op on
|
||||
native Linux. See the opencode-devbox README for details.
|
||||
|
||||
### Consequences / notes
|
||||
|
||||
- **Publish ordering**: release opencode-devbox first so `base-pi-only`
|
||||
carries the target pi version, *then* tag this repo. The smoke test asserts
|
||||
`pi --version` matches the tag and fails loudly if the base is stale.
|
||||
- CI no longer passes `PI_VERSION` as a build-arg (the Dockerfile installs
|
||||
nothing); it still resolves the tag version to feed the smoke base-freshness
|
||||
guard. Smoke size threshold 2200 → 2750 MB (now tracks the pi-only variant).
|
||||
|
||||
_pi version unchanged at `0.78.0` (still latest)._
|
||||
|
||||
## v0.78.0 — 2026-05-29
|
||||
|
||||
|
||||
+19
-7
@@ -38,7 +38,17 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini
|
||||
|
||||
## What's inside
|
||||
|
||||
Inherited from [opencode-devbox base](https://hub.docker.com/r/joakimp/opencode-devbox):
|
||||
pi-devbox is a re-brand of the **pi-only build** — it builds
|
||||
`FROM joakimp/pi-devbox:base-pi-only` and adds no layers of its own. That
|
||||
building-block tag is produced by opencode-devbox's CI (from
|
||||
`Dockerfile.variant` with `INSTALL_OPENCODE=false`) but published here, in the
|
||||
pi-devbox repo, so an opencode-devbox tag never ships without opencode.
|
||||
The pi-only build is lean
|
||||
and pi-focused (no opencode — use `opencode-devbox:latest-with-pi` if you want
|
||||
both).
|
||||
Everything below is inherited from that single source of truth.
|
||||
|
||||
Base tooling:
|
||||
|
||||
- **Debian trixie** (latest stable)
|
||||
- **Node.js** (LTS), **uv** (Python tooling), **rustup** (Rust on-demand)
|
||||
@@ -47,19 +57,21 @@ Inherited from [opencode-devbox base](https://hub.docker.com/r/joakimp/opencode-
|
||||
- **Gitea MCP** server
|
||||
- **Dev tools**: neovim (LazyVim defaults), tmux, bat, eza, fzf, zoxide, ripgrep, git-lfs, make
|
||||
- **Shell**: bash with history tuning, prefix-search bindings, fzf/zoxide integration
|
||||
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the host is set up as an SSH jump to reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER`). No-op on native Linux.
|
||||
|
||||
Added by pi-devbox:
|
||||
pi and companions:
|
||||
|
||||
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned at build time via the `PI_VERSION` build-arg
|
||||
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version set by the pi-only base build
|
||||
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
|
||||
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle` (manage extensions interactively), `mcp-loader` (load MCP servers via settings.json), `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
|
||||
- **mempalace bridge** — MCP extension auto-symlinked from `/opt/mempalace-toolkit` so pi can read/write the same palace as opencode
|
||||
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
|
||||
- **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools
|
||||
- **mempalace bridge** — MCP extension auto-symlinked so pi can read/write the same palace as opencode-devbox
|
||||
|
||||
The entrypoint deploys all of these on first container start. Re-running is idempotent and preserves user edits.
|
||||
The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits.
|
||||
|
||||
## Versioning
|
||||
|
||||
Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. When pi cuts a new upstream version, this image is rebuilt and re-tagged to match.
|
||||
Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. The pi binary is inherited from `joakimp/pi-devbox:base-pi-only`, so each release follows an opencode-devbox release that bakes the target pi version. (`base-pi-only` is an internal building-block tag — pull `latest` or a `vX.Y.Z` tag instead.)
|
||||
|
||||
For container-level rebuilds on the same pi version (security updates, base bumps, fixes) the tag gets a letter suffix: `v0.74.0b`, `v0.74.0c`, …
|
||||
|
||||
|
||||
+29
-56
@@ -1,62 +1,35 @@
|
||||
# pi-devbox — pi coding-agent container
|
||||
#
|
||||
# Builds on top of the opencode-devbox base image, which provides:
|
||||
# Debian trixie, Node.js, AWS CLI, mempalace + MCP server, gitea-mcp,
|
||||
# dev tools (neovim, tmux, bat, eza, fzf, zoxide, ripgrep, uv, rustup,
|
||||
# git-crypt, gitleaks),
|
||||
# user setup (developer/gosu), entrypoints, chromadb prewarm.
|
||||
# As of 2026-06-03 this image is a thin re-brand of the "pi-only" build, which
|
||||
# is the SINGLE SOURCE OF TRUTH for the pi install and its companion repos
|
||||
# (pi-toolkit, pi-extensions, pi-fork, pi-observational-memory). That build is
|
||||
# produced by opencode-devbox's CI (from opencode-devbox/Dockerfile.variant
|
||||
# with INSTALL_OPENCODE=false), but is published as an INTERNAL building-block
|
||||
# tag in THIS repo — joakimp/pi-devbox:base-pi-only — NOT under opencode-devbox.
|
||||
# Rationale: an "opencode-devbox" tag containing no opencode confuses
|
||||
# opencode-devbox users, so the pi-only artifact lives here instead.
|
||||
# Previously pi-devbox/Dockerfile duplicated the install logic, which drifted
|
||||
# from opencode-devbox/Dockerfile.variant; this refactor eliminates the dup.
|
||||
#
|
||||
# This image adds only pi itself and its companion repos.
|
||||
# The pi-only build uses INSTALL_OPENCODE=false, so this image does NOT contain
|
||||
# opencode — it stays a lean, pi-focused image, distinct from
|
||||
# opencode-devbox:latest-with-pi (which carries both).
|
||||
#
|
||||
# Build args:
|
||||
# BASE_IMAGE — base image to build from (default: base-latest)
|
||||
# PI_VERSION — pi npm version: "latest" or a pinned version e.g. "0.74.0"
|
||||
# PI_TOOLKIT_REF — git ref for pi-toolkit (default: main)
|
||||
# PI_EXTENSIONS_REF — git ref for pi-extensions (default: main)
|
||||
|
||||
ARG BASE_IMAGE=joakimp/opencode-devbox:base-latest
|
||||
# Everything is inherited from the pi-only build:
|
||||
# pi + pi-toolkit + pi-extensions + pi-fork (fork) + pi-observational-memory
|
||||
# (recall), the mempalace bridge, the LAN-access helper, entrypoints, and
|
||||
# all base dev tooling.
|
||||
#
|
||||
# NOTE on PUBLISH ORDERING: rebuild opencode-devbox (so `base-pi-only` carries
|
||||
# the target pi version) BEFORE tagging this repo. The smoke test asserts
|
||||
# `pi --version` matches this repo's tag and fails loudly if the base is stale
|
||||
# — turning the version coupling into an enforced ordering check.
|
||||
#
|
||||
# base-pi-only is an internal building-block alias (existence-only, not for
|
||||
# end users — pull joakimp/pi-devbox:latest or a vX.Y.Z tag instead). Override
|
||||
# BASE_IMAGE to pin a specific pi-only build (a version tag or a digest).
|
||||
ARG BASE_IMAGE=joakimp/pi-devbox:base-pi-only
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# PI_VERSION should be passed explicitly by CI as a concrete version
|
||||
# (e.g. PI_VERSION=0.75.5, derived from the git tag). The default `latest`
|
||||
# is for local dev convenience only — it has a known cache-hit footgun
|
||||
# when used in registry-cached CI builds. See .gitea/workflows/docker-
|
||||
# publish.yml § "Resolve PI_VERSION from tag" and AGENTS.md gotcha for
|
||||
# the full story (silent same-bytes-across-releases regression discovered
|
||||
# 2026-05-23 affecting all builds v0.74.0..v0.75.5).
|
||||
ARG PI_VERSION=0.78.0
|
||||
ARG PI_TOOLKIT_REF=main
|
||||
ARG PI_EXTENSIONS_REF=main
|
||||
|
||||
# Install pi and clone companion repos.
|
||||
# NPM_CONFIG_PREFIX is overridden to /usr so the baked binary lands at the
|
||||
# system prefix — same pattern as opencode-devbox's variant Dockerfile.
|
||||
# At runtime, NPM_CONFIG_PREFIX is reset to /home/developer/.pi/npm-global
|
||||
# (inherited from base ENV) so user-installed packages land on the named
|
||||
# volume and survive container recreate.
|
||||
#
|
||||
# git clone is wrapped in a retry loop because gitea.jordbo.se occasionally
|
||||
# returns transient HTTP 500s on the first request after idle.
|
||||
RUN set -e && \
|
||||
git_clone_retry() { \
|
||||
url="$1"; ref="$2"; dest="$3"; \
|
||||
for i in 1 2 3 4 5; do \
|
||||
if git clone --depth 1 --branch "$ref" "$url" "$dest"; then return 0; fi; \
|
||||
rm -rf "$dest"; \
|
||||
echo "git clone $url failed (attempt $i/5), retrying in $((i*5))s..."; \
|
||||
sleep $((i*5)); \
|
||||
done; \
|
||||
return 1; \
|
||||
} && \
|
||||
if [ "${PI_VERSION}" = "latest" ]; then \
|
||||
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent ; \
|
||||
else \
|
||||
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent@${PI_VERSION} ; \
|
||||
fi && \
|
||||
pi --version && \
|
||||
git_clone_retry https://gitea.jordbo.se/joakimp/pi-toolkit.git "${PI_TOOLKIT_REF}" /opt/pi-toolkit && \
|
||||
git_clone_retry https://gitea.jordbo.se/joakimp/pi-extensions.git "${PI_EXTENSIONS_REF}" /opt/pi-extensions && \
|
||||
echo "pi-toolkit at $(cd /opt/pi-toolkit && git rev-parse --short HEAD)" && \
|
||||
echo "pi-extensions at $(cd /opt/pi-extensions && git rev-parse --short HEAD)"
|
||||
|
||||
# WORKDIR / ENTRYPOINT / CMD inherited from base.
|
||||
# WORKDIR / ENTRYPOINT / CMD and all tooling inherited from the base.
|
||||
# No additional layers — the value here is the single-source-of-truth refactor.
|
||||
|
||||
@@ -9,7 +9,16 @@ A Docker container with [pi coding-agent](https://github.com/earendil-works/pi)
|
||||
|
||||
## What's inside
|
||||
|
||||
Inherited from `opencode-devbox:base-latest`:
|
||||
pi-devbox is a thin re-brand of the **`pi-only` build** — it `FROM`s
|
||||
`joakimp/pi-devbox:base-pi-only` and adds no layers of its own. That base build
|
||||
is produced by opencode-devbox's CI (from `opencode-devbox/Dockerfile.variant`
|
||||
with `INSTALL_OPENCODE=false`, the single source of truth for the pi install +
|
||||
companions) but is published **into this repo** as the internal building-block
|
||||
tag `base-pi-only` — *not* under opencode-devbox, so an "opencode-devbox" tag
|
||||
never ships without opencode. Everything below is inherited from that build,
|
||||
which is lean and pi-focused — no opencode.
|
||||
|
||||
Base tooling:
|
||||
|
||||
- **Debian trixie** (current stable)
|
||||
- **Node.js** (LTS), **uv** (Python), **rustup** (Rust on-demand)
|
||||
@@ -18,15 +27,19 @@ Inherited from `opencode-devbox:base-latest`:
|
||||
- **Gitea MCP** server
|
||||
- **Dev tools**: neovim (LazyVim), tmux, bat, eza, fzf, zoxide, ripgrep, jq, git-lfs, make
|
||||
- **Shell**: bash with history tuning, prefix-search, fzf/zoxide integration
|
||||
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the entrypoint sets up the host as an SSH jump so you can reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_LAN_AUTOJUMP_PRIVATE` env; host-owned `~/.config/devbox-shell/ssh-lan.conf` for named-peer jumps). No-op on native Linux.
|
||||
|
||||
Added by pi-devbox:
|
||||
pi and companions:
|
||||
|
||||
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned at build time
|
||||
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned by the pi-only base build
|
||||
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — mosh/tmux-friendly keybindings (Shift+Enter, Ctrl+J, Alt+J newline), AWS env loader, settings template
|
||||
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
|
||||
- **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode
|
||||
- **`fork` tool** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall` tool** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) — baked into `/opt` and registered at runtime
|
||||
- **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode-devbox's palace
|
||||
|
||||
The entrypoint deploys all of these on first container start. Idempotent and preserves user edits.
|
||||
(opencode itself is **not** included — that's the difference from `opencode-devbox:latest-with-pi`. If you want both opencode and pi in one image, use that variant instead.)
|
||||
|
||||
The entrypoint deploys/registers all of these on first container start. Idempotent and preserves user edits.
|
||||
|
||||
---
|
||||
|
||||
@@ -187,6 +200,9 @@ All config flows through `.env`. The full list (with annotations) is in [`.env.e
|
||||
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | (unset) | AWS Bedrock static creds |
|
||||
| `GITEA_ACCESS_TOKEN` / `GITEA_HOST` | (unset) | Gitea MCP server (optional) |
|
||||
| `GITHUB_PERSONAL_ACCESS_TOKEN` | (unset) | GitHub MCP server / git ops over HTTPS |
|
||||
| `DEVBOX_LAN_ACCESS` | `auto` | LAN-access mode: `auto` (jump only on VM-backed hosts), `jump`, `off` |
|
||||
| `HOST_SSH_USER` | (unset) | Host username for the LAN SSH jump (see opencode-devbox README) |
|
||||
| `DEVBOX_LAN_AUTOJUMP_PRIVATE` | `0` | `1` = ProxyJump any private (RFC1918) IP through the host (roaming-friendly; see opencode-devbox README) |
|
||||
| `LANG` / `LANGUAGE` / `LC_ALL` | `en_US.UTF-8` | Locale override |
|
||||
|
||||
---
|
||||
@@ -197,34 +213,35 @@ Tags follow the pi npm package version: `v0.74.0`, `v0.75.0`, … `latest` alway
|
||||
|
||||
Container-level rebuilds on the same pi version (security updates, base bumps, fixes) get a letter suffix: `v0.74.0b`, `v0.74.0c`, …
|
||||
|
||||
When the upstream [pi npm package](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) cuts a new version, this image is rebuilt and re-tagged to match.
|
||||
The pi binary is inherited from `joakimp/pi-devbox:base-pi-only`, so a release of this image must be preceded by an opencode-devbox release that bakes the target pi version into `base-pi-only`. The smoke test enforces this (it asserts `pi --version` matches the tag).
|
||||
|
||||
---
|
||||
|
||||
## Building from source
|
||||
|
||||
If you want to pin a specific pi version, change the base image, or hack on the Dockerfile:
|
||||
This image is a thin re-brand of the pi-only variant, so building it just pulls
|
||||
the base. To pin a specific pi-only build or hack on it:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.jordbo.se/joakimp/pi-devbox
|
||||
cd pi-devbox
|
||||
|
||||
# Edit Dockerfile or override via build args:
|
||||
# Default tracks base-pi-only; override BASE_IMAGE to pin a build:
|
||||
docker compose build \
|
||||
--build-arg PI_VERSION=0.73.0 \
|
||||
--build-arg BASE_IMAGE=joakimp/opencode-devbox:base-latest
|
||||
--build-arg BASE_IMAGE=joakimp/pi-devbox:base-pi-only-v1.15.13c
|
||||
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
To change the pi version, the pi extensions, or the install logic, edit
|
||||
`opencode-devbox/Dockerfile.variant` (the single source of truth) and release
|
||||
opencode-devbox — not this repo.
|
||||
|
||||
Build args supported:
|
||||
|
||||
| Arg | Default | Effect |
|
||||
|---|---|---|
|
||||
| `BASE_IMAGE` | `joakimp/opencode-devbox:base-latest` | Parent image — set to `joakimp/opencode-devbox:base-<sha>` for reproducible builds |
|
||||
| `PI_VERSION` | `latest` | npm version of `@earendil-works/pi-coding-agent` |
|
||||
| `PI_TOOLKIT_REF` | `main` | Git ref for [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) |
|
||||
| `PI_EXTENSIONS_REF` | `main` | Git ref for [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) |
|
||||
| `BASE_IMAGE` | `joakimp/pi-devbox:base-pi-only` | Parent image (internal building-block tag) — set to a `:base-pi-only-vX.Y.Z` tag or a digest for reproducible builds |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ services:
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# PI_VERSION: "latest"
|
||||
# # Pin a specific pi-only build instead of tracking base-pi-only:
|
||||
# BASE_IMAGE: "joakimp/pi-devbox:base-pi-only-v1.15.13c"
|
||||
container_name: pi-devbox
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
+22
-1
@@ -17,7 +17,10 @@ set -euo pipefail
|
||||
|
||||
IMAGE="${1:?usage: $0 <image>}"
|
||||
PASS=0; FAIL=0
|
||||
SIZE_THRESHOLD_MB=2200
|
||||
# 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"
|
||||
@@ -67,6 +70,12 @@ 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 ""
|
||||
@@ -101,6 +110,18 @@ exec_test "extensions ≥ 4 (pi-extensions)" 'count=$(ls -1 $HOME/.pi/age
|
||||
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 ──"
|
||||
|
||||
Reference in New Issue
Block a user