Skip to content

Release Mobile (Tauri) #4

Release Mobile (Tauri)

Release Mobile (Tauri) #4

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