Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a2d06340e | |||
| 23894bc19f | |||
| f0918ba915 | |||
| 1683650240 |
@@ -5,8 +5,29 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
|
# Serialize concurrent runs of the same workflow on the same ref so the
|
||||||
|
# matrix build jobs can't race `docker system prune` in the smoke gates
|
||||||
|
# (pruning from one job can nuke another job's in-flight buildx cache).
|
||||||
|
# cancel-in-progress: false — tag pushes are release events, we never
|
||||||
|
# want to silently drop one.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
# Runner disk pressure notes:
|
||||||
|
# Gitea Actions runners use `catthehacker/ubuntu:act-latest` on a shared host
|
||||||
|
# with limited overlay space (~40 GB, often 70%+ used at start). Building both
|
||||||
|
# architectures of both variants on a single runner exhausted disk around the
|
||||||
|
# nodejs dpkg unpack / git-lfs layer export. To fix this:
|
||||||
|
# * smoke test (amd64 only, load into daemon) runs on its own runner
|
||||||
|
# * each push target (variant × arch) runs on its own runner, pushes by
|
||||||
|
# digest (no local image store), uploads digest as an artifact
|
||||||
|
# * a merge job composes the multi-arch manifest with `imagetools create`
|
||||||
|
# Per-runner disk pressure is now one-quarter of the old single-job peak.
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-base:
|
# ── Smoke test (amd64 only, gates the push jobs) ────────────────────
|
||||||
|
smoke-base:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
@@ -15,30 +36,41 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Force IPv4 for Docker Hub
|
- name: Force IPv4 for Docker Hub
|
||||||
run: |
|
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
# Prefer IPv4 to avoid intermittent IPv6 connectivity failures
|
|
||||||
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
# See docker-publish.yml preamble. `load: true` peak disk = tarball
|
||||||
uses: docker/setup-qemu-action@v4
|
# + unpacked image + buildx cache; the image now crosses the 40 GB
|
||||||
|
# runner overlay's starting headroom. Strip catthehacker-resident
|
||||||
|
# toolchains and any stale docker state up front.
|
||||||
|
- 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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
with:
|
with:
|
||||||
driver-opts: network=host
|
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: |
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/}
|
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and load amd64 image for smoke test
|
- name: Build and load amd64 image for smoke test
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
@@ -49,20 +81,9 @@ jobs:
|
|||||||
tags: opencode-devbox:smoke-base
|
tags: opencode-devbox:smoke-base
|
||||||
|
|
||||||
- name: Smoke test (amd64)
|
- name: Smoke test (amd64)
|
||||||
run: |
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-base --variant base
|
||||||
bash scripts/smoke-test.sh opencode-devbox:smoke-base --variant base
|
|
||||||
|
|
||||||
- name: Build and push (base)
|
smoke-omos:
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}
|
|
||||||
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest
|
|
||||||
|
|
||||||
build-omos:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
@@ -71,30 +92,37 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Force IPv4 for Docker Hub
|
- name: Force IPv4 for Docker Hub
|
||||||
run: |
|
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
# Prefer IPv4 to avoid intermittent IPv6 connectivity failures
|
|
||||||
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Reclaim runner disk
|
||||||
uses: docker/setup-qemu-action@v4
|
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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
with:
|
with:
|
||||||
driver-opts: network=host
|
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: |
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/}
|
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and load amd64 image for smoke test
|
- name: Build and load amd64 image for smoke test
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
@@ -107,24 +135,227 @@ jobs:
|
|||||||
tags: opencode-devbox:smoke-omos
|
tags: opencode-devbox:smoke-omos
|
||||||
|
|
||||||
- name: Smoke test (amd64)
|
- name: Smoke test (amd64)
|
||||||
run: |
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
||||||
bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
|
||||||
|
|
||||||
- name: Build and push (omos)
|
# ── Per-arch push (by digest, no local image) ───────────────────────
|
||||||
|
build-base:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: smoke-base
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
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: Derive platform slug
|
||||||
|
id: platform
|
||||||
|
run: |
|
||||||
|
# POSIX-safe slash substitution — act's runner container ships
|
||||||
|
# /bin/sh as dash, which doesn't support bash's ${VAR//a/b}.
|
||||||
|
echo "pair=$(echo '${{ matrix.platform }}' | tr / -)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: matrix.platform != 'linux/amd64'
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
|
- 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: Build and push by digest
|
||||||
|
id: build
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: ${{ matrix.platform }}
|
||||||
push: true
|
outputs: type=image,name=${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-base-${{ steps.platform.outputs.pair }}
|
||||||
|
path: /tmp/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
build-omos:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: smoke-omos
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
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: Derive platform slug
|
||||||
|
id: platform
|
||||||
|
run: |
|
||||||
|
# POSIX-safe slash substitution — act's runner container ships
|
||||||
|
# /bin/sh as dash, which doesn't support bash's ${VAR//a/b}.
|
||||||
|
echo "pair=$(echo '${{ matrix.platform }}' | tr / -)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: matrix.platform != 'linux/amd64'
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
|
- 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: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
build-args: |
|
build-args: |
|
||||||
INSTALL_OMOS=true
|
INSTALL_OMOS=true
|
||||||
tags: |
|
outputs: type=image,name=${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox,push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: digests-omos-${{ steps.platform.outputs.pair }}
|
||||||
|
path: /tmp/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
# ── Merge per-arch digests into multi-arch tags ─────────────────────
|
||||||
|
merge-base:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-base
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Force IPv4 for Docker Hub
|
||||||
|
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
|
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: /tmp/digests
|
||||||
|
pattern: digests-base-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- 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: Create manifest list and push
|
||||||
|
working-directory: /tmp/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }} \
|
||||||
|
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest \
|
||||||
|
$(printf '${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect \
|
||||||
|
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
|
merge-omos:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-omos
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Force IPv4 for Docker Hub
|
||||||
|
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
|
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: /tmp/digests
|
||||||
|
pattern: digests-omos-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- 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: Create manifest list and push
|
||||||
|
working-directory: /tmp/digests
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos \
|
||||||
|
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos \
|
||||||
|
$(printf '${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox@sha256:%s ' *)
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect \
|
||||||
${{ 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
|
|
||||||
|
|
||||||
update-description:
|
update-description:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-base, build-omos]
|
needs: [merge-base, merge-omos]
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -46,6 +46,34 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||||||
|
|
||||||
|
# The runner's overlay disk starts ~70% full. `load: true` peak disk
|
||||||
|
# is tarball + unpacked image + buildx cache, which tips it over
|
||||||
|
# once the image crosses ~3 GB. Strip catthehacker-resident
|
||||||
|
# toolchains we never use and any stale docker state up front.
|
||||||
|
- 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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
with:
|
with:
|
||||||
@@ -76,6 +104,30 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -6,6 +6,48 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v1.14.31c — 2026-05-01
|
||||||
|
|
||||||
|
**CI: fix bash-specific parameter expansion and bump omos size threshold.**
|
||||||
|
|
||||||
|
- **Fix:** `Derive platform slug` step in the per-arch matrix build jobs (`build-base`, `build-omos`) used `${PLATFORM_PAIR//\//-}` which is a bash parameter-expansion. The runner container executes step scripts via `/bin/sh` (dash), which errored with `Bad substitution`. Rewrote using `tr / -` which is POSIX and behaves identically. Both `build-base` and `build-omos` matrix jobs were blocked on this on `v1.14.31b`.
|
||||||
|
- **Fix:** smoke-test image-size threshold for the `omos` variant bumped from 3000 MB to 3200 MB. The mempalace-toolkit bake-in added ~100 MB to omos; measured 3107 MB on `v1.14.31b`. All functional smoke checks (opencode, node, mempalace CLIs, toolkit wrappers, oh-my-opencode-slim) pass — this is a guardrail recalibration, not a performance concession. The underlying image genuinely grew.
|
||||||
|
- The runner-disk reclaim step from v1.14.31b did its job: `smoke-base` and `validate-base` now pass cleanly. Only `smoke-omos` was blocked this iteration, and only on the threshold.
|
||||||
|
- No image changes beyond what shipped in v1.14.31. Rebuild of v1.14.31 content only.
|
||||||
|
|
||||||
|
## v1.14.31b — 2026-05-01
|
||||||
|
|
||||||
|
**CI: reclaim runner disk before `load: true` smoke builds.**
|
||||||
|
|
||||||
|
- **Fix:** v1.14.31's publish workflow and the `validate` workflow both hit `No space left on device` on the single-arch amd64 smoke/validate builds (`/opt/uv-tools/mempalace/lib/python3.13/site-packages/hf_xet/hf_xet.abi3.so`, `/usr/local/bin/git-lfs`). Root cause is not the build itself but the `load: true` step: peak disk during export equals tarball + unpacked image + buildx cache, and the image has crossed the ~3 GB threshold where this no longer fits in the ~12 GB of free space the runner container starts with. The v1.14.30c refactor split multi-arch into per-arch push-by-digest jobs (which don't `load`), but the smoke gates still do and still hit the wall.
|
||||||
|
- Added a `Reclaim runner disk` step to all four `load: true` jobs (`validate-base`, `validate-omos`, `smoke-base`, `smoke-omos`). The step strips `catthehacker/ubuntu:act-latest`-resident toolchains we never use (hosted-tool-cache, dotnet, android, powershell, swift, ghc, jvm, microsoft, chromium, boost) and runs `docker system prune -af --volumes` + `docker builder prune -af` against the runner's dockerd before `setup-buildx-action`. Expected reclaim is 6–12 GB depending on what's resident.
|
||||||
|
- Added workflow-level `concurrency: { group: ..., cancel-in-progress: false }` on `docker-publish.yml` so concurrent tag pushes can't race `docker system prune` in one job against an in-flight buildx cache in another.
|
||||||
|
- Pruning is deliberately kept out of the per-arch matrix push-by-digest jobs (`build-base`/`build-omos`) — those don't need it (no `load: true`), and pruning in parallel jobs risks one job nuking another's cache.
|
||||||
|
- **Follow-up** (not in this release): image-size reduction via a dedicated `uv tool install mempalace` build stage (strips uv's cache from the final image), pinning `mempalace-toolkit` to a commit SHA with `--depth=1 --filter=blob:none`, and auditing whether `hf_xet` is actually required by mempalace at runtime. These will ship in the next release that rebases on a new opencode version.
|
||||||
|
- No image changes. Rebuild of v1.14.31 content only.
|
||||||
|
|
||||||
|
## v1.14.31 — 2026-05-01
|
||||||
|
|
||||||
|
Bump opencode to 1.14.31.
|
||||||
|
|
||||||
|
**CI infrastructure: split multi-arch publish across separate runners.**
|
||||||
|
|
||||||
|
- **Fix:** The `publish` workflow exhausted runner disk space on `v1.14.30b` and would have hit the same wall on any subsequent release. Both variants built both architectures on a single `catthehacker/ubuntu:act-latest` container with ~40 GB of shared overlay space, and the peak disk footprint during the nodejs dpkg unpack / git-lfs layer export pushed it over the edge (`No space left on device`). The mempalace-toolkit bake-in from v1.14.30b added the final straw; the underlying issue is that QEMU-emulated arm64 layers were stored alongside the amd64 build on the same runner.
|
||||||
|
- `docker-publish.yml` refactored to the canonical `push-by-digest` + manifest-merge pattern: smoke test (amd64) runs on its own runner, each `(variant × arch)` push target runs on its own fresh runner with `outputs: type=image,...,push-by-digest=true,push=true` (no local image store), then a tiny merge job assembles the multi-arch manifest with `docker buildx imagetools create` from digest artifacts.
|
||||||
|
- Per-runner disk peak is now roughly one-quarter of the old single-job peak. The four Docker Hub tags produced per release (`vX.Y.Z[n]`, `latest`, `vX.Y.Z[n]-omos`, `latest-omos`) are unchanged.
|
||||||
|
- Also parallelizes the amd64 and arm64 builds, so wall-clock time for a release should drop noticeably despite the added merge hop.
|
||||||
|
|
||||||
|
## v1.14.30b — 2026-04-30
|
||||||
|
|
||||||
|
**Bake mempalace-toolkit wrappers into the image.**
|
||||||
|
|
||||||
|
- **Fix:** The scheduler templates in [mempalace-toolkit's `contrib/`](https://gitea.jordbo.se/joakimp/mempalace-toolkit/src/branch/main/contrib) assume `mempalace-session` is available inside the container, but the image never actually installed it. Users following the `*-devbox` scheduler docs would silently lose the wrappers on every `docker compose up --force-recreate`, because the only way to get them was a post-hoc `./install.sh --yes` inside the container — which lives in the ephemeral layer. The host-side systemd timer would then fire, `docker exec` in, and hit `mempalace-session: command not found`. Caught during runtime validation on 2026-04-30.
|
||||||
|
- New Dockerfile block clones `mempalace-toolkit` at build time (depth-1) to `/opt/mempalace-toolkit/`, symlinks `bin/mempalace-session` and `bin/mempalace-docs` into `/usr/local/bin/`, and asserts both respond to `--help` before the layer succeeds.
|
||||||
|
- Gated by `ARG INSTALL_MEMPALACE_TOOLKIT=true` (defaults on, depends on `INSTALL_MEMPALACE=true`).
|
||||||
|
- Floated ref via `ARG MEMPALACE_TOOLKIT_REF=main` — override for reproducible builds once the toolkit starts tagging releases.
|
||||||
|
- **Tests:** Smoke test gains three toolkit assertions (`mempalace-session --help`, `mempalace-docs --help`, symlink target check). The resolved-versions preamble now logs the toolkit git short-SHA alongside the other floated components.
|
||||||
|
- **Docs:** README's MemPalace section gains a `Scheduled mining (mempalace-toolkit)` subsection covering the new wrappers and pointing at `contrib/` for scheduling. New build-args table entry for `INSTALL_MEMPALACE_TOOLKIT`.
|
||||||
|
|
||||||
## v1.14.30 — 2026-04-30
|
## v1.14.30 — 2026-04-30
|
||||||
|
|
||||||
Bump opencode to 1.14.30.
|
Bump opencode to 1.14.30.
|
||||||
|
|||||||
@@ -449,6 +449,24 @@ mempalace wake-up
|
|||||||
|
|
||||||
Each workspace gets its own isolated "wing" — memories never leak between projects.
|
Each workspace gets its own isolated "wing" — memories never leak between projects.
|
||||||
|
|
||||||
|
### Scheduled mining (mempalace-toolkit)
|
||||||
|
|
||||||
|
The image bakes in [mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit), a small set of bash wrappers that pair with mempalace for two common routines:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mine opencode session history (reads ~/.local/share/opencode/opencode.db, stages JSONL, mines into wing_conversations)
|
||||||
|
mempalace-session
|
||||||
|
|
||||||
|
# Mine a project's docs into a dedicated wing
|
||||||
|
mempalace-docs /workspace/my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
Both wrappers are idempotent and dedup-aware — re-running them on unchanged input is a cheap no-op.
|
||||||
|
|
||||||
|
For weekly automated runs, the toolkit ships ready-to-use scheduler templates (systemd user timer, launchd user agent, cron) in its [`contrib/`](https://gitea.jordbo.se/joakimp/mempalace-toolkit/src/branch/main/contrib) directory. The `*-devbox` variants are designed for this container: host-side schedulers that `docker exec` into the running opencode-devbox.
|
||||||
|
|
||||||
|
Disable the toolkit (keeps mempalace itself) with `--build-arg INSTALL_MEMPALACE_TOOLKIT=false`. Pin to a specific ref with `--build-arg MEMPALACE_TOOLKIT_REF=v0.3.0` once tagged releases exist.
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
Two separate named volumes keep different data classes apart:
|
Two separate named volumes keep different data classes apart:
|
||||||
|
|||||||
+26
-1
@@ -5,7 +5,7 @@ ARG DEBIAN_VERSION=trixie-slim
|
|||||||
FROM debian:${DEBIAN_VERSION} AS base
|
FROM debian:${DEBIAN_VERSION} AS base
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG OPENCODE_VERSION=1.14.30
|
ARG OPENCODE_VERSION=1.14.31
|
||||||
|
|
||||||
LABEL maintainer="joakimp"
|
LABEL maintainer="joakimp"
|
||||||
LABEL description="Portable opencode developer container"
|
LABEL description="Portable opencode developer container"
|
||||||
@@ -207,6 +207,31 @@ RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
|||||||
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
||||||
|
# Thin wrappers (`mempalace-session`, `mempalace-docs`) that delegate to
|
||||||
|
# the mempalace Python CLI for two common scheduled tasks:
|
||||||
|
# - mempalace-session: mines opencode's SQLite session history into
|
||||||
|
# the palace (wing_conversations). Referenced by contrib/ scheduler
|
||||||
|
# templates (systemd user timer, cron) in the toolkit repo.
|
||||||
|
# - mempalace-docs: mines project docs into a per-project wing.
|
||||||
|
# Repo source of truth: https://gitea.jordbo.se/joakimp/mempalace-toolkit
|
||||||
|
#
|
||||||
|
# Requires INSTALL_MEMPALACE=true (wrappers shell out to `mempalace`).
|
||||||
|
# Disable with --build-arg INSTALL_MEMPALACE_TOOLKIT=false if you don't
|
||||||
|
# use the scheduled-mining workflow.
|
||||||
|
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
||||||
|
ARG MEMPALACE_TOOLKIT_REF=main
|
||||||
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ] && [ "${INSTALL_MEMPALACE_TOOLKIT}" = "true" ]; then \
|
||||||
|
git clone --depth 1 --branch "${MEMPALACE_TOOLKIT_REF}" \
|
||||||
|
https://gitea.jordbo.se/joakimp/mempalace-toolkit.git /opt/mempalace-toolkit && \
|
||||||
|
ln -sf /opt/mempalace-toolkit/bin/mempalace-session /usr/local/bin/mempalace-session && \
|
||||||
|
ln -sf /opt/mempalace-toolkit/bin/mempalace-docs /usr/local/bin/mempalace-docs && \
|
||||||
|
chmod +x /opt/mempalace-toolkit/bin/mempalace-session /opt/mempalace-toolkit/bin/mempalace-docs && \
|
||||||
|
mempalace-session --help >/dev/null && \
|
||||||
|
mempalace-docs --help >/dev/null && \
|
||||||
|
echo "mempalace-toolkit installed at $(cd /opt/mempalace-toolkit && git rev-parse --short HEAD)" ; \
|
||||||
|
fi
|
||||||
|
|
||||||
# rustup — Rust toolchain manager
|
# rustup — Rust toolchain manager
|
||||||
# Installs the rustup-init binary only. Users bootstrap Rust with:
|
# Installs the rustup-init binary only. Users bootstrap Rust with:
|
||||||
# rustup-init -y && source ~/.cargo/env
|
# rustup-init -y && source ~/.cargo/env
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ docker compose build --build-arg NVIM_VERSION=0.12.1 # pin to a specific versi
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `INSTALL_GO` | `false` | Go toolchain (resolves latest stable from go.dev when `GO_VERSION=latest`) |
|
| `INSTALL_GO` | `false` | Go toolchain (resolves latest stable from go.dev when `GO_VERSION=latest`) |
|
||||||
| `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_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) |
|
||||||
| `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. |
|
||||||
@@ -502,6 +503,24 @@ mempalace wake-up
|
|||||||
|
|
||||||
Each workspace gets its own isolated "wing" — memories never leak between projects.
|
Each workspace gets its own isolated "wing" — memories never leak between projects.
|
||||||
|
|
||||||
|
### Scheduled mining (mempalace-toolkit)
|
||||||
|
|
||||||
|
The image bakes in [mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit), a small set of bash wrappers that pair with mempalace for two common routines:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mine opencode session history (reads ~/.local/share/opencode/opencode.db, stages JSONL, mines into wing_conversations)
|
||||||
|
mempalace-session
|
||||||
|
|
||||||
|
# Mine a project's docs into a dedicated wing
|
||||||
|
mempalace-docs /workspace/my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
Both wrappers are idempotent and dedup-aware — re-running them on unchanged input is a cheap no-op.
|
||||||
|
|
||||||
|
For weekly automated runs, the toolkit ships ready-to-use scheduler templates (systemd user timer, launchd user agent, cron) in its [`contrib/`](https://gitea.jordbo.se/joakimp/mempalace-toolkit/src/branch/main/contrib) directory. The `*-devbox` variants are designed for this container: host-side schedulers that `docker exec` into the running opencode-devbox.
|
||||||
|
|
||||||
|
Disable the toolkit (keeps mempalace itself) with `--build-arg INSTALL_MEMPALACE_TOOLKIT=false`. Pin to a specific ref with `--build-arg MEMPALACE_TOOLKIT_REF=v0.3.0` once tagged releases exist.
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
Two separate named volumes keep different data classes apart:
|
Two separate named volumes keep different data classes apart:
|
||||||
|
|||||||
+18
-2
@@ -71,6 +71,9 @@ docker run --rm --entrypoint="" "$IMAGE" sh -c '
|
|||||||
if command -v mempalace >/dev/null 2>&1; then
|
if command -v mempalace >/dev/null 2>&1; then
|
||||||
printf " %-15s %s\n" "mempalace" "$(mempalace --version 2>&1 | head -1 || echo installed)"
|
printf " %-15s %s\n" "mempalace" "$(mempalace --version 2>&1 | head -1 || echo installed)"
|
||||||
fi
|
fi
|
||||||
|
if command -v mempalace-session >/dev/null 2>&1 && [ -d /opt/mempalace-toolkit ]; then
|
||||||
|
printf " %-15s %s\n" "toolkit" "$(git -C /opt/mempalace-toolkit rev-parse --short HEAD 2>/dev/null || echo installed)"
|
||||||
|
fi
|
||||||
'
|
'
|
||||||
echo
|
echo
|
||||||
echo "-- Core binaries --"
|
echo "-- Core binaries --"
|
||||||
@@ -104,6 +107,16 @@ else
|
|||||||
echo " - mempalace not installed (INSTALL_MEMPALACE=false)"
|
echo " - mempalace not installed (INSTALL_MEMPALACE=false)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# mempalace-toolkit wrappers: present unless built with INSTALL_MEMPALACE_TOOLKIT=false
|
||||||
|
# Gated on mempalace presence — wrappers are useless without the CLI.
|
||||||
|
if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v mempalace && command -v mempalace-session" >/dev/null 2>&1; then
|
||||||
|
run "mempalace-session (toolkit)" "mempalace-session --help | head -1"
|
||||||
|
run "mempalace-docs (toolkit)" "mempalace-docs --help | head -1"
|
||||||
|
run "toolkit symlink target" "test -L /usr/local/bin/mempalace-session && readlink /usr/local/bin/mempalace-session"
|
||||||
|
elif docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v mempalace" >/dev/null 2>&1; then
|
||||||
|
echo " - mempalace-toolkit not installed (INSTALL_MEMPALACE_TOOLKIT=false)"
|
||||||
|
fi
|
||||||
|
|
||||||
# bun: only in the omos variant
|
# bun: only in the omos variant
|
||||||
if [ "$VARIANT" = "omos" ]; then
|
if [ "$VARIANT" = "omos" ]; then
|
||||||
run "bun (omos)" "bun --version"
|
run "bun (omos)" "bun --version"
|
||||||
@@ -201,9 +214,12 @@ 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 3000 MB. Adjust as image content evolves.
|
# Thresholds (uncompressed): base 2500 MB, omos 3200 MB. Adjust as image content evolves.
|
||||||
|
# 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
|
||||||
|
# guardrail, not a performance limit.
|
||||||
THRESHOLD=2500
|
THRESHOLD=2500
|
||||||
[ "$VARIANT" = "omos" ] && THRESHOLD=3000
|
[ "$VARIANT" = "omos" ] && THRESHOLD=3200
|
||||||
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