From 2afbf54bd9db35afabae159fb9d4d1be5633c002 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Tue, 10 Feb 2026 16:58:31 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20macOS=20=E7=AD=BE=E5=90=8D=E5=85=AC?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 66 +++++++++++++++++++++++++++++++++++ scripts/macos-notarize-dmg.sh | 24 +++++++++++++ scripts/macos-sign-app.sh | 42 ++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100755 scripts/macos-notarize-dmg.sh create mode 100755 scripts/macos-sign-app.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7d5c9d..4ca1a3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,6 +158,30 @@ jobs: run: | ./scripts/deploy-macos.sh "${{ env.TARGET_NAME }}" "build" "amd64" + - name: Import macOS signing certificate + if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && secrets.MACOS_CERT_IDENTITY != '' && secrets.MACOS_CERT_P12 != '' && secrets.MACOS_CERT_PASSWORD != '' + env: + MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} + MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ github.run_id }}-${{ github.run_attempt }}-keychain + run: | + CERTIFICATE_PATH="$RUNNER_TEMP/macos_signing_certificate.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/macos-signing.keychain-db" + echo "$MACOS_CERT_P12" | base64 --decode > "$CERTIFICATE_PATH" || echo "$MACOS_CERT_P12" | base64 -D > "$CERTIFICATE_PATH" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERTIFICATE_PATH" -P "$MACOS_CERT_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychains -d user -s "$KEYCHAIN_PATH" + + - name: Sign app bundle + if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && secrets.MACOS_CERT_IDENTITY != '' && secrets.MACOS_CERT_P12 != '' && secrets.MACOS_CERT_PASSWORD != '' + env: + CODESIGN_IDENTITY: ${{ secrets.MACOS_CERT_IDENTITY }} + run: | + ./scripts/macos-sign-app.sh "./${{ env.TARGET_NAME }}.app" + - name: Package dmg file if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' run: | @@ -172,6 +196,15 @@ jobs: --filesystem APFS \ ${{ env.ARCHIVE_NAME }}.dmg dmg-build + - name: Notarize and staple dmg + if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && secrets.MACOS_CERT_IDENTITY != '' && secrets.MACOS_CERT_P12 != '' && secrets.MACOS_CERT_PASSWORD != '' && secrets.APPLE_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' && secrets.APPLE_TEAM_ID != '' + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + ./scripts/macos-notarize-dmg.sh "${{ env.ARCHIVE_NAME }}.dmg" + - name: Upload artifact (dmg) if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 @@ -230,6 +263,30 @@ jobs: run: | ./scripts/deploy-macos.sh "${{ env.TARGET_NAME }}" "build" "arm64" + - name: Import macOS signing certificate + if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && secrets.MACOS_CERT_IDENTITY != '' && secrets.MACOS_CERT_P12 != '' && secrets.MACOS_CERT_PASSWORD != '' + env: + MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} + MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ github.run_id }}-${{ github.run_attempt }}-keychain + run: | + CERTIFICATE_PATH="$RUNNER_TEMP/macos_signing_certificate.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/macos-signing.keychain-db" + echo "$MACOS_CERT_P12" | base64 --decode > "$CERTIFICATE_PATH" || echo "$MACOS_CERT_P12" | base64 -D > "$CERTIFICATE_PATH" + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERTIFICATE_PATH" -P "$MACOS_CERT_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychains -d user -s "$KEYCHAIN_PATH" + + - name: Sign app bundle + if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && secrets.MACOS_CERT_IDENTITY != '' && secrets.MACOS_CERT_P12 != '' && secrets.MACOS_CERT_PASSWORD != '' + env: + CODESIGN_IDENTITY: ${{ secrets.MACOS_CERT_IDENTITY }} + run: | + ./scripts/macos-sign-app.sh "./${{ env.TARGET_NAME }}.app" + - name: Package dmg file if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' run: | @@ -244,6 +301,15 @@ jobs: --filesystem APFS \ ${{ env.ARCHIVE_NAME }}.dmg dmg-build + - name: Notarize and staple dmg + if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && secrets.MACOS_CERT_IDENTITY != '' && secrets.MACOS_CERT_P12 != '' && secrets.MACOS_CERT_PASSWORD != '' && secrets.APPLE_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' && secrets.APPLE_TEAM_ID != '' + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + ./scripts/macos-notarize-dmg.sh "${{ env.ARCHIVE_NAME }}.dmg" + - name: Upload artifact (dmg) if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 diff --git a/scripts/macos-notarize-dmg.sh b/scripts/macos-notarize-dmg.sh new file mode 100755 index 0000000..192a30a --- /dev/null +++ b/scripts/macos-notarize-dmg.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Usage: ./scripts/macos-notarize-dmg.sh "" + +set -euo pipefail + +DMG_PATH="${1:-}" + +if [[ -z "${APPLE_ID:-}" || -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" || -z "${APPLE_TEAM_ID:-}" ]]; then + echo "APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD and APPLE_TEAM_ID are required" + exit 1 +fi + +if [[ -z "$DMG_PATH" || ! -f "$DMG_PATH" ]]; then + echo "DMG not found: $DMG_PATH" + exit 1 +fi + +xcrun notarytool submit "$DMG_PATH" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_APP_SPECIFIC_PASSWORD" \ + --team-id "$APPLE_TEAM_ID" \ + --wait + +xcrun stapler staple "$DMG_PATH" diff --git a/scripts/macos-sign-app.sh b/scripts/macos-sign-app.sh new file mode 100755 index 0000000..d631d2f --- /dev/null +++ b/scripts/macos-sign-app.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Usage: ./scripts/macos-sign-app.sh "" + +set -euo pipefail + +APP_PATH="${1:-}" +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +if [[ -z "${CODESIGN_IDENTITY:-}" ]]; then + echo "CODESIGN_IDENTITY is required" + exit 1 +fi + +if [[ -z "$APP_PATH" || ! -d "$APP_PATH" ]]; then + echo "App not found: $APP_PATH" + exit 1 +fi + +# Align with resource/macos-fix.sh order: +# sign framework versions first, then helper apps, then main app. +if [[ -d "$FRAMEWORKS_PATH" ]]; then + for framework in "$FRAMEWORKS_PATH"/*.framework; do + [[ -d "$framework" ]] || continue + if [[ -d "$framework/Versions" ]]; then + for version in "$framework/Versions"/*; do + if [[ -d "$version" && ! -L "$version" ]]; then + codesign --verbose --force --timestamp --options runtime --sign "$CODESIGN_IDENTITY" "$version" + fi + done + else + codesign --verbose --force --timestamp --options runtime --sign "$CODESIGN_IDENTITY" "$framework" + fi + done + + for helper_app in "$FRAMEWORKS_PATH"/*.app; do + [[ -d "$helper_app" ]] || continue + codesign --verbose --force --timestamp --options runtime --sign "$CODESIGN_IDENTITY" "$helper_app" + done +fi + +codesign --verbose --force --timestamp --options runtime --deep --sign "$CODESIGN_IDENTITY" "$APP_PATH" +codesign --verify --deep --strict --verbose=2 "$APP_PATH"