Merge pull request #122030 from NousResearch/ci/bootstrap-installer-build
ci: dispatch-only signed builds of the bootstrap installer
This commit is contained in:
257
.github/workflows/bootstrap-installer-build.yml
vendored
Normal file
257
.github/workflows/bootstrap-installer-build.yml
vendored
Normal 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
|
||||
Reference in New Issue
Block a user