Azalea auto-updater
Edit on GitHubThe desktop app uses Tauri updater with signed releases on Windows, Linux (AppImage), and macOS.
Installer binaries and latest.json are published to Cloudflare R2
(azalea-updates bucket) behind the custom domain:
https://updates.azalea.rexsystems.me
R2 layout
latest.json # updater entrypoint (always at root)
builds/v0.1.2-build.72/
Azalea_0.1.2+72_amd64.AppImage
Azalea_0.1.2+72_amd64.AppImage.sig
Azalea_0.1.2+72_amd64.deb
…
Azalea.app.tar.gz
Azalea_0.1.2+72_aarch64.dmg
Each release lands in builds/<git-tag>/. CI keeps only the live folder (plus
latest.json) so free-tier storage does not accumulate history. Manual prune:
Actions → Cleanup R2.
Endpoints (in order)
https://updates.azalea.rexsystems.me/latest.json(primary; R2)https://azalea.rexsystems.me/updates/latest.json(site fallback)https://github.com/rexsystems/azalea/releases/latest/download/latest.json(GitHub fallback)
Download URLs inside the manifest point at
updates.azalea.rexsystems.me/builds/<tag>/<artifact>.
Platforms in latest.json
| Key | Artifact |
|---|---|
windows-x86_64 |
.nsis.zip or setup .exe |
linux-x86_64 |
.AppImage / .AppImage.tar.gz |
linux-x86_64-rpm |
.rpm (Fedora / Nobara / RHEL installs) |
linux-x86_64-deb |
.deb (Debian / Ubuntu installs) |
linux-aarch64 |
ARM64 .AppImage / .AppImage.tar.gz |
linux-aarch64-rpm |
ARM64 .rpm |
linux-aarch64-deb |
ARM64 .deb |
darwin-aarch64 |
.app.tar.gz (Apple Silicon) |
darwin-x86_64 |
.app.tar.gz (Intel) |
Linux x64 and ARM64 use separate native Blacksmith runners, architecture-specific Rust caches and release artifacts. Both publish signed AppImage, DEB and RPM updater entries in the same release manifest. macOS includes Apple Silicon and Intel builds; Windows currently builds x64. ARM64 desktop packages use an Ubuntu 22.04 build baseline for compatibility with Debian 12 / Raspberry Pi OS 64-bit. Existing x64 builds remain on Ubuntu 24.04.
GitHub Actions secrets
Add these repository secrets on rexsystems/azalea:
| Secret | Value |
|---|---|
TAURI_SIGNING_PRIVATE_KEY |
Contents of ~/.azalea/tauri-signing.key |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Your key password |
R2_ACCESS_KEY_ID |
Cloudflare R2 API token access key id |
R2_SECRET_ACCESS_KEY |
Cloudflare R2 API token secret access key |
R2 API tokens: Cloudflare dashboard → R2 → Manage R2 API Tokens → create token
with Object Read & Write on bucket azalea-updates.
Generate a new Tauri keypair locally:
npx tauri signer generate -w ~/.azalea/tauri-signing.key -p "your-password" -f
The public key is already in apps/desktop/src-tauri/tauri.conf.json. If
you rotate keys, update it there.
macOS builds from CI are not Apple Developer ID signed (Gatekeeper may warn). Users can right-click → Open the first time, or clear quarantine. Auto-update still works via Tauri's own signatures.
CI builds macOS with --bundles app (signed .app.tar.gz for the updater),
then packs a DMG via .github/create-macos-dmg.sh (plain hdiutil, no Finder
AppleScript; that step is what usually breaks on GitHub Actions).
Version + build number
Repo keeps a normal semver core in tauri.conf.json (e.g. 0.1.2).
Each CI release stamps it to core+N before building, e.g. 0.1.2+70 or
0.1.3+1. N is per base version: CI looks at existing tags v{base}-build.*
and uses max+1. When you bump the core (0.1.2 → 0.1.3), N resets to 1.
That stamped value is what the app reports and what latest.json publishes.
Git tags stay URL-safe as v0.1.3-build.1 (no +).
Plain semver treats +build as ignored metadata, so Azalea uses a custom
updater comparator that compares the numeric build when major/minor/patch match.
Artifact URLs encode + as %2B.
After each master release
- CI resolves
0.x.y+N, then builds Windows, Linux (deb/rpm/AppImage), and macOS. - CI merges platform fragments into
latest.jsonwith download URLs underhttps://updates.azalea.rexsystems.me/builds/<tag>/…. - CI uploads installers into
builds/<tag>/and writeslatest.jsonat the bucket root, then prunes older build folders. - CI also attaches the same files to the GitHub Release (archive / fallback).
- Optionally copy
latest.jsoninto azalea-webpublic/updates/so the marketing site fallback stays in sync:
cp artifacts/latest.json ../azalea-web/public/updates/latest.json
Manual seed (first time / before next CI run)
Until the next master release uploads automatically, put the current release assets and manifest on R2:
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=auto
ENDPOINT=https://f92074baafe52a6a6b5b47c82488090d.r2.cloudflarestorage.com
# Upload one release folder + root manifest
TAG=v0.1.2-build.72
aws s3 sync "./$TAG/" "s3://azalea-updates/builds/$TAG/" --endpoint-url "$ENDPOINT"
aws s3 cp latest.json s3://azalea-updates/latest.json \
--endpoint-url "$ENDPOINT" \
--content-type application/json \
--cache-control "public, max-age=60"
# Optional: prune everything except the live build folder
node .github/cleanup-r2.mjs
Confirm: https://updates.azalea.rexsystems.me/latest.json
In the app
Settings → Updates → Check for updates.
Updates only work in release builds (signed installer / AppImage / app
bundle), not in tauri dev.