mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-30 01:42:49 +08:00
[build] add macOS signing and notarization to GitHub Actions
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Never echo commands containing imported certificate or notarization secrets.
|
||||
set +x
|
||||
|
||||
if [[ "${GITHUB_ACTIONS:-}" != "true" ]]; then
|
||||
echo "This script prepares an isolated GitHub Actions signing keychain." >&2
|
||||
exit 1
|
||||
fi
|
||||
: "${RUNNER_TEMP:?RUNNER_TEMP is required}"
|
||||
: "${GITHUB_ENV:?GITHUB_ENV is required}"
|
||||
: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}"
|
||||
|
||||
for secret_name in MACOS_APP_CERTIFICATE_P12_BASE64 MACOS_INSTALLER_CERTIFICATE_P12_BASE64 \
|
||||
MACOS_P12_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
||||
if [[ -z "${!secret_name:-}" ]]; then
|
||||
echo "Missing required Actions secret: $secret_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [[ ! "$APPLE_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]]; then
|
||||
echo "APPLE_TEAM_ID must contain 10 uppercase letters or digits." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SIGNING_DIR="$RUNNER_TEMP/crossdesk-macos-signing"
|
||||
KEYCHAIN_PATH="$SIGNING_DIR/signing.keychain-db"
|
||||
APP_P12="$SIGNING_DIR/application.p12"
|
||||
INSTALLER_P12="$SIGNING_DIR/installer.p12"
|
||||
NOTARY_PROFILE="crossdesk-notary"
|
||||
|
||||
# Refuse to overwrite anything left by an unrelated task. Each hosted matrix
|
||||
# runner has its own RUNNER_TEMP, and the workflow also cleans up with always().
|
||||
umask 077
|
||||
mkdir "$SIGNING_DIR"
|
||||
cleanup_setup() {
|
||||
local status=$?
|
||||
rm -f "$APP_P12" "$INSTALLER_P12"
|
||||
if [[ "$status" -ne 0 ]]; then
|
||||
security delete-keychain "$KEYCHAIN_PATH" >/dev/null 2>&1 || true
|
||||
rm -rf "$SIGNING_DIR"
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup_setup EXIT
|
||||
|
||||
printf '%s' "$MACOS_APP_CERTIFICATE_P12_BASE64" | base64 --decode > "$APP_P12"
|
||||
printf '%s' "$MACOS_INSTALLER_CERTIFICATE_P12_BASE64" | base64 --decode > "$INSTALLER_P12"
|
||||
KEYCHAIN_PASSWORD="$(openssl rand -hex 32)"
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
for certificate in "$APP_P12" "$INSTALLER_P12"; do
|
||||
security import "$certificate" -P "$MACOS_P12_PASSWORD" \
|
||||
-t cert -f pkcs12 -k "$KEYCHAIN_PATH" \
|
||||
-T /usr/bin/codesign -T /usr/bin/productbuild -T /usr/bin/productsign
|
||||
done
|
||||
# Allow the signing tools to use only this job's imported keys without a GUI
|
||||
# password prompt. The user's login keychain and its access controls are untouched.
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s \
|
||||
-k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" >/dev/null
|
||||
|
||||
find_identity() {
|
||||
local policy="$1"
|
||||
local prefix="$2"
|
||||
security find-identity -v -p "$policy" "$KEYCHAIN_PATH" | \
|
||||
awk -F '"' -v prefix="$prefix" -v suffix=" ($APPLE_TEAM_ID)" '
|
||||
index($2, prefix) == 1 &&
|
||||
substr($2, length($2) - length(suffix) + 1) == suffix {
|
||||
print $2
|
||||
count++
|
||||
}
|
||||
END { if (count != 1) exit 1 }'
|
||||
}
|
||||
if ! APP_IDENTITY="$(find_identity codesigning 'Developer ID Application: ')"; then
|
||||
echo "Expected one valid Developer ID Application identity for APPLE_TEAM_ID, including its private key." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! INSTALLER_IDENTITY="$(find_identity basic 'Developer ID Installer: ')"; then
|
||||
echo "Expected one valid Developer ID Installer identity for APPLE_TEAM_ID, including its private key." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
xcrun notarytool store-credentials "$NOTARY_PROFILE" \
|
||||
--apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" --keychain "$KEYCHAIN_PATH" --validate
|
||||
|
||||
{
|
||||
printf 'CROSSDESK_MACOS_KEYCHAIN=%s\n' "$KEYCHAIN_PATH"
|
||||
printf 'CROSSDESK_MACOS_SIGN_IDENTITY=%s\n' "$APP_IDENTITY"
|
||||
printf 'CROSSDESK_MACOS_INSTALLER_IDENTITY=%s\n' "$INSTALLER_IDENTITY"
|
||||
} >> "$GITHUB_ENV"
|
||||
# Keep notarization separate from packaging so CI can retain a signed package
|
||||
# and the submission ID when Apple's processing exceeds the wait timeout.
|
||||
printf 'notary_profile=%s\n' "$NOTARY_PROFILE" >> "$GITHUB_OUTPUT"
|
||||
echo "Prepared Developer ID application and installer identities for team $APPLE_TEAM_ID."
|
||||
@@ -24,6 +24,11 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
sign_macos:
|
||||
description: "Sign and notarize macOS installers (automatic for v* release tags)."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -220,7 +225,9 @@ jobs:
|
||||
VERSION_NUM: ${{ needs.prepare-version.outputs.version }}
|
||||
MACOSX_DEPLOYMENT_TARGET: "14.0"
|
||||
EXPECTED_MACOS_SDK: "26.5"
|
||||
MACOS_SIGNING_REQUIRED: ${{ startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.sign_macos) }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
@@ -237,6 +244,27 @@ jobs:
|
||||
package_script: ./apps/desktop/scripts/macos/pkg_arm64.sh
|
||||
|
||||
steps:
|
||||
- name: Validate macOS signing secrets
|
||||
if: env.MACOS_SIGNING_REQUIRED == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
MACOS_APP_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_APP_CERTIFICATE_P12_BASE64 }}
|
||||
MACOS_INSTALLER_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_INSTALLER_CERTIFICATE_P12_BASE64 }}
|
||||
MACOS_P12_PASSWORD: ${{ secrets.MACOS_P12_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
missing=0
|
||||
for name in MACOS_APP_CERTIFICATE_P12_BASE64 MACOS_INSTALLER_CERTIFICATE_P12_BASE64 \
|
||||
MACOS_P12_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
||||
if [[ -z "${!name:-}" ]]; then
|
||||
echo "::error::Missing required Actions secret: ${name}"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
exit "$missing"
|
||||
|
||||
- name: Select Xcode 26.6
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -275,7 +303,21 @@ jobs:
|
||||
xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=false -vyD
|
||||
xmake b -vy crossdesk
|
||||
|
||||
- name: Import Developer ID certificates and notarization credentials
|
||||
id: macos_signing
|
||||
if: env.MACOS_SIGNING_REQUIRED == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
MACOS_APP_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_APP_CERTIFICATE_P12_BASE64 }}
|
||||
MACOS_INSTALLER_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_INSTALLER_CERTIFICATE_P12_BASE64 }}
|
||||
MACOS_P12_PASSWORD: ${{ secrets.MACOS_P12_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: bash .github/scripts/setup_macos_signing.sh
|
||||
|
||||
- name: Package CrossDesk app
|
||||
id: macos_package
|
||||
run: |
|
||||
chmod +x ${{ matrix.package_script }}
|
||||
${{ matrix.package_script }} ${VERSION_NUM}
|
||||
@@ -296,17 +338,56 @@ jobs:
|
||||
|
||||
./apps/desktop/scripts/macos/verify_app.sh "${APP_PATH}" "${{ matrix.binary_arch }}" "${EXPECTED_MACOS_SDK}"
|
||||
|
||||
- name: Notarize and staple macOS installer
|
||||
id: macos_notarization
|
||||
if: env.MACOS_SIGNING_REQUIRED == 'true'
|
||||
timeout-minutes: 65
|
||||
shell: bash
|
||||
env:
|
||||
CROSSDESK_MACOS_NOTARY_PROFILE: ${{ steps.macos_signing.outputs.notary_profile }}
|
||||
CROSSDESK_MACOS_NOTARY_TIMEOUT: 60m
|
||||
CROSSDESK_MACOS_NOTARY_S3_ACCELERATION: "false"
|
||||
run: |
|
||||
bash ./apps/desktop/scripts/macos/notarize_pkg.sh \
|
||||
"crossdesk-macos-${{ matrix.arch }}-${VERSION_NUM}.pkg"
|
||||
|
||||
- name: Upload macOS notarization diagnostics
|
||||
if: always() && env.MACOS_SIGNING_REQUIRED == 'true'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: macos-notarization-${{ matrix.arch }}-${{ env.VERSION_NUM }}
|
||||
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg.notary*.json
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
|
||||
- name: Preserve signed installer after notarization failure
|
||||
if: failure() && env.MACOS_SIGNING_REQUIRED == 'true' && steps.macos_package.outcome == 'success'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: macos-pending-notarization-${{ matrix.arch }}-${{ env.VERSION_NUM }}
|
||||
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
|
||||
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Move files to release dir
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
|
||||
|
||||
- name: Remove macOS signing credentials
|
||||
if: always() && env.MACOS_SIGNING_REQUIRED == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
security delete-keychain "${RUNNER_TEMP}/crossdesk-macos-signing/signing.keychain-db" 2>/dev/null || true
|
||||
rm -rf "${RUNNER_TEMP:?}/crossdesk-macos-signing"
|
||||
|
||||
# iOS (also available as a standalone workflow)
|
||||
build-ios:
|
||||
name: Build iOS
|
||||
|
||||
Reference in New Issue
Block a user