Tauri, Part 2 — Who Knew Shipping One DMG Could Be This Hard
The build succeeded but the DMG wouldn't come out. Then it came out with a vanishing icon. Then it opened as 'damaged.' The road to shipping a Tauri desktop app.
Tauri, Part 2 — Who Knew Shipping One DMG Could Be This Hard
The Fecit desktop app runs. The web app wrapped in Tauri v2 works fine locally. All that’s left is getting it to users.
Make one DMG file and we’re done — that’s what we thought.
We were not done.
No such thing as cargo
We ran the build script on the server. First error:
No such file or directory: cargo
Tauri is Rust-based. The server had no Rust. Install it with rustup, run again.
That one was easy. The real problems came next.
The DMG won’t build
The Tauri build finished. The .app was there. But DMG packaging stalled.
failed to run bundle_dmg.sh
Finding the cause took a while. Running bundle_dmg.sh by hand revealed create-dmg was missing. brew install create-dmg. Run again.
A different error this time:
AppleEvent timed out. (-1712)
Tauri’s DMG bundler internally uses AppleScript to lay out the icons nicely in Finder. The problem: the server has no GUI session. Trying to remote-control Finder on a Mac you’re SSH’d into just times out.
–skip-jenkins
We found the --skip-jenkins option. It skips the AppleScript and builds the DMG anyway.
The DMG came out. We opened it.
The Applications folder icon appeared — then vanished.
AppleScript is what handles icon placement, and with it skipped, Finder doesn’t know what to show. The DMG existed, but the experience was off.
hdiutil, in the end
We tried a lot of things. Different create-dmg options, handing over a staging folder, swallowing errors with || true.
The simplest approach turned out to be the answer.
Build the DMG directly with hdiutil, which ships with macOS. Put the app and an Applications symlink in a temp folder, and hdiutil create produces a compressed DMG in one shot. No AppleScript, no create-dmg.
The icon layout is plain default-Finder style, nothing fancy — but the Applications folder shows up reliably and drag-and-drop installation works.
And now it’s “damaged”?
Opening the DMG triggered a “damaged” warning.
This isn’t a DMG problem. It’s macOS Gatekeeper blocking an unsigned app. It needs code signing with an Apple Developer certificate, plus notarization. We’ve parked that as the next task.
What about ARM?
The server is an Intel Mac. The default build produces x64 only. Apple Silicon users need an ARM build too.
This is where Rust’s cross-compilation shone. One line — rustup target add aarch64-apple-darwin — adds the ARM target, and building with --target aarch64-apple-darwin produces an ARM binary on the Intel Mac.
One build script now yields two DMGs: x64 and aarch64.
The download page
A DMG is useless if users can’t get it. We added a Desktop download button to the landing page and built a download page at /downloads/.
At first the build script generated inline HTML, which meant editing the script for every design change. We split it out into a static HTML file and changed the build script to only emit a downloads.json manifest. The page fetches the manifest and renders it as a card UI.
Splitting the scripts
Initially one deploy-desktop.sh held everything: web build, Tauri build, DMG creation, download page generation. If the Tauri build failed, the web deploy behind it stalled too.
We split it: deploy-desktop.sh does web app build and deploy only; build-tauri.sh handles the Tauri native build, DMG creation, and placing the download files. Web updates and native builds now run independently.
Looking back
We had no idea one DMG would take this much digging. From installing cargo, through AppleScript timeouts and Gatekeeper blocks, to ARM cross-compilation. Wrapping the web app in Tauri was easy — delivering it to users was an entirely different problem.
Code signing and notarization still remain. But the build pipeline has settled: each version bump, one script produces x64 + ARM DMGs and the download page picks them up automatically.
Next up: installing the Developer ID Application certificate and producing a signed DMG. Until the day it opens clean — no “damaged” warning.