feat: add pi-only variant (pi without opencode) as basis for pi-devbox
Validate / docs-check (push) Successful in 10s
Validate / base-change-warning (push) Successful in 23s
Validate / validate-omos (push) Successful in 4m36s
Validate / validate-omos-with-pi (push) Failing after 5m40s
Validate / validate-with-pi (push) Failing after 7m35s
Validate / validate-pi-only (push) Failing after 3m45s
Validate / validate-base (push) Failing after 16m12s
Validate / docs-check (push) Successful in 10s
Validate / base-change-warning (push) Successful in 23s
Validate / validate-omos (push) Successful in 4m36s
Validate / validate-omos-with-pi (push) Failing after 5m40s
Validate / validate-with-pi (push) Failing after 7m35s
Validate / validate-pi-only (push) Failing after 3m45s
Validate / validate-base (push) Failing after 16m12s
All opencode-devbox variants set INSTALL_OPENCODE=true, so pointing pi-devbox at with-pi dragged opencode along and made it ~a re-tag of latest-with-pi. Add a 5th variant pi-only (INSTALL_OPENCODE=false, INSTALL_PI=true): pi + companions (toolkit, extensions, fork, recall) + base tooling, no opencode (~145 MB lighter than with-pi). - Dockerfile.variant: document pi-only in the variant table. - CI docker-publish-split.yml: new smoke-pi-only + build-variant-pi-only jobs (tags :VERSION-pi-only / :latest-pi-only, multi-arch); wired into promote-base-latest and update-description needs. - validate.yml: new validate-pi-only main-branch gate job. - smoke-test.sh: accept --variant pi-only; threshold 2750 MB; opencode-absent path already handled. - Docs: HUB_TEMPLATE (regenerated DOCKER_HUB.md), README, AGENTS (variant/tag counts 4->5, 8->10 tags), .gitea/README, manual-host-publish.sh (5 variants), plan doc implementation note. This is the single source of truth for joakimp/pi-devbox, which now FROMs latest-pi-only. Versions unchanged (opencode 1.15.13, pi 0.78.0).
This commit is contained in:
+5
-5
@@ -8,14 +8,14 @@ the build pipeline is shaped the way it is, you're in the right place.
|
|||||||
|
|
||||||
| File | Trigger | Role |
|
| File | Trigger | Role |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [`workflows/docker-publish-split.yml`](workflows/docker-publish-split.yml) | `push: tags: v*` | **Production release pipeline.** Two-phase split-base build: shared `base-<hash>` published once (skipped on cache hit), then four parallel variant deltas. ~40–80 min wall clock depending on runner count and whether base needs rebuilding. |
|
| [`workflows/docker-publish-split.yml`](workflows/docker-publish-split.yml) | `push: tags: v*` | **Production release pipeline.** Two-phase split-base build: shared `base-<hash>` published once (skipped on cache hit), then five parallel variant deltas. ~40–80 min wall clock depending on runner count and whether base needs rebuilding. |
|
||||||
| [`workflows/validate.yml`](workflows/validate.yml) | `push: branches: main` + PR | **Lightweight gate.** amd64-only smoke test of all four variants + `DOCKER_HUB.md` sync check. ~30 min. Fires on every push to `main`. |
|
| [`workflows/validate.yml`](workflows/validate.yml) | `push: branches: main` + PR | **Lightweight gate.** amd64-only smoke test of all five variants + `DOCKER_HUB.md` sync check. ~30 min. Fires on every push to `main`. |
|
||||||
|
|
||||||
## Why the split-base pipeline exists
|
## Why the split-base pipeline exists
|
||||||
|
|
||||||
opencode-devbox publishes **four image variants** (`base`, `omos`, `with-pi`, `omos-with-pi`) × **two architectures** (amd64, arm64) = **eight image tags per release**. Today's runners are 2 self-hosted gitea Actions runners. arm64 builds are emulated under QEMU, which is the dominant cost (~3–5x slower than native).
|
opencode-devbox publishes **four image variants** (`base`, `omos`, `with-pi`, `omos-with-pi`) × **two architectures** (amd64, arm64) = **eight image tags per release**. Today's runners are 2 self-hosted gitea Actions runners. arm64 builds are emulated under QEMU, which is the dominant cost (~3–5x slower than native).
|
||||||
|
|
||||||
The four variants share ~95% of their layers (Debian + apt + Node + AWS CLI + mempalace + dev tools + entrypoints). The original `Dockerfile` was a single multi-stage build with `INSTALL_*` build-args gating variant-specific RUNs. BuildKit's per-layer cache key is content-addressed, but as soon as a build-arg-gated `RUN` produces a different layer hash for variant A vs variant B, every subsequent layer also has a different parent → identical commands re-execute per variant. Result: minimal cross-variant cache reuse on a fresh build.
|
The five variants share ~95% of their layers (Debian + apt + Node + AWS CLI + mempalace + dev tools + entrypoints). The original `Dockerfile` was a single multi-stage build with `INSTALL_*` build-args gating variant-specific RUNs. BuildKit's per-layer cache key is content-addressed, but as soon as a build-arg-gated `RUN` produces a different layer hash for variant A vs variant B, every subsequent layer also has a different parent → identical commands re-execute per variant. Result: minimal cross-variant cache reuse on a fresh build.
|
||||||
|
|
||||||
Two improvements were considered:
|
Two improvements were considered:
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ production aliases pointing at the previous good release.
|
|||||||
|
|
||||||
### Step 5: `promote-base-latest`
|
### Step 5: `promote-base-latest`
|
||||||
|
|
||||||
Once all four variants successfully publish, re-tag `base-<hash>` as
|
Once all five variants successfully publish, re-tag `base-<hash>` as
|
||||||
`base-latest` using `crane copy`. This is a **manifest-level re-tag, not
|
`base-latest` using `crane copy`. This is a **manifest-level re-tag, not
|
||||||
a rebuild** — it touches only Docker Hub's image index, takes seconds,
|
a rebuild** — it touches only Docker Hub's image index, takes seconds,
|
||||||
and is atomic.
|
and is atomic.
|
||||||
@@ -252,7 +252,7 @@ on every push to `main` and on PRs. It:
|
|||||||
|
|
||||||
1. Runs `scripts/generate-dockerhub-md.py --check` to enforce
|
1. Runs `scripts/generate-dockerhub-md.py --check` to enforce
|
||||||
`DOCKER_HUB.md` is in sync with `HUB_TEMPLATE`.
|
`DOCKER_HUB.md` is in sync with `HUB_TEMPLATE`.
|
||||||
2. Builds each of the four variants amd64-only (no multi-arch, no push)
|
2. Builds each of the five variants amd64-only (no multi-arch, no push)
|
||||||
and runs `scripts/smoke-test.sh`.
|
and runs `scripts/smoke-test.sh`.
|
||||||
|
|
||||||
This catches regressions before they reach a tag push. Wall clock ~30 min.
|
This catches regressions before they reach a tag push. Wall clock ~30 min.
|
||||||
|
|||||||
@@ -432,6 +432,53 @@ jobs:
|
|||||||
EXPECTED_OMOS_VERSION: ${{ needs.resolve-versions.outputs.omos_version }}
|
EXPECTED_OMOS_VERSION: ${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos-with-pi --variant omos-with-pi
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos-with-pi --variant omos-with-pi
|
||||||
|
|
||||||
|
smoke-pi-only:
|
||||||
|
needs: [base-decide, build-base, resolve-versions]
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
needs.base-decide.result == 'success' &&
|
||||||
|
needs.resolve-versions.result == 'success' &&
|
||||||
|
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
|
- run: |
|
||||||
|
rm -rf /opt/hostedtoolcache /opt/microsoft /opt/az /opt/ghc \
|
||||||
|
/usr/local/.ghcup /usr/share/dotnet /usr/share/swift \
|
||||||
|
/usr/local/lib/android /usr/local/share/powershell \
|
||||||
|
/usr/local/share/chromium /usr/local/share/boost \
|
||||||
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
|
docker system prune -af --volumes || true
|
||||||
|
docker builder prune -af || true
|
||||||
|
- uses: docker/setup-buildx-action@v4
|
||||||
|
with: {driver-opts: network=host}
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.variant
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: opencode-devbox:smoke-pi-only
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
||||||
|
INSTALL_OPENCODE=false
|
||||||
|
INSTALL_OMOS=false
|
||||||
|
INSTALL_PI=true
|
||||||
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
PI_FORK_REF=${{ needs.resolve-versions.outputs.fork_ref }}
|
||||||
|
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||||
|
- env:
|
||||||
|
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-pi-only --variant pi-only
|
||||||
|
|
||||||
# ── Phase 4: multi-arch publish per variant ────────────────────────
|
# ── Phase 4: multi-arch publish per variant ────────────────────────
|
||||||
|
|
||||||
build-variant-base:
|
build-variant-base:
|
||||||
@@ -727,6 +774,81 @@ jobs:
|
|||||||
echo "==> All 3 build+push attempts failed"
|
echo "==> All 3 build+push attempts failed"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
build-variant-pi-only:
|
||||||
|
needs: [base-decide, smoke-pi-only, resolve-versions]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
|
- run: |
|
||||||
|
rm -rf /opt/hostedtoolcache /opt/microsoft /opt/az /opt/ghc \
|
||||||
|
/usr/local/.ghcup /usr/share/dotnet /usr/share/swift \
|
||||||
|
/usr/local/lib/android /usr/local/share/powershell \
|
||||||
|
/usr/local/share/chromium /usr/local/share/boost \
|
||||||
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
|
docker system prune -af --volumes || true
|
||||||
|
docker builder prune -af || true
|
||||||
|
- uses: docker/setup-qemu-action@v3
|
||||||
|
with: {platforms: arm64}
|
||||||
|
- uses: docker/setup-buildx-action@v4
|
||||||
|
with: {driver-opts: network=host}
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Compute version-specific tags
|
||||||
|
id: tags
|
||||||
|
run: |
|
||||||
|
VERSION="${{ env.RELEASE_TAG }}"
|
||||||
|
{ echo "tags<<EOF"
|
||||||
|
echo "${IMAGE}:${VERSION}-pi-only"
|
||||||
|
if [ "${{ env.PROMOTE_LATEST }}" = "true" ]; then
|
||||||
|
echo "${IMAGE}:latest-pi-only"
|
||||||
|
fi
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Build and push variant (with retry)
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAGS: ${{ steps.tags.outputs.tags }}
|
||||||
|
BASE_IMAGE_FULL: ${{ env.IMAGE }}:${{ needs.base-decide.outputs.base_tag }}
|
||||||
|
PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
FORK_REF: ${{ needs.resolve-versions.outputs.fork_ref }}
|
||||||
|
OBSMEM_REF: ${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TAG_FLAGS=()
|
||||||
|
while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}"
|
||||||
|
# 3-attempt retry (see build-base step for rationale). Variant: pi-only.
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
echo "==> Build+push attempt ${attempt}/3"
|
||||||
|
if docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--file Dockerfile.variant \
|
||||||
|
--push \
|
||||||
|
--build-arg "BASE_IMAGE=${BASE_IMAGE_FULL}" \
|
||||||
|
--build-arg "INSTALL_OPENCODE=false" \
|
||||||
|
--build-arg "INSTALL_OMOS=false" \
|
||||||
|
--build-arg "INSTALL_PI=true" \
|
||||||
|
--build-arg "PI_VERSION=${PI_VERSION}" \
|
||||||
|
--build-arg "PI_FORK_REF=${FORK_REF}" \
|
||||||
|
--build-arg "PI_OBSMEM_REF=${OBSMEM_REF}" \
|
||||||
|
"${TAG_FLAGS[@]}" \
|
||||||
|
.; then
|
||||||
|
echo "==> Attempt ${attempt} succeeded"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [[ "${attempt}" -lt 3 ]]; then
|
||||||
|
backoff=$(( attempt * 15 ))
|
||||||
|
echo "==> Attempt ${attempt} failed, sleeping ${backoff}s before retry"
|
||||||
|
sleep "${backoff}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "==> All 3 build+push attempts failed"
|
||||||
|
exit 1
|
||||||
|
|
||||||
# ── Phase 5: promote base-<hash> → base-latest (manifest copy only) ─
|
# ── Phase 5: promote base-<hash> → base-latest (manifest copy only) ─
|
||||||
promote-base-latest:
|
promote-base-latest:
|
||||||
needs:
|
needs:
|
||||||
@@ -735,6 +857,7 @@ jobs:
|
|||||||
- build-variant-omos
|
- build-variant-omos
|
||||||
- build-variant-with-pi
|
- build-variant-with-pi
|
||||||
- build-variant-omos-with-pi
|
- build-variant-omos-with-pi
|
||||||
|
- build-variant-pi-only
|
||||||
# Skip on cache-hit base builds: when need_build=false, base-latest
|
# Skip on cache-hit base builds: when need_build=false, base-latest
|
||||||
# already points at the same digest as base-<hash>, so the retag is
|
# already points at the same digest as base-<hash>, so the retag is
|
||||||
# a tautology and any transient failure of it is purely cosmetic.
|
# a tautology and any transient failure of it is purely cosmetic.
|
||||||
@@ -787,6 +910,7 @@ jobs:
|
|||||||
- build-variant-omos
|
- build-variant-omos
|
||||||
- build-variant-with-pi
|
- build-variant-with-pi
|
||||||
- build-variant-omos-with-pi
|
- build-variant-omos-with-pi
|
||||||
|
- build-variant-pi-only
|
||||||
# Run when at least the base variant published — don't let a single
|
# Run when at least the base variant published — don't let a single
|
||||||
# variant failure (e.g., omos-with-pi smoke threshold) prevent Hub
|
# variant failure (e.g., omos-with-pi smoke threshold) prevent Hub
|
||||||
# description refresh for the other variants that did publish.
|
# description refresh for the other variants that did publish.
|
||||||
|
|||||||
@@ -312,3 +312,62 @@ jobs:
|
|||||||
- name: Smoke test
|
- name: Smoke test
|
||||||
run: |
|
run: |
|
||||||
bash scripts/smoke-test.sh opencode-devbox:ci-omos-with-pi --variant omos-with-pi
|
bash scripts/smoke-test.sh opencode-devbox:ci-omos-with-pi --variant omos-with-pi
|
||||||
|
|
||||||
|
validate-pi-only:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Force IPv4 for Docker Hub
|
||||||
|
run: |
|
||||||
|
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
|
|
||||||
|
- name: Reclaim runner disk
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
df -h / || true
|
||||||
|
rm -rf \
|
||||||
|
/opt/hostedtoolcache \
|
||||||
|
/opt/microsoft \
|
||||||
|
/opt/az \
|
||||||
|
/opt/ghc \
|
||||||
|
/usr/local/.ghcup \
|
||||||
|
/usr/share/dotnet \
|
||||||
|
/usr/share/swift \
|
||||||
|
/usr/local/lib/android \
|
||||||
|
/usr/local/share/powershell \
|
||||||
|
/usr/local/share/chromium \
|
||||||
|
/usr/local/share/boost \
|
||||||
|
/usr/lib/jvm 2>/dev/null || true
|
||||||
|
apt-get clean || true
|
||||||
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* || true
|
||||||
|
docker system df || true
|
||||||
|
docker system prune -af --volumes || true
|
||||||
|
docker builder prune -af || true
|
||||||
|
df -h / || true
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
with:
|
||||||
|
driver-opts: network=host
|
||||||
|
|
||||||
|
- name: Build pi-only image (amd64, load to local daemon)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.variant
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=joakimp/opencode-devbox:base-latest
|
||||||
|
INSTALL_OPENCODE=false
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: opencode-devbox:ci-pi-only
|
||||||
|
|
||||||
|
- name: Smoke test
|
||||||
|
run: |
|
||||||
|
bash scripts/smoke-test.sh opencode-devbox:ci-pi-only --variant pi-only
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Docker image packaging [opencode](https://opencode.ai) into a production-ready d
|
|||||||
## File roles
|
## File roles
|
||||||
|
|
||||||
- `Dockerfile.base` — variant-independent layers (apt, locales, AWS CLI, Node.js, mempalace, gitea-mcp, user setup, chromadb prewarm, ENVs, entrypoints). Published as `joakimp/opencode-devbox:base-<sha12>`. Rebuilt only when its content hash changes.
|
- `Dockerfile.base` — variant-independent layers (apt, locales, AWS CLI, Node.js, mempalace, gitea-mcp, user setup, chromadb prewarm, ENVs, entrypoints). Published as `joakimp/opencode-devbox:base-<sha12>`. Rebuilt only when its content hash changes.
|
||||||
- `Dockerfile.variant` — `FROM`s the base and adds only opencode/omos/pi installs gated by build args: `INSTALL_OPENCODE` (default true), `INSTALL_OMOS`, `INSTALL_PI`, and `INSTALL_MEMPALACE`. All GitHub-sourced binaries are pinned with version ARGs. When `INSTALL_PI=true` it also clones `pi-fork` + `pi-observational-memory` (from `github.com/elpapi42`, refs `PI_FORK_REF`/`PI_OBSMEM_REF`) to `/opt` and runs `npm install` there at build time so the `fork`/`recall` extensions can load (a local-path `pi install` does not npm-install).
|
- `Dockerfile.variant` — `FROM`s the base and adds only opencode/omos/pi installs gated by build args: `INSTALL_OPENCODE` (default true), `INSTALL_OMOS`, `INSTALL_PI`, and `INSTALL_MEMPALACE`. All GitHub-sourced binaries are pinned with version ARGs. When `INSTALL_PI=true` it also clones `pi-fork` + `pi-observational-memory` (from `github.com/elpapi42`, refs `PI_FORK_REF`/`PI_OBSMEM_REF`) to `/opt` and runs `npm install` there at build time so the `fork`/`recall` extensions can load (a local-path `pi install` does not npm-install). The `pi-only` variant sets `INSTALL_OPENCODE=false`, `INSTALL_PI=true` — pi without opencode, the single source of truth for the separate `pi-devbox` image.
|
||||||
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu. Volume ownership loop covers `~/.pi/` when `INSTALL_PI=true`.
|
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu. Volume ownership loop covers `~/.pi/` when `INSTALL_PI=true`.
|
||||||
- `entrypoint-user.sh` — runs as developer: git config, opencode.jsonc generation (delegated to `generate-config.py`), LAN-access setup (delegated to `setup-lan-access.sh`), pi-toolkit + pi-extensions deploy (when pi installed), pi settings.json bootstrap, mempalace pi-bridge symlink, runtime `pi install /opt/{pi-fork,pi-observational-memory}` registration (idempotent), skillset auto-deploy from mounted skillset repo, OMOS setup.
|
- `entrypoint-user.sh` — runs as developer: git config, opencode.jsonc generation (delegated to `generate-config.py`), LAN-access setup (delegated to `setup-lan-access.sh`), pi-toolkit + pi-extensions deploy (when pi installed), pi settings.json bootstrap, mempalace pi-bridge symlink, runtime `pi install /opt/{pi-fork,pi-observational-memory}` registration (idempotent), skillset auto-deploy from mounted skillset repo, OMOS setup.
|
||||||
- `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh` — host-OS-agnostic LAN reachability helper. Detects VM-backed hosts (macOS OrbStack / Docker Desktop, via `host.docker.internal` resolution) and generates a writable `~/.ssh-local/config` using the host as an SSH jump; no-op on native Linux. Controlled by `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_HOST_ALIAS`. Ships the mechanism only (generic `host` jump alias); user targets stay in their bind-mounted `~/.ssh/config`. Non-fatal. Counted in the base hash, so editing it advances `base-latest`.
|
- `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh` — host-OS-agnostic LAN reachability helper. Detects VM-backed hosts (macOS OrbStack / Docker Desktop, via `host.docker.internal` resolution) and generates a writable `~/.ssh-local/config` using the host as an SSH jump; no-op on native Linux. Controlled by `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_HOST_ALIAS`. Ships the mechanism only (generic `host` jump alias); user targets stay in their bind-mounted `~/.ssh/config`. Non-fatal. Counted in the base hash, so editing it advances `base-latest`.
|
||||||
@@ -18,7 +18,7 @@ Docker image packaging [opencode](https://opencode.ai) into a production-ready d
|
|||||||
- `README.md` — authoritative source documentation for everything in this repo. Independent of `DOCKER_HUB.md`: the Hub doc is hand-maintained in the generator's `HUB_TEMPLATE` and intentionally slim, linking back to the gitea README for depth.
|
- `README.md` — authoritative source documentation for everything in this repo. Independent of `DOCKER_HUB.md`: the Hub doc is hand-maintained in the generator's `HUB_TEMPLATE` and intentionally slim, linking back to the gitea README for depth.
|
||||||
- `.gitea/README.md` — **read this first** if you're touching CI. Architectural overview of the build pipeline (production vs split-base), wall-clock estimates, NPM_CONFIG_PREFIX gotcha, runner expectations, migration plan.
|
- `.gitea/README.md` — **read this first** if you're touching CI. Architectural overview of the build pipeline (production vs split-base), wall-clock estimates, NPM_CONFIG_PREFIX gotcha, runner expectations, migration plan.
|
||||||
- `.gitea/workflows/validate.yml` — lightweight amd64 build + smoke test on push to main and PRs. Also runs the DOCKER_HUB.md sync check.
|
- `.gitea/workflows/validate.yml` — lightweight amd64 build + smoke test on push to main and PRs. Also runs the DOCKER_HUB.md sync check.
|
||||||
- `.gitea/workflows/docker-publish-split.yml` — production CI pipeline on tag push (`v*`). Two-phase split-base: computes base hash, conditionally builds base, runs 4 parallel smoke tests, then 4 parallel multi-arch variant builds, promotes `base-latest` alias, updates Docker Hub description.
|
- `.gitea/workflows/docker-publish-split.yml` — production CI pipeline on tag push (`v*`). Two-phase split-base: computes base hash, conditionally builds base, runs 5 parallel smoke tests, then 5 parallel multi-arch variant builds, promotes `base-latest` alias, updates Docker Hub description.
|
||||||
|
|
||||||
## Versioning scheme
|
## Versioning scheme
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ Tags follow `v{opencode_version}[letter]` — e.g. `v1.14.20` for the first buil
|
|||||||
|
|
||||||
Cautionary example: 2026-05-28 morning, `v1.15.12` was cut while opencode-ai was still at `1.15.11`. The commit message itself acknowledged "OPENCODE_VERSION stays at 1.15.11" but tagged `v1.15.12` anyway. Re-cut as `v1.15.11c` the same afternoon (see CHANGELOG). The `v1.15.12` git tag and Hub images stayed as historical artifacts; the slip cost a CI cycle and a CHANGELOG-rewrite. **Run the npm view check at the top of every release-day cut.**
|
Cautionary example: 2026-05-28 morning, `v1.15.12` was cut while opencode-ai was still at `1.15.11`. The commit message itself acknowledged "OPENCODE_VERSION stays at 1.15.11" but tagged `v1.15.12` anyway. Re-cut as `v1.15.11c` the same afternoon (see CHANGELOG). The `v1.15.12` git tag and Hub images stayed as historical artifacts; the slip cost a CI cycle and a CHANGELOG-rewrite. **Run the npm view check at the top of every release-day cut.**
|
||||||
|
|
||||||
CI produces eight Docker Hub tags per release: `vX.Y.Z[n]`, `latest`, `vX.Y.Z[n]-omos`, `latest-omos`, `vX.Y.Z[n]-with-pi`, `latest-with-pi`, `vX.Y.Z[n]-omos-with-pi`, `latest-omos-with-pi` — one tag pair (versioned + floating alias) per build variant.
|
CI produces ten Docker Hub tags per release: `vX.Y.Z[n]`, `latest`, `vX.Y.Z[n]-omos`, `latest-omos`, `vX.Y.Z[n]-with-pi`, `latest-with-pi`, `vX.Y.Z[n]-omos-with-pi`, `latest-omos-with-pi`, `vX.Y.Z[n]-pi-only`, `latest-pi-only` — one tag pair (versioned + floating alias) per build variant (five variants).
|
||||||
|
|
||||||
When bumping the opencode version, bump `OPENCODE_VERSION` in `Dockerfile.variant` and update the comment in `.env.example` if it names a specific model/version for context.
|
When bumping the opencode version, bump `OPENCODE_VERSION` in `Dockerfile.variant` and update the comment in `.env.example` if it names a specific model/version for context.
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ cd /tmp && npm pack @earendil-works/pi-coding-agent@0.75.5 && tar -xzf earendil-
|
|||||||
- **Resolved versions are logged by the smoke test** — `scripts/smoke-test.sh` prints a "Resolved component versions" table as its first step. CI logs always capture what got baked into a given image even when ARGs default to `latest`.
|
- **Resolved versions are logged by the smoke test** — `scripts/smoke-test.sh` prints a "Resolved component versions" table as its first step. CI logs always capture what got baked into a given image even when ARGs default to `latest`.
|
||||||
- **`PI_VERSION` and `OMOS_VERSION` MUST be passed by CI as concrete versions**, not left at the `latest` default. The npm install steps in `Dockerfile.variant` (`npm install -g @earendil-works/pi-coding-agent` / `oh-my-opencode-slim@${OMOS_VERSION}`) produce identical layer-hashes when the ARG values are byte-identical across builds; combined with the registry buildcache (`base-buildcache`) the layer gets reused even when `latest` would have resolved to a newer upstream. This is the same class of bug that bit pi-devbox v0.74.0 → v0.75.5 (silent same-bytes-across-releases regression discovered 2026-05-23, fixed in pi-devbox v0.75.5b). It is currently *masked* in opencode-devbox by `OPENCODE_VERSION` being a hard-coded ARG that bumps every release — that bump invalidates the parent-chain cache key for the downstream pi/omos layers — but the masking would fail the moment a `vN.N.Nb` opencode-version-unchanged release ships that only bumps pi or omos. Preventative fix: `.gitea/workflows/docker-publish-split.yml` has a `resolve-versions` job that runs `npm view @earendil-works/pi-coding-agent version` and `npm view oh-my-opencode-slim version`, exposing concrete values as outputs that every variant smoke + build job consumes via build-args. Smoke tests assert via `EXPECTED_PI_VERSION` / `EXPECTED_OMOS_VERSION` env vars — would catch the regression on the next release rather than four releases later. **If you change the variant build-args list, the resolve-versions job, or the smoke EXPECTED_*_VERSION wiring, audit all affected jobs in lockstep.**
|
- **`PI_VERSION` and `OMOS_VERSION` MUST be passed by CI as concrete versions**, not left at the `latest` default. The npm install steps in `Dockerfile.variant` (`npm install -g @earendil-works/pi-coding-agent` / `oh-my-opencode-slim@${OMOS_VERSION}`) produce identical layer-hashes when the ARG values are byte-identical across builds; combined with the registry buildcache (`base-buildcache`) the layer gets reused even when `latest` would have resolved to a newer upstream. This is the same class of bug that bit pi-devbox v0.74.0 → v0.75.5 (silent same-bytes-across-releases regression discovered 2026-05-23, fixed in pi-devbox v0.75.5b). It is currently *masked* in opencode-devbox by `OPENCODE_VERSION` being a hard-coded ARG that bumps every release — that bump invalidates the parent-chain cache key for the downstream pi/omos layers — but the masking would fail the moment a `vN.N.Nb` opencode-version-unchanged release ships that only bumps pi or omos. Preventative fix: `.gitea/workflows/docker-publish-split.yml` has a `resolve-versions` job that runs `npm view @earendil-works/pi-coding-agent version` and `npm view oh-my-opencode-slim version`, exposing concrete values as outputs that every variant smoke + build job consumes via build-args. Smoke tests assert via `EXPECTED_PI_VERSION` / `EXPECTED_OMOS_VERSION` env vars — would catch the regression on the next release rather than four releases later. **If you change the variant build-args list, the resolve-versions job, or the smoke EXPECTED_*_VERSION wiring, audit all affected jobs in lockstep.**
|
||||||
- **Registry buildkit cache-export is currently disabled** — do NOT re-add `cache-from`/`cache-to` to the `build-base` step in `.gitea/workflows/docker-publish-split.yml` without first verifying that buildkit's `mode=max` cache-export to `registry-1.docker.io` no longer returns HTTP 400 from the Hub CDN edge. The regression surfaced ~2026-05-23 and broke five consecutive opencode-devbox publish attempts (runs #332/333/334/336 + a rerun); root-caused on 2026-05-28 by a manual host-side publish that reproduced the same 400 only on `--cache-to` while image push worked fine. Failure shape is stable (`Offset:0` in the `_state` token, HTML response body = CDN-tier rejection, not registry backend), repo-specific (we're the only repo writing `:base-buildcache` mode=max), and explains why pinning `setup-buildx-action@v4.0.0` didn't help (action pin doesn't change the bundled buildkit version on the catthehacker runner image). Trade-off: dockerfile.base changes pay a full ~3 min rebuild instead of pulling cached layers; unchanged bases short-circuit at the Hub-probe step in `base-decide` and never re-build anyway. Variants don't use registry cache so they're unaffected. Re-enable condition: upstream moby/buildkit fix lands AND a low-risk test run succeeds without 400s. See CHANGELOG v1.15.12 `Unreleased` block for the full diagnostic chain. Manual escape-hatch publish procedure: `docs/manual-host-publish.md`.
|
- **Registry buildkit cache-export is currently disabled** — do NOT re-add `cache-from`/`cache-to` to the `build-base` step in `.gitea/workflows/docker-publish-split.yml` without first verifying that buildkit's `mode=max` cache-export to `registry-1.docker.io` no longer returns HTTP 400 from the Hub CDN edge. The regression surfaced ~2026-05-23 and broke five consecutive opencode-devbox publish attempts (runs #332/333/334/336 + a rerun); root-caused on 2026-05-28 by a manual host-side publish that reproduced the same 400 only on `--cache-to` while image push worked fine. Failure shape is stable (`Offset:0` in the `_state` token, HTML response body = CDN-tier rejection, not registry backend), repo-specific (we're the only repo writing `:base-buildcache` mode=max), and explains why pinning `setup-buildx-action@v4.0.0` didn't help (action pin doesn't change the bundled buildkit version on the catthehacker runner image). Trade-off: dockerfile.base changes pay a full ~3 min rebuild instead of pulling cached layers; unchanged bases short-circuit at the Hub-probe step in `base-decide` and never re-build anyway. Variants don't use registry cache so they're unaffected. Re-enable condition: upstream moby/buildkit fix lands AND a low-risk test run succeeds without 400s. See CHANGELOG v1.15.12 `Unreleased` block for the full diagnostic chain. Manual escape-hatch publish procedure: `docs/manual-host-publish.md`.
|
||||||
- **Push steps wrap `docker buildx build --push` in a 3-attempt retry loop** (15s, 30s backoff) for transient `registry-1.docker.io` blips — rate limits, brief 5xx, CDN flap. Implemented as inline `shell: bash` steps with `docker buildx build` raw rather than `docker/build-push-action@v7` so the loop is visible and tweakable. Affects the 1 base + 4 variant push steps in `.gitea/workflows/docker-publish-split.yml`; smoke-test builds (`load: true`, no push) are untouched. **This does NOT mask deterministic failures** — a true regression (like the cache-export 400 of 2026-05-23..28) fails all 3 attempts identically and the job still fails. Orthogonal to the cache-export disablement above: cache-export was about a deterministic protocol mismatch, retry is about absorbing genuine transients. Both are belt-and-braces with the `ci-release-watcher` skill's transient-rerun heuristic. If you change the matrix of push steps, keep the retry wrapper consistent across them — the pattern is duplicated rather than factored out because Gitea Actions doesn't support reusable composite shell steps cleanly.
|
- **Push steps wrap `docker buildx build --push` in a 3-attempt retry loop** (15s, 30s backoff) for transient `registry-1.docker.io` blips — rate limits, brief 5xx, CDN flap. Implemented as inline `shell: bash` steps with `docker buildx build` raw rather than `docker/build-push-action@v7` so the loop is visible and tweakable. Affects the 1 base + 5 variant push steps in `.gitea/workflows/docker-publish-split.yml`; smoke-test builds (`load: true`, no push) are untouched. **This does NOT mask deterministic failures** — a true regression (like the cache-export 400 of 2026-05-23..28) fails all 3 attempts identically and the job still fails. Orthogonal to the cache-export disablement above: cache-export was about a deterministic protocol mismatch, retry is about absorbing genuine transients. Both are belt-and-braces with the `ci-release-watcher` skill's transient-rerun heuristic. If you change the matrix of push steps, keep the retry wrapper consistent across them — the pattern is duplicated rather than factored out because Gitea Actions doesn't support reusable composite shell steps cleanly.
|
||||||
- **Shell scripts use `set -euo pipefail`** — both entrypoints are strict. Errors in volume chown or SSH permission operations are intentionally suppressed with `|| true`.
|
- **Shell scripts use `set -euo pipefail`** — both entrypoints are strict. Errors in volume chown or SSH permission operations are intentionally suppressed with `|| true`.
|
||||||
- **MemPalace install path** — installed via `uv tool install` into `/opt/uv-tools/mempalace/`. Both the `mempalace` CLI and the `mempalace-mcp` MCP server binary are shipped as entry points by the mempalace package itself and placed on PATH by uv as shims whose shebangs point at the venv's Python. No hand-rolled wrapper is needed. Do not use `pip install --break-system-packages` — that was the previous approach and has been removed. Do not use `["python3", "-m", "mempalace.mcp_server"]` in `opencode.jsonc` — system Python can't import from the uv venv.
|
- **MemPalace install path** — installed via `uv tool install` into `/opt/uv-tools/mempalace/`. Both the `mempalace` CLI and the `mempalace-mcp` MCP server binary are shipped as entry points by the mempalace package itself and placed on PATH by uv as shims whose shebangs point at the venv's Python. No hand-rolled wrapper is needed. Do not use `pip install --break-system-packages` — that was the previous approach and has been removed. Do not use `["python3", "-m", "mempalace.mcp_server"]` in `opencode.jsonc` — system Python can't import from the uv venv.
|
||||||
- **generate-config.py idempotency** — the script MUST never overwrite an existing `opencode.jsonc` or legacy `opencode.json`. Config persists in the `devbox-opencode-config` named volume; accidentally clobbering that file would destroy hand-edits. The smoke test asserts this.
|
- **generate-config.py idempotency** — the script MUST never overwrite an existing `opencode.jsonc` or legacy `opencode.json`. Config persists in the `devbox-opencode-config` named volume; accidentally clobbering that file would destroy hand-edits. The smoke test asserts this.
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ The `with-pi` and `omos-with-pi` variants now bake in two pi extensions from `gi
|
|||||||
- New build-args: `PI_FORK_REPO`, `PI_FORK_REF`, `PI_OBSMEM_REPO`, `PI_OBSMEM_REF`.
|
- New build-args: `PI_FORK_REPO`, `PI_FORK_REF`, `PI_OBSMEM_REPO`, `PI_OBSMEM_REF`.
|
||||||
- Smoke test asserts the `/opt` clones + baked `node_modules` exist and that both packages register in `settings.json`. Size thresholds bumped: `with-pi` 2700→2900 MB, `omos-with-pi` 3700→3900 MB (fork's `@earendil-works` peer deps add ~150 MB).
|
- Smoke test asserts the `/opt` clones + baked `node_modules` exist and that both packages register in `settings.json`. Size thresholds bumped: `with-pi` 2700→2900 MB, `omos-with-pi` 3700→3900 MB (fork's `@earendil-works` peer deps add ~150 MB).
|
||||||
|
|
||||||
|
### Added: `pi-only` variant (basis for `pi-devbox`)
|
||||||
|
|
||||||
|
New fifth published variant built with `INSTALL_OPENCODE=false`, `INSTALL_PI=true` — pi + companions (toolkit, extensions, `fork`, `recall`) and all base tooling, but **without** opencode (~145 MB lighter than `with-pi`).
|
||||||
|
|
||||||
|
- Published as `latest-pi-only` / `vX.Y.Z-pi-only` (multi-arch). New CI jobs `smoke-pi-only` and `build-variant-pi-only`; wired into `promote-base-latest` / `update-description` needs.
|
||||||
|
- This is the **single source of truth** for the separate [`joakimp/pi-devbox`](https://gitea.jordbo.se/joakimp/pi-devbox) image, which now `FROM`s `latest-pi-only` instead of duplicating the pi-install logic. Lets pi-devbox stay lean and pi-focused while the install logic lives in one place.
|
||||||
|
- Smoke size threshold: 2750 MB (`with-pi` minus opencode).
|
||||||
|
|
||||||
_Versions unchanged: opencode-ai `1.15.13`, pi `0.78.0` (both still latest at time of writing)._
|
_Versions unchanged: opencode-ai `1.15.13`, pi `0.78.0` (both still latest at time of writing)._
|
||||||
|
|
||||||
## v1.15.13 — 2026-05-29
|
## v1.15.13 — 2026-05-29
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Designed for teams who want a reproducible coding-agent setup that runs the same
|
|||||||
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
|
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
|
||||||
| `latest-with-pi` / `vX.Y.Z-with-pi` | Base + [pi](https://github.com/earendil-works/pi) as alternative/complementary harness (shares the mempalace install with opencode) |
|
| `latest-with-pi` / `vX.Y.Z-with-pi` | Base + [pi](https://github.com/earendil-works/pi) as alternative/complementary harness (shares the mempalace install with opencode) |
|
||||||
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together |
|
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together |
|
||||||
|
| `latest-pi-only` / `vX.Y.Z-pi-only` | pi without opencode — the lean, pi-focused variant (basis of the separate `joakimp/pi-devbox` image) |
|
||||||
|
|
||||||
All variants support `linux/amd64` and `linux/arm64`.
|
All variants support `linux/amd64` and `linux/arm64`.
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
# omos true true false
|
# omos true true false
|
||||||
# with-pi true false true
|
# with-pi true false true
|
||||||
# omos-with-pi true true true
|
# omos-with-pi true true true
|
||||||
|
# pi-only false false true
|
||||||
|
#
|
||||||
|
# The `pi-only` variant is the single source of truth for the pi-devbox
|
||||||
|
# image (pi + companions, no opencode). It exists so pi-devbox can FROM it
|
||||||
|
# without inheriting opencode, while the pi install logic stays defined
|
||||||
|
# here in one place.
|
||||||
#
|
#
|
||||||
# Pass `--build-arg BASE_IMAGE=<repo>:base-<hash>` to select the base.
|
# Pass `--build-arg BASE_IMAGE=<repo>:base-<hash>` to select the base.
|
||||||
# The CI workflow computes the base hash from Dockerfile.base + rootfs/
|
# The CI workflow computes the base hash from Dockerfile.base + rootfs/
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ All six agents should respond if your provider authentication is working.
|
|||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
Pre-built pi-enabled images are available on Docker Hub as `joakimp/opencode-devbox:latest-with-pi` (base + pi) and `joakimp/opencode-devbox:latest-omos-with-pi` (OMOS + pi). Pulling one of those tags is the fastest path. Alternatively, build from source:
|
Pre-built pi-enabled images are available on Docker Hub as `joakimp/opencode-devbox:latest-with-pi` (base + pi) and `joakimp/opencode-devbox:latest-omos-with-pi` (OMOS + pi). Pulling one of those tags is the fastest path. There is also a `latest-pi-only` variant (pi **without** opencode, `INSTALL_OPENCODE=false`) — it's the lean basis for the separate [`joakimp/pi-devbox`](https://gitea.jordbo.se/joakimp/pi-devbox) image. Alternatively, build from source:
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
# Mirrors what .gitea/workflows/docker-publish-split.yml would do:
|
# Mirrors what .gitea/workflows/docker-publish-split.yml would do:
|
||||||
# 1. Build & push Dockerfile.base → joakimp/opencode-devbox:base-<hash>
|
# 1. Build & push Dockerfile.base → joakimp/opencode-devbox:base-<hash>
|
||||||
# 2. Promote → joakimp/opencode-devbox:base-latest
|
# 2. Promote → joakimp/opencode-devbox:base-latest
|
||||||
# 3. Build & push 4 variants on top of base-<hash>:
|
# 3. Build & push 5 variants on top of base-<hash>:
|
||||||
# :v1.15.12 :latest (INSTALL_OPENCODE only)
|
# :v1.15.12 :latest (INSTALL_OPENCODE only)
|
||||||
# :v1.15.12-omos :latest-omos (+ OMOS)
|
# :v1.15.12-omos :latest-omos (+ OMOS)
|
||||||
# :v1.15.12-with-pi :latest-with-pi (+ pi)
|
# :v1.15.12-with-pi :latest-with-pi (+ pi)
|
||||||
# :v1.15.12-omos-with-pi :latest-omos-with-pi (+ both)
|
# :v1.15.12-omos-with-pi :latest-omos-with-pi (+ both)
|
||||||
|
# :v1.15.12-pi-only :latest-pi-only (pi, no opencode)
|
||||||
#
|
#
|
||||||
# Usage on your host:
|
# Usage on your host:
|
||||||
# 1. Make sure Orbstack/Docker Desktop is running with multi-arch enabled
|
# 1. Make sure Orbstack/Docker Desktop is running with multi-arch enabled
|
||||||
@@ -51,7 +52,7 @@ fi
|
|||||||
|
|
||||||
# -------- 1. base (if needed) --------
|
# -------- 1. base (if needed) --------
|
||||||
if [[ "$SKIP_BASE" == "0" ]]; then
|
if [[ "$SKIP_BASE" == "0" ]]; then
|
||||||
echo "==> [1/5] Build & push Dockerfile.base → ${IMAGE}:${BASE_TAG}"
|
echo "==> [1/7] Build & push Dockerfile.base → ${IMAGE}:${BASE_TAG}"
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform "$PLATFORMS" \
|
--platform "$PLATFORMS" \
|
||||||
-f Dockerfile.base \
|
-f Dockerfile.base \
|
||||||
@@ -61,14 +62,15 @@ if [[ "$SKIP_BASE" == "0" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# -------- 2. promote base-latest --------
|
# -------- 2. promote base-latest --------
|
||||||
echo "==> [2/5] Promote ${IMAGE}:${BASE_TAG} → ${IMAGE}:base-latest"
|
echo "==> [2/7] Promote ${IMAGE}:${BASE_TAG} → ${IMAGE}:base-latest"
|
||||||
docker buildx imagetools create -t "${IMAGE}:base-latest" "${IMAGE}:${BASE_TAG}"
|
docker buildx imagetools create -t "${IMAGE}:base-latest" "${IMAGE}:${BASE_TAG}"
|
||||||
|
|
||||||
# -------- 3-5. variants --------
|
# -------- 3-5. variants --------
|
||||||
build_variant() {
|
build_variant() {
|
||||||
local suffix="$1" # "" | "-omos" | "-with-pi" | "-omos-with-pi"
|
local suffix="$1" # "" | "-omos" | "-with-pi" | "-omos-with-pi" | "-pi-only"
|
||||||
local install_omos="$2"
|
local install_omos="$2"
|
||||||
local install_pi="$3"
|
local install_pi="$3"
|
||||||
|
local install_opencode="${4:-true}"
|
||||||
local extra_args=()
|
local extra_args=()
|
||||||
[[ "$install_pi" == "true" ]] && extra_args+=(--build-arg "PI_VERSION=${PI_VERSION}")
|
[[ "$install_pi" == "true" ]] && extra_args+=(--build-arg "PI_VERSION=${PI_VERSION}")
|
||||||
[[ "$install_omos" == "true" ]] && extra_args+=(--build-arg "OMOS_VERSION=${OMOS_VERSION}")
|
[[ "$install_omos" == "true" ]] && extra_args+=(--build-arg "OMOS_VERSION=${OMOS_VERSION}")
|
||||||
@@ -81,7 +83,7 @@ build_variant() {
|
|||||||
--platform "$PLATFORMS" \
|
--platform "$PLATFORMS" \
|
||||||
-f Dockerfile.variant \
|
-f Dockerfile.variant \
|
||||||
--build-arg "BASE_IMAGE=${IMAGE}:${BASE_TAG}" \
|
--build-arg "BASE_IMAGE=${IMAGE}:${BASE_TAG}" \
|
||||||
--build-arg "INSTALL_OPENCODE=true" \
|
--build-arg "INSTALL_OPENCODE=${install_opencode}" \
|
||||||
--build-arg "INSTALL_OMOS=${install_omos}" \
|
--build-arg "INSTALL_OMOS=${install_omos}" \
|
||||||
--build-arg "INSTALL_PI=${install_pi}" \
|
--build-arg "INSTALL_PI=${install_pi}" \
|
||||||
${extra_args[@]+"${extra_args[@]}"} \
|
${extra_args[@]+"${extra_args[@]}"} \
|
||||||
@@ -91,18 +93,21 @@ build_variant() {
|
|||||||
.
|
.
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "==> [3/5] Variant: base (opencode only)"
|
echo "==> [3/7] Variant: base (opencode only)"
|
||||||
build_variant "" false false
|
build_variant "" false false
|
||||||
|
|
||||||
echo "==> [4/5] Variant: omos"
|
echo "==> [4/7] Variant: omos"
|
||||||
build_variant "-omos" true false
|
build_variant "-omos" true false
|
||||||
|
|
||||||
echo "==> [4/5] Variant: with-pi"
|
echo "==> [5/7] Variant: with-pi"
|
||||||
build_variant "-with-pi" false true
|
build_variant "-with-pi" false true
|
||||||
|
|
||||||
echo "==> [5/5] Variant: omos-with-pi"
|
echo "==> [6/7] Variant: omos-with-pi"
|
||||||
build_variant "-omos-with-pi" true true
|
build_variant "-omos-with-pi" true true
|
||||||
|
|
||||||
|
echo "==> [7/7] Variant: pi-only (pi without opencode)"
|
||||||
|
build_variant "-pi-only" false true false
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "==> Done. Verifying tags on Hub:"
|
echo "==> Done. Verifying tags on Hub:"
|
||||||
for t in \
|
for t in \
|
||||||
@@ -110,6 +115,7 @@ for t in \
|
|||||||
"${RELEASE_TAG}-omos" "latest-omos" \
|
"${RELEASE_TAG}-omos" "latest-omos" \
|
||||||
"${RELEASE_TAG}-with-pi" "latest-with-pi" \
|
"${RELEASE_TAG}-with-pi" "latest-with-pi" \
|
||||||
"${RELEASE_TAG}-omos-with-pi" "latest-omos-with-pi" \
|
"${RELEASE_TAG}-omos-with-pi" "latest-omos-with-pi" \
|
||||||
|
"${RELEASE_TAG}-pi-only" "latest-pi-only" \
|
||||||
"${BASE_TAG}" "base-latest"
|
"${BASE_TAG}" "base-latest"
|
||||||
do
|
do
|
||||||
d=$(docker manifest inspect "${IMAGE}:${t}" 2>/dev/null | python3 -c "import json,sys,hashlib; m=json.load(sys.stdin); print(m.get('digest','-'))" 2>/dev/null || echo "MISSING")
|
d=$(docker manifest inspect "${IMAGE}:${t}" 2>/dev/null | python3 -c "import json,sys,hashlib; m=json.load(sys.stdin); print(m.get('digest','-'))" 2>/dev/null || echo "MISSING")
|
||||||
|
|||||||
@@ -121,6 +121,13 @@ Sources (pinned this week):
|
|||||||
`Dockerfile.variant`. Refactor `pi-devbox/Dockerfile` to `FROM` the `with-pi` variant
|
`Dockerfile.variant`. Refactor `pi-devbox/Dockerfile` to `FROM` the `with-pi` variant
|
||||||
image so pi-install logic (incl. the new fork/obsmem clones) lives in ONE place.
|
image so pi-install logic (incl. the new fork/obsmem clones) lives in ONE place.
|
||||||
|
|
||||||
|
> **Implementation update (2026-06-03):** `FROM with-pi` would have dragged opencode
|
||||||
|
> into pi-devbox (all opencode-devbox variants set `INSTALL_OPENCODE=true`), making it
|
||||||
|
> nearly identical to `latest-with-pi`. So a 5th variant **`pi-only`**
|
||||||
|
> (`INSTALL_OPENCODE=false`, `INSTALL_PI=true`) was added to opencode-devbox, and
|
||||||
|
> pi-devbox now `FROM`s `latest-pi-only`. Same single-source-of-truth win, but
|
||||||
|
> pi-devbox stays lean (no opencode, ~145 MB lighter than with-pi).
|
||||||
|
|
||||||
### Build time — clone to /opt + npm install (mirror pi-toolkit/extensions pattern)
|
### Build time — clone to /opt + npm install (mirror pi-toolkit/extensions pattern)
|
||||||
Add to the single `INSTALL_PI=true` block in `opencode-devbox/Dockerfile.variant`
|
Add to the single `INSTALL_PI=true` block in `opencode-devbox/Dockerfile.variant`
|
||||||
(after refactor, pi-devbox inherits it):
|
(after refactor, pi-devbox inherits it):
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Designed for teams who want a reproducible coding-agent setup that runs the same
|
|||||||
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
|
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
|
||||||
| `latest-with-pi` / `vX.Y.Z-with-pi` | Base + [pi](https://github.com/earendil-works/pi) as alternative/complementary harness (shares the mempalace install with opencode) |
|
| `latest-with-pi` / `vX.Y.Z-with-pi` | Base + [pi](https://github.com/earendil-works/pi) as alternative/complementary harness (shares the mempalace install with opencode) |
|
||||||
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together |
|
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together |
|
||||||
|
| `latest-pi-only` / `vX.Y.Z-pi-only` | pi without opencode — the lean, pi-focused variant (basis of the separate `joakimp/pi-devbox` image) |
|
||||||
|
|
||||||
All variants support `linux/amd64` and `linux/arm64`.
|
All variants support `linux/amd64` and `linux/arm64`.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# - Generated opencode.json has the expected shape
|
# - Generated opencode.json has the expected shape
|
||||||
# - MCP wrapper works (when mempalace is installed)
|
# - MCP wrapper works (when mempalace is installed)
|
||||||
#
|
#
|
||||||
# Usage: ./scripts/smoke-test.sh <image> [--variant base|omos|with-pi|omos-with-pi]
|
# Usage: ./scripts/smoke-test.sh <image> [--variant base|omos|with-pi|omos-with-pi|pi-only]
|
||||||
#
|
#
|
||||||
# Exit codes:
|
# Exit codes:
|
||||||
# 0 all checks passed
|
# 0 all checks passed
|
||||||
@@ -23,7 +23,7 @@ if [ "${2:-}" = "--variant" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$IMAGE" ]; then
|
if [ -z "$IMAGE" ]; then
|
||||||
echo "usage: $0 <image> [--variant base|omos|with-pi|omos-with-pi]" >&2
|
echo "usage: $0 <image> [--variant base|omos|with-pi|omos-with-pi|pi-only]" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -367,6 +367,9 @@ THRESHOLD=2500
|
|||||||
[ "$VARIANT" = "omos" ] && THRESHOLD=3300
|
[ "$VARIANT" = "omos" ] && THRESHOLD=3300
|
||||||
[ "$VARIANT" = "with-pi" ] && THRESHOLD=2900
|
[ "$VARIANT" = "with-pi" ] && THRESHOLD=2900
|
||||||
[ "$VARIANT" = "omos-with-pi" ] && THRESHOLD=3900
|
[ "$VARIANT" = "omos-with-pi" ] && THRESHOLD=3900
|
||||||
|
# pi-only = with-pi minus opencode (its platform binary is ~145 MB), so it
|
||||||
|
# lands a bit under base. Threshold 2750 leaves the same headroom pattern.
|
||||||
|
[ "$VARIANT" = "pi-only" ] && THRESHOLD=2750
|
||||||
if [ "$SIZE_MB" -gt "$THRESHOLD" ]; then
|
if [ "$SIZE_MB" -gt "$THRESHOLD" ]; then
|
||||||
fail "image size ${SIZE_MB} MB exceeds threshold ${THRESHOLD} MB for variant=$VARIANT"
|
fail "image size ${SIZE_MB} MB exceeds threshold ${THRESHOLD} MB for variant=$VARIANT"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user