|
| 1 | +name: Build Mac App Store package |
| 2 | + |
| 3 | +# Manually builds the sandboxed Mac App Store variant and uploads the signed |
| 4 | +# .pkg as a workflow artifact for submission via Transporter or |
| 5 | +# `xcrun altool --upload-app`. Like msix-store.yml, this never touches a GitHub |
| 6 | +# release; it exists so a Store-ready package can be produced on demand. |
| 7 | +# |
| 8 | +# The MAS variant differs from the Developer ID builds in release.yml: |
| 9 | +# - `mas` cargo feature: the Python sidecar, Jupyter, martin, and external |
| 10 | +# plugin installation are compiled out (App Sandbox forbids spawning |
| 11 | +# downloaded executables; guideline 2.5.2 forbids downloading executable |
| 12 | +# code). Client-side engines (DuckDB-WASM, Whitebox WASM, Turf, Pyodide) |
| 13 | +# keep working because WebKit executes them. |
| 14 | +# - App Sandbox entitlements + embedded provisioning profile |
| 15 | +# (tauri.mas.conf.json), signed with "Apple Distribution" instead of |
| 16 | +# "Developer ID Application"; no notarization (not applicable to MAS). |
| 17 | +# - GEOLIBRE_STORE_BUILD=1 strips the in-app update flow (set by |
| 18 | +# scripts/tauri-build.mjs --mas), matching Apple guideline 2.4.5(vii). |
| 19 | +# |
| 20 | +# Required secrets (see docs/mac-app-store.md for how to create them): |
| 21 | +# APPLE_MAS_CERTIFICATE base64 .p12 holding BOTH the |
| 22 | +# "Apple Distribution" and the |
| 23 | +# "3rd Party Mac Developer Installer" |
| 24 | +# certificates with their private keys |
| 25 | +# APPLE_MAS_CERTIFICATE_PASSWORD password of that .p12 |
| 26 | +# APPLE_MAS_SIGNING_IDENTITY e.g. "Apple Distribution: Name (TEAMID)" |
| 27 | +# APPLE_MAS_INSTALLER_IDENTITY e.g. "3rd Party Mac Developer Installer: Name (TEAMID)" |
| 28 | +# APPLE_MAS_PROVISIONING_PROFILE base64 Mac App Store .provisionprofile |
| 29 | +# for org.geolibre.desktop |
| 30 | +# APPLE_TEAM_ID already configured for release.yml |
| 31 | + |
| 32 | +on: |
| 33 | + workflow_dispatch: |
| 34 | + |
| 35 | +permissions: |
| 36 | + contents: read |
| 37 | + |
| 38 | +jobs: |
| 39 | + build: |
| 40 | + name: Build MAS pkg |
| 41 | + runs-on: macos-latest |
| 42 | + steps: |
| 43 | + - name: Checkout repository |
| 44 | + uses: actions/checkout@v7 |
| 45 | + with: |
| 46 | + # npm ci runs third-party lifecycle scripts; keep the checkout token |
| 47 | + # out of the local git config they could read. |
| 48 | + persist-credentials: false |
| 49 | + |
| 50 | + - name: Verify signing secrets are configured |
| 51 | + env: |
| 52 | + APPLE_MAS_CERTIFICATE: ${{ secrets.APPLE_MAS_CERTIFICATE }} |
| 53 | + APPLE_MAS_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_MAS_CERTIFICATE_PASSWORD }} |
| 54 | + APPLE_MAS_PROVISIONING_PROFILE: ${{ secrets.APPLE_MAS_PROVISIONING_PROFILE }} |
| 55 | + APPLE_MAS_SIGNING_IDENTITY: ${{ secrets.APPLE_MAS_SIGNING_IDENTITY }} |
| 56 | + APPLE_MAS_INSTALLER_IDENTITY: ${{ secrets.APPLE_MAS_INSTALLER_IDENTITY }} |
| 57 | + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} |
| 58 | + run: | |
| 59 | + missing=() |
| 60 | + for name in APPLE_MAS_CERTIFICATE APPLE_MAS_CERTIFICATE_PASSWORD \ |
| 61 | + APPLE_MAS_PROVISIONING_PROFILE APPLE_MAS_SIGNING_IDENTITY \ |
| 62 | + APPLE_MAS_INSTALLER_IDENTITY APPLE_TEAM_ID; do |
| 63 | + [[ -n "${!name}" ]] || missing+=("$name") |
| 64 | + done |
| 65 | + if (( ${#missing[@]} )); then |
| 66 | + echo "::error::Missing secrets for the Mac App Store build: ${missing[*]}. See docs/mac-app-store.md." |
| 67 | + exit 1 |
| 68 | + fi |
| 69 | +
|
| 70 | + - name: Set up Node.js |
| 71 | + uses: actions/setup-node@v7 |
| 72 | + with: |
| 73 | + node-version: lts/* |
| 74 | + cache: npm |
| 75 | + cache-dependency-path: package-lock.json |
| 76 | + |
| 77 | + - name: Install Rust stable |
| 78 | + # Pinned to a commit SHA (not the moving `stable` branch) because this |
| 79 | + # job later holds the App Store signing certificates in its keychain. |
| 80 | + # Update deliberately when bumping the toolchain action. |
| 81 | + uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable |
| 82 | + |
| 83 | + - name: Install universal-build Rust targets |
| 84 | + run: rustup target add aarch64-apple-darwin x86_64-apple-darwin |
| 85 | + |
| 86 | + - name: Install frontend dependencies |
| 87 | + run: npm ci |
| 88 | + |
| 89 | + - name: Import signing certificates into a temporary keychain |
| 90 | + env: |
| 91 | + APPLE_MAS_CERTIFICATE: ${{ secrets.APPLE_MAS_CERTIFICATE }} |
| 92 | + APPLE_MAS_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_MAS_CERTIFICATE_PASSWORD }} |
| 93 | + run: | |
| 94 | + keychain="$RUNNER_TEMP/mas-signing.keychain-db" |
| 95 | + keychain_password="$(uuidgen)" |
| 96 | + security create-keychain -p "$keychain_password" "$keychain" |
| 97 | + security set-keychain-settings -lut 21600 "$keychain" |
| 98 | + security unlock-keychain -p "$keychain_password" "$keychain" |
| 99 | + printf '%s' "$APPLE_MAS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/mas.p12" |
| 100 | + security import "$RUNNER_TEMP/mas.p12" -k "$keychain" \ |
| 101 | + -P "$APPLE_MAS_CERTIFICATE_PASSWORD" -f pkcs12 -A \ |
| 102 | + -T /usr/bin/codesign -T /usr/bin/productbuild |
| 103 | + rm "$RUNNER_TEMP/mas.p12" |
| 104 | + security set-key-partition-list -S apple-tool:,apple:,codesign: \ |
| 105 | + -s -k "$keychain_password" "$keychain" > /dev/null |
| 106 | + security list-keychains -d user -s "$keychain" login.keychain-db |
| 107 | + echo "MAS_KEYCHAIN=$keychain" >> "$GITHUB_ENV" |
| 108 | +
|
| 109 | + - name: Install the provisioning profile and render entitlements |
| 110 | + env: |
| 111 | + APPLE_MAS_PROVISIONING_PROFILE: ${{ secrets.APPLE_MAS_PROVISIONING_PROFILE }} |
| 112 | + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} |
| 113 | + run: | |
| 114 | + printf '%s' "$APPLE_MAS_PROVISIONING_PROFILE" | base64 --decode \ |
| 115 | + > apps/geolibre-desktop/src-tauri/mas/embedded.provisionprofile |
| 116 | + scripts/render-mas-entitlements.sh |
| 117 | +
|
| 118 | + - name: Build the sandboxed universal app |
| 119 | + env: |
| 120 | + NODE_OPTIONS: --max-old-space-size=4096 |
| 121 | + VITE_GEE_OAUTH_CLIENT_ID: ${{ secrets.VITE_GEE_OAUTH_CLIENT_ID }} |
| 122 | + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_MAS_SIGNING_IDENTITY }} |
| 123 | + run: npm run tauri:build:mas -- --target universal-apple-darwin |
| 124 | + |
| 125 | + - name: Build the signed installer package |
| 126 | + id: build_pkg |
| 127 | + env: |
| 128 | + APPLE_MAS_INSTALLER_IDENTITY: ${{ secrets.APPLE_MAS_INSTALLER_IDENTITY }} |
| 129 | + run: | |
| 130 | + app="apps/geolibre-desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/GeoLibre Desktop.app" |
| 131 | + [[ -d "$app" ]] || { echo "::error::No app bundle at $app"; exit 1; } |
| 132 | + # Confirm the sandbox entitlement made it into the signature before |
| 133 | + # packaging; an unsandboxed binary is an automatic App Review reject. |
| 134 | + codesign -d --entitlements - --xml "$app" | grep -q "com.apple.security.app-sandbox" \ |
| 135 | + || { echo "::error::App bundle is missing the app-sandbox entitlement"; exit 1; } |
| 136 | + version="$(node -p "require('./apps/geolibre-desktop/src-tauri/tauri.conf.json').version")" |
| 137 | + pkg="GeoLibre.Desktop_${version}_universal_mas.pkg" |
| 138 | + xcrun productbuild --sign "$APPLE_MAS_INSTALLER_IDENTITY" \ |
| 139 | + --component "$app" /Applications "$pkg" |
| 140 | + echo "pkg_path=$pkg" >> "$GITHUB_OUTPUT" |
| 141 | +
|
| 142 | + - name: Upload MAS package artifact |
| 143 | + uses: actions/upload-artifact@v7 |
| 144 | + with: |
| 145 | + name: geolibre-mas-pkg |
| 146 | + path: ${{ steps.build_pkg.outputs.pkg_path }} |
| 147 | + if-no-files-found: error |
| 148 | + |
| 149 | + - name: Remove the temporary keychain |
| 150 | + if: always() |
| 151 | + run: | |
| 152 | + if [[ -n "${MAS_KEYCHAIN:-}" && -f "$MAS_KEYCHAIN" ]]; then |
| 153 | + security delete-keychain "$MAS_KEYCHAIN" |
| 154 | + fi |
0 commit comments