Skip to main content

Auto-Update System Guide

This guide covers the full setup and operation of the NuPOS auto-update mechanism for desktop and Android clients.

Architecture Overview

Desktop (Windows): Tauri updater plugin calls the Cloudflare Worker → Worker fetches release metadata from GitHub API with server-side PAT → returns pre-signed download URLs → Tauri downloads installer directly from CDN (no auth needed) → verifies Ed25519 signature → installs → relaunches. Fully automatic, no manual steps for operators. Why a proxy? The repo is private. GitHub Releases URLs return 404 without auth, and Tauri’s HTTP client (reqwest) strips Authorization headers on cross-origin redirects to GitHub’s CDN. The Worker solves this by resolving pre-signed CDN URLs server-side — no secrets in the app binary. Android (sideloaded): Frontend JS calls the Odoo backend endpoint, then opens the APK URL in the system browser for download and install. Web: No update mechanism — deploy new static files to your server.

1. Initial Setup (One-Time)

1.1 Generate a Signing Keypair

The signing key ensures update integrity — Tauri will not apply unsigned updates. A keypair is already generated (public key is in tauri.conf.json). If you need a new one:
This produces:
  • ~/.tauri/nupos.keyprivate key (NEVER commit this)
  • ~/.tauri/nupos.key.pubpublic key (goes into tauri.conf.json)
The CLI prompts for a password. Store both the key file and the password securely — if lost, existing installations cannot receive updates.

1.2 Configure GitHub Repository Secrets

Set secrets via the CLI (not the web UI, which can corrupt base64 values):
GITHUB_TOKEN is provided automatically by GitHub Actions — do not add it manually.

1.3 Configure the Cloudflare Worker

The Worker is already deployed at https://pos-update-proxy.jespinal.workers.dev. If you need to redeploy or reconfigure:
The GITHUB_PAT must be a fine-grained GitHub PAT with Contents: read permission on the repo.

1.4 Verify tauri.conf.json Configuration

The following should already be set in nu_pos_react/src-tauri/tauri.conf.json:
If you regenerated the keypair, update plugins.updater.pubkey with the contents of the new .pub file. A mismatch causes “The signature was created with a different key than the one provided” errors.

2. Release Process

Step-by-Step

2a. Bump the Version

Edit version.json at the repo root:
Or use the bump skill:
The sync script runs automatically during desktop:dev and desktop:build, but run it manually if you want to verify or commit the stamped files.

2b. Commit and Tag

2c. GitHub Actions Runs Automatically

The release.yml workflow triggers on the v* tag push. It:
  1. Validates (Ubuntu): lint, typecheck, unit tests
  2. Builds (Windows): Compiles the Tauri app with NSIS installer, signs it, creates a draft GitHub Release with:
    • NuPOS-Restaurant_1.1.0_x64-setup.exe — NSIS installer
    • NuPOS-Restaurant_1.1.0_x64-setup.exe.sig — Ed25519 signature
    • NuPOS-Restaurant_1.1.0_x64_en-US.msi — MSI installer
    • NuPOS-Restaurant_1.1.0_x64_en-US.msi.sig — MSI signature

2d. Review and Publish the Release

  1. Go to GitHub → Releases — you’ll see a new Draft release
  2. Edit the release notes if needed (add changelog, known issues, etc.)
  3. Click Publish release

2e. Clients Detect the Update

The Worker caches responses for 5 minutes. After publishing, wait up to 5 minutes for the Worker cache to expire, then existing POS installations will detect the update on next launch.

Manual Trigger (Testing)

You can also trigger the build manually via GitHub Actions → Release POS → Run workflow, without pushing a tag. This is useful for testing the CI pipeline.

3. How the Update Flow Works

On App Launch

Download Flow (Private Repo)

The Worker solves the private repo download problem:
  1. Worker calls GitHub API /releases/assets/{id} with PAT and Accept: application/octet-stream, using redirect: "manual"
  2. GitHub returns 302 with a pre-signed CDN URL in the Location header
  3. Worker returns this pre-signed URL in the Tauri JSON response
  4. Tauri downloads directly from the pre-signed CDN URL — no auth headers needed
Pre-signed URLs expire in ~10 minutes. Since the update check and download happen in sequence, this is sufficient.

Fallback Behavior

If the Worker endpoint is unreachable (no internet, DNS failure, etc.):
  • If POS_API_BASE_URL is set, falls back to the Odoo update endpoint
  • If that also fails, shows a brief error with “Continue Anyway”
  • The POS never gets stuck — operators can always reach the order screen
  • The min_client_version Odoo system parameter serves as a hard backstop at bootstrap time

4. CI Cost Budget

Per-Release (Windows Only)

First build may take ~15-20 real minutes (no Rust cache). Subsequent builds benefit from the cache-warm.yml workflow that runs cargo check on main pushes to warm the Rust cache. Budget: 2,000 free minutes / 18 per release ≈ ~110 releases/month — effectively unlimited. The release workflow only triggers on tag pushes (v*), so normal development pushes consume zero release CI minutes.

Build performance optimizations

  • Rust cache warming: cache-warm.yml runs on main pushes (when Tauri source changes), saving the Rust compilation cache so tag-triggered release builds can restore from it
  • LLD linker: RUSTFLAGS: "-C link-arg=-fuse-ld=lld" for faster linking
  • cache-on-failure: true: Saves partial build cache even on failures

5. Signing Key Management

Key Rotation

If the private key is compromised:
  1. Generate a new keypair: npx @tauri-apps/cli signer generate -w ~/.tauri/nupos-v2.key
  2. Update tauri.conf.json with the new public key
  3. Update the GitHub secrets via CLI: gh secret set TAURI_SIGNING_PRIVATE_KEY < ~/.tauri/nupos-v2.key
  4. Build and ship a release signed with the old key (so existing clients can verify it)
  5. That release now contains the new public key in its binary
  6. All subsequent releases are signed with the new private key

6. Android Update Strategy

The tauri-plugin-updater does not support Android. Android updates use a separate path:
  1. The Odoo backend serves update check responses at GET /pos-api/v1/updates/check/android/aarch64/{version}
  2. On launch, the frontend calls this endpoint and compares versions
  3. If an update is available, the same blocking overlay appears
  4. “Download Update” opens the APK URL in the system browser
  5. Requires “Install from unknown sources” enabled on the tablet (one-time setup)

Adding Android APK to Releases

To also distribute Android APKs via GitHub Releases:
  1. Build the APK: cd nu_pos_react && npm run tauri android build
  2. Attach the APK to the same GitHub Release as the Windows artifacts
  3. Update the Odoo release record to point to the GitHub Release APK URL

7. Testing the Updater Locally

Without GitHub Releases

You can test the update flow with a local HTTP server:
  1. Build the app with version 0.0.1 (set in version.json)
  2. Create a latest.json for version 0.0.2:
  1. Serve the directory: npx serve -l 8080 ./test-update-server
  2. Temporarily change the endpoint in tauri.conf.json to http://localhost:8080/latest.json
  3. Run the 0.0.1 build — it should detect and offer the 0.0.2 update

Verifying the Worker

After publishing a release, verify the Worker endpoint:
Expected output:
The url fields should be pre-signed CDN URLs (not api.github.com URLs).

Health check


8. Troubleshooting

Client doesn’t check for updates

  • Web: Expected — web has no update mechanism.
  • Desktop: Verify the app is a Tauri build (check window title shows version). The plugin reads endpoints from tauri.conf.json which is baked into the binary at build time.
  • Android: Verify POS_API_BASE_URL is set and the Odoo endpoint is reachable from the device.

Worker returns 204 (no releases)

  • Check the GitHub Release is published (not draft)
  • Check the GITHUB_PAT secret is set: cd workers/update-proxy && wrangler secret list
  • Check the PAT has Contents: read permission on the repo

Worker returns 502

  • The GitHub API returned an error. Check the PAT hasn’t expired.
  • Verify with: curl -s -H "Authorization: token <PAT>" https://api.github.com/repos/josespinal/rost_pos_restaurant/releases/latest

Update found but download fails

  • Pre-signed URLs expire in ~10 minutes. If the user waited too long, retry the update check.
  • Check the release has both .exe and .sig files uploaded

Signature verification fails

  • The public key in tauri.conf.json must match the private key in the TAURI_SIGNING_PRIVATE_KEY GitHub secret
  • If you regenerated keys, both must be updated together
  • Set secrets via CLI (gh secret set), not the web UI (which can corrupt base64)
  • Don’t manually re-upload a modified binary without re-signing

CI build fails

  • Missing secrets: Verify TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD are set in GitHub repo settings
  • Rust compilation errors: Check Cargo.toml dependencies match the Tauri version
  • Version mismatch: Ensure version.json, tauri.conf.json, Cargo.toml, and package.json all have the same version (run npm run version:sync)

“Later” defers but update appears every launch

This is by design. The operator can defer with “Later”, but the update check runs on every app launch. Once an update is available, the overlay will appear each time until the update is installed. During download/install, the overlay cannot be dismissed.

Android download doesn’t trigger install

  • Verify REQUEST_INSTALL_PACKAGES permission is in AndroidManifest.xml
  • The user may need to enable “Install from unknown sources” in Android settings
  • Some Android versions require: Settings → Apps → Browser → Install unknown apps

9. Rollback

Tauri does not include a built-in rollback mechanism. If a bad update is deployed:
  1. Fastest: Publish a new release with the fix (bump patch version)
  2. Manual: Reinstall the previous version from the GitHub Releases page
  3. Prevention: Use releaseDraft: true in CI (current setting) to review builds before publishing
For critical POS deployments, keep the last 2-3 release installers downloaded locally as a safety net.

10. Worker Proxy Reference

Source

workers/update-proxy/ in the repo root.

Configuration

wrangler.toml:

Platform key mapping

The Worker maps release asset filenames to Tauri platform keys: Assets without a matching .sig file are excluded from the response (Tauri requires signatures).

Deploying Worker changes


11. File Reference


12. Release Checklist

  • Bump version.json to the new version
  • Run npm run version:sync (or let the CI do it from the tag)
  • Commit: git commit -m "release: vX.Y.Z"
  • Tag: git tag vX.Y.Z
  • Push: git push origin main --tags
  • Wait for CI to complete (check GitHub Actions)
  • Review the draft release on GitHub → edit notes → Publish
  • Verify Worker: curl -s https://pos-update-proxy.jespinal.workers.dev/update/check | python3 -m json.tool
  • (Optional) Update nu_pos.min_client_version in Odoo to enforce the minimum version
  • (Android) Create/update Odoo release record with APK URL if shipping Android update