37960186c6
Not tagged yet; this is the v2.9.0 changeset landing on main. Added - agent-browser + a Playwright-managed headless Chromium in the base (~625 MB after deleting the redundant chromium_headless_shell build), so an agent can drive a real browser and VERIFY front-end work instead of assuming it renders. Ported from pi-devbox. AGENT_BROWSER_EXECUTABLE_PATH points at the stable symlink /usr/local/bin/agent-chrome, which the Dockerfile resolves with `find` rather than hardcoding: Playwright's browser dir is per-version AND per-arch (chrome-linux on arm64, chrome-linux64 on amd64), and the headless shell binary is named chrome-headless-shell so `-name chrome` skips it. - opencode-devbox-version: a reader for the build manifest. The image has baked ground truth to /etc/opencode-devbox/build-manifest.json for several releases, but nothing read it and nothing printed it — so "which image am I running?" meant knowing the path by heart. Three modes (--json/--quiet/human), plus a live-vs-baked drift check, because NPM_CONFIG_PREFIX points at the persistent config volume and a user `npm install -g opencode` can shadow the baked binary. entrypoint-user.sh prints it as its first output. - ENV COLORTERM=truecolor, completing a true-colour story the image already half-shipped (terminfo entries + Neovim termguicolors, but no capability advertisement, so bat/delta fell back to 256 colours). - Smoke assertions for agent-browser, that agent-chrome resolves to an executable (catches a Playwright layout change, not just a dangling symlink), COLORTERM, the manifest's release_tag, and all three version-command modes. Changed - opencode 1.17.20 -> 1.18.13. Verified by diffing upstream source, not release notes: core config.ts, config/provider.ts and schema.json are byte-identical, so generate-config.py needs no change. 1.18.13 (published mid-audit) was re-verified separately — 249 files in the compare payload, under GitHub's 300-file cap, so the list is complete rather than truncated; content is the Electron app plus localisation; the five contract-surface files hash identical at both tags. The bg-subagents removal trigger has NOT fired: runtime-flags.ts still gates the flag behind OPENCODE_EXPERIMENTAL at all three tags. - yq: dropped Debian's apt package (the unrelated Python kislyuk/yq — jq syntax, 3.x line) for mikefarah's Go yq v4 from GitHub. The cloud-init repo's provision.sh/deploy.sh need v4 syntax, and THIRD_PARTY.md already credited "yq (mikefarah)" while the image shipped the Python one, so this also closes a documented-vs-shipped mismatch. Smoke pins the contract to mikefarah v4. BEHAVIOUR CHANGE for any in-image script calling yq with jq-style syntax. - mempalace pin 3.5.0 -> 3.6.0, in lockstep with pi-devbox (5724302). Reviewed for MCP tool-schema changes before bumping — none, and nothing touches diary_write. - Default models -> claude-opus-5 (anthropic, and bedrock's global.anthropic.claude-opus-5) and openai/gpt-5.6. gpt-5.4 had gone stale: gpt-5.6 shipped four days before the v2.8.0 cut. Affects only new containers with no OPENCODE_MODEL and no existing config. - Smoke size thresholds +650 MB (base 2950->3600, omos 3650->4300), sized to keep the same ~250 MB headroom so the guardrail still catches runaway growth rather than routine apt drift. Do NOT copy pi-devbox's number: it sums `docker history`, this repo uses `docker image inspect .Size`. Documentation - New README section "Choosing a provider and model", making explicit that the baked defaults are only defaults and nobody is locked to Anthropic/Bedrock, including the three real gotchas: defaults seed only a NEW config, an existing opencode.jsonc on the persistent volume is never rewritten, and switching model needs no rebuild. - New README section "Browser automation (agent-browser)"; opencode-devbox-version documented under Build provenance; COLORTERM under Terminal compatibility. - README Build Args table drift fixed — FOUR missing args added (AGENT_BROWSER_VERSION, PLAYWRIGHT_VERSION, YQ_VERSION and GITLEAKS_VERSION, the last of which had existed as an ARG but was never listed), plus rows for the two pinned args absent entirely (MEMPALACE_VERSION, DEBIAN_VERSION), plus a refreshed stale OPENCODE_VERSION example. Third consecutive release to find drift in this table. - AGENTS.md: the stale MemPalace anyOf convention rewritten. It described a perl RUN block already DELETED at the 3.5.0 bump and asserted "PyPI latest is 3.4.0 (== our pin), no release contains the fix yet, the workaround must stay" — all three false. Replaced with a pin-review rule. Two new conventions added: the agent-browser/Chromium size coupling, and the yq identity trap. - THIRD_PARTY.md: agent-browser, Playwright, Chromium. Verified locally with the CI-pinned hadolint 2.14.0 and actionlint 1.7.7, the shell guard, DOCKER_HUB.md sync, bash -n, py_compile, and by generating the config for all three providers.
149 lines
7.3 KiB
Docker
149 lines
7.3 KiB
Docker
# opencode-devbox — variant image
|
|
#
|
|
# FROMs a base-<hash> image produced by Dockerfile.base and adds only
|
|
# the variant-specific tools (opencode, pi, oh-my-opencode-slim, Go).
|
|
#
|
|
# The two published variants are produced from THIS Dockerfile by
|
|
# varying build args:
|
|
#
|
|
# variant INSTALL_OPENCODE INSTALL_OMOS
|
|
# ──────── ──────────────── ────────────
|
|
# base true false
|
|
# omos true true
|
|
#
|
|
# pi was removed in v2.0.0 (it had been deprecated since v1.17.2). It now
|
|
# ships from its own self-contained image: joakimp/pi-devbox:latest
|
|
# (https://gitea.jordbo.se/joakimp/pi-devbox). See docs/CLEANUP-v2.0.0.md
|
|
# for the removal history.
|
|
#
|
|
# Pass `--build-arg BASE_IMAGE=<repo>:base-<hash>` to select the base.
|
|
# The CI workflow computes the base hash from Dockerfile.base + rootfs/
|
|
# + entrypoint*.sh and feeds it in.
|
|
#
|
|
# IMPORTANT: the base image sets NPM_CONFIG_PREFIX to
|
|
# /home/developer/.config/opencode/npm-global so runtime `npm install -g`
|
|
# by the developer user lands on the named volume. At BUILD time we want
|
|
# the baked binaries on /usr so they survive the volume mount. Each
|
|
# `npm install -g` below therefore prefixes the command with
|
|
# `NPM_CONFIG_PREFIX=/usr`.
|
|
|
|
ARG BASE_IMAGE
|
|
FROM ${BASE_IMAGE}
|
|
|
|
ARG TARGETARCH
|
|
ARG USER_NAME=developer
|
|
|
|
# ── 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 OPENCODE_VERSION=1.18.13
|
|
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
|
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
|
opencode --version ; \
|
|
fi
|
|
|
|
# ── Optional: Go ─────────────────────────────────────────────────────
|
|
ARG INSTALL_GO=false
|
|
ARG GO_VERSION=latest
|
|
RUN if [ "${INSTALL_GO}" = "true" ]; then \
|
|
GOARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
|
|
V="${GO_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://go.dev/dl/?mode=json" | \
|
|
awk -F'"' '/"version":/ { sub(/^go/,"",$4); print $4; exit }'); \
|
|
fi && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing Go ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://go.dev/dl/go${V}.linux-${GOARCH}.tar.gz" | tar -C /usr/local -xz && \
|
|
ln -s /usr/local/go/bin/go /usr/local/bin/go && \
|
|
ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \
|
|
fi
|
|
|
|
# ── Optional: oh-my-opencode-slim (multi-agent orchestration) ────────
|
|
# Installs Bun runtime and the oh-my-opencode-slim npm package.
|
|
# OMOS_VERSION has a cache-hit footgun when left at the `latest` default
|
|
# in registry-cached CI builds: the resulting build-arg string is byte-
|
|
# identical across builds, so the layer-hash is identical, so the
|
|
# registry buildcache silently reuses the layer from whatever omos
|
|
# version was current when the cache was first populated. CI resolves it
|
|
# via `npm view oh-my-opencode-slim version` and passes the concrete
|
|
# value as a build-arg (see resolve-versions in docker-publish-split.yml).
|
|
ARG INSTALL_OMOS=false
|
|
ARG OMOS_VERSION=latest
|
|
RUN if [ "${INSTALL_OMOS}" = "true" ]; then \
|
|
ARCH=$(uname -m) && \
|
|
if [ "$ARCH" = "x86_64" ]; then \
|
|
BUN_ARCH="x64-baseline"; \
|
|
elif [ "$ARCH" = "aarch64" ]; then \
|
|
BUN_ARCH="aarch64"; \
|
|
fi && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-${BUN_ARCH}.zip" -o /tmp/bun.zip && \
|
|
unzip -o /tmp/bun.zip -d /tmp/bun && \
|
|
mv /tmp/bun/bun-linux-${BUN_ARCH}/bun /usr/local/bin/bun && \
|
|
chmod +x /usr/local/bin/bun && \
|
|
ln -sf bun /usr/local/bin/bunx && \
|
|
rm -rf /tmp/bun /tmp/bun.zip && \
|
|
bun --version && \
|
|
test -L /usr/local/bin/bunx && \
|
|
NPM_CONFIG_PREFIX=/usr npm install -g oh-my-opencode-slim@${OMOS_VERSION}; \
|
|
fi
|
|
|
|
# ── Build provenance: OCI labels + on-disk manifest ──────────────────
|
|
# These ARGs are declared LAST, immediately before the layer that uses
|
|
# them, so a changing BUILD_DATE / RELEASE_TAG / SOURCE_REVISION never
|
|
# invalidates the expensive npm-install layers above. OPENCODE_VERSION,
|
|
# OMOS_VERSION and INSTALL_OMOS are already in scope from earlier in this
|
|
# stage and need no re-declaration; MEMPALACE_TOOLKIT_REF is consumed in
|
|
# Dockerfile.base, so it is re-declared here only to land in the labels.
|
|
ARG RELEASE_TAG=dev
|
|
ARG BUILD_DATE=
|
|
ARG SOURCE_REVISION=
|
|
ARG MEMPALACE_TOOLKIT_REF=main
|
|
|
|
LABEL org.opencontainers.image.version="${RELEASE_TAG}" \
|
|
org.opencontainers.image.revision="${SOURCE_REVISION}" \
|
|
org.opencontainers.image.created="${BUILD_DATE}" \
|
|
se.jordbo.opencode-devbox.opencode-version="${OPENCODE_VERSION}" \
|
|
se.jordbo.opencode-devbox.install-omos="${INSTALL_OMOS}" \
|
|
se.jordbo.opencode-devbox.omos-version="${OMOS_VERSION}" \
|
|
se.jordbo.opencode-devbox.mempalace-toolkit-ref="${MEMPALACE_TOOLKIT_REF}"
|
|
|
|
# The manifest is written from GROUND TRUTH — the live `opencode --version`,
|
|
# the omos package's installed version (when present), and the actual
|
|
# checked-out HEAD of /opt/mempalace-toolkit (cloned in the base) — not
|
|
# merely the intended build-args. That way it also exposes a dependency
|
|
# that silently resolved to something other than the requested value.
|
|
# oh-my-opencode-slim is present only in the omos variant (JSON null
|
|
# otherwise). NOTE: omos is installed under prefix /usr at build time, so
|
|
# we resolve its dir via `npm root -g` with that prefix rather than the
|
|
# runtime NPM_CONFIG_PREFIX the base sets for the developer volume.
|
|
RUN set -e; \
|
|
mkdir -p /etc/opencode-devbox; \
|
|
rev() { git -C "$1" rev-parse HEAD 2>/dev/null || echo "unknown"; }; \
|
|
OPENCODE_V="$(opencode --version 2>/dev/null | head -n1 | tr -d '\r\n')"; \
|
|
OMOS_REV='null'; \
|
|
if [ "${INSTALL_OMOS}" = "true" ]; then \
|
|
OMOS_DIR="$(NPM_CONFIG_PREFIX=/usr npm root -g 2>/dev/null)/oh-my-opencode-slim"; \
|
|
OMOS_V="$(node -e "process.stdout.write(require('${OMOS_DIR}/package.json').version)" 2>/dev/null || echo unknown)"; \
|
|
OMOS_REV="\"${OMOS_V}\""; \
|
|
fi; \
|
|
{ \
|
|
echo '{'; \
|
|
echo " \"release_tag\": \"${RELEASE_TAG}\","; \
|
|
echo " \"build_date\": \"${BUILD_DATE}\","; \
|
|
echo " \"source_revision\": \"${SOURCE_REVISION}\","; \
|
|
echo " \"opencode_version\": \"${OPENCODE_V}\","; \
|
|
echo " \"components\": {"; \
|
|
echo " \"opencode\": \"${OPENCODE_V}\","; \
|
|
echo " \"oh-my-opencode-slim\": ${OMOS_REV},"; \
|
|
echo " \"mempalace-toolkit\": \"$(rev /opt/mempalace-toolkit)\""; \
|
|
echo " }"; \
|
|
echo '}'; \
|
|
} > /etc/opencode-devbox/build-manifest.json; \
|
|
echo "── build manifest ──"; cat /etc/opencode-devbox/build-manifest.json
|
|
|
|
# WORKDIR / ENTRYPOINT / CMD inherited from base.
|