macOS Build, Sign, Notarize & Release #21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: macOS Build, Sign, Notarize & Release | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| jobs: | |
| build-sign-notarize: | |
| runs-on: macos-26 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set version info | |
| run: | | |
| VERSION="${{ github.event.release.tag_name }}" | |
| SHORT_VERSION="${VERSION#v}" | |
| BUILD_NUMBER=$(grep 'CURRENT_PROJECT_VERSION' Nook.xcodeproj/project.pbxproj \ | |
| | head -n1 | grep -oE '[0-9]+' | tail -n1) | |
| echo "VERSION=$VERSION" >> $GITHUB_ENV | |
| echo "SHORT_VERSION=$SHORT_VERSION" >> $GITHUB_ENV | |
| echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV | |
| echo "Building version $SHORT_VERSION (build $BUILD_NUMBER)" | |
| - name: Import Developer ID certificate | |
| env: | |
| APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| printf "%s" "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > signing_certificate.p12 | |
| security create-keychain -p "" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "" build.keychain | |
| security import signing_certificate.p12 \ | |
| -k build.keychain \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -T /usr/bin/codesign | |
| security list-keychains -d user -s build.keychain login.keychain | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain | |
| IDENTITY=$(security find-identity -v -p codesigning build.keychain \ | |
| | grep "Developer ID Application" | head -n1 | awk '{print $2}') | |
| echo "SIGNING_IDENTITY=$IDENTITY" >> $GITHUB_ENV | |
| echo "Using signing identity: $IDENTITY" | |
| - name: Build app | |
| env: | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| set -e | |
| mkdir -p build | |
| echo "Attempting universal build (arm64 + x86_64)..." | |
| if ! xcodebuild -scheme Nook -configuration Release \ | |
| -arch arm64 -arch x86_64 \ | |
| -derivedDataPath build \ | |
| CODE_SIGN_IDENTITY="$SIGNING_IDENTITY" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ | |
| CODE_SIGN_ENTITLEMENTS="Nook/Nook.entitlements" \ | |
| PROVISIONING_PROFILE_SPECIFIER=""; then | |
| echo "Universal build failed, retrying Apple Silicon only..." | |
| xcodebuild -scheme Nook -configuration Release \ | |
| -arch arm64 \ | |
| -derivedDataPath build \ | |
| CODE_SIGN_IDENTITY="$SIGNING_IDENTITY" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ | |
| CODE_SIGN_ENTITLEMENTS="Nook/Nook.entitlements" \ | |
| PROVISIONING_PROFILE_SPECIFIER="" | |
| fi | |
| cp -R "build/Build/Products/Release/Nook.app" ./Nook.app | |
| - name: Notarize app | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| run: | | |
| xcrun notarytool store-credentials "nook-notary" \ | |
| --apple-id "$APPLE_ID" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --password "$APPLE_APP_SPECIFIC_PASSWORD" | |
| zip -r Nook.zip "Nook.app" | |
| xcrun notarytool submit "Nook.zip" \ | |
| --keychain-profile "nook-notary" \ | |
| --wait | |
| - name: Staple notarization ticket | |
| run: xcrun stapler staple "Nook.app" | |
| - name: Verify signature | |
| run: | | |
| codesign --verify --deep --strict --verbose=2 "Nook.app" | |
| spctl --assess --type execute --verbose "Nook.app" | |
| - name: Create DMG | |
| run: | | |
| hdiutil create -volname "Nook $SHORT_VERSION" \ | |
| -srcfolder "Nook.app" \ | |
| -ov -format UDZO "Nook-${VERSION}.dmg" | |
| - name: Upload DMG to release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: Nook-*.dmg | |
| fail_on_unmatched_files: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Update appcast on gh-pages | |
| run: | | |
| DMG_URL="https://github.com/${{ github.repository }}/releases/download/${VERSION}/Nook-${VERSION}.dmg" | |
| DATE=$(date -R) | |
| git fetch origin gh-pages | |
| git checkout gh-pages | |
| ENTRY=$(cat << XMLEOF | |
| <item> | |
| <title>Version ${SHORT_VERSION}</title> | |
| <sparkle:releaseNotesLink>https://github.com/${{ github.repository }}/releases/tag/${VERSION}</sparkle:releaseNotesLink> | |
| <pubDate>${DATE}</pubDate> | |
| <enclosure | |
| url="${DMG_URL}" | |
| sparkle:version="${BUILD_NUMBER}" | |
| sparkle:shortVersionString="${SHORT_VERSION}" | |
| type="application/octet-stream"/> | |
| </item> | |
| XMLEOF | |
| ) | |
| python3 -c "import sys; content = open('appcast.xml').read(); entry = sys.argv[1]; content = content.replace('</channel>', entry + '\n</channel>', 1); open('appcast.xml', 'w').write(content)" "$ENTRY" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add appcast.xml | |
| git commit -m "Release ${VERSION}" | |
| git push origin gh-pages |