name: Release
'on':
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: Existing Git tag to publish, for example v0.5.0
required: true
type: string
permissions:
contents: write
jobs:
prepare:
name: Resolve release tag
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
steps:
- name: Resolve tag and version
id: resolve
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ inputs.tag }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
tag="$INPUT_TAG"
else
tag="$REF_NAME"
fi
if ! [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release tags must match vMAJOR.MINOR.PATCH: $tag" >&2
exit 1
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
create-release:
name: Create draft release
needs: prepare
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Create draft release when missing
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists in $GH_REPO"
exit 0
fi
if ! gh api "repos/$GH_REPO/git/ref/tags/$TAG" >/dev/null 2>&1; then
echo "Tag $TAG does not exist in $GH_REPO" >&2
exit 1
fi
gh release create "$TAG" \
--draft \
--generate-notes \
--title "$TAG"
build-assets:
name: Build ${{ matrix.target }}
needs: [prepare, create-release]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runner: windows-latest
target: x86_64-pc-windows-msvc
- runner: macos-latest
target: aarch64-apple-darwin
- runner: macos-15-intel
target: x86_64-apple-darwin
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
ref: ${{ needs.prepare.outputs.tag }}
- name: Setup Rust
uses: leynos/shared-actions/.github/actions/setup-rust@074f7d8ba75a6e5d18532b72cbe38fccbda4e9c6
- name: Setup uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990
with:
enable-cache: false
- name: Build release archive
shell: bash
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
TARGET: ${{ matrix.target }}
run: >-
uv run --script scripts/release_archive.py
"$TARGET"
--release-version "$RELEASE_VERSION"
- name: Ensure checksum sidecars
shell: bash
working-directory: dist
run: |
set -euo pipefail
# Arrays are avoided: expanding an empty array under `set -u`
# fails on the Bash 3.2 that macOS runners provide.
shopt -s nullglob
archives=0
for archive in *.tgz; do
archives=$((archives + 1))
if [ -f "${archive}.sha256" ]; then
continue
fi
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$archive" > "${archive}.sha256"
else
shasum --algorithm 256 "$archive" > "${archive}.sha256"
fi
done
if [ "$archives" -eq 0 ]; then
echo "release_archive.py produced no .tgz archive" >&2
exit 1
fi
- name: Upload release archive and checksum sidecar
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: gh release upload "$TAG" dist/*.tgz dist/*.tgz.sha256 --clobber
audit-draft-assets:
name: Audit draft assets (${{ matrix.target }})
needs: [prepare, build-assets]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runner: windows-latest
target: x86_64-pc-windows-msvc
- runner: macos-latest
target: aarch64-apple-darwin
- runner: macos-15-intel
target: x86_64-apple-darwin
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_PORT: "8765"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
ref: ${{ needs.prepare.outputs.tag }}
- name: Setup Rust
uses: leynos/shared-actions/.github/actions/setup-rust@074f7d8ba75a6e5d18532b72cbe38fccbda4e9c6
- name: Download draft release assets
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
rm -rf release-dist
mkdir -p release-dist
gh release download "$TAG" \
--dir release-dist \
--pattern "*.tgz" \
--pattern "*.tgz.sha256"
- name: Verify draft asset checksums
shell: bash
working-directory: release-dist
run: |
set -euo pipefail
# Arrays are avoided: expanding an empty array under `set -u`
# fails on the Bash 3.2 that macOS runners provide.
shopt -s nullglob
verified=0
for sidecar in *.tgz.sha256; do
if command -v sha256sum >/dev/null 2>&1; then
sha256sum --check --strict "$sidecar"
else
shasum --algorithm 256 --check --strict "$sidecar"
fi
verified=$((verified + 1))
done
if [ "$verified" -eq 0 ]; then
echo "No .sha256 sidecars were published alongside the archives" >&2
exit 1
fi
- name: Start local HTTPS archive server
id: archive-server
uses: ./.github/actions/setup-local-https-server
with:
port: ${{ env.RELEASE_PORT }}
cert-dir: ${{ runner.temp }}/binstall-cert
serve-dir: release-dist
- name: Audit cargo-binstall assets
shell: bash
env:
ROOT_CERTIFICATE: ${{ steps.archive-server.outputs.ca-certificate }}
RUNNER_OS: ${{ runner.os }}
SERVER_PID: ${{ steps.archive-server.outputs.server-pid }}
TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
trap 'kill "$SERVER_PID" 2>/dev/null || true' EXIT
install_dir="$PWD/.binstall-bin"
rm -rf "$install_dir"
mkdir -p "$install_dir"
pkg_url="https://127.0.0.1:${RELEASE_PORT}/{ name }-{ target }-v{ version }{ archive-suffix }"
for target in \
x86_64-unknown-linux-gnu \
aarch64-unknown-linux-gnu \
x86_64-pc-windows-msvc \
aarch64-apple-darwin \
x86_64-apple-darwin
do
cargo binstall -y \
--manifest-path Cargo.toml \
--targets "$target" \
--strategies crate-meta-data \
--pkg-url "$pkg_url" \
--root-certificates "$ROOT_CERTIFICATE" \
--install-path "$RUNNER_TEMP/binstall-dry-run" \
--force \
--dry-run \
pg-embed-setup-unpriv
done
cargo binstall -y \
--manifest-path Cargo.toml \
--targets "$TARGET" \
--strategies crate-meta-data \
--pkg-url "$pkg_url" \
--root-certificates "$ROOT_CERTIFICATE" \
--install-path "$install_dir" \
--force \
pg-embed-setup-unpriv
binary_ext=""
if [[ "$RUNNER_OS" == "Windows" ]]; then
binary_ext=".exe"
fi
test -f "$install_dir/pg_embedded_setup_unpriv${binary_ext}"
test -f "$install_dir/pg_worker${binary_ext}"
"$install_dir/pg_embedded_setup_unpriv${binary_ext}" --version
publish-release:
name: Publish release
needs: [prepare, audit-draft-assets]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Publish draft release
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
gh release edit "$TAG" --draft=false
verify-published-assets:
name: Verify published asset URLs
needs: [prepare, publish-release]
runs-on: ubuntu-24.04
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
ref: ${{ needs.prepare.outputs.tag }}
- name: Setup Rust
uses: leynos/shared-actions/.github/actions/setup-rust@074f7d8ba75a6e5d18532b72cbe38fccbda4e9c6
- name: Dry-run published release asset URLs
run: |
set -euo pipefail
for target in \
x86_64-unknown-linux-gnu \
aarch64-unknown-linux-gnu \
x86_64-pc-windows-msvc \
aarch64-apple-darwin \
x86_64-apple-darwin
do
cargo binstall -y \
--manifest-path Cargo.toml \
--targets "$target" \
--strategies crate-meta-data \
--install-path "$RUNNER_TEMP/binstall-url-dry-run" \
--force \
--dry-run \
pg-embed-setup-unpriv
done