add placeholder generated version #93
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: Build and Sign | |
| on: | |
| push: | |
| branches: | |
| - master | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-sign: | |
| runs-on: macos-14 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-tags: true | |
| - name: Select Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: "16.1" | |
| - name: Cache DerivedData | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData | |
| ~/.swiftpm | |
| key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} | |
| - name: Cache BoringSSL build | |
| uses: actions/cache@v4 | |
| with: | |
| path: pkcs11/vendor/boringssl/build | |
| key: ${{ runner.os }}-boringssl-${{ hashFiles('pkcs11/vendor/boringssl/CMakeLists.txt', 'pkcs11/vendor/boringssl/crypto/**') }} | |
| - name: Install certificate and provisioning profile | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} | |
| PROVISIONING_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }} | |
| INSTALLER_CERTIFICATE: ${{ secrets.INSTALLER_CERTIFICATE }} | |
| INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }} | |
| run: | | |
| # Install Developer ID Application certificate | |
| echo "$MACOS_CERTIFICATE" | base64 --decode > signing.p12 | |
| security create-keychain -p "" build.keychain | |
| security import signing.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign | |
| rm signing.p12 | |
| # Install Developer ID Installer certificate (for pkg signing) | |
| if [ -n "$INSTALLER_CERTIFICATE" ]; then | |
| echo "$INSTALLER_CERTIFICATE" | base64 --decode > installer.p12 | |
| security import installer.p12 -k build.keychain -P "$INSTALLER_CERTIFICATE_PASSWORD" -T /usr/bin/productsign | |
| rm installer.p12 | |
| fi | |
| security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "" build.keychain | |
| # Install provisioning profile | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/nailed.provisionprofile | |
| # Also save for embedding | |
| echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode > build_profile.provisionprofile | |
| - name: Build PKCS#11 dylib | |
| run: | | |
| cd pkcs11 | |
| make clean || true | |
| make -j$(sysctl -n hw.ncpu) | |
| echo "Built dylib:" | |
| ls -la libnailed_pkcs11.dylib | |
| file libnailed_pkcs11.dylib | |
| - name: Sign PKCS#11 dylib | |
| env: | |
| CODE_SIGN_IDENTITY: ${{ vars.CODE_SIGN_IDENTITY }} | |
| run: | | |
| DYLIB="pkcs11/libnailed_pkcs11.dylib" | |
| # Sign the dylib with hardened runtime | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$CODE_SIGN_IDENTITY" \ | |
| "$DYLIB" | |
| # Verify signature | |
| codesign --verify --verbose=2 "$DYLIB" | |
| echo "Dylib signature info:" | |
| codesign -dvv "$DYLIB" | |
| - name: Build app (Release, unsigned) | |
| run: | | |
| set -e | |
| xcodebuild \ | |
| -project nailed.xcodeproj \ | |
| -scheme nailed \ | |
| -configuration Release \ | |
| -archivePath $PWD/build/nailed.xcarchive \ | |
| CODE_SIGN_IDENTITY="-" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| clean archive | xcpretty && exit ${PIPESTATUS[0]} | |
| - name: Export and sign .app | |
| env: | |
| CODE_SIGN_IDENTITY: ${{ vars.CODE_SIGN_IDENTITY }} | |
| DEVELOPMENT_TEAM: ${{ vars.DEVELOPMENT_TEAM }} | |
| run: | | |
| mkdir -p build/export | |
| ditto build/nailed.xcarchive/Products/Applications/nailed.app build/export/nailed.app | |
| APP="build/export/nailed.app" | |
| # Embed provisioning profile | |
| cp build_profile.provisionprofile "$APP/Contents/embedded.provisionprofile" | |
| # Create entitlements with resolved variables | |
| sed "s/\$(AppIdentifierPrefix)/${DEVELOPMENT_TEAM}./g" \ | |
| nailed/nailed.entitlements > build/entitlements.plist | |
| echo "Resolved entitlements:" | |
| cat build/entitlements.plist | |
| # Sign nested bundles first (inside-out signing) | |
| find "$APP/Contents/Resources" -name "*.bundle" -type d | while read bundle; do | |
| echo "Signing bundle: $bundle" | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$CODE_SIGN_IDENTITY" \ | |
| "$bundle" | |
| done | |
| # Sign any frameworks | |
| if [ -d "$APP/Contents/Frameworks" ]; then | |
| find "$APP/Contents/Frameworks" -name "*.framework" -type d | while read framework; do | |
| echo "Signing framework: $framework" | |
| codesign --force --options runtime --timestamp \ | |
| --sign "$CODE_SIGN_IDENTITY" \ | |
| "$framework" | |
| done | |
| fi | |
| # Sign the main app with entitlements | |
| codesign --force --options runtime --timestamp \ | |
| --entitlements build/entitlements.plist \ | |
| --sign "$CODE_SIGN_IDENTITY" \ | |
| "$APP" | |
| # Verify signature | |
| codesign --verify --verbose=2 "$APP" | |
| # Show signature info | |
| echo "Signature info:" | |
| codesign -dvv "$APP" | |
| # Show embedded entitlements | |
| echo "Embedded entitlements:" | |
| codesign -d --entitlements - "$APP" 2>/dev/null || true | |
| echo "Signature verification passed" | |
| - name: Create installer package | |
| env: | |
| INSTALLER_SIGN_IDENTITY: ${{ vars.INSTALLER_SIGN_IDENTITY }} | |
| run: | | |
| chmod +x scripts/build-pkg.sh | |
| # Extract version from tag or use default | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VERSION="0.0.0-dev" | |
| fi | |
| scripts/build-pkg.sh \ | |
| "build/export/nailed.app" \ | |
| "pkcs11/libnailed_pkcs11.dylib" \ | |
| "build/nailed.pkg" \ | |
| "$INSTALLER_SIGN_IDENTITY" \ | |
| "$VERSION" | |
| echo "Package created:" | |
| ls -la build/nailed.pkg | |
| # Verify package signature | |
| if [ -n "$INSTALLER_SIGN_IDENTITY" ]; then | |
| pkgutil --check-signature build/nailed.pkg | |
| fi | |
| - name: Upload PKG artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: nailed-${{ github.run_id }} | |
| path: build/nailed.pkg | |
| - name: Submit for notarization | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} | |
| APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} | |
| run: | | |
| mkdir -p ~/private_keys | |
| echo "$APPLE_API_KEY_BASE64" | base64 --decode > ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 | |
| xcrun notarytool submit build/nailed.pkg \ | |
| --key ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 \ | |
| --key-id "$APPLE_API_KEY_ID" \ | |
| --issuer "$APPLE_API_KEY_ISSUER" \ | |
| --output-format json | tee notarization-result.json | |
| SUBMISSION_ID=$(cat notarization-result.json | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") | |
| echo "Submission ID: $SUBMISSION_ID" | |
| echo "$SUBMISSION_ID" > build/submission-id.txt | |
| - name: Wait for notarization and staple | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} | |
| run: | | |
| SUBMISSION_ID=$(cat build/submission-id.txt) | |
| echo "Waiting for notarization (up to 2 minutes)..." | |
| # Wait for notarization to complete (timeout after 2 minutes) | |
| if xcrun notarytool wait "$SUBMISSION_ID" \ | |
| --key ~/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8 \ | |
| --key-id "$APPLE_API_KEY_ID" \ | |
| --issuer "$APPLE_API_KEY_ISSUER" \ | |
| --timeout 2m; then | |
| echo "Notarization completed, stapling..." | |
| xcrun stapler staple build/nailed.pkg | |
| xcrun stapler validate build/nailed.pkg | |
| echo "STAPLED=true" >> $GITHUB_ENV | |
| else | |
| echo "Notarization not completed within 2 minutes, use staple.yml workflow later" | |
| echo "STAPLED=false" >> $GITHUB_ENV | |
| fi | |
| rm -rf ~/private_keys | |
| - name: Upload submission ID | |
| if: startsWith(github.ref, 'refs/tags/v') && env.STAPLED == 'false' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: notarization-${{ github.run_id }} | |
| path: build/submission-id.txt | |
| - name: Upload stapled PKG | |
| if: startsWith(github.ref, 'refs/tags/v') && env.STAPLED == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: nailed-${{ github.run_id }}-stapled | |
| path: build/nailed.pkg | |
| - name: Create release | |
| if: startsWith(github.ref, 'refs/tags/v') && env.STAPLED == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| # Rename pkg to include version | |
| cp build/nailed.pkg "build/nailed-${VERSION}.pkg" | |
| # Create release with the stapled pkg | |
| gh release create "$VERSION" \ | |
| --title "Nailed $VERSION" \ | |
| --generate-notes \ | |
| "build/nailed-${VERSION}.pkg" |