Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f46c4ed017 | |||
| bf811f2170 | |||
| 23bf383a37 | |||
| 5006b01170 | |||
| f51e9f52a1 |
@@ -67,3 +67,28 @@ SSH_KEY_PATH=~/.ssh
|
|||||||
# OMOS_TMUX=false # Enable tmux multiplexer integration
|
# OMOS_TMUX=false # Enable tmux multiplexer integration
|
||||||
# OMOS_SKILLS=true # Install recommended skills (simplify, agent-browser, cartography)
|
# OMOS_SKILLS=true # Install recommended skills (simplify, agent-browser, cartography)
|
||||||
# OMOS_RESET=false # Force regenerate oh-my-opencode-slim config on next start
|
# OMOS_RESET=false # Force regenerate oh-my-opencode-slim config on next start
|
||||||
|
|
||||||
|
# ── pi coding-agent (alternative/complementary harness) ─────────────────
|
||||||
|
# Requires image built with INSTALL_PI=true.
|
||||||
|
# When the image is built with both INSTALL_OPENCODE=true (default) and
|
||||||
|
# INSTALL_PI=true, both harnesses share the same mempalace install and
|
||||||
|
# palace path — wing data is mutually visible to either harness.
|
||||||
|
#
|
||||||
|
# Pi version is baked at build time via PI_VERSION (default: latest at
|
||||||
|
# build). `pi update` inside the container would write to the npm global
|
||||||
|
# prefix, which is not on a named volume — updates do not persist across
|
||||||
|
# `--rm` containers. Rebuild the image to upgrade pi.
|
||||||
|
#
|
||||||
|
# Pi config (settings.json, extensions toggle state) persists in the
|
||||||
|
# devbox-pi-config named volume mounted at ~/.pi/.
|
||||||
|
#
|
||||||
|
# To launch pi from a `compose run` invocation:
|
||||||
|
# docker compose run --rm devbox pi
|
||||||
|
# To attach to a running container:
|
||||||
|
# docker compose exec -u developer devbox pi
|
||||||
|
# Default `compose run` (no args) drops to bash; pick the harness yourself.
|
||||||
|
#
|
||||||
|
# Build args (set in docker-compose.yml or via --build-arg on docker build):
|
||||||
|
# INSTALL_PI=true # default false; opt-in
|
||||||
|
# PI_VERSION=latest # pin a specific version, e.g. 0.73.0
|
||||||
|
# INSTALL_OPENCODE=false # build a pi-only image (still has Bun in -omos)
|
||||||
|
|||||||
@@ -164,6 +164,115 @@ jobs:
|
|||||||
- name: Smoke test (amd64)
|
- name: Smoke test (amd64)
|
||||||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
||||||
|
|
||||||
|
smoke-with-pi:
|
||||||
|
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 and load amd64 image for smoke test
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
build-args: |
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: opencode-devbox:smoke-with-pi
|
||||||
|
|
||||||
|
- name: Smoke test (amd64)
|
||||||
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-with-pi --variant with-pi
|
||||||
|
|
||||||
|
smoke-omos-with-pi:
|
||||||
|
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 and load amd64 image for smoke test
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
build-args: |
|
||||||
|
INSTALL_OMOS=true
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: opencode-devbox:smoke-omos-with-pi
|
||||||
|
|
||||||
|
- name: Smoke test (amd64)
|
||||||
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos-with-pi --variant omos-with-pi
|
||||||
|
|
||||||
# ── Multi-arch push (single job per variant, comma-separated platforms) ─
|
# ── Multi-arch push (single job per variant, comma-separated platforms) ─
|
||||||
build-base:
|
build-base:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -300,9 +409,144 @@ jobs:
|
|||||||
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos
|
||||||
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos
|
||||||
|
|
||||||
|
build-with-pi:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: smoke-with-pi
|
||||||
|
timeout-minutes: 90
|
||||||
|
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 builder prune -af || true
|
||||||
|
df -h / || true
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
with:
|
||||||
|
driver-opts: network=host
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: version
|
||||||
|
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push (multi-arch)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: |
|
||||||
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-with-pi
|
||||||
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-with-pi
|
||||||
|
|
||||||
|
build-omos-with-pi:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: smoke-omos-with-pi
|
||||||
|
timeout-minutes: 90
|
||||||
|
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 builder prune -af || true
|
||||||
|
df -h / || true
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
with:
|
||||||
|
driver-opts: network=host
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: version
|
||||||
|
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push (multi-arch)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
INSTALL_OMOS=true
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: |
|
||||||
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos-with-pi
|
||||||
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos-with-pi
|
||||||
|
|
||||||
update-description:
|
update-description:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-base, build-omos]
|
needs: [build-base, build-omos, build-with-pi, build-omos-with-pi]
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -147,3 +147,116 @@ jobs:
|
|||||||
- name: Smoke test
|
- name: Smoke test
|
||||||
run: |
|
run: |
|
||||||
bash scripts/smoke-test.sh opencode-devbox:ci-omos --variant omos
|
bash scripts/smoke-test.sh opencode-devbox:ci-omos --variant omos
|
||||||
|
|
||||||
|
validate-with-pi:
|
||||||
|
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 with-pi image (amd64, load to local daemon)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
build-args: |
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: opencode-devbox:ci-with-pi
|
||||||
|
|
||||||
|
- name: Smoke test
|
||||||
|
run: |
|
||||||
|
bash scripts/smoke-test.sh opencode-devbox:ci-with-pi --variant with-pi
|
||||||
|
|
||||||
|
validate-omos-with-pi:
|
||||||
|
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 omos+with-pi image (amd64, load to local daemon)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
build-args: |
|
||||||
|
INSTALL_OMOS=true
|
||||||
|
INSTALL_PI=true
|
||||||
|
tags: opencode-devbox:ci-omos-with-pi
|
||||||
|
|
||||||
|
- name: Smoke test
|
||||||
|
run: |
|
||||||
|
bash scripts/smoke-test.sh opencode-devbox:ci-omos-with-pi --variant omos-with-pi
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Docker image packaging [opencode](https://opencode.ai) into a production-ready d
|
|||||||
|
|
||||||
## File roles
|
## File roles
|
||||||
|
|
||||||
- `Dockerfile` — single multi-stage build for both variants. OMOS variant is controlled by `INSTALL_OMOS=true` build arg; mempalace is controlled by `INSTALL_MEMPALACE` (default `true`). All GitHub-sourced binaries are pinned with version ARGs.
|
- `Dockerfile` — single multi-stage build. Variants are gated by build args: `INSTALL_OMOS` (Bun + multi-agent layer), `INSTALL_OPENCODE` (default true), `INSTALL_PI` (default false), `INSTALL_MEMPALACE` (default true). All GitHub-sourced binaries are pinned with version ARGs.
|
||||||
- `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.
|
- `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`), 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`), pi-toolkit + pi-extensions deploy (when pi installed), pi settings.json bootstrap, mempalace pi-bridge symlink, skillset auto-deploy from mounted skillset repo, OMOS setup.
|
||||||
- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.jsonc` from env vars. Never overwrites an existing config (checks both `.json` and `.jsonc`). Auto-registers MCP servers for detected tools (mempalace via `mempalace-mcp`, gitea-mcp, context7 remote endpoint).
|
- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.jsonc` from env vars. Never overwrites an existing config (checks both `.json` and `.jsonc`). Auto-registers MCP servers for detected tools (mempalace via `mempalace-mcp`, gitea-mcp, context7 remote endpoint).
|
||||||
- `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows.
|
- `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows.
|
||||||
- `scripts/generate-dockerhub-md.py` — generates `DOCKER_HUB.md` from `README.md` using explicit section rules. `--check` fails if the committed file is out of sync (enforced by the `validate` workflow).
|
- `scripts/generate-dockerhub-md.py` — generates `DOCKER_HUB.md` from `README.md` using explicit section rules. `--check` fails if the committed file is out of sync (enforced by the `validate` workflow).
|
||||||
@@ -40,7 +40,10 @@ When bumping the opencode version, also bump `OPENCODE_VERSION` in `Dockerfile`
|
|||||||
- **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.
|
||||||
- **Skillset auto-deploy** — on every container start, `entrypoint-user.sh` looks for a skillset repo (detection order: `$SKILLSET_CONTAINER_PATH` → `$HOME/skillset` → `/workspace/skillset`) and runs `deploy-skills.sh --bootstrap --prune-stale`. This creates relative symlinks in `~/.agents/skills/` and `~/.config/opencode/instructions/`. Do NOT bind-mount `~/.agents/skills/` from the host — the container manages its own skills with relative symlinks that differ from the host's. The named volume `devbox-opencode-config` persists the deployed config across restarts.
|
- **Skillset auto-deploy** — on every container start, `entrypoint-user.sh` looks for a skillset repo (detection order: `$SKILLSET_CONTAINER_PATH` → `$HOME/skillset` → `/workspace/skillset`) and runs `deploy-skills.sh --bootstrap --prune-stale`. This creates relative symlinks in `~/.agents/skills/` and `~/.config/opencode/instructions/`. Do NOT bind-mount `~/.agents/skills/` from the host — the container manages its own skills with relative symlinks that differ from the host's. The named volume `devbox-opencode-config` persists the deployed config across restarts.
|
||||||
- **Config persistence via named volume** — `devbox-opencode-config` is a Docker named volume mounted at `~/.config/opencode/`. It is NOT a host bind mount by default. This separation allows both native and containerized opencode to coexist on the same machine without symlink conflicts. Users who need to override can replace the named volume with a host bind mount in their compose file.
|
- **Config persistence via named volume** — `devbox-opencode-config` is a Docker named volume mounted at `~/.config/opencode/`. It is NOT a host bind mount by default. This separation allows both native and containerized opencode to coexist on the same machine without symlink conflicts. Users who need to override can replace the named volume with a host bind mount in their compose file. **Same pattern for pi:** `devbox-pi-config` is mounted at `~/.pi/` and persists user toggles (`/ext`-disabled extensions) and `~/.pi/agent/settings.json` edits across container recreate.
|
||||||
|
- **pi install contract** — `INSTALL_PI=true` (default false) opt-in build arg. `pi` is npm-installed globally at build time; the npm prefix is NOT on a named volume, so `pi update` inside the container does not persist across `--rm` containers. Image rebuild is the upgrade path — same contract as `OPENCODE_VERSION`. The pi-toolkit and pi-extensions repos are git-cloned into `/opt/` at build time, then their `install.sh` runs from `entrypoint-user.sh` on each container start to symlink into `~/.pi/agent/` (which lives on the named volume). The mempalace pi-bridge is symlinked manually from `/opt/mempalace-toolkit/extensions/pi/mempalace.ts` — we do NOT call mempalace-toolkit's full `install.sh` because its `install_skill` step would race with skillset auto-deploy `--prune-stale`.
|
||||||
|
- **Pi deploy ordering matters in entrypoint-user.sh** — `pi-toolkit` runs first (creates `keybindings.json` symlink and writes pi-env.zsh), then `pi-extensions`, then `settings.json` template bootstrap, then mempalace bridge symlink. mempalace-toolkit's `check_pi_toolkit` probe (when called from the host install path) expects keybindings to already be present — not currently called from container, but ordering matches host convention.
|
||||||
|
- **Default CMD is `bash -l`** — not a harness. `docker compose run --rm devbox` drops the user into a login shell to choose: `aws sso login`, then `opencode` or `pi` (or any tool). Pass the harness explicitly to launch directly: `docker compose run --rm devbox opencode` / `docker compose run --rm devbox pi`. `docker compose exec` bypasses entrypoint+CMD entirely (existing user workflow unchanged).
|
||||||
- **Docker Hub description update** — uses `/v2/auth/token` endpoint (not the deprecated `/v2/users/login`). Auth uses `identifier`/`secret` fields, returns `access_token`, sent as `Bearer`. Short description must be ≤100 bytes.
|
- **Docker Hub description update** — uses `/v2/auth/token` endpoint (not the deprecated `/v2/users/login`). Auth uses `identifier`/`secret` fields, returns `access_token`, sent as `Bearer`. Short description must be ≤100 bytes.
|
||||||
|
|
||||||
## CI quirks
|
## CI quirks
|
||||||
|
|||||||
@@ -14,6 +14,21 @@ Bump opencode to 1.14.41.
|
|||||||
|
|
||||||
No container-level changes in this release. Dockerfile bump only.
|
No container-level changes in this release. Dockerfile bump only.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
**Optional pi as second harness.** Will become `v1.14.41b` on release.
|
||||||
|
|
||||||
|
- **Feature:** New `INSTALL_PI=true` build arg installs [pi](https://github.com/mariozechner/pi-coding-agent) as an alternative or complementary harness alongside opencode. Both harnesses share the same mempalace install and palace path — wing/diary entries are mutually visible. Adds ~150 MB to the image. Pi version pinned by `PI_VERSION` (default: latest at build time); `pi update` inside the container does not persist across `--rm` containers — image rebuild is the upgrade path, same contract as `OPENCODE_VERSION`.
|
||||||
|
- **Feature:** New `INSTALL_OPENCODE=false` build arg builds an image without opencode (e.g. for pi-only use). Default remains `true`. Existing builds and tags are unaffected.
|
||||||
|
- **Feature:** New `devbox-pi-config` named volume mounted at `~/.pi/` persists pi user state (settings.json, `/ext`-disabled extensions) across container recreate. Mirrors the `devbox-opencode-config` pattern from v1.14.33.
|
||||||
|
- **Feature:** Container clones [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) (keybindings, env loader, settings template) and [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) (6 extensions including ext-toggle, todo, ssh-controlmaster, notify, git-checkpoint, confirm-destructive) into `/opt/` at build time. New `PI_TOOLKIT_REF` and `PI_EXTENSIONS_REF` build args (default `main`) pin git refs. The mempalace pi-bridge `mempalace.ts` is symlinked from the existing `/opt/mempalace-toolkit/` clone.
|
||||||
|
- **Behavior change:** Default container CMD changed from `["opencode"]` to `["bash", "-l"]`. `docker compose run --rm devbox` (no command) now drops to a login shell so users can pick `opencode` or `pi` (or run `aws sso login` first). To preserve the old behavior, pass the harness explicitly: `docker compose run --rm devbox opencode`. `docker compose exec` workflows are unaffected (they bypass the entrypoint and CMD).
|
||||||
|
- **Performance:** chromadb's all-MiniLM-L6-v2 ONNX embedding model (~80 MB) is now pre-warmed at image build time under `~/.cache/chroma/onnx_models/`. Without this, mempalace's `init` step in entrypoint-user.sh would download the model silently on first container start (suppressed via `>/dev/null 2>&1`), stalling startup by minutes on a fresh image. Pre-warming runs as `gosu developer` so the cache lands at the right path and is owned by the runtime user.
|
||||||
|
- **Bugfix:** entrypoint-user.sh now redirects stdin from `/dev/null` for the `mempalace init --yes` call. Without this, the interactive `Mine this directory now? [Y/n]` prompt at the end of init would silently block forever when the container was started with `docker run -it` (TTY keeps stdin open). EOF on stdin makes the prompt fall through to its default.
|
||||||
|
- **Smoke-test:** New `--variant with-pi` (threshold 2700 MB) and `--variant omos-with-pi` (3400 MB). Pi-specific assertions verify pi binary, pi-toolkit clone, pi-extensions clone, deployed keybindings symlink, extension count ≥ 4, mempalace bridge symlink, and settings.json bootstrap. Pi state assertions use `docker exec` from the host (not `run`-inside-container) since the container has no docker CLI.
|
||||||
|
- **CI:** `.gitea/workflows/{validate,docker-publish}.yml` extended with `with-pi` and `omos-with-pi` matrix entries. Each release now produces eight Docker Hub tags: `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`.
|
||||||
|
- **Docs:** README adds a "pi (alternative/complementary harness)" section. AGENTS.md codifies pi install contract, deploy ordering in entrypoint-user.sh, and rationale for not calling mempalace-toolkit's full `install.sh` from container.
|
||||||
|
|
||||||
## v1.14.40 — 2026-05-07
|
## v1.14.40 — 2026-05-07
|
||||||
|
|
||||||
Bump opencode to 1.14.40.
|
Bump opencode to 1.14.40.
|
||||||
|
|||||||
+79
-4
@@ -271,9 +271,50 @@ RUN curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors https://deb.nodesour
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# ── Install opencode via npm ─────────────────────────────────────────
|
# ── Install opencode via npm ─────────────────────────────────────────
|
||||||
# v1.x is distributed as an npm package with platform-specific binaries
|
# v1.x is distributed as an npm package with platform-specific binaries.
|
||||||
RUN npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
# Disable with --build-arg INSTALL_OPENCODE=false to build a slimmer
|
||||||
opencode --version
|
# image without opencode (e.g. when only pi is needed). For a fully
|
||||||
|
# pi-only stripped image (no Bun, no opencode), see the pi-devbox repo.
|
||||||
|
ARG INSTALL_OPENCODE=true
|
||||||
|
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
||||||
|
npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
||||||
|
opencode --version ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Optional: pi coding-agent ────────────────────────────────────────
|
||||||
|
# Installs pi as an alternative/complementary harness. Coexists with
|
||||||
|
# opencode in the same image — both share the mempalace install and
|
||||||
|
# palace path, so wing data is mutually visible to either harness.
|
||||||
|
#
|
||||||
|
# pi-toolkit (keybindings.json + pi-env.zsh + settings.example.json)
|
||||||
|
# and pi-extensions (confirm-destructive, ext-toggle, git-checkpoint,
|
||||||
|
# notify, ssh-controlmaster, todo, …) are cloned into /opt/ at build
|
||||||
|
# time. entrypoint-user.sh runs each repo's install.sh on container
|
||||||
|
# start so symlinks land under ~/.pi/agent/ on the named volume.
|
||||||
|
#
|
||||||
|
# Pi version is pinned by PI_VERSION (default: latest at build time).
|
||||||
|
# `pi update` inside the container would write to the npm global
|
||||||
|
# prefix, which is not on a volume — so updates do NOT persist across
|
||||||
|
# `--rm` containers. Same contract as OPENCODE_VERSION: rebuild the
|
||||||
|
# image to upgrade pi.
|
||||||
|
ARG INSTALL_PI=false
|
||||||
|
ARG PI_VERSION=latest
|
||||||
|
ARG PI_TOOLKIT_REF=main
|
||||||
|
ARG PI_EXTENSIONS_REF=main
|
||||||
|
RUN if [ "${INSTALL_PI}" = "true" ]; then \
|
||||||
|
if [ "${PI_VERSION}" = "latest" ]; then \
|
||||||
|
npm install -g @mariozechner/pi-coding-agent ; \
|
||||||
|
else \
|
||||||
|
npm install -g @mariozechner/pi-coding-agent@${PI_VERSION} ; \
|
||||||
|
fi && \
|
||||||
|
pi --version && \
|
||||||
|
git clone --depth 1 --branch "${PI_TOOLKIT_REF}" \
|
||||||
|
https://gitea.jordbo.se/joakimp/pi-toolkit.git /opt/pi-toolkit && \
|
||||||
|
git clone --depth 1 --branch "${PI_EXTENSIONS_REF}" \
|
||||||
|
https://gitea.jordbo.se/joakimp/pi-extensions.git /opt/pi-extensions && \
|
||||||
|
echo "pi-toolkit at $(cd /opt/pi-toolkit && git rev-parse --short HEAD)" && \
|
||||||
|
echo "pi-extensions at $(cd /opt/pi-extensions && git rev-parse --short HEAD)" ; \
|
||||||
|
fi
|
||||||
|
|
||||||
# ── AWS CLI v2 (for SSO/Bedrock authentication) ─────────────────────
|
# ── AWS CLI v2 (for SSO/Bedrock authentication) ─────────────────────
|
||||||
RUN ARCH=$(case "${TARGETARCH}" in \
|
RUN ARCH=$(case "${TARGETARCH}" in \
|
||||||
@@ -342,14 +383,41 @@ RUN groupadd --gid ${USER_GID} ${USER_NAME} && \
|
|||||||
echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/${USER_NAME}
|
echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/${USER_NAME}
|
||||||
|
|
||||||
# Create standard directories
|
# Create standard directories
|
||||||
|
#
|
||||||
|
# ~/.pi/agent/extensions/ is created proactively so the named volume
|
||||||
|
# mount has a real owner from the first start. The directory is also
|
||||||
|
# what mempalace-toolkit's install_pi_extension probes to decide
|
||||||
|
# whether to deploy the pi↔mempalace bridge — must exist before that
|
||||||
|
# step runs in entrypoint-user.sh.
|
||||||
RUN mkdir -p /workspace \
|
RUN mkdir -p /workspace \
|
||||||
/home/${USER_NAME}/.config/opencode/skills \
|
/home/${USER_NAME}/.config/opencode/skills \
|
||||||
|
/home/${USER_NAME}/.pi/agent/extensions \
|
||||||
/home/${USER_NAME}/.agents/skills \
|
/home/${USER_NAME}/.agents/skills \
|
||||||
/home/${USER_NAME}/.local/share/opencode \
|
/home/${USER_NAME}/.local/share/opencode \
|
||||||
/home/${USER_NAME}/.cache/bash \
|
/home/${USER_NAME}/.cache/bash \
|
||||||
/home/${USER_NAME}/.ssh && \
|
/home/${USER_NAME}/.ssh && \
|
||||||
chown -R ${USER_NAME}:${USER_NAME} /workspace /home/${USER_NAME}
|
chown -R ${USER_NAME}:${USER_NAME} /workspace /home/${USER_NAME}
|
||||||
|
|
||||||
|
# ── Pre-warm chromadb embedding model ──────────────────────────────
|
||||||
|
# Mempalace uses chromadb's ONNXMiniLM_L6_V2 embedding function, which
|
||||||
|
# downloads ~80 MB of all-MiniLM-L6-v2 ONNX weights from chromadb's CDN
|
||||||
|
# on first use. Without pre-warming this happens silently (output is
|
||||||
|
# suppressed by the entrypoint init step) and stalls first container
|
||||||
|
# start by minutes on a slow network. We bake the cache at build time
|
||||||
|
# under the developer user's home so the runtime first-start is fast.
|
||||||
|
#
|
||||||
|
# Cache path comes from chromadb's hardcoded `Path.home() / .cache /
|
||||||
|
# chroma / onnx_models / all-MiniLM-L6-v2`. Run as gosu developer so
|
||||||
|
# Path.home() resolves correctly and ownership is right from the start.
|
||||||
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||||
|
gosu ${USER_NAME} /opt/uv-tools/mempalace/bin/python -c "\
|
||||||
|
from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2; \
|
||||||
|
ef = ONNXMiniLM_L6_V2(); \
|
||||||
|
_ = ef(['warmup']); \
|
||||||
|
print('chromadb embedding model warmed: all-MiniLM-L6-v2')" && \
|
||||||
|
ls -lh /home/${USER_NAME}/.cache/chroma/onnx_models/all-MiniLM-L6-v2/ ; \
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Shell defaults (bash history, aliases, readline) ─────────────────
|
# ── Shell defaults (bash history, aliases, readline) ─────────────────
|
||||||
# Shipped under /etc/skel-devbox/ rather than copied directly to the
|
# Shipped under /etc/skel-devbox/ rather than copied directly to the
|
||||||
# user's home. The entrypoint copies them to /home/developer/ only if
|
# user's home. The entrypoint copies them to /home/developer/ only if
|
||||||
@@ -374,4 +442,11 @@ RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
|
|||||||
WORKDIR /workspace
|
WORKDIR /workspace
|
||||||
|
|
||||||
ENTRYPOINT ["entrypoint.sh"]
|
ENTRYPOINT ["entrypoint.sh"]
|
||||||
CMD ["opencode"]
|
# Default to a login shell. `docker compose run --rm devbox` drops
|
||||||
|
# the user into bash to choose: `aws sso login`, then `opencode`
|
||||||
|
# or `pi`. To launch a harness directly, pass it explicitly:
|
||||||
|
# docker compose run --rm devbox opencode
|
||||||
|
# docker compose run --rm devbox pi
|
||||||
|
# `docker compose exec` bypasses the entrypoint and CMD entirely, so
|
||||||
|
# this default has no effect on attach-style workflows.
|
||||||
|
CMD ["bash", "-l"]
|
||||||
|
|||||||
@@ -341,6 +341,10 @@ docker compose build --build-arg NVIM_VERSION=0.12.1 # pin to a specific versi
|
|||||||
| `INSTALL_MEMPALACE` | `true` | [MemPalace](https://github.com/MemPalace/mempalace) local AI memory system (~300 MB — disable to shrink image if you don't need MCP memory) |
|
| `INSTALL_MEMPALACE` | `true` | [MemPalace](https://github.com/MemPalace/mempalace) local AI memory system (~300 MB — disable to shrink image if you don't need MCP memory) |
|
||||||
| `INSTALL_MEMPALACE_TOOLKIT` | `true` | [mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit) bash wrappers (`mempalace-session`, `mempalace-docs`). Cloned at build time from `MEMPALACE_TOOLKIT_REF` (default `main`). Requires `INSTALL_MEMPALACE=true`. |
|
| `INSTALL_MEMPALACE_TOOLKIT` | `true` | [mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit) bash wrappers (`mempalace-session`, `mempalace-docs`). Cloned at build time from `MEMPALACE_TOOLKIT_REF` (default `main`). Requires `INSTALL_MEMPALACE=true`. |
|
||||||
| `INSTALL_OMOS` | `false` | [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration (installs Bun and plugin) |
|
| `INSTALL_OMOS` | `false` | [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration (installs Bun and plugin) |
|
||||||
|
| `INSTALL_OPENCODE` | `true` | Install opencode. Set `false` to build a pi-only image (still includes Bun if `INSTALL_OMOS=true`; for a fully stripped pi-only image see the `pi-devbox` repo). |
|
||||||
|
| `INSTALL_PI` | `false` | Install [pi](https://github.com/mariozechner/pi-coding-agent) as alternative/complementary harness. Both clones [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) (~5 MB) and [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) (~1 MB) into `/opt/`; entrypoint deploys them on container start. ~150 MB total image growth. |
|
||||||
|
| `PI_VERSION` | `latest` | npm version of `@mariozechner/pi-coding-agent`. Floats by default (image rebuild = pi update). |
|
||||||
|
| `PI_TOOLKIT_REF`, `PI_EXTENSIONS_REF` | `main` | Git refs for the toolkit/extensions clones. Pin to a tag/commit for reproducibility. |
|
||||||
| `OPENCODE_VERSION` | *(pinned per release)* | opencode npm version. Drives the image tag and is intentionally not floated. |
|
| `OPENCODE_VERSION` | *(pinned per release)* | opencode npm version. Drives the image tag and is intentionally not floated. |
|
||||||
| `NODE_VERSION` | `22` | Node.js major version. Pinned to protect against upstream breaking changes across majors. |
|
| `NODE_VERSION` | `22` | Node.js major version. Pinned to protect against upstream breaking changes across majors. |
|
||||||
| `GOSU_VERSION`, `FZF_VERSION`, `GIT_LFS_VERSION`, `NVIM_VERSION`, `BAT_VERSION`, `EZA_VERSION`, `ZOXIDE_VERSION`, `UV_VERSION`, `GITEA_MCP_VERSION`, `GO_VERSION`, `OMOS_VERSION` | `latest` | All GitHub/Gitea/go.dev-hosted binaries resolve to the newest upstream release at build time. Override with a specific version to pin. Resolved versions are logged in CI output. |
|
| `GOSU_VERSION`, `FZF_VERSION`, `GIT_LFS_VERSION`, `NVIM_VERSION`, `BAT_VERSION`, `EZA_VERSION`, `ZOXIDE_VERSION`, `UV_VERSION`, `GITEA_MCP_VERSION`, `GO_VERSION`, `OMOS_VERSION` | `latest` | All GitHub/Gitea/go.dev-hosted binaries resolve to the newest upstream release at build time. Override with a specific version to pin. Resolved versions are logged in CI output. |
|
||||||
@@ -402,6 +406,59 @@ ping all agents
|
|||||||
|
|
||||||
All six agents should respond if your provider authentication is working.
|
All six agents should respond if your provider authentication is working.
|
||||||
|
|
||||||
|
## pi (alternative/complementary harness)
|
||||||
|
|
||||||
|
[pi](https://github.com/mariozechner/pi-coding-agent) is a lightweight TUI coding-agent that can run alongside opencode in the same container. Both harnesses share the mempalace install and palace data — wing/diary entries created by one are visible to the other.
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build --build-arg INSTALL_PI=true
|
||||||
|
# Or: pin a pi version
|
||||||
|
docker compose build --build-arg INSTALL_PI=true --build-arg PI_VERSION=0.73.0
|
||||||
|
# Or: pi-only image (no opencode, smaller)
|
||||||
|
docker compose build --build-arg INSTALL_PI=true --build-arg INSTALL_OPENCODE=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
The default `compose run --rm devbox` invocation drops to a login bash so you can choose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm devbox # bash, then `pi` or `opencode` or `aws sso login`
|
||||||
|
docker compose run --rm devbox pi # launch pi directly
|
||||||
|
docker compose run --rm devbox opencode
|
||||||
|
```
|
||||||
|
|
||||||
|
For an attached `compose up -d` container, both harnesses are reachable via `compose exec`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -u developer devbox pi
|
||||||
|
docker compose exec -u developer devbox opencode
|
||||||
|
docker compose exec -u developer devbox bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### What gets installed
|
||||||
|
|
||||||
|
- **`pi` CLI** — npm-installed globally at build time. Version pinned by `PI_VERSION`.
|
||||||
|
- **pi-toolkit** — keybindings.json (mosh/tmux newline fixes), pi-env.zsh (AWS env loader), settings.json template. Cloned to `/opt/pi-toolkit`; deployed to `~/.pi/agent/` on first container start.
|
||||||
|
- **pi-extensions** — 6 extensions: `confirm-destructive`, `ext-toggle` (`/ext` slash command), `git-checkpoint`, `notify`, `ssh-controlmaster`, `todo`. Cloned to `/opt/pi-extensions`; symlinked into `~/.pi/agent/extensions/`.
|
||||||
|
- **mempalace bridge** — `mempalace.ts` extension symlinked from the cloned mempalace-toolkit. Provides pi's MCP tools for palace search/diary/kg.
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
|
||||||
|
`~/.pi/` is mounted on the `devbox-pi-config` named volume. User toggles via `/ext`, edits to `~/.pi/agent/settings.json`, and any pi state survive container recreate. `pi update` writes to the npm global prefix which is *not* on a volume — image rebuild is the upgrade path.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The entrypoint copies `pi-toolkit/settings.example.json` to `~/.pi/agent/settings.json` on first start. Edit it to set provider/model:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -u developer devbox $EDITOR ~/.pi/agent/settings.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The AWS env loader (`pi-env.zsh`) reads `~/.config/pi/.env` if you bind-mount one; otherwise pi uses container env vars passed via `.env`.
|
||||||
|
|
||||||
## AWS Bedrock Authentication
|
## AWS Bedrock Authentication
|
||||||
|
|
||||||
When using AWS Bedrock as your LLM provider, you need:
|
When using AWS Bedrock as your LLM provider, you need:
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ services:
|
|||||||
# args:
|
# args:
|
||||||
# INSTALL_GO: "false"
|
# INSTALL_GO: "false"
|
||||||
# INSTALL_OMOS: "false"
|
# INSTALL_OMOS: "false"
|
||||||
|
# INSTALL_PI: "false"
|
||||||
|
# # PI_VERSION: "latest"
|
||||||
|
# # INSTALL_OPENCODE: "true"
|
||||||
container_name: opencode-devbox
|
container_name: opencode-devbox
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
@@ -55,6 +58,7 @@ services:
|
|||||||
# the container's skill/instruction symlinks independent from the host,
|
# the container's skill/instruction symlinks independent from the host,
|
||||||
# allowing both native and containerized opencode on the same machine.
|
# allowing both native and containerized opencode on the same machine.
|
||||||
- devbox-opencode-config:/home/developer/.config/opencode
|
- devbox-opencode-config:/home/developer/.config/opencode
|
||||||
|
- devbox-pi-config:/home/developer/.pi
|
||||||
|
|
||||||
# NOTE: Do NOT bind-mount ~/.agents/skills/ from the host. The
|
# NOTE: Do NOT bind-mount ~/.agents/skills/ from the host. The
|
||||||
# container manages its own skills directory independently — the
|
# container manages its own skills directory independently — the
|
||||||
@@ -121,6 +125,7 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
devbox-opencode-config:
|
devbox-opencode-config:
|
||||||
|
devbox-pi-config:
|
||||||
devbox-data:
|
devbox-data:
|
||||||
devbox-state:
|
devbox-state:
|
||||||
devbox-shell-history:
|
devbox-shell-history:
|
||||||
|
|||||||
+44
-1
@@ -25,7 +25,12 @@ if command -v mempalace &>/dev/null && [ -d /workspace ]; then
|
|||||||
PALACE_DIR="${HOME}/.mempalace"
|
PALACE_DIR="${HOME}/.mempalace"
|
||||||
if [ ! -d "$PALACE_DIR/palace" ]; then
|
if [ ! -d "$PALACE_DIR/palace" ]; then
|
||||||
echo "Initializing MemPalace for workspace (non-interactive)..."
|
echo "Initializing MemPalace for workspace (non-interactive)..."
|
||||||
mempalace init --yes /workspace >/dev/null 2>&1 || true
|
# </dev/null: mempalace init has an interactive "Mine this directory
|
||||||
|
# now? [Y/n]" prompt that --yes does not auto-answer in all paths.
|
||||||
|
# Without redirected stdin, the process blocks here forever when run
|
||||||
|
# from `docker run -it` (the TTY keeps stdin open). EOF on stdin
|
||||||
|
# makes the prompt fall through to its default (skip).
|
||||||
|
mempalace init --yes /workspace </dev/null >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -44,6 +49,44 @@ fi
|
|||||||
# generated) and no-ops if OPENCODE_PROVIDER is unset.
|
# generated) and no-ops if OPENCODE_PROVIDER is unset.
|
||||||
python3 /usr/local/lib/opencode-devbox/generate-config.py
|
python3 /usr/local/lib/opencode-devbox/generate-config.py
|
||||||
|
|
||||||
|
# ── pi: deploy toolkit + extensions + mempalace bridge ─────────────
|
||||||
|
# Runs only when pi was baked into the image (INSTALL_PI=true at build).
|
||||||
|
# Each install.sh is idempotent and backs up real files before linking,
|
||||||
|
# so re-running across container restarts is safe.
|
||||||
|
#
|
||||||
|
# Order: pi-toolkit first (creates ~/.pi/agent/keybindings.json symlink
|
||||||
|
# and writes the AWS env loader), then pi-extensions (symlinks our 6
|
||||||
|
# extensions), then settings.json bootstrap from the toolkit template,
|
||||||
|
# then the mempalace bridge symlink (one-liner; mempalace-toolkit's
|
||||||
|
# install_skill is intentionally skipped to avoid racing with skillset
|
||||||
|
# auto-deploy below).
|
||||||
|
if command -v pi &>/dev/null; then
|
||||||
|
if [ -d /opt/pi-toolkit ]; then
|
||||||
|
(cd /opt/pi-toolkit && ./install.sh --yes) || \
|
||||||
|
echo "WARN: pi-toolkit install.sh failed (continuing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /opt/pi-extensions ]; then
|
||||||
|
(cd /opt/pi-extensions && ./install.sh --yes) || \
|
||||||
|
echo "WARN: pi-extensions install.sh failed (continuing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bootstrap settings.json from template if absent (pi rewrites this
|
||||||
|
# file at runtime — lastChangelogVersion, etc — so we can't symlink it).
|
||||||
|
if [ ! -f "$HOME/.pi/agent/settings.json" ] && \
|
||||||
|
[ -f /opt/pi-toolkit/settings.example.json ]; then
|
||||||
|
cp /opt/pi-toolkit/settings.example.json "$HOME/.pi/agent/settings.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# pi↔mempalace MCP bridge — single extension symlink.
|
||||||
|
if [ -f /opt/mempalace-toolkit/extensions/pi/mempalace.ts ] && \
|
||||||
|
command -v mempalace &>/dev/null && \
|
||||||
|
[ ! -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then
|
||||||
|
ln -sf /opt/mempalace-toolkit/extensions/pi/mempalace.ts \
|
||||||
|
"$HOME/.pi/agent/extensions/mempalace.ts"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Skillset: deploy skills/instructions from mounted skillset repo ──
|
# ── Skillset: deploy skills/instructions from mounted skillset repo ──
|
||||||
# When the skillset repo is mounted (at $HOME/skillset or /workspace/skillset),
|
# When the skillset repo is mounted (at $HOME/skillset or /workspace/skillset),
|
||||||
# run the deploy script to create relative symlinks for skills and instructions.
|
# run the deploy script to create relative symlinks for skills and instructions.
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ for dir in \
|
|||||||
/home/"$USER_NAME"/.vscode-server \
|
/home/"$USER_NAME"/.vscode-server \
|
||||||
/home/"$USER_NAME"/.config/opencode \
|
/home/"$USER_NAME"/.config/opencode \
|
||||||
/home/"$USER_NAME"/.config/nvim \
|
/home/"$USER_NAME"/.config/nvim \
|
||||||
|
/home/"$USER_NAME"/.pi \
|
||||||
/home/"$USER_NAME"/.agents/skills; do
|
/home/"$USER_NAME"/.agents/skills; do
|
||||||
[ -d "$dir" ] || continue
|
[ -d "$dir" ] || continue
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ SECTION_RULES: dict[str, str] = {
|
|||||||
"Usage": "keep",
|
"Usage": "keep",
|
||||||
"Configuration": "trim", # drop dev-build sub-sections
|
"Configuration": "trim", # drop dev-build sub-sections
|
||||||
"oh-my-opencode-slim (Multi-Agent Orchestration)": "keep",
|
"oh-my-opencode-slim (Multi-Agent Orchestration)": "keep",
|
||||||
|
"pi (alternative/complementary harness)": "drop", # full README only, would push past 25 kB
|
||||||
"AWS Bedrock Authentication": "keep",
|
"AWS Bedrock Authentication": "keep",
|
||||||
"MemPalace — persistent AI memory": "keep",
|
"MemPalace — persistent AI memory": "keep",
|
||||||
"Gitea MCP server": "keep",
|
"Gitea MCP server": "keep",
|
||||||
|
|||||||
+71
-6
@@ -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]
|
# Usage: ./scripts/smoke-test.sh <image> [--variant base|omos|with-pi|omos-with-pi]
|
||||||
#
|
#
|
||||||
# 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]" >&2
|
echo "usage: $0 <image> [--variant base|omos|with-pi|omos-with-pi]" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -50,7 +50,12 @@ echo "-- Resolved component versions --"
|
|||||||
# always record what got baked into this image, even when Dockerfile
|
# always record what got baked into this image, even when Dockerfile
|
||||||
# ARGs default to "latest".
|
# ARGs default to "latest".
|
||||||
docker run --rm --entrypoint="" "$IMAGE" sh -c '
|
docker run --rm --entrypoint="" "$IMAGE" sh -c '
|
||||||
|
if command -v opencode >/dev/null 2>&1; then
|
||||||
printf " %-15s %s\n" "opencode" "$(opencode --version 2>&1 | head -1)"
|
printf " %-15s %s\n" "opencode" "$(opencode --version 2>&1 | head -1)"
|
||||||
|
fi
|
||||||
|
if command -v pi >/dev/null 2>&1; then
|
||||||
|
printf " %-15s %s\n" "pi" "$(pi --version 2>&1 | head -1)"
|
||||||
|
fi
|
||||||
printf " %-15s %s\n" "node" "$(node --version)"
|
printf " %-15s %s\n" "node" "$(node --version)"
|
||||||
printf " %-15s %s\n" "npm" "$(npm --version)"
|
printf " %-15s %s\n" "npm" "$(npm --version)"
|
||||||
printf " %-15s %s\n" "nvim" "$(nvim --version | head -1)"
|
printf " %-15s %s\n" "nvim" "$(nvim --version | head -1)"
|
||||||
@@ -77,7 +82,13 @@ docker run --rm --entrypoint="" "$IMAGE" sh -c '
|
|||||||
'
|
'
|
||||||
echo
|
echo
|
||||||
echo "-- Core binaries --"
|
echo "-- Core binaries --"
|
||||||
run "opencode" "opencode --version"
|
# opencode is gated on INSTALL_OPENCODE=true (default). When absent, the
|
||||||
|
# image is a pi-only build (or a pure base — no harness at all).
|
||||||
|
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v opencode" >/dev/null 2>&1; then
|
||||||
|
run "opencode" "opencode --version"
|
||||||
|
else
|
||||||
|
echo " - opencode not installed (INSTALL_OPENCODE=false)"
|
||||||
|
fi
|
||||||
run "node" "node --version"
|
run "node" "node --version"
|
||||||
run "npm" "npm --version"
|
run "npm" "npm --version"
|
||||||
run "git" "git --version"
|
run "git" "git --version"
|
||||||
@@ -117,8 +128,60 @@ elif docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v mempalace" >/dev
|
|||||||
echo " - mempalace-toolkit not installed (INSTALL_MEMPALACE_TOOLKIT=false)"
|
echo " - mempalace-toolkit not installed (INSTALL_MEMPALACE_TOOLKIT=false)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# bun: only in the omos variant
|
# pi: present when built with INSTALL_PI=true. Verifies pi itself plus
|
||||||
if [ "$VARIANT" = "omos" ]; then
|
# the runtime-deployed pi-toolkit + pi-extensions + mempalace bridge
|
||||||
|
# symlinks under ~/.pi/agent/. Note: extension symlinks are created by
|
||||||
|
# entrypoint-user.sh on first start, so we test by running the entry
|
||||||
|
# point chain (not just `docker run --entrypoint=""`).
|
||||||
|
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v pi" >/dev/null 2>&1; then
|
||||||
|
run "pi" "pi --version"
|
||||||
|
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 the full entrypoint as developer to verify install.sh deployment.
|
||||||
|
# Spin up a long-running container so we can `docker exec` into it from
|
||||||
|
# the host — the `run` helper above invokes commands INSIDE the image
|
||||||
|
# and has no docker CLI to nest with.
|
||||||
|
CID=$(docker run -d --rm "$IMAGE" tail -f /dev/null)
|
||||||
|
trap 'docker rm -f "$CID" >/dev/null 2>&1 || true' EXIT
|
||||||
|
|
||||||
|
# Wait for entrypoint-user.sh to finish deploying pi-toolkit + extensions.
|
||||||
|
# Marker: keybindings.json symlink lands once pi-toolkit/install.sh has run.
|
||||||
|
# Up to 30s — omos-with-pi has more setup work than base+pi.
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if docker exec "$CID" test -L /home/developer/.pi/agent/keybindings.json 2>/dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
exec_test() {
|
||||||
|
local label="$1"; shift
|
||||||
|
local out
|
||||||
|
if out=$(docker exec -u developer "$CID" sh -c "$*" 2>&1); then
|
||||||
|
pass "$label ($(echo "$out" | head -1))"
|
||||||
|
else
|
||||||
|
fail "$label: $out"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
exec_test "~/.pi/agent/keybindings.json (pi-toolkit)" \
|
||||||
|
'test -L $HOME/.pi/agent/keybindings.json && echo ok'
|
||||||
|
exec_test "~/.pi/agent/extensions/*.ts ≥ 4 (pi-extensions)" \
|
||||||
|
'count=$(ls -1 $HOME/.pi/agent/extensions/*.ts 2>/dev/null | wc -l); [ $count -ge 4 ] && echo "$count extensions"'
|
||||||
|
exec_test "~/.pi/agent/extensions/mempalace.ts (bridge)" \
|
||||||
|
'test -L $HOME/.pi/agent/extensions/mempalace.ts && echo ok'
|
||||||
|
exec_test "~/.pi/agent/settings.json (template bootstrap)" \
|
||||||
|
'test -f $HOME/.pi/agent/settings.json && echo ok'
|
||||||
|
|
||||||
|
docker rm -f "$CID" >/dev/null 2>&1 || true
|
||||||
|
trap - EXIT
|
||||||
|
else
|
||||||
|
echo " - pi not installed (INSTALL_PI=false)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# bun: only in the omos and omos-with-pi variants
|
||||||
|
if [ "$VARIANT" = "omos" ] || [ "$VARIANT" = "omos-with-pi" ]; then
|
||||||
run "bun (omos)" "bun --version"
|
run "bun (omos)" "bun --version"
|
||||||
run "bunx symlink (omos)" "test -L /usr/local/bin/bunx && readlink /usr/local/bin/bunx"
|
run "bunx symlink (omos)" "test -L /usr/local/bin/bunx && readlink /usr/local/bin/bunx"
|
||||||
# oh-my-opencode-slim is npm-installed globally (not a bun install);
|
# oh-my-opencode-slim is npm-installed globally (not a bun install);
|
||||||
@@ -221,12 +284,14 @@ SIZE_BYTES=$(docker image inspect --format='{{.Size}}' "$IMAGE")
|
|||||||
SIZE_MB=$((SIZE_BYTES / 1024 / 1024))
|
SIZE_MB=$((SIZE_BYTES / 1024 / 1024))
|
||||||
echo " Uncompressed size: ${SIZE_MB} MB"
|
echo " Uncompressed size: ${SIZE_MB} MB"
|
||||||
|
|
||||||
# Thresholds (uncompressed): base 2500 MB, omos 3200 MB. Adjust as image content evolves.
|
# Thresholds (uncompressed): base 2500 MB, omos 3200 MB, with-pi adds ~150 MB.
|
||||||
# omos bumped 3000→3200 on v1.14.31c — mempalace-toolkit bake-in pushed the
|
# omos bumped 3000→3200 on v1.14.31c — mempalace-toolkit bake-in pushed the
|
||||||
# omos variant to ~3.1 GB. Functional smoke checks all pass; this is a
|
# omos variant to ~3.1 GB. Functional smoke checks all pass; this is a
|
||||||
# guardrail, not a performance limit.
|
# guardrail, not a performance limit.
|
||||||
THRESHOLD=2500
|
THRESHOLD=2500
|
||||||
[ "$VARIANT" = "omos" ] && THRESHOLD=3200
|
[ "$VARIANT" = "omos" ] && THRESHOLD=3200
|
||||||
|
[ "$VARIANT" = "with-pi" ] && THRESHOLD=2700
|
||||||
|
[ "$VARIANT" = "omos-with-pi" ] && THRESHOLD=3400
|
||||||
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