Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions .github/workflows/build-base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# build-base.yml — Multi-arch base image build and push
#
# Builds opencode-base (Fedora 41 + Go + system packages) for
# linux/arm64 and linux/amd64 using native runners (no QEMU).
#
# Triggers:
# - Weekly schedule (Monday 04:00 UTC)
# - Manual dispatch
#
# Requirements: FR-024, FR-027

name: Build Base Image

on:
schedule:
- cron: "0 4 * * 1"
workflow_dispatch:

env:
REGISTRY: quay.io
IMAGE_NAME: unbound-force/opencode-base

jobs:
build-amd64:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build base image (amd64)
run: podman build -t opencode-base-amd64 -f Containerfile.base .

- name: Smoke test (amd64)
run: |
echo "==> Base image smoke tests (amd64) ..."
podman run --rm --entrypoint go opencode-base-amd64 version
podman run --rm --entrypoint node opencode-base-amd64 --version
podman run --rm --entrypoint git opencode-base-amd64 --version
podman run --rm --entrypoint gh opencode-base-amd64 --version

USER_OUTPUT=$(podman run --rm --entrypoint whoami opencode-base-amd64)
if [ "$USER_OUTPUT" != "dev" ]; then
echo "ERROR: Expected 'dev' but got '$USER_OUTPUT'"
exit 1
fi
echo "All base image smoke tests passed (amd64)"

- name: Save image
run: podman save opencode-base-amd64 -o /tmp/opencode-base-amd64.tar

- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: opencode-base-amd64
path: /tmp/opencode-base-amd64.tar
retention-days: 1

build-arm64:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build base image (arm64)
run: podman build -t opencode-base-arm64 -f Containerfile.base .

- name: Smoke test (arm64)
run: |
echo "==> Base image smoke tests (arm64) ..."
podman run --rm --entrypoint go opencode-base-arm64 version
podman run --rm --entrypoint node opencode-base-arm64 --version
podman run --rm --entrypoint git opencode-base-arm64 --version
podman run --rm --entrypoint gh opencode-base-arm64 --version

USER_OUTPUT=$(podman run --rm --entrypoint whoami opencode-base-arm64)
if [ "$USER_OUTPUT" != "dev" ]; then
echo "ERROR: Expected 'dev' but got '$USER_OUTPUT'"
exit 1
fi
echo "All base image smoke tests passed (arm64)"

- name: Save image
run: podman save opencode-base-arm64 -o /tmp/opencode-base-arm64.tar

- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: opencode-base-arm64
path: /tmp/opencode-base-arm64.tar
retention-days: 1

push-manifest:
needs: [build-amd64, build-arm64]
runs-on: ubuntu-latest
steps:
- name: Download amd64 image
uses: actions/download-artifact@v4
with:
name: opencode-base-amd64
path: /tmp

- name: Download arm64 image
uses: actions/download-artifact@v4
with:
name: opencode-base-arm64
path: /tmp

- name: Load images
run: |
podman load -i /tmp/opencode-base-amd64.tar
podman load -i /tmp/opencode-base-arm64.tar

- name: Login to quay.io
run: |
podman login quay.io \
-u "${{ secrets.QUAY_USERNAME }}" \
-p "${{ secrets.QUAY_PASSWORD }}"

- name: Create and push manifest
run: |
podman manifest create opencode-base
podman manifest add opencode-base containers-storage:localhost/opencode-base-amd64
podman manifest add opencode-base containers-storage:localhost/opencode-base-arm64
podman manifest push opencode-base \
"docker://${REGISTRY}/${IMAGE_NAME}:latest"
160 changes: 160 additions & 0 deletions .github/workflows/build-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# build-push.yml — Multi-arch dev image build and push
#
# Builds opencode-dev for linux/arm64 and linux/amd64 using native
# runners (no QEMU). Uses opencode-base as the foundation layer.
#
# Triggers:
# - Push to main: build + push with "latest" tag
# - Version tag (v*): build + push with version tag
# - Pull request: build only (no push) for validation
#
# Requirements: FR-017, FR-026

name: Build and Push Container Image

on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]

env:
REGISTRY: quay.io
IMAGE_NAME: unbound-force/opencode-dev

jobs:
build-amd64:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build dev image (amd64)
run: podman build -t opencode-dev-amd64 -f Containerfile .

- name: Smoke test (amd64)
run: |
echo "==> Smoke tests (amd64) ..."
podman run --rm opencode-dev-amd64 uf --version
podman run --rm opencode-dev-amd64 opencode --version
podman run --rm opencode-dev-amd64 dewey version
podman run --rm opencode-dev-amd64 replicator --version
podman run --rm opencode-dev-amd64 gaze --version
podman run --rm opencode-dev-amd64 go version
podman run --rm opencode-dev-amd64 golangci-lint --version
podman run --rm opencode-dev-amd64 govulncheck -version
podman run --rm opencode-dev-amd64 node --version
podman run --rm opencode-dev-amd64 npm --version
podman run --rm opencode-dev-amd64 git --version
podman run --rm opencode-dev-amd64 gh --version

USER_OUTPUT=$(podman run --rm --entrypoint whoami opencode-dev-amd64)
if [ "$USER_OUTPUT" != "dev" ]; then
echo "ERROR: Expected 'dev' but got '$USER_OUTPUT'"
exit 1
fi
echo "All smoke tests passed (amd64)"

- name: Save image
run: podman save opencode-dev-amd64 -o /tmp/opencode-dev-amd64.tar

- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: opencode-dev-amd64
path: /tmp/opencode-dev-amd64.tar
retention-days: 1

