Compare commits
4 Commits
4cce39d167
...
668592da0d
| Author | SHA1 | Date | |
|---|---|---|---|
| 668592da0d | |||
| 3cbcb44cf5 | |||
| 73a7f96056 | |||
| f7c34091b1 |
+30
-7
@@ -30,10 +30,10 @@ The split-base architecture is what the `docker-publish-split.yml` workflow exer
|
|||||||
┌──────────────────┐
|
┌──────────────────┐
|
||||||
│ base-decide │ compute base-<hash>;
|
│ base-decide │ compute base-<hash>;
|
||||||
│ │ probe Docker Hub.
|
│ │ probe Docker Hub.
|
||||||
│ hash inputs: │
|
│ hash inputs: │ (resolve-versions
|
||||||
│ Dockerfile.base│
|
│ Dockerfile.base│ runs in parallel:
|
||||||
│ rootfs/ │
|
│ rootfs/ │ npm view pi/omos
|
||||||
│ entrypoint*.sh │
|
│ entrypoint*.sh │ → concrete versions)
|
||||||
└────────┬─────────┘
|
└────────┬─────────┘
|
||||||
│
|
│
|
||||||
┌─────────────┴─────────────┐
|
┌─────────────┴─────────────┐
|
||||||
@@ -73,10 +73,10 @@ The split-base architecture is what the `docker-publish-split.yml` workflow exer
|
|||||||
└──────────────────────────┘
|
└──────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 1: `base-decide`
|
### Step 1: `base-decide` (and `resolve-versions` in parallel)
|
||||||
|
|
||||||
Compute a SHA-256 hash over the inputs that determine the base image's
|
**`base-decide`** computes a SHA-256 hash over the inputs that determine
|
||||||
content:
|
the base image's content:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
{
|
{
|
||||||
@@ -106,6 +106,29 @@ This is the core cache-reuse mechanism. Version-bump-only releases
|
|||||||
that change anything in the base — apt packages, AWS CLI, Node version,
|
that change anything in the base — apt packages, AWS CLI, Node version,
|
||||||
locale list, entrypoint scripts — pay the full base-build cost once.
|
locale list, entrypoint scripts — pay the full base-build cost once.
|
||||||
|
|
||||||
|
**`resolve-versions`** runs alongside `base-decide` (no `needs:`
|
||||||
|
dependency between them) and resolves the floating npm packages whose
|
||||||
|
`*_VERSION` build-args default to `latest`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PI_VERSION=$(npm view @earendil-works/pi-coding-agent version)
|
||||||
|
OMOS_VERSION=$(npm view oh-my-opencode-slim version)
|
||||||
|
```
|
||||||
|
|
||||||
|
The outputs (`pi_version`, `omos_version`) are consumed by every variant
|
||||||
|
smoke and build job that installs pi or omos. **Why this exists:** without
|
||||||
|
it, the `npm install -g` RUN layer in `Dockerfile.variant` hashes
|
||||||
|
identically across builds (same ARG default, same command string), so
|
||||||
|
the registry buildcache silently reuses the layer from whatever upstream
|
||||||
|
version was current when the cache was first populated. This is the
|
||||||
|
cache-hit silent-regression class of bug that shipped pi-devbox v0.74.0
|
||||||
|
through v0.75.5 with identical image bytes (fixed in pi-devbox v0.75.5b
|
||||||
|
2026-05-23). Currently masked here by `OPENCODE_VERSION` bumping every
|
||||||
|
release (parent-chain cache-key invalidation), but masking would fail on
|
||||||
|
a `vN.N.Nb` opencode-version-unchanged release that only bumps pi or
|
||||||
|
omos. Smoke jobs additionally assert `EXPECTED_PI_VERSION` /
|
||||||
|
`EXPECTED_OMOS_VERSION` against the resolved values.
|
||||||
|
|
||||||
### Step 2: `build-base` (conditional)
|
### Step 2: `build-base` (conditional)
|
||||||
|
|
||||||
Only runs when `need_build=true`. Multi-arch (amd64 + arm64) build of
|
Only runs when `need_build=true`. Multi-arch (amd64 + arm64) build of
|
||||||
|
|||||||
@@ -102,6 +102,42 @@ jobs:
|
|||||||
echo "Base tag ${IMAGE}:${{ steps.compute.outputs.base_tag }} missing — will build."
|
echo "Base tag ${IMAGE}:${{ steps.compute.outputs.base_tag }} missing — will build."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Phase 1b: resolve floating npm versions (pi, omos) to concrete
|
||||||
|
# versions so the variant build-args carry a different value when an
|
||||||
|
# upstream package bumps. Without this, when PI_VERSION / OMOS_VERSION
|
||||||
|
# default to 'latest', the docker/build-push-action build-arg string
|
||||||
|
# is byte-identical across builds, so the resulting layer-hash is
|
||||||
|
# identical, so the registry buildcache silently reuses the layer
|
||||||
|
# from whatever pi/omos version was current when the cache was first
|
||||||
|
# populated. Same class of bug as pi-devbox v0.74.0..v0.75.5 (fixed in
|
||||||
|
# v0.75.5b 2026-05-23). Currently masked here because OPENCODE_VERSION
|
||||||
|
# is hard-coded in Dockerfile.variant and bumps every release —
|
||||||
|
# invalidating the parent-chain cache key for the pi/omos layers — but
|
||||||
|
# that masking would fail the moment we cut a vN.N.Nb opencode-version-
|
||||||
|
# unchanged release that only bumps pi or omos. Fix is preventative.
|
||||||
|
resolve-versions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
outputs:
|
||||||
|
pi_version: ${{ steps.resolve.outputs.pi_version }}
|
||||||
|
omos_version: ${{ steps.resolve.outputs.omos_version }}
|
||||||
|
steps:
|
||||||
|
- name: Resolve pi + omos versions from npm registry
|
||||||
|
id: resolve
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
# Query the npm registry directly via curl+jq rather than `npm view`.
|
||||||
|
# catthehacker/ubuntu:act-latest ships Node/npm under /opt/acttoolcache/
|
||||||
|
# and adds it to PATH only via /etc/environment — which act_runner never
|
||||||
|
# sources (it reads the Docker image's ENV instructions, not /etc/environment).
|
||||||
|
# curl and jq are both guaranteed present in every job in this workflow.
|
||||||
|
PI_VERSION=$(curl -sf "https://registry.npmjs.org/@earendil-works%2Fpi-coding-agent/latest" | jq -r '.version')
|
||||||
|
OMOS_VERSION=$(curl -sf "https://registry.npmjs.org/oh-my-opencode-slim/latest" | jq -r '.version')
|
||||||
|
echo "pi_version=${PI_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "omos_version=${OMOS_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Resolved PI_VERSION=${PI_VERSION}, OMOS_VERSION=${OMOS_VERSION}"
|
||||||
|
|
||||||
# ── Phase 2: build & push base (multi-arch), only when needed ──────
|
# ── Phase 2: build & push base (multi-arch), only when needed ──────
|
||||||
build-base:
|
build-base:
|
||||||
needs: [base-decide]
|
needs: [base-decide]
|
||||||
@@ -211,10 +247,11 @@ jobs:
|
|||||||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-base --variant base
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-base --variant base
|
||||||
|
|
||||||
smoke-omos:
|
smoke-omos:
|
||||||
needs: [base-decide, build-base]
|
needs: [base-decide, build-base, resolve-versions]
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
needs.base-decide.result == 'success' &&
|
needs.base-decide.result == 'success' &&
|
||||||
|
needs.resolve-versions.result == 'success' &&
|
||||||
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
@@ -249,13 +286,17 @@ jobs:
|
|||||||
INSTALL_OPENCODE=true
|
INSTALL_OPENCODE=true
|
||||||
INSTALL_OMOS=true
|
INSTALL_OMOS=true
|
||||||
INSTALL_PI=false
|
INSTALL_PI=false
|
||||||
- run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
OMOS_VERSION=${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
|
- env:
|
||||||
|
EXPECTED_OMOS_VERSION: ${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
||||||
|
|
||||||
smoke-with-pi:
|
smoke-with-pi:
|
||||||
needs: [base-decide, build-base]
|
needs: [base-decide, build-base, resolve-versions]
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
needs.base-decide.result == 'success' &&
|
needs.base-decide.result == 'success' &&
|
||||||
|
needs.resolve-versions.result == 'success' &&
|
||||||
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
@@ -290,13 +331,17 @@ jobs:
|
|||||||
INSTALL_OPENCODE=true
|
INSTALL_OPENCODE=true
|
||||||
INSTALL_OMOS=false
|
INSTALL_OMOS=false
|
||||||
INSTALL_PI=true
|
INSTALL_PI=true
|
||||||
- run: bash scripts/smoke-test.sh opencode-devbox:smoke-with-pi --variant with-pi
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
- env:
|
||||||
|
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-with-pi --variant with-pi
|
||||||
|
|
||||||
smoke-omos-with-pi:
|
smoke-omos-with-pi:
|
||||||
needs: [base-decide, build-base]
|
needs: [base-decide, build-base, resolve-versions]
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
needs.base-decide.result == 'success' &&
|
needs.base-decide.result == 'success' &&
|
||||||
|
needs.resolve-versions.result == 'success' &&
|
||||||
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
(needs.build-base.result == 'success' || needs.build-base.result == 'skipped')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
@@ -331,7 +376,12 @@ jobs:
|
|||||||
INSTALL_OPENCODE=true
|
INSTALL_OPENCODE=true
|
||||||
INSTALL_OMOS=true
|
INSTALL_OMOS=true
|
||||||
INSTALL_PI=true
|
INSTALL_PI=true
|
||||||
- run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos-with-pi --variant omos-with-pi
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
OMOS_VERSION=${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
|
- env:
|
||||||
|
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_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
|
||||||
|
|
||||||
# ── Phase 4: multi-arch publish per variant ────────────────────────
|
# ── Phase 4: multi-arch publish per variant ────────────────────────
|
||||||
|
|
||||||
@@ -384,7 +434,7 @@ jobs:
|
|||||||
tags: ${{ steps.tags.outputs.tags }}
|
tags: ${{ steps.tags.outputs.tags }}
|
||||||
|
|
||||||
build-variant-omos:
|
build-variant-omos:
|
||||||
needs: [base-decide, smoke-omos]
|
needs: [base-decide, smoke-omos, resolve-versions]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
@@ -429,10 +479,11 @@ jobs:
|
|||||||
INSTALL_OPENCODE=true
|
INSTALL_OPENCODE=true
|
||||||
INSTALL_OMOS=true
|
INSTALL_OMOS=true
|
||||||
INSTALL_PI=false
|
INSTALL_PI=false
|
||||||
|
OMOS_VERSION=${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
tags: ${{ steps.tags.outputs.tags }}
|
tags: ${{ steps.tags.outputs.tags }}
|
||||||
|
|
||||||
build-variant-with-pi:
|
build-variant-with-pi:
|
||||||
needs: [base-decide, smoke-with-pi]
|
needs: [base-decide, smoke-with-pi, resolve-versions]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
@@ -477,10 +528,11 @@ jobs:
|
|||||||
INSTALL_OPENCODE=true
|
INSTALL_OPENCODE=true
|
||||||
INSTALL_OMOS=false
|
INSTALL_OMOS=false
|
||||||
INSTALL_PI=true
|
INSTALL_PI=true
|
||||||
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
tags: ${{ steps.tags.outputs.tags }}
|
tags: ${{ steps.tags.outputs.tags }}
|
||||||
|
|
||||||
build-variant-omos-with-pi:
|
build-variant-omos-with-pi:
|
||||||
needs: [base-decide, smoke-omos-with-pi]
|
needs: [base-decide, smoke-omos-with-pi, resolve-versions]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
@@ -525,6 +577,8 @@ jobs:
|
|||||||
INSTALL_OPENCODE=true
|
INSTALL_OPENCODE=true
|
||||||
INSTALL_OMOS=true
|
INSTALL_OMOS=true
|
||||||
INSTALL_PI=true
|
INSTALL_PI=true
|
||||||
|
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
OMOS_VERSION=${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
tags: ${{ steps.tags.outputs.tags }}
|
tags: ${{ steps.tags.outputs.tags }}
|
||||||
|
|
||||||
# ── Phase 5: promote base-<hash> → base-latest (manifest copy only) ─
|
# ── Phase 5: promote base-<hash> → base-latest (manifest copy only) ─
|
||||||
|
|||||||
@@ -70,8 +70,9 @@ cd /tmp && npm pack @earendil-works/pi-coding-agent@0.75.5 && tar -xzf earendil-
|
|||||||
Release-day checklist: README → (regenerate DOCKER_HUB.md only if HUB_TEMPLATE changed) → promote CHANGELOG Unreleased → grep AGENTS.md for stale counts → commit → tag → push tag.
|
Release-day checklist: README → (regenerate DOCKER_HUB.md only if HUB_TEMPLATE changed) → promote CHANGELOG Unreleased → grep AGENTS.md for stale counts → commit → tag → push tag.
|
||||||
|
|
||||||
**Between releases the same coupling applies.** Doc drift is not just a release-day concern — a workflow tweak, entrypoint change, or `generate-config.py` refactor can leave any of these four files lying. Before committing a non-release change, grep the docs for references to what you touched: `git diff --name-only HEAD | xargs -I{} grep -l 'thing-you-changed' README.md AGENTS.md DOCKER_HUB.md .gitea/README.md .env.example`. If a doc says "four variants" / "two phases" / "runs on amd64 only" and your change made that no longer true, fix it in the same commit.
|
**Between releases the same coupling applies.** Doc drift is not just a release-day concern — a workflow tweak, entrypoint change, or `generate-config.py` refactor can leave any of these four files lying. Before committing a non-release change, grep the docs for references to what you touched: `git diff --name-only HEAD | xargs -I{} grep -l 'thing-you-changed' README.md AGENTS.md DOCKER_HUB.md .gitea/README.md .env.example`. If a doc says "four variants" / "two phases" / "runs on amd64 only" and your change made that no longer true, fix it in the same commit.
|
||||||
- **GitHub/Gitea-sourced binaries float by default** — gosu, fzf, git-lfs, nvim, bat, eza, zoxide, uv, gitea-mcp, Go, oh-my-opencode-slim all default to `latest`. Each build-time install step reads the `/releases/latest` Location redirect (or the go.dev JSON feed for Go) and derives the concrete version. Use the same `ARCH` case-switch pattern for multi-arch support (amd64/arm64). Intentional pins: `OPENCODE_VERSION` (drives the image tag), `NODE_VERSION=22` (major pin), `DEBIAN_VERSION=trixie-slim` (OS base). Adding a new upstream tool: follow the existing floated-version pattern, don't hardcode a specific tag.
|
- **GitHub/Gitea-sourced binaries float by default** — gosu, fzf, git-lfs, gitleaks, nvim, bat, eza, zoxide, uv, gitea-mcp, Go, oh-my-opencode-slim all default to `latest`. Each build-time install step reads the `/releases/latest` Location redirect (or the go.dev JSON feed for Go) and derives the concrete version. Use the same `ARCH` case-switch pattern for multi-arch support (amd64/arm64) — mind project-specific arch-name deviations (gitleaks uses `x64`, bat/eza/zoxide use `x86_64`/`aarch64`, gosu uses `amd64`/`arm64`). Intentional pins: `OPENCODE_VERSION` (drives the image tag), `NODE_VERSION=22` (major pin), `DEBIAN_VERSION=trixie-slim` (OS base). Adding a new upstream tool: follow the existing floated-version pattern, don't hardcode a specific tag.
|
||||||
- **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.**
|
||||||
- **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.
|
||||||
|
|||||||
@@ -8,6 +8,48 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Base: SSH ControlMaster default on a writable socket path
|
||||||
|
|
||||||
|
Devboxes typically mount `~/.ssh` from the host as **read-only** (security: keys remain readable but agents can't tamper with config / known_hosts / authorized_keys / plant a malicious ProxyCommand). OpenSSH's default `ControlPath` lands inside `~/.ssh/cm/`, which is unwritable on such mounts — so any attempt to use `ControlMaster auto` (or anything that wants to multiplex) fails with:
|
||||||
|
|
||||||
|
```
|
||||||
|
unix_listener: cannot bind to path /home/.../.ssh/cm/...: Read-only file system
|
||||||
|
kex_exchange_identification: Connection closed by remote host
|
||||||
|
```
|
||||||
|
|
||||||
|
The second line is downstream: when ControlMaster fails the ssh client falls back to a fresh TCP connection, and on residential CGNAT (most European ISPs) the per-(src,dst) concurrent-flow cap (~4) silently drops further SYNs once exceeded — manifesting as banner-exchange timeouts that look like a remote problem.
|
||||||
|
|
||||||
|
- **`Dockerfile.base`** — new section right after the apt block bakes `/etc/ssh/ssh_config.d/00-devbox-controlmaster.conf` with `Host *` defaults: `ControlMaster auto`, `ControlPath /tmp/sshcm/%r@%h:%p`, `ControlPersist 10m`, plus `ServerAliveInterval 30` / `ServerAliveCountMax 6` for resilience to mid-stream NAT timeouts. `/tmp` is per-container and always writable, so the read-only `~/.ssh` mount is left untouched. Debian's stock `/etc/ssh/ssh_config` includes `ssh_config.d/*.conf` *before* its own `Host *` block, so user `~/.ssh/config` overrides still win.
|
||||||
|
- **`entrypoint-user.sh`** — creates `/tmp/sshcm` mode 700 on every container start. `/tmp` is per-container so the dir doesn't survive recreation; baking it into a Dockerfile layer would be wrong. Mode 700 is required — OpenSSH refuses to use a `ControlPath` directory others can write to.
|
||||||
|
- **`scripts/smoke-test.sh`** — two new assertions: (a) the conf file exists at the expected path; (b) `ssh -G example.invalid` reports a `controlpath` rooted at `/tmp/sshcm/`. The second catches the silent regression where something later in the SSH config chain shadows the bake-in.
|
||||||
|
- **No size/threshold impact:** the conf file is ~250 bytes.
|
||||||
|
|
||||||
|
Downstream pi-devbox and any other variant inherits this on its next build against `base-latest`. Discovered while running a recon-shell from inside pi-devbox to a Proxmox node — fresh ssh hit banner timeout, debug output pointed at the read-only socket dir.
|
||||||
|
|
||||||
|
### Base: gitleaks added; git-crypt confirmed already installed
|
||||||
|
|
||||||
|
`gitleaks` is now baked into `Dockerfile.base` (Go-compiled binary fetched from GitHub releases, same `/releases/latest` redirect-resolution pattern as gosu/fzf/git-lfs/etc.). It pairs with `git-crypt`, which has been installed via apt all along but wasn't asserted by smoke or called out in user-facing docs. Several of the user's repos use both as part of their secret-management setup (gitleaks pre-commit hook + git-crypt for selectively-encrypted canonical config); having them in the devbox means `pi install`-style hooks fire correctly inside the container instead of warning that gitleaks is missing.
|
||||||
|
|
||||||
|
- **`Dockerfile.base`** — new `GITLEAKS_VERSION=latest` ARG + install RUN block right after `git-lfs`. Arch suffix is `x64` (not `x86_64` or `amd64`) on this project; comment in the Dockerfile flags the deviation. Adds ~21 MB to the base layer.
|
||||||
|
- **`scripts/smoke-test.sh`** — adds `git-crypt` and `gitleaks` to the "Resolved component versions" table and to the "Core binaries" assertion list. Now fails fast if either binary disappears from the base.
|
||||||
|
- **`README.md`** — "What's in the image" tree updated to name `gitleaks` alongside `git-crypt` in the dev-tools line.
|
||||||
|
- **No threshold bumps:** 21 MB on a 2500–3700 MB envelope is noise; existing variant thresholds keep their headroom.
|
||||||
|
|
||||||
|
This is a base-layer change — `base-decide` will compute a fresh `base-<hash>`, `build-base` will run on the next release (no cache hit), and all four variants will rebuild against the new base. **Downstream pi-devbox** picks up gitleaks automatically on its next release that resolves `joakimp/opencode-devbox:base-latest` to the new digest — no Dockerfile change needed there.
|
||||||
|
|
||||||
|
### CI: preventative fix for PI_VERSION/OMOS_VERSION cache-hit silent regression
|
||||||
|
|
||||||
|
Mirrors the pi-devbox v0.75.5b fix (2026-05-23) onto the four-variant pipeline here. The `with-pi`, `omos`, and `omos-with-pi` variants all install upstream npm packages (`@earendil-works/pi-coding-agent`, `oh-my-opencode-slim`) whose `*_VERSION` build-args defaulted to `latest`. When the build-arg string is byte-identical across builds, the resulting layer-hash is identical, and the registry buildcache (`base-buildcache` / variant cache-from chain) silently reuses the layer from whatever upstream version was current when the cache was first populated — the same mechanism that caused pi-devbox v0.74.0 through v0.75.5 to ship the same image bytes.
|
||||||
|
|
||||||
|
Currently masked here because `OPENCODE_VERSION` is a hard-coded ARG that bumps every release — changing a parent layer invalidates the downstream cache key for the pi/omos install layers. Masking would fail the moment we cut a `vN.N.Nb` opencode-version-unchanged release that only bumps pi or omos. Filed as a parked followup that bedtime; fixing it preventatively now.
|
||||||
|
|
||||||
|
- **`.gitea/workflows/docker-publish-split.yml`** — new `resolve-versions` job runs `npm view @earendil-works/pi-coding-agent version` and `npm view oh-my-opencode-slim version`, exposing concrete strings as job outputs. All six affected jobs (`smoke-omos`, `smoke-with-pi`, `smoke-omos-with-pi`, `build-variant-omos`, `build-variant-with-pi`, `build-variant-omos-with-pi`) now `needs:` it and pass the concrete versions as `PI_VERSION` / `OMOS_VERSION` build-args. `smoke-base` and `build-variant-base` are unaffected (no pi or omos).
|
||||||
|
- **`scripts/smoke-test.sh`** — new `run_expect` helper asserts an expected substring in command output. The pi-version check uses `EXPECTED_PI_VERSION` when set; the omos check uses `EXPECTED_OMOS_VERSION` against `npm ls -g`. Both env vars are wired from `resolve-versions` outputs in the smoke jobs. Catches the regression on the next release rather than four releases later.
|
||||||
|
- **`Dockerfile.variant`** — comment block above each affected `ARG` (`OPENCODE_VERSION`, `PI_VERSION`, `OMOS_VERSION`) documenting the cache-hit footgun + which ones are CI-resolved vs source-pinned.
|
||||||
|
- **`AGENTS.md`** — new convention bullet explaining the cache-hit class of bug and naming the resolve-versions job + EXPECTED_*_VERSION wiring as the contract to keep in lockstep.
|
||||||
|
|
||||||
|
No image-content change expected on the next release vs what `latest` would have resolved to anyway — this is purely about making sure the cache invalidates correctly going forward.
|
||||||
|
|
||||||
## v1.15.10 — 2026-05-23
|
## v1.15.10 — 2026-05-23
|
||||||
|
|
||||||
opencode 1.15.6 → 1.15.10 bump (four upstream patch releases over two days). Plus implicit pi 0.75.4 → 0.75.5 in the `with-pi` and `omos-with-pi` variants since `PI_VERSION=latest` resolves at build time.
|
opencode 1.15.6 → 1.15.10 bump (four upstream patch releases over two days). Plus implicit pi 0.75.4 → 0.75.5 in the `with-pi` and `omos-with-pi` variants since `PI_VERSION=latest` resolves at build time.
|
||||||
|
|||||||
@@ -71,6 +71,44 @@ RUN apt-get update && \
|
|||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ── SSH client defaults: ControlMaster on a writable socket path ──────
|
||||||
|
# Why this exists: the devbox typically mounts ~/.ssh from the host as
|
||||||
|
# read-only (security: keys are readable, but agents can't tamper with
|
||||||
|
# config / known_hosts / authorized_keys / plant a malicious ProxyCommand).
|
||||||
|
# OpenSSH's default ControlPath is ~/.ssh/cm/... which is unwritable on
|
||||||
|
# such mounts, so any attempt to use ControlMaster fails. Symptoms:
|
||||||
|
# unix_listener: cannot bind to path /home/.../.ssh/cm/...: Read-only file system
|
||||||
|
# kex_exchange_identification: Connection closed by remote host
|
||||||
|
# The latter manifests downstream of CGNAT per-destination flow caps
|
||||||
|
# (~4 concurrent flows on most European residential ISPs) which silently
|
||||||
|
# drop further SYNs once exceeded — making fresh ssh attempts fail with
|
||||||
|
# banner-exchange timeouts that look like a remote problem.
|
||||||
|
#
|
||||||
|
# Fix: set a system-wide default ControlPath in /tmp (per-container,
|
||||||
|
# tmpfs-friendly, always writable) so multiplexing Just Works without
|
||||||
|
# touching the read-only ~/.ssh mount. Per-host overrides in user's
|
||||||
|
# ~/.ssh/config still win — Debian's default /etc/ssh/ssh_config has
|
||||||
|
# `Include /etc/ssh/ssh_config.d/*.conf` *before* the `Host *` block,
|
||||||
|
# so user config can override these defaults if desired.
|
||||||
|
#
|
||||||
|
# ControlPersist=10m means the master socket sticks around 10 min after
|
||||||
|
# the last session closes, so consecutive ssh calls in a workflow reuse
|
||||||
|
# the same TCP flow. Companion entrypoint-user.sh creates /tmp/sshcm
|
||||||
|
# (mode 700) on each container start.
|
||||||
|
RUN mkdir -p /etc/ssh/ssh_config.d && \
|
||||||
|
printf '%s\n' \
|
||||||
|
'# Devbox-baked default. See Dockerfile.base "SSH client defaults".' \
|
||||||
|
'# Override per-host in ~/.ssh/config if the master socket location' \
|
||||||
|
'# needs to differ.' \
|
||||||
|
'Host *' \
|
||||||
|
' ControlMaster auto' \
|
||||||
|
' ControlPath /tmp/sshcm/%r@%h:%p' \
|
||||||
|
' ControlPersist 10m' \
|
||||||
|
' ServerAliveInterval 30' \
|
||||||
|
' ServerAliveCountMax 6' \
|
||||||
|
> /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf && \
|
||||||
|
chmod 644 /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf
|
||||||
|
|
||||||
# ── Go-compiled tools (install from GitHub to avoid CVEs in Debian's old Go builds)
|
# ── Go-compiled tools (install from GitHub to avoid CVEs in Debian's old Go builds)
|
||||||
#
|
#
|
||||||
# Version policy for the binaries below:
|
# Version policy for the binaries below:
|
||||||
@@ -126,6 +164,24 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;;
|
|||||||
git lfs install --system && \
|
git lfs install --system && \
|
||||||
git-lfs --version
|
git-lfs --version
|
||||||
|
|
||||||
|
# gitleaks — secret scanner (used as a pre-commit hook in several of the
|
||||||
|
# repos this devbox is meant to operate on; pairs with git-crypt below).
|
||||||
|
# Distributed as a Go-compiled tarball; arch suffix is `x64` (not `x86_64`
|
||||||
|
# or `amd64`) on this project — mind the deviation from the surrounding
|
||||||
|
# tools' naming.
|
||||||
|
ARG GITLEAKS_VERSION=latest
|
||||||
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x64" ;; arm64) echo "arm64" ;; *) echo "x64" ;; esac) && \
|
||||||
|
V="${GITLEAKS_VERSION}" && \
|
||||||
|
if [ "$V" = "latest" ]; then \
|
||||||
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/gitleaks/gitleaks/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
||||||
|
fi && \
|
||||||
|
V="${V#v}" && \
|
||||||
|
[ -n "$V" ] && \
|
||||||
|
echo "Installing gitleaks ${V}" && \
|
||||||
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/gitleaks/gitleaks/releases/download/v${V}/gitleaks_${V}_linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin gitleaks && \
|
||||||
|
chmod +x /usr/local/bin/gitleaks && \
|
||||||
|
gitleaks version
|
||||||
|
|
||||||
# neovim — modern text editor
|
# neovim — modern text editor
|
||||||
ARG NVIM_VERSION=latest
|
ARG NVIM_VERSION=latest
|
||||||
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ ARG TARGETARCH
|
|||||||
ARG USER_NAME=developer
|
ARG USER_NAME=developer
|
||||||
|
|
||||||
# ── Install opencode via npm ─────────────────────────────────────────
|
# ── Install opencode via npm ─────────────────────────────────────────
|
||||||
|
# OPENCODE_VERSION is intentionally pinned in this Dockerfile (not
|
||||||
|
# 'latest'). It drives the release tag and gets bumped via a source
|
||||||
|
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
||||||
|
# v0.75.5 cannot apply here.
|
||||||
ARG INSTALL_OPENCODE=true
|
ARG INSTALL_OPENCODE=true
|
||||||
ARG OPENCODE_VERSION=1.15.10
|
ARG OPENCODE_VERSION=1.15.10
|
||||||
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
||||||
@@ -42,6 +46,18 @@ RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
|||||||
# pi-toolkit and pi-extensions are cloned into /opt/. entrypoint-user.sh
|
# pi-toolkit and pi-extensions are cloned into /opt/. entrypoint-user.sh
|
||||||
# runs each repo's install.sh on container start so symlinks land under
|
# runs each repo's install.sh on container start so symlinks land under
|
||||||
# ~/.pi/agent/ on the named volume.
|
# ~/.pi/agent/ on the named volume.
|
||||||
|
# PI_VERSION should be passed explicitly by CI as a concrete version
|
||||||
|
# (resolved from `npm view @earendil-works/pi-coding-agent version`,
|
||||||
|
# see .gitea/workflows/docker-publish-split.yml § resolve-versions).
|
||||||
|
# The default `latest` is for local dev convenience only — it has a
|
||||||
|
# known cache-hit footgun when used in registry-cached CI builds: the
|
||||||
|
# resulting build-arg string is byte-identical across builds, the
|
||||||
|
# layer-hash is identical, and the registry buildcache silently reuses
|
||||||
|
# the layer from whatever pi version was current when the cache was
|
||||||
|
# first populated. Currently masked here because OPENCODE_VERSION (a
|
||||||
|
# parent layer) bumps every release; will manifest the moment a
|
||||||
|
# vN.N.Nb opencode-version-unchanged release ships. See pi-devbox
|
||||||
|
# v0.75.5b 2026-05-23 for the discovery + canonical fix.
|
||||||
ARG INSTALL_PI=false
|
ARG INSTALL_PI=false
|
||||||
ARG PI_VERSION=latest
|
ARG PI_VERSION=latest
|
||||||
ARG PI_TOOLKIT_REF=main
|
ARG PI_TOOLKIT_REF=main
|
||||||
@@ -89,6 +105,10 @@ RUN if [ "${INSTALL_GO}" = "true" ]; then \
|
|||||||
|
|
||||||
# ── Optional: oh-my-opencode-slim (multi-agent orchestration) ────────
|
# ── Optional: oh-my-opencode-slim (multi-agent orchestration) ────────
|
||||||
# Installs Bun runtime and the oh-my-opencode-slim npm package.
|
# Installs Bun runtime and the oh-my-opencode-slim npm package.
|
||||||
|
# OMOS_VERSION shares the same cache-hit footgun as PI_VERSION when
|
||||||
|
# left at the `latest` default in registry-cached CI builds. CI
|
||||||
|
# resolves it via `npm view oh-my-opencode-slim version` and passes
|
||||||
|
# the concrete value as a build-arg. See PI_VERSION block above.
|
||||||
ARG INSTALL_OMOS=false
|
ARG INSTALL_OMOS=false
|
||||||
ARG OMOS_VERSION=latest
|
ARG OMOS_VERSION=latest
|
||||||
RUN if [ "${INSTALL_OMOS}" = "true" ]; then \
|
RUN if [ "${INSTALL_OMOS}" = "true" ]; then \
|
||||||
|
|||||||
@@ -762,7 +762,7 @@ Container (Debian trixie)
|
|||||||
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
|
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
|
||||||
├── AWS CLI v2 (SSO + Bedrock auth)
|
├── AWS CLI v2 (SSO + Bedrock auth)
|
||||||
├── neovim 0.12, tmux, htop, bat, eza, zoxide, uv, rustup, make, gcc, g++, rsync
|
├── neovim 0.12, tmux, htop, bat, eza, zoxide, uv, rustup, make, gcc, g++, rsync
|
||||||
├── git, git-crypt, age, ssh, ripgrep, fd, fzf, jq, curl, tree
|
├── git, git-crypt, age, gitleaks, ssh, ripgrep, fd, fzf, jq, curl, tree
|
||||||
├── Node.js (for MCP servers)
|
├── Node.js (for MCP servers)
|
||||||
├── Bun (optional — included with oh-my-opencode-slim)
|
├── Bun (optional — included with oh-my-opencode-slim)
|
||||||
├── entrypoint.sh (UID adjustment, git config, provider setup)
|
├── entrypoint.sh (UID adjustment, git config, provider setup)
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── SSH ControlMaster socket dir ────────────────────────────────
|
||||||
|
# Companion to /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf in the
|
||||||
|
# base image — that file declares ControlPath=/tmp/sshcm/%r@%h:%p; this
|
||||||
|
# creates the directory with the right permissions on every container
|
||||||
|
# start. /tmp is per-container so the dir doesn't survive recreation;
|
||||||
|
# baking it into a Dockerfile layer would be wrong.
|
||||||
|
# Mode 700 is required — OpenSSH refuses to use a ControlPath dir that
|
||||||
|
# others can write to.
|
||||||
|
mkdir -p /tmp/sshcm
|
||||||
|
chmod 700 /tmp/sshcm
|
||||||
|
|
||||||
# ── Shell defaults: copy baked files from /etc/skel-devbox/ if absent
|
# ── Shell defaults: copy baked files from /etc/skel-devbox/ if absent
|
||||||
# Respects host bind-mounts and user customizations — existing files
|
# Respects host bind-mounts and user customizations — existing files
|
||||||
# are never overwritten. To restore defaults: rm ~/.bash_aliases (or
|
# are never overwritten. To restore defaults: rm ~/.bash_aliases (or
|
||||||
|
|||||||
@@ -43,6 +43,25 @@ run() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Stricter version of `run` that also asserts an expected substring in
|
||||||
|
# the command's stdout. Used to catch the "image bytes silently identical
|
||||||
|
# to previous release" class of regression — Docker layer-cache hit on
|
||||||
|
# a bare `npm install -g <pkg>` (or @latest) because the build-arg
|
||||||
|
# string is identical across builds, even when 'latest' would have
|
||||||
|
# resolved differently. Discovered in pi-devbox 2026-05-23 (every
|
||||||
|
# release v0.74.0..v0.75.5 shipped the same image bytes); preventatively
|
||||||
|
# applied here for PI_VERSION + OMOS_VERSION.
|
||||||
|
run_expect() {
|
||||||
|
local label="$1"; local cmd="$2"; local expect="$3"
|
||||||
|
local out
|
||||||
|
out=$(docker run --rm --entrypoint="" "$IMAGE" sh -c "$cmd" 2>&1) || true
|
||||||
|
if echo "$out" | grep -Fq "$expect"; then
|
||||||
|
pass "$label (got $expect)"
|
||||||
|
else
|
||||||
|
fail "$label — expected substring '$expect', got: $out"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
echo "=== Smoke test: $IMAGE (variant: $VARIANT) ==="
|
echo "=== Smoke test: $IMAGE (variant: $VARIANT) ==="
|
||||||
echo
|
echo
|
||||||
echo "-- Resolved component versions --"
|
echo "-- Resolved component versions --"
|
||||||
@@ -68,6 +87,8 @@ docker run --rm --entrypoint="" "$IMAGE" sh -c '
|
|||||||
printf " %-15s %s\n" "rg" "$(rg --version | head -1)"
|
printf " %-15s %s\n" "rg" "$(rg --version | head -1)"
|
||||||
printf " %-15s %s\n" "gosu" "$(gosu --version)"
|
printf " %-15s %s\n" "gosu" "$(gosu --version)"
|
||||||
printf " %-15s %s\n" "git-lfs" "$(git-lfs --version)"
|
printf " %-15s %s\n" "git-lfs" "$(git-lfs --version)"
|
||||||
|
printf " %-15s %s\n" "git-crypt" "$(git-crypt --version 2>&1 | head -1)"
|
||||||
|
printf " %-15s %s\n" "gitleaks" "$(gitleaks version 2>&1 | head -1)"
|
||||||
printf " %-15s %s\n" "gitea-mcp" "$(gitea-mcp --version 2>&1 | head -1)"
|
printf " %-15s %s\n" "gitea-mcp" "$(gitea-mcp --version 2>&1 | head -1)"
|
||||||
printf " %-15s %s\n" "aws" "$(aws --version 2>&1)"
|
printf " %-15s %s\n" "aws" "$(aws --version 2>&1)"
|
||||||
if command -v bun >/dev/null 2>&1; then
|
if command -v bun >/dev/null 2>&1; then
|
||||||
@@ -103,11 +124,20 @@ run "fzf" "fzf --version"
|
|||||||
run "fd" "fd --version"
|
run "fd" "fd --version"
|
||||||
run "rg" "rg --version | head -1"
|
run "rg" "rg --version | head -1"
|
||||||
run "jq" "jq --version"
|
run "jq" "jq --version"
|
||||||
|
run "git-crypt" "git-crypt --version | head -1"
|
||||||
|
run "gitleaks" "gitleaks version"
|
||||||
run "aws" "aws --version"
|
run "aws" "aws --version"
|
||||||
run "gitea-mcp" "gitea-mcp --version"
|
run "gitea-mcp" "gitea-mcp --version"
|
||||||
run "gosu" "gosu --version"
|
run "gosu" "gosu --version"
|
||||||
run "tmux" "tmux -V"
|
run "tmux" "tmux -V"
|
||||||
|
|
||||||
|
# SSH ControlMaster baked defaults: the config file must exist (image-level)
|
||||||
|
# and ssh -G must report ControlPath rooted at /tmp/sshcm/ for an arbitrary
|
||||||
|
# host. Catches both regressions: someone removing the conf file, OR something
|
||||||
|
# else later in the config chain shadowing the ControlPath setting.
|
||||||
|
run "ssh-config-cm-file" "test -f /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf"
|
||||||
|
run_expect "ssh-config-cm-path" "ssh -G example.invalid 2>/dev/null | grep -i ^controlpath" "/tmp/sshcm/"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "-- Optional / variant-gated --"
|
echo "-- Optional / variant-gated --"
|
||||||
# mempalace: present unless built with INSTALL_MEMPALACE=false
|
# mempalace: present unless built with INSTALL_MEMPALACE=false
|
||||||
@@ -134,7 +164,11 @@ fi
|
|||||||
# entrypoint-user.sh on first start, so we test by running the entry
|
# entrypoint-user.sh on first start, so we test by running the entry
|
||||||
# point chain (not just `docker run --entrypoint=""`).
|
# point chain (not just `docker run --entrypoint=""`).
|
||||||
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v pi" >/dev/null 2>&1; then
|
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v pi" >/dev/null 2>&1; then
|
||||||
|
if [ -n "${EXPECTED_PI_VERSION:-}" ]; then
|
||||||
|
run_expect "pi version matches build-arg" "pi --version" "$EXPECTED_PI_VERSION"
|
||||||
|
else
|
||||||
run "pi" "pi --version"
|
run "pi" "pi --version"
|
||||||
|
fi
|
||||||
run "pi-toolkit clone" "test -d /opt/pi-toolkit && git -C /opt/pi-toolkit rev-parse --short HEAD"
|
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"
|
run "pi-extensions clone" "test -d /opt/pi-extensions && git -C /opt/pi-extensions rev-parse --short HEAD"
|
||||||
|
|
||||||
@@ -192,6 +226,11 @@ if [ "$VARIANT" = "omos" ] || [ "$VARIANT" = "omos-with-pi" ]; then
|
|||||||
# queries the user prefix and would miss the baked binaries even though
|
# queries the user prefix and would miss the baked binaries even though
|
||||||
# they're correctly on PATH at /usr/bin.
|
# they're correctly on PATH at /usr/bin.
|
||||||
run "oh-my-opencode-slim" "NPM_CONFIG_PREFIX=/usr npm ls -g --depth=0 2>/dev/null | grep oh-my-opencode-slim"
|
run "oh-my-opencode-slim" "NPM_CONFIG_PREFIX=/usr npm ls -g --depth=0 2>/dev/null | grep oh-my-opencode-slim"
|
||||||
|
if [ -n "${EXPECTED_OMOS_VERSION:-}" ]; then
|
||||||
|
run_expect "omos version matches build-arg" \
|
||||||
|
"NPM_CONFIG_PREFIX=/usr npm ls -g --depth=0 2>/dev/null | grep oh-my-opencode-slim" \
|
||||||
|
"$EXPECTED_OMOS_VERSION"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v bun" >/dev/null 2>&1; then
|
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v bun" >/dev/null 2>&1; then
|
||||||
fail "bun should NOT be in base image but was found"
|
fail "bun should NOT be in base image but was found"
|
||||||
|
|||||||
Reference in New Issue
Block a user