---
name: Release
"on":
push:
tags:
- "v*.*.*"
workflow_dispatch:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
version:
name: Resolve Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Get version from tag or Cargo.toml
id: get_version
run: |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
else
# Dry-run (workflow_dispatch): version from Cargo.toml, marked -dev
v=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2)
echo "version=${v}-dev" >> "$GITHUB_OUTPUT"
fi
build:
name: Build (${{ matrix.platform }})
needs: [version]
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux-amd64
ext: ""
- os: ubuntu-24.04-arm
platform: linux-arm64
ext: ""
- os: macos-latest
platform: macos-arm64
ext: ""
- os: windows-latest
platform: windows-amd64
ext: ".exe"
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 with:
toolchain: stable
- name: Build release binary
run: cargo build --release --locked
- name: Stage named artifact
shell: bash
env:
VERSION: ${{ needs.version.outputs.version }}
PLATFORM: ${{ matrix.platform }}
EXT: ${{ matrix.ext }}
run: |
mkdir -p dist
cp "target/release/rlm-cli${EXT}" \
"dist/rlm-cli-${VERSION}-${PLATFORM}${EXT}"
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 with:
subject-path: dist/*
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: rlm-cli-${{ needs.version.outputs.version }}-${{ matrix.platform }}
path: dist/*
if-no-files-found: error
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 with:
toolchain: stable
- name: Run tests
run: cargo test --all-features --locked
audit:
name: Cargo Audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Install cargo-audit
uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154 with:
tool: cargo-audit
- name: Audit dependencies for known vulnerabilities
run: cargo audit
sbom:
name: SBOM (generate + attest)
needs: [version, build]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Download all binaries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: dist
merge-multiple: true
- name: Generate CycloneDX SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 with:
path: .
format: cyclonedx-json
output-file: rlm-cli-${{ needs.version.outputs.version }}-sbom.cdx.json
upload-artifact: false
upload-release-assets: false
- name: Attest SBOM (binds every binary to the SBOM)
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e with:
subject-path: dist/*
sbom-path: rlm-cli-${{ needs.version.outputs.version }}-sbom.cdx.json
- name: Upload SBOM artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: rlm-cli-${{ needs.version.outputs.version }}-sbom
path: rlm-cli-${{ needs.version.outputs.version }}-sbom.cdx.json
if-no-files-found: error
verify:
name: Verify Attestations
needs: [version, build, sbom]
runs-on: ubuntu-latest
permissions:
contents: read
attestations: read
steps:
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: dist
merge-multiple: true
- name: Fail-closed attestation verification
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
shopt -s nullglob
files=(dist/*)
# Every platform binary plus the SBOM must be present — a
# partial set must never reach the release.
if [ "${#files[@]}" -ne 5 ]; then
echo "::error::expected 5 artifacts, found ${#files[@]}"
exit 1
fi
for f in "${files[@]}"; do
case "$f" in
*-sbom.cdx.json) continue ;;
esac
echo "Verifying provenance: ${f}"
gh attestation verify "$f" --repo "$GITHUB_REPOSITORY"
echo "Verifying SBOM attestation: ${f}"
gh attestation verify "$f" --repo "$GITHUB_REPOSITORY" \
--predicate-type https://cyclonedx.org/bom
done
release:
name: Create Release
if: startsWith(github.ref, 'refs/tags/')
needs: [version, verify, audit, test]
runs-on: ubuntu-latest
environment: copilot
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: dist
merge-multiple: true
- name: Generate checksums
env:
VERSION: ${{ needs.version.outputs.version }}
run: |
cd dist
sha256sum -- * > "rlm-cli-${VERSION}-checksums.txt"
- name: Create GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda with:
tag_name: ${{ github.ref }}
name: Release ${{ needs.version.outputs.version }}
generate_release_notes: true
draft: false
prerelease: false
files: dist/*
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}