build-arm64:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build dev image (arm64)
run: podman build -t opencode-dev-arm64 -f Containerfile .

- name: Smoke test (arm64)
run: |
echo "==> Smoke tests (arm64) ..."
podman run --rm opencode-dev-arm64 uf --version
podman run --rm opencode-dev-arm64 opencode --version
podman run --rm opencode-dev-arm64 dewey version
podman run --rm opencode-dev-arm64 replicator --version
podman run --rm opencode-dev-arm64 gaze --version
podman run --rm opencode-dev-arm64 go version
podman run --rm opencode-dev-arm64 golangci-lint --version
podman run --rm opencode-dev-arm64 govulncheck -version
podman run --rm opencode-dev-arm64 node --version
podman run --rm opencode-dev-arm64 npm --version
podman run --rm opencode-dev-arm64 git --version
podman run --rm opencode-dev-arm64 gh --version

USER_OUTPUT=$(podman run --rm --entrypoint whoami opencode-dev-arm64)
if [ "$USER_OUTPUT" != "dev" ]; then
echo "ERROR: Expected 'dev' but got '$USER_OUTPUT'"
exit 1
fi
echo "All smoke tests passed (arm64)"

- name: Save image
run: podman save opencode-dev-arm64 -o /tmp/opencode-dev-arm64.tar

- name: Upload image artifact
uses: actions/upload-artifact@v4
with:
name: opencode-dev-arm64
path: /tmp/opencode-dev-arm64.tar
retention-days: 1

push-manifest:
if: github.event_name == 'push'
needs: [build-amd64, build-arm64]
runs-on: ubuntu-latest
steps:
- name: Determine image tag
id: tags
run: |
if [[ "${{ github.ref_type }}" == "tag" ]]; then
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
else
echo "tag=latest" >> "$GITHUB_OUTPUT"
fi

- name: Download amd64 image
uses: actions/download-artifact@v4
with:
name: opencode-dev-amd64
path: /tmp

- name: Download arm64 image
uses: actions/download-artifact@v4
with:
name: opencode-dev-arm64
path: /tmp

- name: Load images
run: |
podman load -i /tmp/opencode-dev-amd64.tar
podman load -i /tmp/opencode-dev-arm64.tar

- name: Login to quay.io
run: |
podman login quay.io \
-u "${{ secrets.QUAY_USERNAME }}" \
-p "${{ secrets.QUAY_PASSWORD }}"

- name: Create and push manifest
run: |
podman manifest create opencode-dev
podman manifest add opencode-dev containers-storage:localhost/opencode-dev-amd64
podman manifest add opencode-dev containers-storage:localhost/opencode-dev-arm64
podman manifest push opencode-dev \
"docker://${REGISTRY}/${IMAGE_NAME}:${{ steps.tags.outputs.tag }}"

- name: Push latest tag (version tag push)
if: github.ref_type == 'tag'
run: |
podman manifest push opencode-dev \
"docker://${REGISTRY}/${IMAGE_NAME}:latest"
Empty file modified .specify/scripts/bash/check-prerequisites.sh
100644 → 100755
Empty file.
Empty file modified .specify/scripts/bash/create-new-feature.sh
100644 → 100755
Empty file.
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,9 @@ valid results.

- [Discussion #88](https://github.com/orgs/unbound-force/discussions/88) — full architecture and design rationale
- [Issue #1](https://github.com/unbound-force/containerfile/issues/1) — implementation issue with all deliverables

## Active Technologies
- Shell scripts (bash), Containerfile (OCI/Docker syntax), YAML (devfile 2.2.0, compose, GitHub Actions) + Podman, Go 1.24+, Node.js 20+, npm, Git, gh CLI (001-initial-containerfile)

## Recent Changes
- 001-initial-containerfile: Added initial Containerfile, Containerfile.udi, devfiles, helper scripts, podman-compose, CI workflow, README
49 changes: 49 additions & 0 deletions Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Containerfile — Unbound Force OpenCode Dev Container
#
# Multi-arch OCI image (linux/arm64, linux/amd64) with the full UF
# toolchain for AI-assisted development inside Podman containers.
#
# Base: opencode-base (Fedora 41 + Go 1.25 + system packages + dev user)
# Strategy: Single-stage build — agents need Go + Node.js at runtime
# User: Non-root "dev" user (inherited from base)
#
# Build:
# podman build -t opencode-dev -f Containerfile .
#
# Smoke test:
# podman run --rm opencode-dev uf --version
# podman run --rm --entrypoint whoami opencode-dev # prints "dev"

FROM quay.io/unbound-force/opencode-base:latest

# ---------------------------------------------------------------------------
# Install UF tools as dev user (go install + npm)
# ---------------------------------------------------------------------------

COPY --chown=dev:dev scripts/install-uf-tools.sh /home/dev/scripts/install-uf-tools.sh
RUN chmod +x /home/dev/scripts/install-uf-tools.sh
RUN /home/dev/scripts/install-uf-tools.sh

# ---------------------------------------------------------------------------
# Install OpenCode via official curl installer
# ---------------------------------------------------------------------------

RUN curl -fsSL https://opencode.ai/install | bash

# ---------------------------------------------------------------------------
# Copy entrypoint and helper scripts
# ---------------------------------------------------------------------------

USER root
COPY --chown=dev:dev scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
COPY --chown=dev:dev scripts/extract-changes.sh /usr/local/bin/extract-changes.sh
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/extract-changes.sh

# ---------------------------------------------------------------------------
# Final runtime configuration
# ---------------------------------------------------------------------------

USER dev
WORKDIR /home/dev

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
Loading
Loading