Public Release #576
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |