publish signed mobile artifacts #240
Workflow file for this run
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: publish signed mobile artifacts | |
| on: | |
| push: | |
| tags: | |
| - "v[0-9]+.[0-9]+.[0-9]+" | |
| workflow_dispatch: | |
| inputs: | |
| versionNumber: | |
| description: 'version number' | |
| required: true | |
| default: '0.1.1' | |
| env: | |
| DOTNET_NOLOGO: true # Disable the .NET logo | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Disable the .NET first time experience | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable sending .NET CLI telemetry | |
| DOTNET_VERSION: '10.0.x' # .NET version to use | |
| XCODE_VERSION: '26.1' # Xcode version that supports iOS 26 for dotnet maui build | |
| IOS_FRAMEWORK: 'net10.0-ios' # iOS framework target | |
| ANDROID_FRAMEWORK: 'net10.0-android' # Android framework target | |
| jobs: | |
| publish-android: | |
| runs-on: windows-2022 | |
| name: Android Build | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v2 | |
| - name: Pull Version from Tag | |
| uses: nowsprinting/check-version-format-action@v3 | |
| id: version | |
| with: | |
| prefix: 'v' | |
| - name: Use Version From Tag | |
| shell: bash | |
| if: steps.version.outputs.is_valid == 'true' | |
| run: | | |
| echo "VERSION=${{steps.version.outputs.full_without_prefix}}" >> $GITHUB_ENV | |
| echo "${VERSION} is our version number" | |
| - name: Use Version from User Input | |
| shell: bash | |
| if: steps.version.outputs.is_valid == 'false' | |
| run: | | |
| echo "VERSION=${{github.event.inputs.versionNumber}}" >> $GITHUB_ENV | |
| echo "${VERSION} is our version number" | |
| - name: create a custom version using run number offset by 1000 (run_number + 1000) | |
| shell: bash | |
| run: | | |
| echo "VERSION_NUMBER_WITH_OFFSET=$((1000+GITHUB_RUN_NUMBER))" >> $GITHUB_ENV | |
| - name: App Settings Variable Substitution | |
| uses: microsoft/variable-substitution@v1 | |
| with: | |
| files: 'appsettings.json' | |
| env: | |
| AppSettings.IfpaApiKey: ${{ secrets.IFPA_API_KEY }} | |
| - name: Add Google Maps API Key to Android Manifest | |
| run: | | |
| $pathToXml = "Platforms/Android/AndroidManifest.xml" | |
| $xml=[xml](Get-Content $pathToXml) | |
| $xml.manifest.application."meta-data".value = "${{ secrets.GOOGLE_MAPS_API_KEY }}" | |
| $xml.Save($pathToXml) | |
| # # Store Android Signing Keystore and password in Secrets using base64 encoding | |
| # # https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/certutil | |
| # # commandline util to encode to base64 on windows | |
| - name: Setup Keystore File | |
| run: | | |
| echo "${{ secrets.IFPA_ANDROID_KEYSTORE }}" > ifpamaui.keystore.asc | |
| certutil -decode ifpamaui.keystore.asc ifpa-keystore.jks | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Install MAUI Workload | |
| run: dotnet workload install maui android | |
| - name: Restore Dependencies | |
| run: dotnet restore IfpaMaui.csproj | |
| - name: Build MAUI Android | |
| run: dotnet publish IfpaMaui.csproj -c Release -f ${{ env.ANDROID_FRAMEWORK }} -p:Version=$Env:VERSION -p:ApplicationDisplayVersion=$Env:VERSION -p:ApplicationVersion=${{ env.VERSION_NUMBER_WITH_OFFSET }} --no-restore -p:AndroidKeyStore=True /p:AndroidSigningKeyStore=ifpa-keystore.jks /p:AndroidSigningKeyAlias=upload /p:AndroidSigningKeyPass="${{ secrets.IFPA_ANDROID_KEYSTORE_PASSWORD }}" /p:AndroidSigningStorePass="${{ secrets.IFPA_ANDROID_KEYSTORE_PASSWORD }}" --no-restore | |
| - name: Upload Android Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| if-no-files-found: error | |
| name: ifpamaui-android-signed-build | |
| path: bin/Release/net9.0-android/publish/com.edgiardina.ifpa-Signed.aab | |
| - name: Deploy to Google Play (Production) | |
| uses: r0adkll/upload-google-play@v1 | |
| with: | |
| serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} | |
| packageName: com.edgiardina.ifpa | |
| releaseFiles: bin/Release/net9.0-android/publish/com.edgiardina.ifpa-Signed.aab | |
| releaseName: ${{ env.VERSION }} | |
| track: production | |
| publish-ios: | |
| runs-on: macos-15 | |
| environment: Release | |
| name: iOS Build | |
| steps: | |
| - uses: actions/checkout@v2 | |
| - name: Install the Apple certificate and provisioning profiles | |
| env: | |
| BUILD_CERTIFICATE_BASE64: ${{ secrets.DISTRIBUTION_CERT_BASE64 }} | |
| P12_PASSWORD: ${{ secrets.DISTRIBUTION_CERT_PW }} | |
| BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.DISTRIBUTION_PROVISION_PROFILE_BASE64 }} | |
| DISTRIBUTION_WIDGET_PROVISION_PROFILE_BASE64: ${{ secrets.DISTRIBUTION_WIDGET_PROVISION_PROFILE_BASE64 }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| run: | | |
| CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 | |
| APP_PP_PATH=$RUNNER_TEMP/app_pp.mobileprovision | |
| WIDGET_PP_PATH=$RUNNER_TEMP/widget_pp.mobileprovision | |
| KEYCHAIN_PATH=$HOME/Library/Keychains/app-signing.keychain-db | |
| # Decode cert and both provisioning profiles | |
| echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH | |
| echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $APP_PP_PATH | |
| echo -n "$DISTRIBUTION_WIDGET_PROVISION_PROFILE_BASE64" | base64 --decode -o $WIDGET_PP_PATH | |
| # Create and unlock custom keychain | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| # Import cert with access for codesign and security | |
| security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH -T /usr/bin/codesign -T /usr/bin/security | |
| # Set keychain search and default to make it visible to MSBuild | |
| security list-keychains -s $KEYCHAIN_PATH login.keychain-db | |
| security default-keychain -s $KEYCHAIN_PATH | |
| # Grant signing tools access to the private key | |
| security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| # OPTIONAL: Try to trust the certificate globally | |
| CERT_SHA=$(security find-certificate -c "Apple Distribution" -Z $KEYCHAIN_PATH | awk '/SHA-1/ { print $NF }') | |
| if [[ -n "$CERT_SHA" ]]; then | |
| CERT_PATH=$(security find-certificate -Z -a -p $KEYCHAIN_PATH | awk 'BEGIN {p=0} /BEGIN CERTIFICATE/ {p=1} {if(p)print} /END CERTIFICATE/ {p=0}' > temp_cert.cer && echo "temp_cert.cer") | |
| sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$CERT_PATH" || echo "⚠️ Could not trust cert — continuing anyway" | |
| fi | |
| # ✅ Install main app provisioning profile | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| APP_PROFILE_UUID=$(grep -aA1 UUID $APP_PP_PATH | grep -io "[-A-F0-9]\{36\}" | head -1) | |
| if [[ -z "$APP_PROFILE_UUID" ]]; then | |
| echo "❌ Failed to extract UUID from main app provisioning profile" | |
| exit 1 | |
| fi | |
| cp $APP_PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$APP_PROFILE_UUID.mobileprovision | |
| echo "PROVISIONING_UUID=$APP_PROFILE_UUID" >> $GITHUB_ENV | |
| # ✅ Install widget provisioning profile | |
| WIDGET_PROFILE_UUID=$(grep -aA1 UUID $WIDGET_PP_PATH | grep -io "[-A-F0-9]\{36\}" | head -1) | |
| if [[ -z "$WIDGET_PROFILE_UUID" ]]; then | |
| echo "❌ Failed to extract UUID from widget provisioning profile" | |
| exit 1 | |
| fi | |
| cp $WIDGET_PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$WIDGET_PROFILE_UUID.mobileprovision | |
| echo "WIDGET_PROVISIONING_UUID=$WIDGET_PROFILE_UUID" >> $GITHUB_ENV | |
| # Debug output to confirm identity is available | |
| echo "🔍 Signing identities:" | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| - name: Pull Version from Tag | |
| uses: nowsprinting/check-version-format-action@v3 | |
| id: version | |
| with: | |
| prefix: 'v' | |
| - name: Use Version From Tag | |
| shell: bash | |
| if: steps.version.outputs.is_valid == 'true' | |
| run: | | |
| echo "VERSION=${{steps.version.outputs.full_without_prefix}}" >> $GITHUB_ENV | |
| echo "${VERSION} is our version number" | |
| - name: Use Version from User Input | |
| shell: bash | |
| if: steps.version.outputs.is_valid == 'false' | |
| run: | | |
| echo "VERSION=${{github.event.inputs.versionNumber}}" >> $GITHUB_ENV | |
| echo "${VERSION} is our version number" | |
| - name: create a custom version using run number offset by 1000 (run_number + 1000) | |
| run: | | |
| echo "VERSION_NUMBER_WITH_OFFSET=$((1000+GITHUB_RUN_NUMBER))" >> $GITHUB_ENV | |
| - name: App Settings Variable Substitution | |
| uses: microsoft/variable-substitution@v1 | |
| with: | |
| files: 'appsettings.json' | |
| env: | |
| AppSettings.IfpaApiKey: ${{ secrets.IFPA_API_KEY }} | |
| - name: Update Widget API key in Info.plist | |
| env: | |
| BUILD_NUMBER: ${{ env.VERSION_NUMBER_WITH_OFFSET }} | |
| APP_VERSION: ${{ env.VERSION }} | |
| IFPA_API_KEY: ${{ secrets.IFPA_API_KEY }} | |
| run: | | |
| WIDGET_PLIST="NativeIFPA/RankWidget/Info.plist" | |
| echo "🔐 Replacing IFPAApiKey" | |
| /usr/libexec/PlistBuddy -c "Set :IFPAApiKey $IFPA_API_KEY" "$WIDGET_PLIST" || \ | |
| /usr/libexec/PlistBuddy -c "Add :IFPAApiKey string $IFPA_API_KEY" "$WIDGET_PLIST" | |
| - name: Use Xcode Version For Widget Build | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| # Download iOS platform + simulators if missing | |
| - name: Install iOS platform + simulator runtime | |
| run: | | |
| xcodebuild -downloadPlatform iOS | |
| xcrun simctl list runtimes | |
| - name: Build RankWidgetExtension | |
| run: | | |
| xcodebuild \ | |
| -project NativeIFPA/IFPA.xcodeproj \ | |
| -scheme RankWidgetExtension \ | |
| -configuration Release \ | |
| -destination 'generic/platform=iOS' \ | |
| -sdk iphoneos \ | |
| -derivedDataPath NativeIFPA/DerivedData \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="iPhone Distribution: Ed Giardina (KH5JKUPW2Q)" \ | |
| CODE_SIGN_ENTITLEMENTS=RankWidgetExtension.entitlements \ | |
| DEVELOPMENT_TEAM=KH5JKUPW2Q \ | |
| PROVISIONING_PROFILE="$WIDGET_PROVISIONING_UUID" | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| # The following command is due to some sort of maui build bug and iOS won't accept the binary: | |
| # run: dotnet workload install ios maui --source https://api.nuget.org/v3/index.json --version 8.0.401 | |
| # ITMS-91017: Invalid SDK usage - The following frameworks aren't permitted for apps using an alternative web browser engine: [BrowserEngineKit]. | |
| - name: Install MAUI Workload | |
| run: dotnet workload install ios maui --source https://api.nuget.org/v3/index.json | |
| - name: Restore nuget packages | |
| run: dotnet restore IfpaMaui.csproj | |
| - name : Publish iOS App | |
| shell: bash | |
| run: | | |
| dotnet publish -f ${{ env.IOS_FRAMEWORK }} -c Release \ | |
| -p:ArchiveOnBuild=true \ | |
| -p:Version=${VERSION} \ | |
| -p:ApplicationDisplayVersion=${VERSION} \ | |
| -p:ApplicationVersion=${{ env.VERSION_NUMBER_WITH_OFFSET }} \ | |
| -p:CodesignKey="iPhone Distribution: Ed Giardina (KH5JKUPW2Q)" \ | |
| -p:ProvisioningProfileUuid=${{ env.PROVISIONING_UUID }} | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ifpamaui-ios-signed-build | |
| if-no-files-found: error | |
| path: | | |
| bin/Release/${{ env.IOS_FRAMEWORK }}/**/*.ipa | |
| - name: Upload app to App Store Connect | |
| env: | |
| APP_STORE_CONNECT_USERNAME: ${{ secrets.APP_STORE_CONNECT_USERNAME }} | |
| APP_STORE_CONNECT_PASSWORD: ${{ secrets.APP_STORE_CONNECT_PASSWORD }} | |
| run: | | |
| xcrun altool --upload-app -t ios -f "bin/Release/${{ env.IOS_FRAMEWORK }}/ios-arm64/publish/IfpaMaui.ipa" -u "$APP_STORE_CONNECT_USERNAME" -p "$APP_STORE_CONNECT_PASSWORD" |