Skip to content

Public Release

Public Release #576

name: Public Release
on:
# Daily edge builds (scheduled)
schedule:
- cron: "0 8 * * *" # Runs every day at midnight Bay Area time (UTC-8)
# Stable releases (tag push) and Edge builds (main push)
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
branches:
- main
paths-ignore:
- "docs/**"
# Manual dispatch with options
workflow_dispatch:
inputs:
release_type:
description: "Release type"
required: true
default: "edge"
type: choice
options:
- stable
- edge
tag:
description: "Tag/version for stable release (required for stable)"
required: false
default: ""
build_targets:
description: "Docker targets to build"
required: false
default: "runtime"
type: choice
options:
- runtime
- none
env:
CARGO_TERM_COLOR: always
MACOSX_DEPLOYMENT_TARGET: "12.0"
defaults:
run:
shell: bash
permissions:
contents: write
actions: read
packages: write
pull-requests: read
attestations: write
id-token: write
security-events: write
jobs:
# ============================================================================
# Job 1: Determine build type and set all conditional flags
# ============================================================================
determine-build-type:
if: github.repository == 'oxy-hq/oxy'
name: Determine Build Type
runs-on: ubuntu-latest
outputs:
channel: ${{ steps.config.outputs.channel }}
version: ${{ steps.config.outputs.version }}
display_name: ${{ steps.config.outputs.display_name }}
should_build: ${{ steps.config.outputs.should_build }}
should_build_docker: ${{ steps.config.outputs.should_build_docker }}
release_repo: ${{ steps.config.outputs.release_repo }}
docker_tag_prefix: ${{ steps.config.outputs.docker_tag_prefix }}
needs_changesets: ${{ steps.config.outputs.needs_changesets }}
is_stable: ${{ steps.config.outputs.is_stable }}
short_sha: ${{ steps.config.outputs.short_sha }}
current_date: ${{ steps.config.outputs.current_date }}
compat_tag: ${{ steps.config.outputs.compat_tag }}
steps:
- name: Determine configuration
id: config
run: |
SHORT_SHA=${GITHUB_SHA::7}
CURRENT_DATE=$(date -u +'%Y%m%d')
BUILD_TARGETS="${{ inputs.build_targets || 'runtime' }}"
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "current_date=${CURRENT_DATE}" >> $GITHUB_OUTPUT
# Determine channel based on trigger
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == refs/tags/* ]]; then
# Tag push = stable release
CHANNEL="stable"
VERSION="${{ github.ref_name }}"
DISPLAY_NAME="Release ${VERSION}"
SHOULD_BUILD="true"
RELEASE_REPO="oxy-hq/oxy"
DOCKER_TAG_PREFIX=""
NEEDS_CHANGESETS="false"
IS_STABLE="true"
COMPAT_TAG=""
elif [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.release_type }}" == "stable" ]]; then
# Manual stable release
CHANNEL="stable"
VERSION="${{ inputs.tag }}"
if [ -z "$VERSION" ]; then
echo "::error::Tag is required for stable release"
exit 1
fi
DISPLAY_NAME="Release ${VERSION}"
SHOULD_BUILD="true"
RELEASE_REPO="oxy-hq/oxy"
DOCKER_TAG_PREFIX=""
NEEDS_CHANGESETS="false"
IS_STABLE="true"
COMPAT_TAG=""
else
# Push to main, scheduled, or manual edge
CHANNEL="edge"
VERSION="edge-${SHORT_SHA}"
DISPLAY_NAME="Edge Build ${SHORT_SHA}"
# Scheduled builds always run, main pushes check changesets
if [[ "${{ github.event_name }}" == "schedule" ]]; then
SHOULD_BUILD="true"
NEEDS_CHANGESETS="false"
else
SHOULD_BUILD="defer" # Will be determined by changesets
NEEDS_CHANGESETS="true"
fi
RELEASE_REPO="oxy-hq/oxy-nightly"
DOCKER_TAG_PREFIX="edge-"
IS_STABLE="false"
COMPAT_TAG="edge-main-${SHORT_SHA}"
fi
# Determine if Docker should be built
if [ "$BUILD_TARGETS" = "none" ]; then
SHOULD_BUILD_DOCKER="false"
else
SHOULD_BUILD_DOCKER="true"
fi
echo "channel=${CHANNEL}" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "display_name=${DISPLAY_NAME}" >> $GITHUB_OUTPUT
echo "should_build=${SHOULD_BUILD}" >> $GITHUB_OUTPUT
echo "should_build_docker=${SHOULD_BUILD_DOCKER}" >> $GITHUB_OUTPUT
echo "release_repo=${RELEASE_REPO}" >> $GITHUB_OUTPUT
echo "docker_tag_prefix=${DOCKER_TAG_PREFIX}" >> $GITHUB_OUTPUT
echo "needs_changesets=${NEEDS_CHANGESETS}" >> $GITHUB_OUTPUT
echo "is_stable=${IS_STABLE}" >> $GITHUB_OUTPUT
echo "compat_tag=${COMPAT_TAG}" >> $GITHUB_OUTPUT
echo "### Build Configuration" >> $GITHUB_STEP_SUMMARY
echo "| Setting | Value |" >> $GITHUB_STEP_SUMMARY
echo "|---------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${CHANNEL} |" >> $GITHUB_STEP_SUMMARY
echo "| Version | ${VERSION} |" >> $GITHUB_STEP_SUMMARY
echo "| Release Repo | ${RELEASE_REPO} |" >> $GITHUB_STEP_SUMMARY
echo "| Docker Targets | ${BUILD_TARGETS} |" >> $GITHUB_STEP_SUMMARY
echo "| Needs Changesets | ${NEEDS_CHANGESETS} |" >> $GITHUB_STEP_SUMMARY
# ============================================================================
# Job 2: Changesets (for edge builds only)
# ============================================================================
changesets:
name: Changesets
needs: [ determine-build-type ]
if: needs.determine-build-type.outputs.needs_changesets == 'true'
uses: ./.github/workflows/changesets.yaml
# ============================================================================
# Job 3a: Download Latest Edge Binary (for Dockerfile-only changes)
#
# Optimization: When only Dockerfile changes (no code/web-app changes), skip
# the expensive build-cli job and instead download the latest edge release.
# This job only runs for:
# - Edge builds (not stable)
# - When docker changeset is true
# - But oxy and web-app changesets are false
# - Only downloads Linux binaries (needed for Docker images)
# ============================================================================
download-edge-binary:
name: Download latest edge binary for ${{ matrix.job.target }}
needs: [ determine-build-type, changesets ]
if: |
always() &&
needs.determine-build-type.result == 'success' &&
needs.determine-build-type.outputs.channel == 'edge' &&
needs.changesets.result == 'success' &&
needs.changesets.outputs.docker == 'true' &&
needs.changesets.outputs.oxy == 'false' &&
needs.changesets.outputs.web-app == 'false'
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-22.04-arm
target: aarch64-unknown-linux-gnu
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Create artifacts folder
run: mkdir -p artifacts
- name: Download latest edge binary
env:
OXY_VERSION: latest
run: |
# Download using the install script
bash install_oxy_nightly.sh
# Find where it was installed
if [ "$(id -u)" -eq 0 ]; then
INSTALL_DIR="/usr/local/bin"
else
INSTALL_DIR="$HOME/.local/bin"
fi
# Copy to artifacts
cp "$INSTALL_DIR/oxy" artifacts/oxy-${{ matrix.job.target }}
chmod +x artifacts/oxy-${{ matrix.job.target }}
- name: Create binary checksum
run: shasum --algorithm 256 --binary oxy-${{ matrix.job.target }} | tee
SHA256SUM-oxy-${{ matrix.job.target }}.txt
working-directory: artifacts
- name: Upload release artifacts
uses: actions/upload-artifact@v7
with:
name: cli-${{ matrix.job.target }}
path: artifacts/**/*
if-no-files-found: error
retention-days: 1
# ============================================================================
# Job 3b: Build CLI (4 targets)
# ============================================================================
build-cli:
name: Build ${{ needs.determine-build-type.outputs.channel }} CLI for ${{
matrix.job.target }}
needs: [ determine-build-type, changesets ]
if: |
always() &&
needs.determine-build-type.result == 'success' &&
(needs.determine-build-type.outputs.should_build == 'true' ||
(needs.changesets.result == 'success' &&
(needs.changesets.outputs.oxy == 'true' || needs.changesets.outputs.web-app == 'true')))
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-15-intel
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-22.04-arm
target: aarch64-unknown-linux-gnu
steps:
- uses: actions/create-github-app-token@v3
name: Create GitHub App Token
id: app-token
with:
app-id: ${{ vars.ARGO_APP_ID }}
private-key: ${{ secrets.ARGO_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: |
oxy
oxy-internal
oxy-nightly
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ needs.determine-build-type.outputs.is_stable == 'true' &&
needs.determine-build-type.outputs.version || github.sha }}
token: ${{ steps.app-token.outputs.token }}
- name: Create artifacts folder
run: mkdir -p artifacts
- uses: rui314/setup-mold@v1
if: runner.os == 'Linux'
with:
make-default: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
targets: ${{ matrix.job.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: build-cli-${{ matrix.job.os }}-${{ matrix.job.target }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ steps.app-token.outputs.token }}
- uses: pnpm/action-setup@v5
with:
run_install: false
- name: Install Node.js
uses: actions/setup-node@v6
id: setup-node
with:
node-version: 24.x
cache: "pnpm"
# Cache pnpm store for faster dependency installation
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm store cache
uses: actions/cache@v5
with:
path: ${{ env.STORE_PATH }}
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-${{ runner.os }}-
# Cache TypeScript build info for incremental compilation
- name: Cache TypeScript build info
uses: actions/cache@v5
with:
path: |
web-app/tsconfig.app.tsbuildinfo
web-app/tsconfig.node.tsbuildinfo
key: typescript-${{ runner.os }}-${{ hashFiles('web-app/tsconfig*.json',
'web-app/src/**/*.ts', 'web-app/src/**/*.tsx') }}
restore-keys: |
typescript-${{ runner.os }}-
# Cache Vite optimized deps
- name: Cache Vite
uses: actions/cache@v5
with:
path: web-app/node_modules/.vite
key: vite-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{
hashFiles('web-app/vite.config.*', 'web-app/src/**') }}
restore-keys: |
vite-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
vite-${{ runner.os }}-
# Cache Turbo for monorepo builds
- name: Cache Turbo
uses: actions/cache@v5
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-
- name: Install dependencies & Build web-app
env:
NODE_OPTIONS: "--max-old-space-size=8192"
run: |
pnpm install --frozen-lockfile --prefer-offline
pnpm build
- name: Build oxy CLI
run: |
cargo build --release --target ${{ matrix.job.target }}
mv target/${{ matrix.job.target }}/release/oxy artifacts/oxy-${{ matrix.job.target }}
- name: Generate config schema (once)
if: matrix.job.target == 'aarch64-apple-darwin'
run: |
cargo run --release --target ${{ matrix.job.target }} -- gen-config-schema
cp -a json-schemas/. artifacts/
cp Cargo.lock artifacts/
- name: Create binary checksum
run: shasum --algorithm 256 --binary oxy-${{ matrix.job.target }} | tee
SHA256SUM-oxy-${{ matrix.job.target }}.txt
working-directory: artifacts
- name: Upload release artifacts
uses: actions/upload-artifact@v7
with:
name: cli-${{ matrix.job.target }}
path: artifacts/**/*
if-no-files-found: error
retention-days: 1
# ============================================================================
# Job 4: Release CLI
# ============================================================================
release-cli:
name: Release ${{ needs.determine-build-type.outputs.channel }} CLI
runs-on: ubuntu-latest
needs: [ determine-build-type, build-cli ]
# Concurrency: Lock releases per channel and version/ref
concurrency:
group: >-
${{ github.event_name == 'schedule' &&
format('release-edge-scheduled-{0}', github.run_id) ||
startsWith(github.ref, 'refs/tags/') && format('release-stable-{0}', github.ref_name) ||
'release-edge-main' }}
cancel-in-progress: ${{ github.event_name == 'push' && !startsWith(github.ref,
'refs/tags/') }}
if: |
always() &&
needs.determine-build-type.result == 'success' &&
needs.build-cli.result == 'success'
steps:
- uses: actions/create-github-app-token@v3
name: Create GitHub App Token
id: app-token
with:
app-id: ${{ vars.ARGO_APP_ID }}
private-key: ${{ secrets.ARGO_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: |
oxy
oxy-internal
oxy-nightly
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Download release assets from artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
merge-multiple: true
- name: List all artifacts
run: ls -R ./artifacts
- name: Combine checksums
run: cat artifacts/SHA256SUM-oxy-*.txt | tee artifacts/SHA256SUMS.txt
- name: Ensure binaries are executable
run: chmod +x artifacts/oxy-*
# Edge only: Create transitional compatibility tag
- name: Create transitional compatibility tag (edge only)
if: needs.determine-build-type.outputs.compat_tag != ''
run: |
git tag ${{ needs.determine-build-type.outputs.compat_tag }} || true
git push origin ${{ needs.determine-build-type.outputs.compat_tag }} || true
- name: Get commit message
id: commit-msg
run: |
{
echo "message<<EOF"
git log -1 --pretty=format:'%s' ${{ github.sha }}
echo ""
echo "EOF"
} >> $GITHUB_OUTPUT
# Stable release
- name: Install git-cliff (stable only)
if: needs.determine-build-type.outputs.is_stable == 'true'
uses: taiki-e/install-action@v2
with:
tool: git-cliff
- name: Generate release notes (stable only)
if: needs.determine-build-type.outputs.is_stable == 'true'
run: git cliff --tag ${{ needs.determine-build-type.outputs.version }} --latest
-o /tmp/release-notes.md
- name: Create stable release
if: needs.determine-build-type.outputs.is_stable == 'true'
uses: softprops/action-gh-release@v3
with:
draft: false
prerelease: true
tag_name: ${{ needs.determine-build-type.outputs.version }}
body_path: /tmp/release-notes.md
files: |
artifacts/oxy-*
artifacts/SHA256SUM-*.txt
artifacts/SHA256SUMS.txt
artifacts/*.json
artifacts/*.whl
fail_on_unmatched_files: false
# Edge release
- name: Create edge release
if: needs.determine-build-type.outputs.is_stable == 'false'
uses: softprops/action-gh-release@v3
with:
token: ${{ steps.app-token.outputs.token }}
draft: false
repository: ${{ needs.determine-build-type.outputs.release_repo }}
prerelease: false
make_latest: true
tag_name: ${{ needs.determine-build-type.outputs.version }}
name: ${{ needs.determine-build-type.outputs.display_name }} (${{
needs.determine-build-type.outputs.version }})
files: |
artifacts/oxy-*
artifacts/*.json
fail_on_unmatched_files: false
body: |
Channel: ${{ needs.determine-build-type.outputs.channel }}
Date: ${{ needs.determine-build-type.outputs.current_date }}
Commit: [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
Message: ${{ steps.commit-msg.outputs.message }}
Primary Tag: ${{ needs.determine-build-type.outputs.version }}
Transitional Tag: ${{ needs.determine-build-type.outputs.compat_tag || 'n/a' }}
To install latest:
`bash <(curl --proto '=https' --tlsv1.2 -LsSf https://nightly.oxy.tech)`
To install this exact build:
`OXY_VERSION=${{ needs.determine-build-type.outputs.version }} bash <(curl --proto '=https' --tlsv1.2 -LsSf https://nightly.oxy.tech)`
# ============================================================================
# Job 5: Build and Publish Docker Images
# ============================================================================
build-docker-images:
name: Build ${{ matrix.target.name }} ${{
needs.determine-build-type.outputs.channel }} Docker for ${{ matrix.arch
}}
needs: [ determine-build-type, build-cli, download-edge-binary ]
if: |
always() &&
(needs.build-cli.result == 'success' || needs.download-edge-binary.result == 'success') &&
needs.determine-build-type.outputs.should_build_docker == 'true'
concurrency:
group: docker-${{ needs.determine-build-type.outputs.channel }}-${{
matrix.target.name }}-${{ matrix.arch }}
cancel-in-progress: true
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: true
matrix:
arch: [ linux/amd64, linux/arm64 ]
target:
- name: runtime
image: oxy
target: runtime
include:
- arch: linux/amd64
runner: ubuntu-latest
rust_target: x86_64-unknown-linux-gnu
arch_tag: amd64
- arch: linux/arm64
runner: ubuntu-24.04-arm
rust_target: aarch64-unknown-linux-gnu
arch_tag: arm64
steps:
- name: Check if should build ${{ matrix.target.name }}
id: should_build
run: |
BUILD_TARGETS="${{ inputs.build_targets || 'runtime' }}"
TARGET="${{ matrix.target.name }}"
if [ "$BUILD_TARGETS" = "runtime" ] || [ "$BUILD_TARGETS" = "$TARGET" ]; then
echo "should_build=true" >> $GITHUB_OUTPUT
else
echo "should_build=false" >> $GITHUB_OUTPUT
echo "Skipping $TARGET"
fi
- name: Checkout code
if: steps.should_build.outputs.should_build == 'true'
uses: actions/checkout@v6
with:
ref: ${{ needs.determine-build-type.outputs.is_stable == 'true' &&
needs.determine-build-type.outputs.version || github.sha }}
- name: Download pre-built binary
if: steps.should_build.outputs.should_build == 'true'
uses: actions/download-artifact@v8
with:
name: cli-${{ matrix.rust_target }}
path: artifacts
- name: Prepare binary for Docker build
if: steps.should_build.outputs.should_build == 'true'
run: |
cp artifacts/oxy-${{ matrix.rust_target }} oxy
chmod +x oxy
- name: Set up Docker Buildx
if: steps.should_build.outputs.should_build == 'true'
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
if: steps.should_build.outputs.should_build == 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine tags
if: steps.should_build.outputs.should_build == 'true'
id: meta
run: |
VERSION="${{ needs.determine-build-type.outputs.version }}"
CHANNEL="${{ needs.determine-build-type.outputs.channel }}"
DATE="${{ needs.determine-build-type.outputs.current_date }}"
SHORT_SHA="${{ needs.determine-build-type.outputs.short_sha }}"
IMAGE="ghcr.io/${{ github.repository_owner }}/${{ matrix.target.image }}"
ARCH="${{ matrix.arch_tag }}"
if [ "$CHANNEL" = "stable" ]; then
TAGS="${IMAGE}:${VERSION}-${ARCH}"
TAGS="${TAGS},${IMAGE}:latest-${ARCH}"
else
# Edge
TAGS="${IMAGE}:edge-${DATE}-${ARCH}"
TAGS="${TAGS},${IMAGE}:edge-${SHORT_SHA}-${ARCH}"
TAGS="${TAGS},${IMAGE}:edge-latest-${ARCH}"
# Transitional compatibility tag
TAGS="${TAGS},${IMAGE}:edge-main-${SHORT_SHA}-${ARCH}"
fi
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
- name: Build and push ${{ matrix.target.name }} image
if: steps.should_build.outputs.should_build == 'true'
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.prebuilt
target: ${{ matrix.target.target }}
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: BINARY_PATH=oxy
provenance: false
# ============================================================================
# Job 6: Trivy Docker Image Security Scan
# ============================================================================
trivy-image-scan:
name: Trivy Docker Image Scan
needs: [ determine-build-type, build-docker-images ]
if: |
always() &&
needs.build-docker-images.result == 'success'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine image to scan
id: image
run: |
VERSION="${{ needs.determine-build-type.outputs.version }}"
CHANNEL="${{ needs.determine-build-type.outputs.channel }}"
DATE="${{ needs.determine-build-type.outputs.current_date }}"
IMAGE="ghcr.io/${{ github.repository_owner }}/oxy"
if [ "$CHANNEL" = "stable" ]; then
echo "ref=${IMAGE}:${VERSION}-amd64" >> $GITHUB_OUTPUT
else
echo "ref=${IMAGE}:edge-${DATE}-amd64" >> $GITHUB_OUTPUT
fi
- name: Run Trivy vulnerability scanner (image)
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: image
image-ref: ${{ steps.image.outputs.ref }}
format: table
output: trivy-image-results.txt
severity: CRITICAL,HIGH
exit-code: "0"
- name: Publish image scan results to workflow summary
if: always()
run: |
if [[ -s trivy-image-results.txt ]]; then
{
echo "### Docker Image Vulnerability Scan"
echo "<details><summary>Click to expand</summary>"
echo ""
echo '```'
cat trivy-image-results.txt
echo '```'
echo "</details>"
} >> $GITHUB_STEP_SUMMARY
else
echo "### Docker Image Vulnerability Scan" >> $GITHUB_STEP_SUMMARY
echo "No CRITICAL or HIGH vulnerabilities found." >> $GITHUB_STEP_SUMMARY
fi
# ============================================================================
# Job 7: Create and Push Multi-Arch Manifests
# ============================================================================
create-manifests:
name: Create ${{ matrix.target.name }} ${{
needs.determine-build-type.outputs.channel }} manifest
needs: [ determine-build-type, build-docker-images ]
# Concurrency: Lock manifest creation per target and channel
concurrency:
group: manifest-${{ needs.determine-build-type.outputs.channel }}-${{
matrix.target.name }}
cancel-in-progress: false
if: |
always() &&
needs.build-docker-images.result == 'success'
runs-on: ubuntu-24.04-arm
strategy:
matrix:
target:
- name: runtime
image: oxy
steps:
- name: Check if should publish ${{ matrix.target.name }}
id: should_publish
run: |
BUILD_TARGETS="${{ inputs.build_targets || 'runtime' }}"
TARGET="${{ matrix.target.name }}"
if [ "$BUILD_TARGETS" = "runtime" ] || [ "$BUILD_TARGETS" = "$TARGET" ]; then
echo "should_publish=true" >> $GITHUB_OUTPUT
else
echo "should_publish=false" >> $GITHUB_OUTPUT
echo "Skipping manifest for $TARGET"
fi
- name: Log in to GitHub Container Registry
if: steps.should_publish.outputs.should_publish == 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push multi-arch manifests
if: steps.should_publish.outputs.should_publish == 'true'
run: |
VERSION="${{ needs.determine-build-type.outputs.version }}"
CHANNEL="${{ needs.determine-build-type.outputs.channel }}"
DATE="${{ needs.determine-build-type.outputs.current_date }}"
SHORT_SHA="${{ needs.determine-build-type.outputs.short_sha }}"
COMPAT_TAG="${{ needs.determine-build-type.outputs.compat_tag }}"
IMAGE="ghcr.io/${{ github.repository_owner }}/${{ matrix.target.image }}"
echo "Creating $CHANNEL manifests for ${{ matrix.target.image }}..."
if [ "$CHANNEL" = "stable" ]; then
# Version manifest
docker manifest create ${IMAGE}:${VERSION} \
${IMAGE}:${VERSION}-amd64 \
${IMAGE}:${VERSION}-arm64
docker manifest push ${IMAGE}:${VERSION}
# Latest manifest
docker manifest create ${IMAGE}:latest \
${IMAGE}:latest-amd64 \
${IMAGE}:latest-arm64
docker manifest push ${IMAGE}:latest
echo "✅ Published ${IMAGE}:${VERSION} and ${IMAGE}:latest"
else
# Edge: Date manifest
docker manifest create ${IMAGE}:edge-${DATE} \
${IMAGE}:edge-${DATE}-amd64 \
${IMAGE}:edge-${DATE}-arm64
docker manifest push ${IMAGE}:edge-${DATE}
# Edge: Latest manifest
docker manifest create ${IMAGE}:edge-latest \
${IMAGE}:edge-latest-amd64 \
${IMAGE}:edge-latest-arm64
docker manifest push ${IMAGE}:edge-latest
# Edge: Transitional compatibility manifest
if [ -n "$COMPAT_TAG" ]; then
docker manifest create ${IMAGE}:${COMPAT_TAG} \
${IMAGE}:${COMPAT_TAG}-amd64 \
${IMAGE}:${COMPAT_TAG}-arm64
docker manifest push ${IMAGE}:${COMPAT_TAG}
echo "✅ Published compatibility manifest: ${COMPAT_TAG}"
fi
echo "✅ Published edge manifests for ${{ matrix.target.image }}"
fi
# ============================================================================
# Job 8: Create Deployment Summary
# ============================================================================
deployment-summary:
name: Create Deployment Summary
needs: [ determine-build-type, create-manifests ]
if: |
always() &&
needs.create-manifests.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Create deployment summary
run: |
VERSION="${{ needs.determine-build-type.outputs.version }}"
CHANNEL="${{ needs.determine-build-type.outputs.channel }}"
BUILD_TARGETS="${{ inputs.build_targets || 'runtime' }}"
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
# Docker Images Published
## Published Images
EOF
if [ "$BUILD_TARGETS" = "runtime" ]; then
if [ "$CHANNEL" = "stable" ]; then
TAG="${VERSION}"
else
TAG="edge-latest"
fi
cat >> $GITHUB_STEP_SUMMARY << EOF
### Oxy Runtime
- **Image:** \`ghcr.io/${{ github.repository_owner }}/oxy:${TAG}\`
- **Architectures:** linux/amd64, linux/arm64
- **Pull:**
\`\`\`bash
docker pull ghcr.io/${{ github.repository_owner }}/oxy:${TAG}
\`\`\`
EOF
fi
cat >> $GITHUB_STEP_SUMMARY << EOF
---
**Channel:** \`${CHANNEL}\`
**Version:** \`${VERSION}\`
**Built from:** \`${{ github.sha }}\`
**Workflow:** [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
EOF