Signing, notarization, and trusted public releases¶
CI produces unsigned artifacts unless the secrets below are present. Unsigned macOS builds will be blocked by Gatekeeper; Windows SmartScreen may warn; Linux packages still install.
Do not commit certificates, .p12 files, or API keys.
macOS (Apple Silicon and Intel, native runners)¶
Required for a Gatekeeper-trusted DMG:
- Developer ID Application certificate (
.p12) and password. - Apple ID (or App Store Connect API) for notarization.
- Hardened runtime is enabled in
tauri.conf.json.Entitlements.plistmust includecom.apple.security.device.camera(head tracking) andcom.apple.security.network.client(Twin/MRI/Agents and vision models). - Sign the sidecar onedir (
adelpha-python-runtimeand native.so/.dylibunder_internal/) with the same Developer ID before bundling, or Tauri will ship an unsigned helper and Gatekeeper will fail.
A Developer ID is a paid Apple Developer Program membership (~US$99/year). The in-app updater key is free (see below). Local unsigned DMGs still run with Right-click → Open.
GitHub Actions secrets (names only):
| Secret | Purpose |
|---|---|
APPLE_CERTIFICATE |
Base64 .p12 |
APPLE_CERTIFICATE_PASSWORD |
Certificate password |
APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID |
Notarytool |
APPLE_API_KEY / APPLE_API_ISSUER |
Alternative to Apple ID |
Tauri env: APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY,
APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID.
If security import fails (SecKeychainItemImport: One or more parameters
passed to a function were not valid), the certificate secret is not a valid
base64 .p12 or the password does not match. CI then retries an unsigned
.app and wraps it with hdiutil. Gatekeeper will still require
Right-click → Open.
Intel DMG is built on macos-15-intel (x86_64). Apple Silicon DMG is built on
macos-15. A universal DMG is not produced: the Python sidecar cannot be
assumed universal.
Windows (x64 NSIS)¶
| Secret | Purpose |
|---|---|
WINDOWS_CERTIFICATE |
Base64 Authenticode .pfx |
WINDOWS_CERTIFICATE_PASSWORD |
PFX password |
Tauri: WINDOWS_CERTIFICATE, WINDOWS_CERTIFICATE_PASSWORD.
ARM64 Windows is not in the default matrix (native deps unproven).
WebView2: the NSIS installer uses Tauri’s download bootstrapper when the runtime is missing.
Linux¶
.deb and AppImage are not code-signed in this repo. Distribute checksums
(SHA256SUMS) from CI. Hardware access for a future MaRCoS USB/network
device may need udev rules installed by the user, not by a silent postinst
that programs FPGAs.
In-app updates (Settings → Updates)¶
Packaged Adelpha checks GitHub Releases for a newer signed build:
https://github.com/imr-framework/adelpha/releases/latest/download/latest.json
Settings → Check for updates uses that feed. Automatic updates (off by default) checks about 12 seconds after launch, then downloads, installs, and relaunches when a newer version is published.
The public minisign key is already in src-tauri/tauri.conf.json
(plugins.updater.pubkey). The matching private key must never be
committed. It lives locally as src-tauri/updater.key (gitignored) and
in GitHub Actions as secrets.
Losing the private key permanently bricks in-app updates for every install that already has this public key. Keep an offline backup.
GitHub Actions secrets¶
| Secret | Purpose |
|---|---|
TAURI_SIGNING_PRIVATE_KEY |
Full contents of src-tauri/updater.key |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Password for that key (empty string if unencrypted) |
If those secrets are missing, CI still builds installers but skips
updater signatures (createUpdaterArtifacts: false). Pull requests
should stay in that mode. Tag releases that should feed Settings must
have the secrets configured.
Publishing a version the app can install¶
- Bump
versioninsrc-tauri/tauri.conf.json(and matchingpackage.json/Cargo.tomlif you keep them in lockstep) above whatever is already installed.0.1.0will not see an update until a later version is on GitHub Latest. - Push a tag
vX.Y.Z(example:v0.2.0). - The
desktop.ymlworkflow builds each platform, then thepublish-releasejob writeslatest.json(when signatures exist) and uploads artifacts to that GitHub Release.
latest.json is assembled by packaging/updater/assemble_latest_json.py
from:
| Platform key | Artifact |
|---|---|
darwin-aarch64 / darwin-aarch64-app |
Adelpha-darwin-aarch64.app.tar.gz + .sig |
darwin-x86_64 / darwin-x86_64-app |
Adelpha-darwin-x86_64.app.tar.gz + .sig |
windows-x86_64 |
NSIS *setup.exe + .sig |
linux-x86_64 |
AppImage + .sig |
Platforms without a matching .sig are omitted. The updater plugin
rejects a feed that lists a platform with an empty URL. macOS updater
tarballs are produced even when Apple codesign fails (packaging/updater/make_macos_updater_artifact.sh).
The DMG is still unsigned in that case; Settings trusts the updater
signature, Gatekeeper does not.
Local make dist-current and npm run tauri:build sign updater artifacts when
TAURI_SIGNING_PRIVATE_KEY or TAURI_SIGNING_PRIVATE_KEY_PATH is set, or when
src-tauri/updater.key exists (packaging/tauri-build.sh points Tauri at that
file). Otherwise they build without updater signatures. Apple Developer ID /
Authenticode (the tables above) are separate: Gatekeeper trusts the OS
signature; Settings trusts the updater signature.