|
name: Mac app |
|
on: push |
|
env: |
|
PATH: /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin |
|
|
|
jobs: |
|
build_mac_notarized: |
|
runs-on: self-hosted |
|
env: |
|
DIST_DIR: dist-mac-notarized |
|
steps: |
|
- name: Checkout repository |
|
uses: actions/checkout@v4 |
|
with: |
|
submodules: recursive |
|
lfs: true |
|
- name: Really fetch large files |
|
run: |
|
git lfs install |
|
git lfs pull |
|
- name: Install the Apple Mac certificate and provisioning profile |
|
env: |
|
DEVELOPER_ID_CER: ${{ secrets.DEVELOPER_ID_CER }} |
|
DEVELOPER_ID_CER_PASSWORD: ${{ secrets.DEVELOPER_ID_CER_PASSWORD }} |
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} |
|
run: | |
|
# https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development |
|
# https://defn.io/2023/09/22/distributing-mac-apps-with-github-actions/ |
|
DEVELOPER_ID_CER_PATH=$RUNNER_TEMP/devid.p12 |
|
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db |
|
echo -n "$DEVELOPER_ID_CER" | base64 --decode -o $DEVELOPER_ID_CER_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 $DEVELOPER_ID_CER_PATH -P "$DEVELOPER_ID_CER_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH |
|
security list-keychain -d user -s $KEYCHAIN_PATH |
|
security find-identity -p codesigning -v |
|
- name: Build Koja.Works.app for Mac |
|
run: | |
|
mkdir -p $DIST_DIR |
|
xcodebuild \ |
|
archive \ |
|
-project Koja.xcodeproj/ \ |
|
-scheme Koja \ |
|
-configuration Release \ |
|
-destination 'generic/platform=macOS' \ |
|
-archivePath $DIST_DIR/Koja.xcarchive \ |
|
CODE_SIGN_STYLE=Manual \ |
|
CODE_SIGN_IDENTITY="Developer ID Application: Nevyn Bengtsson (M4Q.......)" \ |
|
KOJA_APP_PROVISIONING_PROFILE="" |
|
xcodebuild \ |
|
-exportArchive \ |
|
-archivePath $DIST_DIR/Koja.xcarchive \ |
|
-exportOptionsPlist Koja/NotarizedExportOptions.plist \ |
|
-exportPath $DIST_DIR/ \ |
|
-allowProvisioningUpdates |
|
npx create-dmg $DIST_DIR/Koja.Works.app $DIST_DIR/ |
|
mv $DIST_DIR/Koja*.dmg $DIST_DIR/Koja.Works.dmg |
|
- name: Notarize Koja.Works.dmg |
|
env: |
|
NOTARY_USERNAME: YOUR APPLE ID |
|
NOTARY_PASSWORD: ${{ secrets.NOTARY_PASSWORD }} |
|
run: | |
|
xcrun notarytool submit \ |
|
--team-id 'M4Q.......' \ |
|
--apple-id "$NOTARY_USERNAME" \ |
|
--password "$NOTARY_PASSWORD" \ |
|
--wait \ |
|
$DIST_DIR/Koja.Works.dmg |
|
xcrun stapler staple $DIST_DIR/Koja.Works.dmg |
|
- name: Upload Koja.Works.dmg |
|
uses: actions/upload-artifact@v4 |
|
with: |
|
name: Koja.Works.dmg |
|
path: ${{ env.DIST_DIR }}/Koja.Works.dmg |
|
- name: Clean up keychain and provisioning profile |
|
if: ${{ always() }} |
|
run: | |
|
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db |
|
|
|
|
|
|
|
|
|
build_mac_appstore: |
|
runs-on: self-hosted |
|
if: github.ref == 'refs/heads/main' |
|
env: |
|
DIST_DIR: dist-mac-appstore |
|
outputs: |
|
DELIVERY_UUID: ${{ steps.upload-to-testflight.outputs.DELIVERY_UUID }} |
|
steps: |
|
- name: Checkout repository |
|
uses: actions/checkout@v4 |
|
with: |
|
submodules: recursive |
|
lfs: true |
|
- name: Really fetch large files |
|
run: | |
|
git lfs install |
|
git lfs pull |
|
- name: Install the App Store certificate and provisioning profile |
|
env: |
|
APPSTORE_CER: ${{ secrets.APPSTORE_CER }} |
|
APPSTORE_CER_PASSWORD: ${{ secrets.APPSTORE_CER_PASSWORD }} |
|
MACINSTALLER_CER: ${{ secrets.MACINSTALLER_CER }} |
|
MACINSTALLER_CER_PASSWORD: ${{ secrets.MACINSTALLER_CER_PASSWORD }} |
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} |
|
MAC_APPSTORE_PROVISIONING_PROFILE: ${{ secrets.MAC_APPSTORE_PROVISIONING_PROFILE }} |
|
run: | |
|
echo "🔑 Installing appstore distribution and installer certificates and private keys..." |
|
# Apple documentation says to use "Apple Distribution" to sign the code. |
|
# https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac |
|
KEYCHAIN_PATH=$RUNNER_TEMP/appstore-signing.keychain-db |
|
|
|
APPSTORE_CER_PATH=$RUNNER_TEMP/appstore.p12 |
|
echo -n "$APPSTORE_CER" | base64 --decode -o $APPSTORE_CER_PATH |
|
|
|
MACINSTALLER_CER_PATH=$RUNNER_TEMP/macinstaller.p12 |
|
echo -n "$MACINSTALLER_CER" | base64 --decode -o $MACINSTALLER_CER_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 $APPSTORE_CER_PATH -P "$APPSTORE_CER_PASSWORD" -A -k $KEYCHAIN_PATH |
|
security import $MACINSTALLER_CER_PATH -P "$MACINSTALLER_CER_PASSWORD" -A -k $KEYCHAIN_PATH |
|
security set-key-partition-list -S apple-tool:codesign,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" |
|
security list-keychain -d user -s $KEYCHAIN_PATH |
|
echo "📋 Available code signing identities:" |
|
security find-identity -p codesigning -v |
|
|
|
# TODO: download provisioning profile from appstore connect API instead |
|
echo "📃 Installing provisioning profile..." |
|
mkdir -p "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles" |
|
PROVIS_TMP="$RUNNER_TEMP/temp.provisioningprofile" |
|
|
|
echo -n "$MAC_APPSTORE_PROVISIONING_PROFILE" | base64 --decode -o "$PROVIS_TMP" |
|
|
|
DECODED_PLIST="$RUNNER_TEMP/KojaMacAppstore.plist" |
|
security cms -D -i "$PROVIS_TMP" -o "$DECODED_PLIST" |
|
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" "$DECODED_PLIST") |
|
echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV |
|
|
|
# Note: This path is new for Xcode 16 |
|
PROVIS_PATH="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/${PROFILE_UUID}.provisionprofile" |
|
cp "$PROVIS_TMP" "$PROVIS_PATH" |
|
|
|
echo "✅ Installed provisioning profile with UUID: $PROFILE_UUID" |
|
- name: Calculate build version |
|
run: | |
|
BUILD_VERSION="100.${{ github.run_id }}.${{ github.run_attempt }}" |
|
echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV |
|
echo "Build version: $BUILD_VERSION" |
|
|
|
- name: Build Koja.Works.app for App Store/TestFlight |
|
run: | |
|
mkdir -p $DIST_DIR |
|
xcodebuild \ |
|
archive \ |
|
-project Koja.xcodeproj/ \ |
|
-scheme Koja \ |
|
-configuration Release \ |
|
-destination 'generic/platform=macOS' \ |
|
-archivePath $DIST_DIR/KojaAppstore.xcarchive \ |
|
CODE_SIGN_STYLE=Manual \ |
|
DEVELOPMENT_TEAM=M4Q....... \ |
|
CODE_SIGN_IDENTITY="Apple Distribution: Nevyn Bengtsson (M4Q.......)" \ |
|
CURRENT_PROJECT_VERSION=$BUILD_VERSION \ |
|
KOJA_APP_PROVISIONING_PROFILE="KojaMacAppstore" |
|
xcodebuild \ |
|
-exportArchive \ |
|
-archivePath $DIST_DIR/KojaAppstore.xcarchive \ |
|
-exportOptionsPlist Koja/MacAppStoreExportOptions.plist \ |
|
-exportPath $DIST_DIR/ \ |
|
-allowProvisioningUpdates |
|
- name: Read short version from the xcarchive |
|
run: | |
|
ARCHIVE_APP="$DIST_DIR/KojaAppstore.xcarchive/Products/Applications/Koja.Works.app" |
|
PLIST="$ARCHIVE_APP/Contents/Info.plist" |
|
SHORT_VERSION=$(/usr/libexec/PlistBuddy \ |
|
-c "Print :CFBundleShortVersionString" "$PLIST") |
|
echo "SHORT_VERSION=$SHORT_VERSION" >> $GITHUB_ENV |
|
echo "🔍 This is version $SHORT_VERSION, build $BUILD_VERSION." |
|
- name: Upload to TestFlight |
|
id: upload-to-testflight |
|
env: |
|
APPLE_ID: YOUR APPLE ID |
|
APP_STORE_APP_SPECIFIC_PASSWORD: ${{ secrets.NOTARY_PASSWORD }} |
|
run: | |
|
echo "🕵️♀️ Validating…" |
|
xcrun altool --validate-app -f $DIST_DIR/Koja.Works.pkg \ |
|
-t macos --username "$APPLE_ID" --password "$APP_STORE_APP_SPECIFIC_PASSWORD" --team-id "M4Q......." |
|
|
|
echo "🚀 Uploading build $BUILD_VERSION (ver $SHORT_VERSION) to TestFlight…" |
|
UPLOAD_OUT=$(xcrun altool --upload-package $DIST_DIR/Koja.Works.pkg \ |
|
-t macos \ |
|
--apple-id 673....... \ |
|
--bundle-id works.koja.app \ |
|
--bundle-short-version-string $SHORT_VERSION \ |
|
--bundle-version $BUILD_VERSION \ |
|
--username "$APPLE_ID" --password "$APP_STORE_APP_SPECIFIC_PASSWORD" --team-id "M4Q......."\ |
|
2>&1) |
|
echo $UPLOAD_OUT |
|
# todo: use jwt auth for this instead of app specific password |
|
|
|
DELIVERY_UUID=$(printf "%s\n" "$UPLOAD_OUT" \ |
|
| grep -Eo 'Delivery UUID: [0-9a-fA-F-]+' \ |
|
| awk '{print $3}') |
|
echo "DELIVERY_UUID=$DELIVERY_UUID" >> $GITHUB_OUTPUT |
|
|
|
echo "✅ Delivery UUID: $DELIVERY_UUID" |
|
|
|
- name: Clean up keychain |
|
if: ${{ always() }} |
|
run: | |
|
security delete-keychain $RUNNER_TEMP/appstore-signing.keychain-db |
|
rm -rf "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles" |
|
|
|
submit_to_mac_testflight: |
|
needs: build_mac_appstore |
|
uses: ./.github/workflows/publish-to-testflight.yml |
|
with: |
|
group_name: "Koja%20External" |
|
delivery_uuid: "${{ needs.build_mac_appstore.outputs.DELIVERY_UUID }}" |
|
secrets: |
|
APPCONNECT_API_ISSUER: ${{ secrets.APPCONNECT_API_ISSUER }} |
|
APPCONNECT_API_KEY_ID: ${{ secrets.APPCONNECT_API_KEY_ID }} |
|
APPCONNECT_API_KEY_PRIVATE: ${{ secrets.APPCONNECT_API_KEY_PRIVATE }} |
|
|