Skip to content

App Store Release

App Store Release #4

name: App Store Release
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v1.1.0)'
required: true
permissions:
contents: write
jobs:
release-appstore:
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag }}
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
- name: Sync node version and setup cache
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Install Tauri CLI
run: cargo install tauri-cli --version "^2.0"
- name: Install frontend dependencies
run: npm install
- name: Install App Store Certificates
env:
APPLE_APPSTORE_CERTIFICATE: ${{ secrets.APPLE_APPSTORE_CERTIFICATE }}
APPLE_INSTALLER_CERTIFICATE: ${{ secrets.APPLE_INSTALLER_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_PROVISIONING_PROFILE: ${{ secrets.APPLE_PROVISIONING_PROFILE }}
run: |
# Create keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
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 App Store Application certificate
echo "$APPLE_APPSTORE_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/appstore.p12
security import $RUNNER_TEMP/appstore.p12 -P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 -k $KEYCHAIN_PATH
# Import Installer certificate
echo "$APPLE_INSTALLER_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/installer.p12
security import $RUNNER_TEMP/installer.p12 -P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Install provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo "$APPLE_PROVISIONING_PROFILE" | base64 --decode > \
~/Library/MobileDevice/Provisioning\ Profiles/embedded.provisionprofile
# Verify certificates
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Build App Store Version
env:
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
VITE_AUTH_BASE_URL: https://lokusmd.com
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_ENABLE_CRASH_REPORTS: 'true'
VITE_SENTRY_ENVIRONMENT: 'production'
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
VITE_POSTHOG_KEY: ${{ secrets.VITE_POSTHOG_KEY }}
VITE_POSTHOG_HOST: ${{ secrets.VITE_POSTHOG_HOST }}
# Mac App Store flags
VITE_DISABLE_UPDATE_CHECKER: 'true'
VITE_APPSTORE: 'true'
run: |
npm run build
cd src-tauri
cargo tauri build --config tauri.appstore.conf.json --config '{"build":{"beforeBuildCommand":""}}'
- name: Sign and Package for App Store
env:
APPLE_APPSTORE_SIGNING_IDENTITY: ${{ secrets.APPLE_APPSTORE_SIGNING_IDENTITY }}
APPLE_INSTALLER_SIGNING_IDENTITY: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}
run: |
APP_PATH="src-tauri/target/release/bundle/macos/Lokus.app"
PKG_PATH="src-tauri/target/release/Lokus-${{ github.event.inputs.tag }}-appstore.pkg"
# Embed provisioning profile
cp ~/Library/MobileDevice/Provisioning\ Profiles/embedded.provisionprofile \
"$APP_PATH/Contents/embedded.provisionprofile"
# Sign the app with App Store identity
codesign --deep --force --verify --verbose \
--sign "$APPLE_APPSTORE_SIGNING_IDENTITY" \
--entitlements src-tauri/entitlements-appstore.plist \
"$APP_PATH"
# Create installer package
productbuild --sign "$APPLE_INSTALLER_SIGNING_IDENTITY" \
--component "$APP_PATH" /Applications \
"$PKG_PATH"
# Verify the package
pkgutil --check-signature "$PKG_PATH"
- name: Upload App Store Package
uses: softprops/action-gh-release@v1
with:
files: src-tauri/target/release/Lokus-${{ github.event.inputs.tag }}-appstore.pkg
tag_name: ${{ github.event.inputs.tag }}