From c139be326f3ac8f9da6716260b74eaccb6bb89f0 Mon Sep 17 00:00:00 2001 From: pi Date: Wed, 3 Jun 2026 15:51:41 +0200 Subject: [PATCH] refactor: re-brand the opencode-devbox with-pi variant (single source of truth) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi-devbox no longer installs pi itself. The Dockerfile is now a thin FROM joakimp/opencode-devbox:latest-with-pi (overridable via BASE_IMAGE), inheriting pi + pi-toolkit + pi-extensions + pi-fork (fork) + pi-observational-memory (recall) + the LAN-access helper + all base tooling from the single source of truth. Eliminates the install-logic duplication that drifted against opencode-devbox/Dockerfile.variant (decision #3). Consequences (documented in CHANGELOG/AGENTS): - The image now ALSO contains opencode (with-pi has INSTALL_OPENCODE=true). A leaner pi-only image would need a dedicated pi-only variant upstream. - Publish ordering: release opencode-devbox first so latest-with-pi 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 — turning the version coupling into an enforced ordering guard. CI: drop PI_VERSION build-arg (Dockerfile installs nothing); keep tag->version resolution to feed the smoke base-freshness guard. Smoke adds fork/recall clone + node_modules + settings.json registration checks; size threshold 2200 -> 2900 MB (now tracks with-pi). Docs updated across README, AGENTS, DOCKER_HUB, .env.example, docker-compose. --- .env.example | 14 +++++ .gitea/workflows/docker-publish.yml | 39 +++++--------- AGENTS.md | 33 ++++++------ CHANGELOG.md | 31 ++++++++++- DOCKER_HUB.md | 21 +++++--- Dockerfile | 80 +++++++++-------------------- README.md | 36 ++++++++----- docker-compose.yml | 3 +- scripts/smoke-test.sh | 23 ++++++++- 9 files changed, 160 insertions(+), 120 deletions(-) diff --git a/.env.example b/.env.example index c0e5591..8e90fc8 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,20 @@ 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, or add +# `ProxyJump host` to targets in your bind-mounted ~/.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= + # ── Git Configuration ──────────────────────────────────────────────── GIT_USER_NAME= GIT_USER_EMAIL= diff --git a/.gitea/workflows/docker-publish.yml b/.gitea/workflows/docker-publish.yml index bc89fec..ac8ee1d 100644 --- a/.gitea/workflows/docker-publish.yml +++ b/.gitea/workflows/docker-publish.yml @@ -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 ` 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" diff --git a/AGENTS.md b/AGENTS.md index 70b3789..57dd96d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 **with-pi** 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 opencode-devbox:latest-with-pi` (overridable via `BASE_IMAGE` arg). No install logic of its own — pi + companions are inherited from the with-pi variant. 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 `latest-with-pi`, so the **publish ordering matters**: rebuild opencode-devbox first so `latest-with-pi` 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/opencode-devbox:latest-with-pi` 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/opencode-devbox:latest-with-pi` — the single source of truth for the pi install + companions. Rebuilt whenever opencode-devbox releases. +- **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. Because it FROMs with-pi, the image **also contains opencode**. +- **Companion repos**: cloned to `/opt/` by the with-pi 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 2900 MB (tracks the with-pi 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 `latest-with-pi`, override the `BASE_IMAGE` arg (a version 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" (now includes opencode + fork/recall). +- `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 with-pi 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`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1351d66..bc2697c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,36 @@ Tags follow the pi npm version: `v{pi_version}[letter]` — bare tag for the fir ## Unreleased -_(no changes since v0.78.0)_ +### Changed: refactored to re-brand the opencode-devbox `with-pi` variant + +pi-devbox no longer installs pi itself. The `Dockerfile` is now a thin +`FROM joakimp/opencode-devbox:latest-with-pi` (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`. + +### Added (inherited from the with-pi 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 + +- Because it FROMs `with-pi`, the image **now also contains opencode**. If a + leaner pi-only image is wanted, add a pi-only variant to opencode-devbox and + FROM that instead. +- **Publish ordering**: release opencode-devbox first so `latest-with-pi` + 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 → 2900 MB (now tracks with-pi). + +_pi version unchanged at `0.78.0` (still latest)._ ## v0.78.0 — 2026-05-29 diff --git a/DOCKER_HUB.md b/DOCKER_HUB.md index 8bf1036..8d8ff3d 100644 --- a/DOCKER_HUB.md +++ b/DOCKER_HUB.md @@ -38,7 +38,11 @@ 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 **opencode-devbox `with-pi` variant** — it builds +`FROM joakimp/opencode-devbox:latest-with-pi` and adds no layers of its own. +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 +51,22 @@ 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 with-pi 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 +- **opencode** — also present (the image FROMs the with-pi variant) -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 `opencode-devbox:latest-with-pi`, so each release follows an opencode-devbox release that bakes the target pi version. 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`, … diff --git a/Dockerfile b/Dockerfile index 4c3f672..7ae3fb2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,62 +1,30 @@ # 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 opencode-devbox +# "with-pi" variant, which is the SINGLE SOURCE OF TRUTH for the pi install +# and its companion repos (pi-toolkit, pi-extensions, pi-fork, +# pi-observational-memory). Previously pi-devbox/Dockerfile duplicated that +# install logic, which drifted from opencode-devbox/Dockerfile.variant; this +# refactor eliminates the duplication. # -# This image adds only pi itself and its companion repos. +# Everything is inherited from the with-pi image: +# 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. # -# 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 +# NOTES / consequences of FROM-ing with-pi: +# - This image now ALSO contains opencode (with-pi has INSTALL_OPENCODE=true). +# If a leaner pi-only image is wanted later, add a dedicated pi-only variant +# to opencode-devbox and FROM that instead. +# - PUBLISH ORDERING: rebuild opencode-devbox (so `latest-with-pi` 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. +# +# Override BASE_IMAGE to pin a specific with-pi build (e.g. a version tag or a +# digest) instead of tracking latest-with-pi. +ARG BASE_IMAGE=joakimp/opencode-devbox:latest-with-pi 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. diff --git a/README.md b/README.md index c0a7036..2b174b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,11 @@ 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 re-brand of the **opencode-devbox `with-pi` variant** — it `FROM`s +`opencode-devbox:latest-with-pi` and adds no layers of its own. Everything below +is inherited from that single source of truth. + +Base tooling: - **Debian trixie** (current stable) - **Node.js** (LTS), **uv** (Python), **rustup** (Rust on-demand) @@ -18,15 +22,18 @@ 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` env). 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 with-pi 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` +- **`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 +- **opencode** — also present, since the image FROMs the with-pi variant -The entrypoint deploys all of these on first container start. Idempotent and preserves user edits. +The entrypoint deploys/registers all of these on first container start. Idempotent and preserves user edits. --- @@ -187,6 +194,8 @@ 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) | | `LANG` / `LANGUAGE` / `LC_ALL` | `en_US.UTF-8` | Locale override | --- @@ -197,34 +206,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 `opencode-devbox:latest-with-pi`, so a release of this image must be preceded by an opencode-devbox release that bakes the target pi version into `latest-with-pi`. 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 with-pi variant, so building it just pulls +the base. To pin a specific with-pi 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 latest-with-pi; 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/opencode-devbox:v1.15.13-with-pi 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-` 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/opencode-devbox:latest-with-pi` | Parent image — set to a `:vX.Y.Z-with-pi` tag or a digest for reproducible builds | --- diff --git a/docker-compose.yml b/docker-compose.yml index 1fd237f..3233edd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,8 @@ services: # build: # context: . # args: - # PI_VERSION: "latest" + # # Pin a specific with-pi build instead of tracking latest-with-pi: + # BASE_IMAGE: "joakimp/opencode-devbox:v1.15.13-with-pi" container_name: pi-devbox stdin_open: true tty: true diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 38eb8aa..649a3c8 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -17,7 +17,10 @@ set -euo pipefail IMAGE="${1:?usage: $0 }" PASS=0; FAIL=0 -SIZE_THRESHOLD_MB=2200 +# Since the refactor to FROM opencode-devbox:latest-with-pi, this image equals +# the with-pi variant (pi + opencode + companions + fork/recall node_modules), +# so the threshold tracks with-pi's (2900 MB), not the old pi-only 2200 MB. +SIZE_THRESHOLD_MB=2900 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 with-pi 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/`, 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 ──"