-
-
Notifications
You must be signed in to change notification settings - Fork 487
feat(desktop): add a sandboxed Mac App Store build variant #1581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| name: Build Mac App Store package | ||
|
|
||
| # Manually builds the sandboxed Mac App Store variant and uploads the signed | ||
| # .pkg as a workflow artifact for submission via Transporter or | ||
| # `xcrun altool --upload-app`. Like msix-store.yml, this never touches a GitHub | ||
| # release; it exists so a Store-ready package can be produced on demand. | ||
| # | ||
| # The MAS variant differs from the Developer ID builds in release.yml: | ||
| # - `mas` cargo feature: the Python sidecar, Jupyter, martin, and external | ||
| # plugin installation are compiled out (App Sandbox forbids spawning | ||
| # downloaded executables; guideline 2.5.2 forbids downloading executable | ||
| # code). Client-side engines (DuckDB-WASM, Whitebox WASM, Turf, Pyodide) | ||
| # keep working because WebKit executes them. | ||
| # - App Sandbox entitlements + embedded provisioning profile | ||
| # (tauri.mas.conf.json), signed with "Apple Distribution" instead of | ||
| # "Developer ID Application"; no notarization (not applicable to MAS). | ||
| # - GEOLIBRE_STORE_BUILD=1 strips the in-app update flow (set by | ||
| # scripts/tauri-build.mjs --mas), matching Apple guideline 2.4.5(vii). | ||
| # | ||
| # Required secrets (see docs/mac-app-store.md for how to create them): | ||
| # APPLE_MAS_CERTIFICATE base64 .p12 holding BOTH the | ||
| # "Apple Distribution" and the | ||
| # "3rd Party Mac Developer Installer" | ||
| # certificates with their private keys | ||
| # APPLE_MAS_CERTIFICATE_PASSWORD password of that .p12 | ||
| # APPLE_MAS_SIGNING_IDENTITY e.g. "Apple Distribution: Name (TEAMID)" | ||
| # APPLE_MAS_INSTALLER_IDENTITY e.g. "3rd Party Mac Developer Installer: Name (TEAMID)" | ||
| # APPLE_MAS_PROVISIONING_PROFILE base64 Mac App Store .provisionprofile | ||
| # for org.geolibre.desktop | ||
| # APPLE_TEAM_ID already configured for release.yml | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build MAS pkg | ||
| runs-on: macos-latest | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v7 | ||
| with: | ||
| # npm ci runs third-party lifecycle scripts; keep the checkout token | ||
| # out of the local git config they could read. | ||
| persist-credentials: false | ||
|
|
||
| - name: Verify signing secrets are configured | ||
| env: | ||
| APPLE_MAS_CERTIFICATE: ${{ secrets.APPLE_MAS_CERTIFICATE }} | ||
| APPLE_MAS_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_MAS_CERTIFICATE_PASSWORD }} | ||
| APPLE_MAS_PROVISIONING_PROFILE: ${{ secrets.APPLE_MAS_PROVISIONING_PROFILE }} | ||
| APPLE_MAS_SIGNING_IDENTITY: ${{ secrets.APPLE_MAS_SIGNING_IDENTITY }} | ||
| APPLE_MAS_INSTALLER_IDENTITY: ${{ secrets.APPLE_MAS_INSTALLER_IDENTITY }} | ||
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||
| run: | | ||
| missing=() | ||
| for name in APPLE_MAS_CERTIFICATE APPLE_MAS_CERTIFICATE_PASSWORD \ | ||
| APPLE_MAS_PROVISIONING_PROFILE APPLE_MAS_SIGNING_IDENTITY \ | ||
| APPLE_MAS_INSTALLER_IDENTITY APPLE_TEAM_ID; do | ||
| [[ -n "${!name}" ]] || missing+=("$name") | ||
| done | ||
| if (( ${#missing[@]} )); then | ||
| echo "::error::Missing secrets for the Mac App Store build: ${missing[*]}. See docs/mac-app-store.md." | ||
| exit 1 | ||
| fi | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| - name: Set up Node.js | ||
| uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: lts/* | ||
| cache: npm | ||
| cache-dependency-path: package-lock.json | ||
|
|
||
| - name: Install Rust stable | ||
| # Pinned to a commit SHA (not the moving `stable` branch) because this | ||
| # job later holds the App Store signing certificates in its keychain. | ||
| # Update deliberately when bumping the toolchain action. | ||
| uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable | ||
|
|
||
| - name: Install universal-build Rust targets | ||
| run: rustup target add aarch64-apple-darwin x86_64-apple-darwin | ||
|
|
||
| - name: Install frontend dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Import signing certificates into a temporary keychain | ||
| env: | ||
| APPLE_MAS_CERTIFICATE: ${{ secrets.APPLE_MAS_CERTIFICATE }} | ||
| APPLE_MAS_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_MAS_CERTIFICATE_PASSWORD }} | ||
| run: | | ||
| keychain="$RUNNER_TEMP/mas-signing.keychain-db" | ||
| keychain_password="$(uuidgen)" | ||
| security create-keychain -p "$keychain_password" "$keychain" | ||
| security set-keychain-settings -lut 21600 "$keychain" | ||
| security unlock-keychain -p "$keychain_password" "$keychain" | ||
| printf '%s' "$APPLE_MAS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/mas.p12" | ||
| security import "$RUNNER_TEMP/mas.p12" -k "$keychain" \ | ||
| -P "$APPLE_MAS_CERTIFICATE_PASSWORD" -f pkcs12 -A \ | ||
| -T /usr/bin/codesign -T /usr/bin/productbuild | ||
| rm "$RUNNER_TEMP/mas.p12" | ||
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | ||
| -s -k "$keychain_password" "$keychain" > /dev/null | ||
| security list-keychains -d user -s "$keychain" login.keychain-db | ||
| echo "MAS_KEYCHAIN=$keychain" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Install the provisioning profile and render entitlements | ||
| env: | ||
| APPLE_MAS_PROVISIONING_PROFILE: ${{ secrets.APPLE_MAS_PROVISIONING_PROFILE }} | ||
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||
| run: | | ||
| printf '%s' "$APPLE_MAS_PROVISIONING_PROFILE" | base64 --decode \ | ||
| > apps/geolibre-desktop/src-tauri/mas/embedded.provisionprofile | ||
| scripts/render-mas-entitlements.sh | ||
|
|
||
| - name: Build the sandboxed universal app | ||
| env: | ||
| NODE_OPTIONS: --max-old-space-size=4096 | ||
| VITE_GEE_OAUTH_CLIENT_ID: ${{ secrets.VITE_GEE_OAUTH_CLIENT_ID }} | ||
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_MAS_SIGNING_IDENTITY }} | ||
| run: npm run tauri:build:mas -- --target universal-apple-darwin | ||
|
|
||
| - name: Build the signed installer package | ||
| id: build_pkg | ||
| env: | ||
| APPLE_MAS_INSTALLER_IDENTITY: ${{ secrets.APPLE_MAS_INSTALLER_IDENTITY }} | ||
| run: | | ||
| app="apps/geolibre-desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/GeoLibre Desktop.app" | ||
| [[ -d "$app" ]] || { echo "::error::No app bundle at $app"; exit 1; } | ||
| # Confirm the sandbox entitlement made it into the signature before | ||
| # packaging; an unsandboxed binary is an automatic App Review reject. | ||
| codesign -d --entitlements - --xml "$app" | grep -q "com.apple.security.app-sandbox" \ | ||
| || { echo "::error::App bundle is missing the app-sandbox entitlement"; exit 1; } | ||
| version="$(node -p "require('./apps/geolibre-desktop/src-tauri/tauri.conf.json').version")" | ||
| pkg="GeoLibre.Desktop_${version}_universal_mas.pkg" | ||
| xcrun productbuild --sign "$APPLE_MAS_INSTALLER_IDENTITY" \ | ||
| --component "$app" /Applications "$pkg" | ||
| echo "pkg_path=$pkg" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Upload MAS package artifact | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: geolibre-mas-pkg | ||
| path: ${{ steps.build_pkg.outputs.pkg_path }} | ||
| if-no-files-found: error | ||
|
|
||
| - name: Remove the temporary keychain | ||
| if: always() | ||
| run: | | ||
| if [[ -n "${MAS_KEYCHAIN:-}" && -f "$MAS_KEYCHAIN" ]]; then | ||
| security delete-keychain "$MAS_KEYCHAIN" | ||
| fi | ||
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <!-- | ||
| Merged into the generated macOS Info.plist by the Tauri bundler (all macOS | ||
| builds, Developer ID and Mac App Store alike). | ||
|
|
||
| ITSAppUsesNonExemptEncryption: the app only uses HTTPS/TLS (exempt under | ||
| category 5 part 2), so App Store Connect should not ask for export compliance | ||
| documents on every upload. | ||
| --> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>ITSAppUsesNonExemptEncryption</key> | ||
| <false/> | ||
| </dict> | ||
| </plist> |
39 changes: 39 additions & 0 deletions
39
apps/geolibre-desktop/src-tauri/mas/Entitlements.mas.plist.template
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <!-- | ||
| App Sandbox entitlements for the Mac App Store build ONLY. The Developer ID | ||
| builds in release.yml do not use this file; they keep the default hardened | ||
| runtime without the sandbox. | ||
|
|
||
| ${APPLE_TEAM_ID} is substituted by scripts/render-mas-entitlements.sh before | ||
| the build (App Store binaries must carry application-identifier and | ||
| team-identifier entitlements matching the provisioning profile). The rendered | ||
| Entitlements.mas.plist is gitignored. | ||
|
|
||
| Keep this list minimal: every entitlement is reviewed by App Review, and the | ||
| sandbox is the reason the MAS build compiles out the Python sidecar, Jupyter, | ||
| martin, and native DuckDB (see the `mas` cargo feature). | ||
|
|
||
| - network.client: map tiles, basemap styles, remote GeoJSON/COG/PMTiles, | ||
| collab websocket. | ||
| - network.server: the Earth Engine OAuth flow listens on a loopback port for | ||
| the redirect callback. | ||
| - files.user-selected.read-write: open/save project files and datasets picked | ||
| through the file dialogs. | ||
|
giswqs marked this conversation as resolved.
|
||
| --> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>com.apple.security.app-sandbox</key> | ||
| <true/> | ||
| <key>com.apple.application-identifier</key> | ||
| <string>${APPLE_TEAM_ID}.org.geolibre.desktop</string> | ||
| <key>com.apple.developer.team-identifier</key> | ||
| <string>${APPLE_TEAM_ID}</string> | ||
| <key>com.apple.security.network.client</key> | ||
| <true/> | ||
| <key>com.apple.security.network.server</key> | ||
| <true/> | ||
| <key>com.apple.security.files.user-selected.read-write</key> | ||
| <true/> | ||
| </dict> | ||
| </plist> | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.