Merge pull request #122030 from NousResearch/ci/bootstrap-installer-build

ci: dispatch-only signed builds of the bootstrap installer
This commit is contained in:
ethernet
2026-09-24 20:02:06 -04:00
committed by GitHub

View File

@@ -0,0 +1,257 @@
name: Bootstrap installer build
# Hand-dispatched, signed builds of the Tauri bootstrap installer
# (apps/bootstrap-installer, "Hermes-Setup"): Windows x64 exe signed with
# Azure Trusted Signing, macOS arm64 dmg signed with the Developer ID cert and
# notarized. Uploaded as run artifacts only; nothing is published.
#
# No caches of any kind: no actions/cache, no setup-node cache, and fresh
# npm/cargo/electron-builder cache dirs under RUNNER_TEMP.
on:
workflow_dispatch:
inputs:
ref:
description: Commit, branch or tag to build
required: true
default: main
pin_branch:
description: Branch the installer follows at install time (HERMES_BUILD_PIN_BRANCH)
required: true
default: main
pin_commit:
description: Optional commit pin baked in instead (HERMES_BUILD_PIN_COMMIT)
required: false
default: ''
permissions:
contents: read
id-token: write
concurrency:
group: bootstrap-installer-build-${{ github.run_id }}
cancel-in-progress: false
env:
HERMES_BUILD_PIN_BRANCH: ${{ inputs.pin_branch }}
HERMES_BUILD_PIN_COMMIT: ${{ inputs.pin_commit }}
CARGO_INCREMENTAL: '0'
SCCACHE_DISABLE: '1'
jobs:
windows-x64:
name: Hermes-Setup.exe (windows x64, signed)
runs-on: windows-latest
environment: release-signing
timeout-minutes: 90
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24.11.1
- name: Fresh cache dirs
shell: bash
run: |
{
echo "CARGO_HOME=$RUNNER_TEMP/cargo-home"
echo "CARGO_TARGET_DIR=$RUNNER_TEMP/target"
echo "npm_config_cache=$RUNNER_TEMP/npm-cache"
echo "ELECTRON_BUILDER_CACHE=$RUNNER_TEMP/electron-builder-cache"
} >> "$GITHUB_ENV"
echo "$USERPROFILE/.cargo/bin" >> "$GITHUB_PATH"
- name: Require Azure signing vars
shell: bash
env:
AZURE_SIGN_ENDPOINT: ${{ vars.AZURE_SIGN_ENDPOINT }}
AZURE_SIGN_ACCOUNT: ${{ vars.AZURE_SIGN_ACCOUNT }}
AZURE_SIGN_PROFILE: ${{ vars.AZURE_SIGN_PROFILE }}
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
run: |
missing=()
for name in AZURE_SIGN_ENDPOINT AZURE_SIGN_ACCOUNT AZURE_SIGN_PROFILE AZURE_CLIENT_ID AZURE_TENANT_ID; do
[ -z "${!name}" ] && missing+=("$name")
done
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Azure Trusted Signing vars missing from release-signing: ${missing[*]}"
exit 1
fi
- name: Build
shell: bash
run: |
rustup target add x86_64-pc-windows-msvc
npm ci --workspace apps/bootstrap-installer --include-workspace-root=false --prefer-online --no-audit --no-fund
cd apps/bootstrap-installer
npx tauri build --target x86_64-pc-windows-msvc --no-bundle
exe="$CARGO_TARGET_DIR/x86_64-pc-windows-msvc/release/Hermes-Setup.exe"
mkdir -p "$RUNNER_TEMP/out"
cp "$exe" "$RUNNER_TEMP/out/Hermes-Setup.exe"
- name: Azure login (OIDC)
uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
allow-no-subscriptions: true
- name: Mint federated token for the signing dlib
shell: bash
run: |
file="$RUNNER_TEMP/azure-federated-token"
mint() {
curl -sSf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" \
| jq -r .value > "$file.tmp" && mv -f "$file.tmp" "$file"
}
mint
( while sleep 240; do mint || true; done ) &
echo "AZURE_FEDERATED_TOKEN_FILE=$file" >> "$GITHUB_ENV"
- name: Sign (same Azure Trusted Signing path as the desktop MSIX)
shell: bash
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
AZURE_SIGN_ENDPOINT: ${{ vars.AZURE_SIGN_ENDPOINT }}
AZURE_SIGN_ACCOUNT: ${{ vars.AZURE_SIGN_ACCOUNT }}
AZURE_SIGN_PROFILE: ${{ vars.AZURE_SIGN_PROFILE }}
AZURE_SIGN_PUBLISHER: ${{ vars.AZURE_SIGN_PUBLISHER }}
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
AZURE_TOKEN_CREDENTIALS: prod
run: |
npm ci --workspace apps/desktop --include-workspace-root=false --prefer-online --no-audit --no-fund
# shellcheck disable=SC2016 # JS template literal, not shell
EXE="$(cygpath -w "$RUNNER_TEMP/out/Hermes-Setup.exe")" node --input-type=module -e '
import { pathToFileURL } from "node:url"
const m = await import(pathToFileURL("apps/desktop/scripts/batch-sign-binaries.mjs").href)
const r = await m.batchSignBinaries([process.env.EXE], { cache: null })
if (r.skipped || r.signed !== 1) throw new Error(`not signed: ${JSON.stringify(r)}`)
'
- name: Verify signature
shell: pwsh
run: |
$sig = Get-AuthenticodeSignature "$env:RUNNER_TEMP\out\Hermes-Setup.exe"
$sig | Format-List Status, StatusMessage, SignerCertificate, TimeStamperCertificate
if ($sig.Status -ne 'Valid') { throw "signature status: $($sig.Status)" }
if (-not $sig.TimeStamperCertificate) { throw "signature is not timestamped" }
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Hermes-Setup-windows-x64
path: ${{ runner.temp }}/out/Hermes-Setup.exe
if-no-files-found: error
macos-arm64:
name: Hermes-Setup.dmg (macos arm64, signed + notarized)
runs-on: macos-latest
environment: release-signing
timeout-minutes: 90
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24.11.1
- name: Fresh cache dirs
run: |
{
echo "CARGO_HOME=$RUNNER_TEMP/cargo-home"
echo "CARGO_TARGET_DIR=$RUNNER_TEMP/target"
echo "npm_config_cache=$RUNNER_TEMP/npm-cache"
} >> "$GITHUB_ENV"
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Import Developer ID certificate
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: |
missing=()
for name in CSC_LINK CSC_KEY_PASSWORD APPLE_API_KEY_P8 APPLE_API_KEY_ID APPLE_API_ISSUER; do
[ -z "${!name}" ] && missing+=("$name")
done
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::signing/notarization secrets missing from release-signing: ${missing[*]}"
exit 1
fi
p12="$RUNNER_TEMP/cert.p12"
printf '%s' "$CSC_LINK" | base64 --decode > "$p12"
kc="$RUNNER_TEMP/signing.keychain-db"
pw="$(uuidgen)"
security create-keychain -p "$pw" "$kc"
security set-keychain-settings -lut 21600 "$kc"
security unlock-keychain -p "$pw" "$kc"
security import "$p12" -k "$kc" -P "$CSC_KEY_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$pw" "$kc" >/dev/null
# shellcheck disable=SC2046 # the existing search list must split
security list-keychains -d user -s "$kc" $(security list-keychains -d user | tr -d '"')
rm -f "$p12"
identity="$(security find-identity -v -p codesigning "$kc" | grep -o '"Developer ID Application: [^"]*"' | head -1 | tr -d '"')"
test -n "$identity" || { echo "::error::no Developer ID Application identity in CSC_LINK"; exit 1; }
printf '%s\n' "$APPLE_API_KEY_P8" > "$RUNNER_TEMP/apple-api-key.p8"
{
echo "APPLE_SIGNING_IDENTITY=$identity"
echo "APPLE_API_KEY_PATH=$RUNNER_TEMP/apple-api-key.p8"
} >> "$GITHUB_ENV"
- name: Build, sign and notarize
env:
# Tauri notarizes the .app with an App Store Connect API key.
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: |
rustup target add aarch64-apple-darwin
npm ci --workspace apps/bootstrap-installer --include-workspace-root=false --prefer-online --no-audit --no-fund
cd apps/bootstrap-installer
npx tauri build --target aarch64-apple-darwin --bundles app,dmg
- name: Sign, notarize and staple the dmg
run: |
shopt -s nullglob
bundle="$CARGO_TARGET_DIR/aarch64-apple-darwin/release/bundle"
dmgs=("$bundle"/dmg/*.dmg)
test ${#dmgs[@]} -eq 1
mkdir -p "$RUNNER_TEMP/out"
dmg="$RUNNER_TEMP/out/Hermes-Setup.dmg"
cp "${dmgs[0]}" "$dmg"
codesign --force --timestamp --sign "$APPLE_SIGNING_IDENTITY" "$dmg"
xcrun notarytool submit "$dmg" --wait \
--key "$APPLE_API_KEY_PATH" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER"
xcrun stapler staple "$dmg"
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
- name: Verify the app and dmg are signed and notarized
run: |
shopt -s nullglob
apps=("$CARGO_TARGET_DIR"/aarch64-apple-darwin/release/bundle/macos/*.app)
test ${#apps[@]} -eq 1
codesign --verify --strict --verbose=2 "${apps[0]}"
xcrun stapler validate "${apps[0]}"
spctl -a -vv -t exec "${apps[0]}"
dmg="$RUNNER_TEMP/out/Hermes-Setup.dmg"
codesign --verify --verbose=2 "$dmg"
xcrun stapler validate "$dmg"
spctl -a -vv -t open --context context:primary-signature "$dmg"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Hermes-Setup-macos-arm64
path: ${{ runner.temp }}/out/Hermes-Setup.dmg
if-no-files-found: error