Release workflow (pgrok/pgrok)
The Release workflow from pgrok/pgrok, explained and optimized by Latchkey.
A
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Release workflow from the pgrok/pgrok repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
workflow (.yml)
name: Release
on:
release:
types: [published]
push:
branches:
- main
pull_request:
paths:
- '.github/workflows/release.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GOPROXY: "https://proxy.golang.org"
permissions:
contents: write
jobs:
prepare-release:
name: Prepare release
if: ${{ github.repository == 'pgrok/pgrok' }}
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
release_tag: ${{ steps.version.outputs.release_tag }}
steps:
- name: Check out code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
release_tag="${{ github.event.release.tag_name }}"
version="${release_tag#v}"
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
release_tag="latest-commit-build"
version="$(git rev-parse --short HEAD)"
else
release_tag="release-archive-testing"
version="$(git rev-parse --short HEAD)"
fi
{
echo "version=$version"
echo "release_tag=$release_tag"
} >> "$GITHUB_OUTPUT"
- name: Ensure rolling tag and release exist
if: ${{ github.event_name != 'release' }}
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.version.outputs.release_tag }}
run: |
git tag -f "$RELEASE_TAG"
git push origin "$RELEASE_TAG" --force
RELEASE_TITLE="Release archive testing"
RELEASE_NOTES="Automated testing release for workflow development."
if [ "$RELEASE_TAG" = "latest-commit-build" ]; then
RELEASE_TITLE="Latest commit build"
RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main."
fi
# Single-flight: this job is the only one that creates the release,
# so the build matrix never races on `gh release create`.
if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
gh release create "$RELEASE_TAG" \
--title "$RELEASE_TITLE" \
--notes "$RELEASE_NOTES" \
--prerelease
fi
pgrok:
name: pgrok ${{ matrix.goos }}/${{ matrix.goarch }}
needs: prepare-release
if: ${{ github.repository == 'pgrok/pgrok' }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- {goos: linux, goarch: amd64, runner: ubuntu-latest}
- {goos: linux, goarch: arm64, runner: ubuntu-latest}
- {goos: linux, goarch: "386", runner: ubuntu-latest}
- {goos: darwin, goarch: amd64, runner: macos-latest}
- {goos: darwin, goarch: arm64, runner: macos-latest}
- {goos: windows, goarch: amd64, runner: ubuntu-latest}
- {goos: windows, goarch: arm64, runner: ubuntu-latest}
- {goos: windows, goarch: "386", runner: ubuntu-latest}
steps:
- name: Check out code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: 1.26.x
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
mkdir -p dist
BINARY_NAME="pgrok"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="pgrok.exe"
fi
go build -v \
-ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \
-trimpath -o "dist/$BINARY_NAME" ./pgrok/cli
- name: Import Apple signing certificate
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
run: |
if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
echo "Missing required Apple signing secrets." >&2
exit 1
fi
CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
- name: Sign macOS binary
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
run: |
if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
echo "Missing required Apple signing identity secret." >&2
exit 1
fi
security find-identity -v -p codesigning
codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrok"
codesign --verify --verbose=2 "dist/pgrok"
- name: Create archives
working-directory: dist
run: |
VERSION="${{ needs.prepare-release.outputs.version }}"
ARCHIVE_BASE="pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}"
BINARY_NAME="pgrok"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="pgrok.exe"
fi
zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME"
if [ "${{ matrix.goos }}" = "linux" ]; then
tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME"
fi
- name: Notarize macOS archive
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
run: |
if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
echo "Missing required Apple notarization secrets." >&2
exit 1
fi
VERSION="${{ needs.prepare-release.outputs.version }}"
ARCHIVE_PATH="dist/pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
xcrun notarytool submit "$ARCHIVE_PATH" \
--key "$NOTARY_KEY_PATH" \
--key-id "$APPLE_NOTARY_KEY_ID" \
--issuer "$APPLE_NOTARY_ISSUER_ID" \
--wait
- name: Upload to release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
run: |
PATTERN="pgrok_.*_${{ matrix.goos }}_${{ matrix.goarch }}\."
gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do
gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
done
gh release upload "$RELEASE_TAG" dist/pgrok_*.zip --clobber
if [ "${{ matrix.goos }}" = "linux" ]; then
gh release upload "$RELEASE_TAG" dist/pgrok_*.tar.gz --clobber
fi
pgrokd:
name: pgrokd ${{ matrix.goos }}/${{ matrix.goarch }}
needs: prepare-release
if: ${{ github.repository == 'pgrok/pgrok' }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- {goos: linux, goarch: amd64, runner: ubuntu-latest}
- {goos: linux, goarch: arm64, runner: ubuntu-latest}
- {goos: linux, goarch: "386", runner: ubuntu-latest}
- {goos: darwin, goarch: amd64, runner: macos-latest}
- {goos: darwin, goarch: arm64, runner: macos-latest}
- {goos: windows, goarch: amd64, runner: ubuntu-latest}
- {goos: windows, goarch: arm64, runner: ubuntu-latest}
- {goos: windows, goarch: "386", runner: ubuntu-latest}
steps:
- name: Check out code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: 1.26.x
- name: Install pnpm
uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
with:
version: 11
run_install: |
- cwd: pgrokd/web
- name: Build web app
run: pnpm --dir pgrokd/web run build
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
mkdir -p dist
BINARY_NAME="pgrokd"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="pgrokd.exe"
fi
go build -v -tags prod \
-ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \
-trimpath -o "dist/$BINARY_NAME" ./pgrokd/cli
- name: Import Apple signing certificate
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }}
APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
run: |
if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then
echo "Missing required Apple signing secrets." >&2
exit 1
fi
CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH"
security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
- name: Sign macOS binary
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
run: |
if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then
echo "Missing required Apple signing identity secret." >&2
exit 1
fi
security find-identity -v -p codesigning
codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrokd"
codesign --verify --verbose=2 "dist/pgrokd"
- name: Create archives
working-directory: dist
run: |
VERSION="${{ needs.prepare-release.outputs.version }}"
ARCHIVE_BASE="pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}"
BINARY_NAME="pgrokd"
if [ "${{ matrix.goos }}" = "windows" ]; then
BINARY_NAME="pgrokd.exe"
fi
zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME"
if [ "${{ matrix.goos }}" = "linux" ]; then
tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME"
fi
- name: Notarize macOS archive
if: ${{ matrix.goos == 'darwin' }}
env:
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
run: |
if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then
echo "Missing required Apple notarization secrets." >&2
exit 1
fi
VERSION="${{ needs.prepare-release.outputs.version }}"
ARCHIVE_PATH="dist/pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8"
printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH"
xcrun notarytool submit "$ARCHIVE_PATH" \
--key "$NOTARY_KEY_PATH" \
--key-id "$APPLE_NOTARY_KEY_ID" \
--issuer "$APPLE_NOTARY_ISSUER_ID" \
--wait
- name: Upload to release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }}
run: |
PATTERN="pgrokd_.*_${{ matrix.goos }}_${{ matrix.goarch }}\."
gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do
gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true
done
gh release upload "$RELEASE_TAG" dist/pgrokd_*.zip --clobber
if [ "${{ matrix.goos }}" = "linux" ]; then
gh release upload "$RELEASE_TAG" dist/pgrokd_*.tar.gz --clobber
fi
notify-failure:
name: Notify on failure
runs-on: ubuntu-latest
needs: [pgrok, pgrokd]
if: ${{ failure() && github.repository == 'pgrok/pgrok' && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- name: Send email on failure
uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1
with:
smtp_username: ${{ secrets.SMTP_USERNAME }}
smtp_password: ${{ secrets.SMTP_PASSWORD }}
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Release on: release: types: [published] push: branches: - main pull_request: paths: - '.github/workflows/release.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: GOPROXY: "https://proxy.golang.org" permissions: contents: write jobs: prepare-release: timeout-minutes: 30 name: Prepare release if: ${{ github.repository == 'pgrok/pgrok' }} runs-on: latchkey-small outputs: version: ${{ steps.version.outputs.version }} release_tag: ${{ steps.version.outputs.release_tag }} steps: - name: Check out code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Determine version id: version run: | if [ "${{ github.event_name }}" = "release" ]; then release_tag="${{ github.event.release.tag_name }}" version="${release_tag#v}" elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then release_tag="latest-commit-build" version="$(git rev-parse --short HEAD)" else release_tag="release-archive-testing" version="$(git rev-parse --short HEAD)" fi { echo "version=$version" echo "release_tag=$release_tag" } >> "$GITHUB_OUTPUT" - name: Ensure rolling tag and release exist if: ${{ github.event_name != 'release' }} env: GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ steps.version.outputs.release_tag }} run: | git tag -f "$RELEASE_TAG" git push origin "$RELEASE_TAG" --force RELEASE_TITLE="Release archive testing" RELEASE_NOTES="Automated testing release for workflow development." if [ "$RELEASE_TAG" = "latest-commit-build" ]; then RELEASE_TITLE="Latest commit build" RELEASE_NOTES="Automated build from the latest commit on main branch. This release is updated automatically with every push to main." fi # Single-flight: this job is the only one that creates the release, # so the build matrix never races on `gh release create`. if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then gh release create "$RELEASE_TAG" \ --title "$RELEASE_TITLE" \ --notes "$RELEASE_NOTES" \ --prerelease fi pgrok: timeout-minutes: 30 name: pgrok ${{ matrix.goos }}/${{ matrix.goarch }} needs: prepare-release if: ${{ github.repository == 'pgrok/pgrok' }} runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - {goos: linux, goarch: amd64, runner: ubuntu-latest} - {goos: linux, goarch: arm64, runner: ubuntu-latest} - {goos: linux, goarch: "386", runner: ubuntu-latest} - {goos: darwin, goarch: amd64, runner: macos-latest} - {goos: darwin, goarch: arm64, runner: macos-latest} - {goos: windows, goarch: amd64, runner: ubuntu-latest} - {goos: windows, goarch: arm64, runner: ubuntu-latest} - {goos: windows, goarch: "386", runner: ubuntu-latest} steps: - name: Check out code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Go uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version: 1.26.x - name: Build binary env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} CGO_ENABLED: "0" run: | mkdir -p dist BINARY_NAME="pgrok" if [ "${{ matrix.goos }}" = "windows" ]; then BINARY_NAME="pgrok.exe" fi go build -v \ -ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \ -trimpath -o "dist/$BINARY_NAME" ./pgrok/cli - name: Import Apple signing certificate if: ${{ matrix.goos == 'darwin' }} env: APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }} APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }} APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} run: | if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then echo "Missing required Apple signing secrets." >&2 exit 1 fi CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12" KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH" security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" security list-keychains -d user -s "$KEYCHAIN_PATH" security default-keychain -s "$KEYCHAIN_PATH" security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - name: Sign macOS binary if: ${{ matrix.goos == 'darwin' }} env: APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }} run: | if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then echo "Missing required Apple signing identity secret." >&2 exit 1 fi security find-identity -v -p codesigning codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrok" codesign --verify --verbose=2 "dist/pgrok" - name: Create archives working-directory: dist run: | VERSION="${{ needs.prepare-release.outputs.version }}" ARCHIVE_BASE="pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}" BINARY_NAME="pgrok" if [ "${{ matrix.goos }}" = "windows" ]; then BINARY_NAME="pgrok.exe" fi zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME" if [ "${{ matrix.goos }}" = "linux" ]; then tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME" fi - name: Notarize macOS archive if: ${{ matrix.goos == 'darwin' }} env: APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }} APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }} APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} run: | if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then echo "Missing required Apple notarization secrets." >&2 exit 1 fi VERSION="${{ needs.prepare-release.outputs.version }}" ARCHIVE_PATH="dist/pgrok_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip" NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8" printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH" xcrun notarytool submit "$ARCHIVE_PATH" \ --key "$NOTARY_KEY_PATH" \ --key-id "$APPLE_NOTARY_KEY_ID" \ --issuer "$APPLE_NOTARY_ISSUER_ID" \ --wait - name: Upload to release env: GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }} run: | PATTERN="pgrok_.*_${{ matrix.goos }}_${{ matrix.goarch }}\." gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true done gh release upload "$RELEASE_TAG" dist/pgrok_*.zip --clobber if [ "${{ matrix.goos }}" = "linux" ]; then gh release upload "$RELEASE_TAG" dist/pgrok_*.tar.gz --clobber fi pgrokd: timeout-minutes: 30 name: pgrokd ${{ matrix.goos }}/${{ matrix.goarch }} needs: prepare-release if: ${{ github.repository == 'pgrok/pgrok' }} runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - {goos: linux, goarch: amd64, runner: ubuntu-latest} - {goos: linux, goarch: arm64, runner: ubuntu-latest} - {goos: linux, goarch: "386", runner: ubuntu-latest} - {goos: darwin, goarch: amd64, runner: macos-latest} - {goos: darwin, goarch: arm64, runner: macos-latest} - {goos: windows, goarch: amd64, runner: ubuntu-latest} - {goos: windows, goarch: arm64, runner: ubuntu-latest} - {goos: windows, goarch: "386", runner: ubuntu-latest} steps: - name: Check out code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Go uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version: 1.26.x - name: Install pnpm uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0 with: version: 11 run_install: | - cwd: pgrokd/web - name: Build web app run: pnpm --dir pgrokd/web run build - name: Build binary env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} CGO_ENABLED: "0" run: | mkdir -p dist BINARY_NAME="pgrokd" if [ "${{ matrix.goos }}" = "windows" ]; then BINARY_NAME="pgrokd.exe" fi go build -v -tags prod \ -ldflags "-X main.version=${{ needs.prepare-release.outputs.version }}" \ -trimpath -o "dist/$BINARY_NAME" ./pgrokd/cli - name: Import Apple signing certificate if: ${{ matrix.goos == 'darwin' }} env: APPLE_DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_BASE64 }} APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }} APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} run: | if [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" ] || [ -z "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" ] || [ -z "$APPLE_KEYCHAIN_PASSWORD" ]; then echo "Missing required Apple signing secrets." >&2 exit 1 fi CERTIFICATE_PATH="$RUNNER_TEMP/developer_id_application.p12" KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" printf '%s' "$APPLE_DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d > "$CERTIFICATE_PATH" security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security import "$CERTIFICATE_PATH" -P "$APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" security list-keychains -d user -s "$KEYCHAIN_PATH" security default-keychain -s "$KEYCHAIN_PATH" security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - name: Sign macOS binary if: ${{ matrix.goos == 'darwin' }} env: APPLE_DEVELOPER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_IDENTITY }} run: | if [ -z "$APPLE_DEVELOPER_IDENTITY" ]; then echo "Missing required Apple signing identity secret." >&2 exit 1 fi security find-identity -v -p codesigning codesign --force --options runtime --timestamp --sign "$APPLE_DEVELOPER_IDENTITY" "dist/pgrokd" codesign --verify --verbose=2 "dist/pgrokd" - name: Create archives working-directory: dist run: | VERSION="${{ needs.prepare-release.outputs.version }}" ARCHIVE_BASE="pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}" BINARY_NAME="pgrokd" if [ "${{ matrix.goos }}" = "windows" ]; then BINARY_NAME="pgrokd.exe" fi zip "${ARCHIVE_BASE}.zip" "$BINARY_NAME" if [ "${{ matrix.goos }}" = "linux" ]; then tar -czvf "${ARCHIVE_BASE}.tar.gz" "$BINARY_NAME" fi - name: Notarize macOS archive if: ${{ matrix.goos == 'darwin' }} env: APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }} APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }} APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} run: | if [ -z "$APPLE_NOTARY_ISSUER_ID" ] || [ -z "$APPLE_NOTARY_KEY_BASE64" ] || [ -z "$APPLE_NOTARY_KEY_ID" ]; then echo "Missing required Apple notarization secrets." >&2 exit 1 fi VERSION="${{ needs.prepare-release.outputs.version }}" ARCHIVE_PATH="dist/pgrokd_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}.zip" NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARY_KEY_ID}.p8" printf '%s' "$APPLE_NOTARY_KEY_BASE64" | base64 -d > "$NOTARY_KEY_PATH" xcrun notarytool submit "$ARCHIVE_PATH" \ --key "$NOTARY_KEY_PATH" \ --key-id "$APPLE_NOTARY_KEY_ID" \ --issuer "$APPLE_NOTARY_ISSUER_ID" \ --wait - name: Upload to release env: GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }} run: | PATTERN="pgrokd_.*_${{ matrix.goos }}_${{ matrix.goarch }}\." gh release view "$RELEASE_TAG" --json assets --jq ".assets[].name" | grep -E "$PATTERN" | while read -r asset; do gh release delete-asset "$RELEASE_TAG" "$asset" --yes || true done gh release upload "$RELEASE_TAG" dist/pgrokd_*.zip --clobber if [ "${{ matrix.goos }}" = "linux" ]; then gh release upload "$RELEASE_TAG" dist/pgrokd_*.tar.gz --clobber fi notify-failure: timeout-minutes: 30 name: Notify on failure runs-on: latchkey-small needs: [pgrok, pgrokd] if: ${{ failure() && github.repository == 'pgrok/pgrok' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} steps: - name: Send email on failure uses: unknwon/send-email-on-failure@89339a1bc93f4ad1d30f3b7e4911fcba985c9adb # v1 with: smtp_username: ${{ secrets.SMTP_USERNAME }} smtp_password: ${{ secrets.SMTP_PASSWORD }}
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
This workflow runs 4 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.