Release Mobile (Tauri) #4
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: Release Mobile (Tauri) | |
| on: | |
| # Disabled automatic tag triggers until mobile release pipeline is ready. | |
| # push: | |
| # tags: | |
| # - 'v*' | |
| workflow_call: | |
| inputs: | |
| version: | |
| required: true | |
| type: string | |
| release_id: | |
| description: 'Existing GitHub Release ID to upload artifacts to' | |
| required: false | |
| type: string | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Release tag (e.g. v0.2.0)' | |
| required: true | |
| type: string | |
| # Minimal permissions — only contents:write for release uploads | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-mobile | |
| cancel-in-progress: false | |
| env: | |
| CARGO_INCREMENTAL: 0 | |
| RUST_BACKTRACE: short | |
| NODE_VERSION: '22' | |
| PNPM_VERSION: '9' | |
| jobs: | |
| android: | |
| runs-on: ubuntu-22.04 | |
| name: Release Android (signed APK + AAB) | |
| # Use the 'release' environment for scoped secrets | |
| environment: release | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri -> target | |
| - name: Install Linux dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| libssl-dev \ | |
| libgtk-3-dev | |
| - name: Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: 17 | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Install Android NDK | |
| run: sdkmanager "ndk;27.0.12077973" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Decode Android keystore | |
| run: | | |
| echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > /tmp/keystore.jks | |
| # Verify keystore was decoded correctly | |
| if [ ! -s /tmp/keystore.jks ]; then | |
| echo "Error: keystore file is empty or missing" | |
| exit 1 | |
| fi | |
| - name: Initialize Android project | |
| run: pnpm tauri android init | |
| - name: Sync Android version | |
| run: node scripts/sync-version.cjs | |
| - name: Inject signing config into build.gradle.kts | |
| run: node scripts/inject-android-signing.cjs | |
| - name: Build Android (release, signed) | |
| env: | |
| NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/27.0.12077973 | |
| ANDROID_KEYSTORE: /tmp/keystore.jks | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: pnpm tauri android build | |
| - name: Rename APK with version | |
| run: | | |
| VERSION="${{ inputs.version || inputs.tag || github.ref_name }}" | |
| VERSION="${VERSION#v}" | |
| APK_DIR="src-tauri/gen/android/app/build/outputs/apk" | |
| AAB_DIR="src-tauri/gen/android/app/build/outputs/bundle" | |
| # Find and rename APKs | |
| find "$APK_DIR" -name '*.apk' -type f | while read -r f; do | |
| dir=$(dirname "$f") | |
| mv "$f" "$dir/forwardemail-mail_${VERSION}_android.apk" | |
| done | |
| # Find and rename AABs | |
| find "$AAB_DIR" -name '*.aab' -type f | while read -r f; do | |
| dir=$(dirname "$f") | |
| mv "$f" "$dir/forwardemail-mail_${VERSION}_android.aab" | |
| done | |
| - name: Upload signed APK/AAB to release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| TAG="v${{ inputs.version || inputs.tag || github.ref_name }}" | |
| # Collect built artifacts | |
| FILES=() | |
| while IFS= read -r f; do FILES+=("$f"); done < <(find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' -type f) | |
| while IFS= read -r f; do FILES+=("$f"); done < <(find src-tauri/gen/android/app/build/outputs/bundle -name '*.aab' -type f) | |
| if [ ${#FILES[@]} -eq 0 ]; then | |
| echo "::error::No APK/AAB files found to upload" | |
| exit 1 | |
| fi | |
| echo "Uploading ${#FILES[@]} file(s) to release $TAG" | |
| gh release upload "$TAG" "${FILES[@]}" --clobber | |
| - name: Clean up keystore | |
| if: always() | |
| run: | | |
| rm -f /tmp/keystore.jks | |
| # Overwrite before delete to prevent recovery | |
| dd if=/dev/urandom of=/tmp/keystore.jks bs=1024 count=10 2>/dev/null || true | |
| rm -f /tmp/keystore.jks | |
| ios: | |
| if: false # Disabled until iOS signing is set up | |
| runs-on: macos-latest | |
| name: Release iOS (signed IPA) | |
| # Use the 'release' environment for scoped secrets | |
| environment: release | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri -> target | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: ${{ env.PNPM_VERSION }} | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Import Apple certificates | |
| uses: apple-actions/import-codesign-certs@v3 | |
| with: | |
| p12-file-base64: ${{ secrets.IOS_CERTIFICATE_BASE64 }} | |
| p12-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} | |
| - name: Install provisioning profile | |
| run: | | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| echo "${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}" | \ | |
| base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision | |
| - name: Initialize iOS project | |
| run: pnpm tauri ios init | |
| - name: Build iOS (release) | |
| env: | |
| APPLE_DEVELOPMENT_TEAM: ${{ secrets.APPLE_TEAM_ID }} | |
| run: pnpm tauri ios build | |
| - name: Upload iOS IPA to release | |
| uses: softprops/action-gh-release@v2 | |
| if: success() | |
| with: | |
| tag_name: ${{ github.event.inputs.tag || github.ref_name }} | |
| files: | | |
| src-tauri/gen/apple/build/**/*.ipa | |
| draft: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Clean up provisioning profile | |
| if: always() | |
| run: | | |
| rm -f ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision |