Skip to content

Desktop Signed Preview Candidates #34

Desktop Signed Preview Candidates

Desktop Signed Preview Candidates #34

name: Desktop Signed Preview Candidates
on:
workflow_dispatch:
push:
tags:
- "desktop-v*"
concurrency:
group: desktop-signed-preview-${{ github.ref }}
cancel-in-progress: false
permissions:
actions: read
contents: read
jobs:
validate:
name: Validate desktop candidate source
runs-on: ubuntu-latest
outputs:
candidate_artifacts: ${{ steps.resolve-candidate.outputs.candidate_artifacts }}
candidate_run_id: ${{ steps.resolve-candidate.outputs.candidate_run_id }}
candidate_run_attempt: ${{ steps.resolve-candidate.outputs.candidate_run_attempt }}
steps:
- name: Checkout immutable source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version-file: .node-version
cache: npm
- name: Verify Node toolchain
run: node scripts/check-node-version.mjs
- name: Require main or a matching desktop release tag
shell: bash
run: |
set -euo pipefail
version="$(node -p 'require("./desktop/package.json").version')"
if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
[[ "$GITHUB_REF" == refs/tags/desktop-v* ]] || {
echo "Desktop release publication requires a desktop-v* tag push" >&2
exit 1
}
[[ "$GITHUB_REF_NAME" == "desktop-v${version}" ]] || {
echo "Tag $GITHUB_REF_NAME does not match desktop-v${version}" >&2
exit 1
}
git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main || {
echo "Desktop release tag must point to a commit on origin/main" >&2
exit 1
}
else
[[ "$GITHUB_REF" == "refs/heads/main" ]] || {
echo "Manual signed candidates may only be built from main" >&2
exit 1
}
fi
- name: Resolve the pinned manual candidate run
id: resolve-candidate
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/desktop-v') }}
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
RELAYER_DESKTOP_VERSION: ${{ github.ref_name }}
run: |
set -euo pipefail
export RELAYER_DESKTOP_VERSION="${RELAYER_DESKTOP_VERSION#desktop-v}"
export RELAYER_DESKTOP_TAG_OBJECT_TYPE="$(git cat-file -t "$GITHUB_REF")"
export RELAYER_DESKTOP_TAG_MESSAGE="$(git for-each-ref --format='%(contents)' "$GITHUB_REF")"
node desktop/release/preview-candidate-run.mjs
- name: Install locked dependencies
if: ${{ github.event_name == 'workflow_dispatch' }}
run: npm ci
- name: Run repository checks
if: ${{ github.event_name == 'workflow_dispatch' }}
run: npm run check
package-macos:
name: Sign and notarize ${{ matrix.target }} Preview
needs: validate
if: ${{ github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
include:
# Apple Silicon is the only signed Preview target. Intel macOS is
# disabled alongside the blocked Windows job; restore this entry and
# the macos-x64 publisher target together to resume Intel Previews.
- target: macos-arm64
runner: macos-15
update_base_url: https://updates.relayerlabs.ai/desktop/macos/arm64
runs-on: ${{ matrix.runner }}
environment:
name: desktop-production
steps:
- name: Checkout immutable source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version-file: .node-version
cache: npm
- name: Verify Node toolchain
run: node scripts/check-node-version.mjs
- name: Install locked dependencies
run: npm ci
- name: Build signed and notarized Preview candidate
shell: bash
env:
RELAYER_DESKTOP_TARGET: ${{ matrix.target }}
RELAYER_DESKTOP_SIGN_IDENTITY: ${{ secrets.RELAYER_DESKTOP_SIGN_IDENTITY }}
RELAYER_DESKTOP_APPLE_API_KEY_P8: ${{ secrets.RELAYER_DESKTOP_APPLE_API_KEY }}
RELAYER_DESKTOP_CANDIDATE_RUN_ID: ${{ github.run_id }}
RELAYER_DESKTOP_CANDIDATE_RUN_ATTEMPT: ${{ github.run_attempt }}
APPLE_API_KEY_ID: ${{ secrets.RELAYER_DESKTOP_APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.RELAYER_DESKTOP_APPLE_API_ISSUER }}
CSC_LINK: ${{ secrets.RELAYER_DESKTOP_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.RELAYER_DESKTOP_CSC_KEY_PASSWORD }}
RELAYER_DESKTOP_SOURCE_COMMIT: ${{ github.sha }}
RELAYER_DESKTOP_UPDATE_BASE_URL: ${{ matrix.update_base_url }}
run: |
set -euo pipefail
required=(
RELAYER_DESKTOP_SIGN_IDENTITY
RELAYER_DESKTOP_APPLE_API_KEY_P8
APPLE_API_KEY_ID
APPLE_API_ISSUER
CSC_LINK
CSC_KEY_PASSWORD
)
for name in "${required[@]}"; do
[[ -n "${!name:-}" ]] || { echo "Missing required desktop release secret: $name" >&2; exit 1; }
done
api_key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
cleanup() { rm -f "$api_key_path"; }
trap cleanup EXIT
umask 077
printf '%s' "$RELAYER_DESKTOP_APPLE_API_KEY_P8" > "$api_key_path"
unset RELAYER_DESKTOP_APPLE_API_KEY_P8
export APPLE_API_KEY="$api_key_path"
npm run desktop:dist:preview
- name: Install pinned telemetry upload client without credentials
run: npm install --no-save --package-lock=false @sentry/cli@3.7.0
- name: Upload version-matched telemetry artifacts
shell: bash
env:
RELAYER_DESKTOP_RELEASE: "1"
RELAYER_DESKTOP_CHANNEL: preview
RELAYER_DESKTOP_TARGET: ${{ matrix.target }}
RELAYER_DESKTOP_SOURCE_COMMIT: ${{ github.sha }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_CLI_BINARY: ${{ github.workspace }}/node_modules/.bin/sentry-cli
SENTRY_ORG: relayer-labs-llc
SENTRY_PROJECT: graphcomplete-desktop
run: node desktop/release/telemetry-artifacts.mjs
- name: Upload sealed candidate artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: relayer-desktop-preview-${{ matrix.target }}-${{ github.sha }}
if-no-files-found: error
retention-days: 90
path: |
desktop/dist/Relayer-*.dmg
desktop/dist/Relayer-*.zip
desktop/dist/Relayer-*.blockmap
desktop/dist/beta-mac.yml
desktop/dist/Relayer-*-SHA256SUMS.txt
desktop/dist/Relayer-*-RELEASE.json
desktop/dist/telemetry/**
package-windows:
# Enable candidate execution after the exact publisher is configured. Add
# Windows publication only after Azure signing succeeds. Stable additionally
# requires the interactive AVD updater canary.
if: ${{ false }}
name: Windows x64 Preview (blocked)
needs: validate
runs-on: windows-2025
environment:
name: desktop-production-windows
permissions:
contents: read
id-token: write
steps:
- name: Checkout immutable source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version-file: .node-version
cache: npm
- name: Verify Node toolchain
run: node scripts/check-node-version.mjs
- name: Install locked dependencies
run: npm ci
- name: Require Azure Artifact Signing configuration
shell: pwsh
env:
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
RELAYER_WINDOWS_CERTIFICATE_PROFILE: ${{ vars.RELAYER_WINDOWS_CERTIFICATE_PROFILE }}
RELAYER_WINDOWS_PUBLISHER_NAME: ${{ vars.RELAYER_WINDOWS_PUBLISHER_NAME }}
run: |
$required = @(
"AZURE_CLIENT_ID",
"AZURE_TENANT_ID",
"AZURE_SUBSCRIPTION_ID",
"RELAYER_WINDOWS_CERTIFICATE_PROFILE",
"RELAYER_WINDOWS_PUBLISHER_NAME"
)
foreach ($name in $required) {
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
throw "Missing required Windows signing variable: $name"
}
}
- name: Exchange GitHub OIDC identity for Azure access
uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Build Artifact Signing signed Preview candidate
shell: pwsh
env:
# azure/login establishes the short-lived Azure CLI session. Keep the
# service-principal identity explicit for Artifact Signing's
# DefaultAzureCredential chain; no client secret is used.
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
RELAYER_DESKTOP_TARGET: windows-x64
RELAYER_DESKTOP_SOURCE_COMMIT: ${{ github.sha }}
RELAYER_DESKTOP_CANDIDATE_RUN_ID: ${{ github.run_id }}
RELAYER_DESKTOP_CANDIDATE_RUN_ATTEMPT: ${{ github.run_attempt }}
RELAYER_DESKTOP_UPDATE_BASE_URL: https://updates.relayerlabs.ai/desktop/windows/x64
RELAYER_WINDOWS_SIGNING_ENDPOINT: https://eus.codesigning.azure.net/
RELAYER_WINDOWS_SIGNING_ACCOUNT: relayercodesigning
RELAYER_WINDOWS_CERTIFICATE_PROFILE: ${{ vars.RELAYER_WINDOWS_CERTIFICATE_PROFILE }}
RELAYER_WINDOWS_PUBLISHER_NAME: ${{ vars.RELAYER_WINDOWS_PUBLISHER_NAME }}
run: npm run desktop:dist:preview
- name: Install pinned telemetry upload client without credentials
run: npm install --no-save --package-lock=false @sentry/cli@3.7.0
- name: Upload version-matched telemetry artifacts
shell: pwsh
env:
RELAYER_DESKTOP_RELEASE: "1"
RELAYER_DESKTOP_CHANNEL: preview
RELAYER_DESKTOP_TARGET: windows-x64
RELAYER_DESKTOP_SOURCE_COMMIT: ${{ github.sha }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_CLI_BINARY: ${{ github.workspace }}\node_modules\.bin\sentry-cli.cmd
SENTRY_ORG: relayer-labs-llc
SENTRY_PROJECT: graphcomplete-desktop
run: node desktop/release/telemetry-artifacts.mjs
- name: Upload sealed candidate artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: relayer-desktop-preview-windows-x64-${{ github.sha }}
if-no-files-found: error
retention-days: 90
path: |
desktop/dist/Relayer-*.exe
desktop/dist/Relayer-*.exe.blockmap
desktop/dist/beta.yml
desktop/dist/Relayer-*-SHA256SUMS.txt
desktop/dist/Relayer-*-RELEASE.json
desktop/dist/telemetry/**
publish-preview-macos:
name: Publish ${{ matrix.target }} Preview update
# A tag publishes only the manual candidate run pinned by its annotation.
# The skipped tag packaging job remains a declared dependency so a future
# target matrix cannot accidentally rebuild before publication.
if: ${{ always() && github.event_name == 'push' && needs.validate.result == 'success' && startsWith(github.ref, 'refs/tags/desktop-v') }}
runs-on: ubuntu-latest
needs: [validate, package-macos]
strategy:
fail-fast: false
matrix:
target: [macos-arm64]
environment:
name: desktop-update-preview
permissions:
actions: read
contents: read
id-token: write
steps:
- name: Checkout immutable source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 1
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version-file: .node-version
cache: npm
- name: Verify Node toolchain
run: node scripts/check-node-version.mjs
- name: Install publication dependencies only
run: npm ci --ignore-scripts
- name: Download sealed candidate
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
artifact-ids: ${{ fromJSON(needs.validate.outputs.candidate_artifacts)[matrix.target].id }}
path: desktop/dist
merge-multiple: true
run-id: ${{ needs.validate.outputs.candidate_run_id }}
github-token: ${{ github.token }}
- name: Assume Preview publication role
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a
with:
role-to-assume: ${{ vars.DESKTOP_UPDATE_PREVIEW_ROLE_ARN }}
aws-region: us-east-1
- name: Publish immutable artifacts and move Preview pointer
env:
DESKTOP_UPDATE_BUCKET: ${{ vars.DESKTOP_UPDATE_BUCKET }}
RELAYER_DESKTOP_CANDIDATE_ARTIFACT_DIGEST: ${{ fromJSON(needs.validate.outputs.candidate_artifacts)[matrix.target].digest }}
RELAYER_DESKTOP_CANDIDATE_ARTIFACT_ID: ${{ fromJSON(needs.validate.outputs.candidate_artifacts)[matrix.target].id }}
RELAYER_DESKTOP_CANDIDATE_RUN_ID: ${{ needs.validate.outputs.candidate_run_id }}
RELAYER_DESKTOP_CANDIDATE_RUN_ATTEMPT: ${{ needs.validate.outputs.candidate_run_attempt }}
RELAYER_DESKTOP_TARGET: ${{ matrix.target }}
run: npm run desktop:release:publish-preview -- --bucket "$DESKTOP_UPDATE_BUCKET"
- name: Preserve publication receipt
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: relayer-desktop-preview-publication-${{ matrix.target }}-${{ github.sha }}
if-no-files-found: error
retention-days: 90
path: desktop/dist/preview-publication-${{ matrix.target }}-*.json