Mac App Store¶
GeoLibre Desktop is published on the Mac App Store:
Get GeoLibre Desktop on the Mac App Store
For the user-facing comparison of the two macOS builds, see Downloads. This page documents how the Store build is produced and why it differs.
GeoLibre Desktop ships on macOS two ways:
- Developer ID (the default): the notarized
.dmgpublished byrelease.ymlon every GitHub release and distributed through the website, Homebrew, and winget-style channels. Full feature set, hardened runtime, no App Sandbox. - Mac App Store (MAS): a sandboxed variant built by the
mas-store.ymlworkflow (ornpm run tauri:build:maslocally). This page documents that variant.
The App Store imposes two hard rules the default build cannot satisfy:
- App Sandbox is mandatory. The app is confined to its container plus files the user explicitly picks in an open dialog. Spawning downloaded, unsigned executables is impossible.
- Guideline 2.5.2: the app may not download or execute code that changes
its features. The default build bootstraps
uv, a Python environment, and the martin tile server at runtime, all of which are downloads of executable code.
So the MAS build compiles those services out instead of shipping them broken.
What the MAS build removes¶
The mas cargo feature (apps/geolibre-desktop/src-tauri) replaces these
Tauri commands with stubs, and GEOLIBRE_MAS_BUILD=1 hides the matching UI:
- Python sidecar (Whitebox server engine, Conversion/Raster sidecar engines, PostGIS endpoints, SamGeo segmentation, SedonaDB SQL).
- Jupyter server (the Notebook panel falls back to the bundled JupyterLite, which the web build already uses; Pyodide executes inside WebKit, which App Review permits).
- martin tile server and its GitHub binary download (Add Data → PostgreSQL is hidden).
- External plugin installation (zip archives and registry installs). Built-in and bundled drop-in plugins keep working.
- In-app update checks (
GEOLIBRE_STORE_BUILD=1, same as the Microsoft Store MSIX build): a Store app updates only through the Store. - Earth Engine sign-in (Processing → Earth Engine, and the GeoAgent plugin's Earth Engine overlay). Unlike the items above this is not a sandbox limit but an entitlement one — see below.
Earth Engine and the network.server entitlement¶
Earth Engine sign-in uses Google's OAuth loopback-redirect flow for native
apps: src-tauri/src/earth_engine_oauth.rs binds a listener on 127.0.0.1,
opens the consent page in the system browser, and accepts the browser's inbound
redirect to receive the token. Accepting an inbound connection requires
com.apple.security.network.server.
App Review rejected GeoLibre Desktop 2.4.0 over exactly this (guideline
2.4.5, submission 76036eca-dc21-45d3-873e-39b015d37036): an automated analysis
flags the server entitlement when it cannot find matching functionality, and the
bind happens lazily inside a Tauri command, so nothing static points at it.
Rather than defend the entitlement in App Review Information, both Apple targets
drop the feature: the module is gated
#[cfg(not(any(feature = "mas", target_os = "ios")))] and replaced with stub
commands that bind nothing, so neither the Mac App Store build nor the iOS app
ever opens a listening socket. The entitlement is gone from
mas/Entitlements.mas.plist.template, and mas-store.yml fails the build if it
reappears in the signature. (iOS has no entitlements file — those sandbox keys
are macOS-only — but it ships through the same review pipeline, so it gets the
same treatment.)
On the UI side, isEarthEngineAvailable()
(packages/plugins/src/plugins/earth-engine-auth.ts) hides the Processing menu
item and the command-palette entry, and authenticateEarthEngine throws early
if anything else reaches it. It detects the macOS build from the
__GEOLIBRE_MAS_BUILD__ define and iOS from the user agent, and only applies
inside the packaged app — the web build keeps Earth Engine everywhere,
including Safari on iOS, because a browser uses Google's popup/redirect flow and
binds nothing.
If a future feature genuinely needs to accept an inbound connection, re-adding the entitlement is not sufficient on its own: describe the functionality in App Review Information before submitting, or the automated check rejects it again.
Everything client-side is unchanged and fully functional: MapLibre/deck.gl rendering, Add Data for local and remote files, DuckDB-WASM vector reading, Whitebox WASM tools, Turf/Pyodide vector tools, browser-engine conversions, client raster tools, the SQL Workspace on CereusDB/PGlite, the Python console, onnxruntime-web detection/segment-everything, geocoding, and collaboration.
Known limitations under the sandbox¶
- Loose shapefiles: the sandbox only grants access to the files the user
picked, so the automatic read of
.dbf/.prj/.shxsiblings next to a picked.shpis denied. The MAS build compensates by matching companions out of the same dialog selection, so multi-select every shapefile part (.shp,.dbf,.prj, ...) or load a zipped shapefile. A.shppicked alone loads without attributes or CRS. This also applies when a saved project restores a loose.shplayer: re-add it or use a zip. - Environment variables: the "read API keys from the shell environment" convenience returns nothing, because a sandboxed app does not inherit a login shell environment. Enter keys in Settings instead.
- Reopening projects that reference local files: the sandbox grant for a
picked file ends with the process unless the app stores a security-scoped
bookmark, which GeoLibre does not do yet. After a relaunch, a project layer
backed by a local path outside the container fails to restore its data;
re-add the file to reload it. Adopting security-scoped bookmarks (e.g. via
tauri-plugin-persisted-scope's macOS bookmark support) is the planned fix.
Building locally¶
Unlike the other desktop builds, this one embeds the JupyterLite site, so
the jupyter lite CLI has to be installed before you build:
pip install -r apps/geolibre-desktop/jupyterlite/requirements.txt
Without it the build fails with an explicit error rather than producing an app
whose Notebook panel loads the wrong thing (Tauri answers a missing asset with
index.html, so a MAS build with no JupyterLite renders a second copy of
GeoLibre inside the panel).
# One-time: render the entitlements with your team id.
APPLE_TEAM_ID=XXXXXXXXXX scripts/render-mas-entitlements.sh
# Put a Mac App Store provisioning profile for org.geolibre.desktop at
# apps/geolibre-desktop/src-tauri/mas/embedded.provisionprofile
# (App Store Connect → Profiles → macOS App Development/Distribution).
APPLE_SIGNING_IDENTITY="Apple Distribution: Your Name (XXXXXXXXXX)" \
npm run tauri:build:mas -- --target universal-apple-darwin
# Wrap the .app into a Store-submittable installer package.
xcrun productbuild \
--sign "3rd Party Mac Developer Installer: Your Name (XXXXXXXXXX)" \
--component "apps/geolibre-desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/GeoLibre Desktop.app" \
/Applications GeoLibre-Desktop-mas.pkg
npm run tauri:build:mas wires everything else: the mas cargo feature, the
tauri.mas.conf.json overlay (App Sandbox entitlements, embedded provisioning
profile, no bundled sidecar resources, hardened runtime off), and the
GEOLIBRE_MAS_BUILD/GEOLIBRE_STORE_BUILD frontend flags. It refuses to run
with --native-duckdb (native DuckDB loads its spatial extension as unsigned
native code at runtime).
The sandboxed app can be smoke-tested by running the built
GeoLibre Desktop.app directly; codesign -d --entitlements - --xml on the
bundle should show com.apple.security.app-sandbox, and must not show
com.apple.security.network.server.
CI: the mas-store.yml workflow¶
workflow_dispatch only, mirroring msix-store.yml: it builds the universal
sandboxed app, signs it, verifies the sandbox entitlement, produces the signed
.pkg, and uploads it as the geolibre-mas-pkg artifact. It does not touch
GitHub releases and does not upload to App Store Connect.
Required repository secrets:
| Secret | Contents |
|---|---|
APPLE_MAS_CERTIFICATE |
base64 .p12 containing both the "Apple Distribution" and "3rd Party Mac Developer Installer" certificates with private keys |
APPLE_MAS_CERTIFICATE_PASSWORD |
password of that .p12 |
APPLE_MAS_SIGNING_IDENTITY |
Apple Distribution: Name (TEAMID) |
APPLE_MAS_INSTALLER_IDENTITY |
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 |
Creating the inputs (Apple Developer account required):
- In the developer portal, create an App ID for
org.geolibre.desktop(macOS, no extra capabilities). - Create the two certificates ("Apple Distribution" and "3rd Party Mac
Developer Installer") via Keychain Access CSRs, then export both together
from Keychain Access as one
.p12:base64 -i certs.p12 | pbcopy. - Create a Mac App Store Connect provisioning profile for that App ID and
the distribution certificate:
base64 -i profile.provisionprofile | pbcopy.
Submitting¶
- Create the app record in App Store Connect for
org.geolibre.desktop(the same team as the iOSorg.geolibre.apprecord; the two are separate apps). This is already done for GeoLibre Desktop, App Store ID6796848769(the numeric listing identifier App Store Connect labels "Apple ID" on the App Information page), so later releases start at step 2. - Download the
geolibre-mas-pkgartifact and upload the.pkgwith the Transporter app (orxcrun altool --upload-app -f <pkg> -t macos). - Fill in screenshots, description, and the privacy questionnaire
(see
docs/privacy.md; the app makes no tracking calls). - In the review notes, mention that processing engines run client-side as WebAssembly inside the webview and that the app downloads only data (map tiles, user-requested datasets), never code.
Version numbers come from tauri.conf.json (CFBundleShortVersionString);
every upload needs a version or build number higher than the last one in App
Store Connect.