Files
opencode-devbox/.gitea/workflows/docker-publish.yml
T
joakimp 113c9f0bb0 Infrastructure pass: CI smoke tests, floating versions, chown sentinel, generate-config script
Main changes:

- Extract opencode.json generation from entrypoint-user.sh into a
  standalone Python script (rootfs/usr/local/lib/opencode-devbox/
  generate-config.py). Preserves the never-overwrite-existing-config
  guarantee. Cuts entrypoint-user.sh from 176 to 97 lines.

- Install MemPalace via 'uv tool install' into an isolated venv at
  /opt/uv-tools/mempalace/ with a /usr/local/bin/mempalace-mcp-server
  wrapper, replacing the 'pip install --break-system-packages' escape
  hatch. The wrapper is what generate-config.py references in the
  auto-generated opencode.json. Also fix 'mempalace init' in
  entrypoint-user.sh to use --yes so first-start initialization isn't
  interactive (this used to hang or print prompts into the user's
  terminal). Gated by INSTALL_MEMPALACE build arg (default true) so
  users who don't need AI memory can shave ~300 MB.

- Sentinel-file pattern in entrypoint.sh volume-ownership loop: write
  .devbox-owner after a successful chown -R, skip the recursive walk
  on subsequent starts when the sentinel matches FINAL_UID:FINAL_GID.
  Cuts multi-second startup costs to milliseconds on large volumes
  (nvim plugins, palace data). UID changes still trigger a full chown.

- Float all GitHub/Gitea-hosted binary versions: gosu, fzf, git-lfs,
  neovim, bat, eza, zoxide, uv, gitea-mcp now default to 'latest' and
  resolve the newest upstream release at build time via the /releases/
  latest redirect. Go (go.dev JSON feed) and oh-my-opencode-slim (npm
  @latest) likewise. Intentional pins still in place: OPENCODE_VERSION,
  NODE_VERSION=22, DEBIAN_VERSION=trixie-slim. Each *_VERSION ARG
  accepts an explicit value to lock a specific version when needed.

- New scripts/smoke-test.sh verifies binary presence, opencode startup,
  entrypoint user drop, generate-config idempotency, bun's presence-
  per-variant, and image size against thresholds (2500 MB base, 3000
  MB OMOS). Prints resolved component versions as its first step so
  CI logs always record what got baked into a given image.

- New .gitea/workflows/validate.yml runs on push to main and PRs:
  single-arch amd64 build, smoke test, DOCKER_HUB.md sync check. Tag-
  triggered docker-publish.yml now smoke-tests each variant on amd64
  before the full multi-arch push.

- scripts/generate-dockerhub-md.py auto-generates DOCKER_HUB.md from
  README.md using explicit SECTION_RULES. --check mode fails CI when
  the committed file is out of sync. Enforces the 25 kB Docker Hub
  limit. Adding a new README section forces an explicit keep/drop/
  replace decision.

- Remove dead INSTALL_PYTHON build arg (was a no-op since mempalace
  added python3 unconditionally).
2026-04-28 23:28:43 +02:00

160 lines
5.0 KiB
YAML

name: Publish Docker Image
on:
push:
tags:
- 'v*'
jobs:
build-base:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Force IPv4 for Docker Hub
run: |
# Prefer IPv4 to avoid intermittent IPv6 connectivity failures
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
- name: Set up QEMU
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: 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
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: false
load: true
tags: opencode-devbox:smoke-base
- name: Smoke test (amd64)
run: |
bash scripts/smoke-test.sh opencode-devbox:smoke-base --variant base
- name: Build and push (base)
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
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Force IPv4 for Docker Hub
run: |
# Prefer IPv4 to avoid intermittent IPv6 connectivity failures
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
- name: Set up QEMU
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: 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
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: false
load: true
build-args: |
INSTALL_OMOS=true
tags: opencode-devbox:smoke-omos
- name: Smoke test (amd64)
run: |
bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
- name: Build and push (omos)
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
build-args: |
INSTALL_OMOS=true
tags: |
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos
update-description:
runs-on: ubuntu-latest
needs: [build-base, build-omos]
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Update Docker Hub description
run: |
TOKEN=$(curl -s -X POST https://hub.docker.com/v2/auth/token \
-H "Content-Type: application/json" \
-d '{"identifier":"${{ vars.DOCKERHUB_USERNAME }}","secret":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
| jq -r .access_token)
if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
echo "::error::Failed to authenticate with Docker Hub API"
exit 1
fi
HTTP_CODE=$(jq -n \
--rawfile full DOCKER_HUB.md \
--arg short "Portable AI dev environment for opencode. Debian-based with git, Node.js, AWS CLI, and SSH support." \
'{"full_description": $full, "description": $short}' | \
curl -s -o /tmp/hub-response.txt -w "%{http_code}" -X PATCH \
"https://hub.docker.com/v2/repositories/${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox/" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @-)
echo "Docker Hub API returned: $HTTP_CODE"
if [ "$HTTP_CODE" != "200" ]; then
echo "Response body:"
cat /tmp/hub-response.txt
echo "::error::Docker Hub description update failed with HTTP $HTTP_CODE"
exit 1
fi