Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.laportenard.com/llms.txt

Use this file to discover all available pages before exploring further.

The auto-update system keeps desktop (Tauri) and Android (sideloaded APK) clients up to date. Desktop uses a Cloudflare Worker that proxies GitHub Releases from the private repo. Android uses an Odoo backend endpoint.

Architecture

1

CI builds and publishes to GitHub Releases

The release.yml workflow triggers on v* tag push, builds the signed NSIS installer, and creates a GitHub Release with the installer .exe and its .sig signature file.
2

Client checks for updates on launch

An UpdateGate component wraps the app and blocks rendering until the update check completes. On web, it passes through immediately.
3

Worker proxies GitHub API

The Tauri updater plugin calls the Cloudflare Worker at /update/check. The Worker authenticates with the GitHub API using a server-side PAT, fetches the latest release metadata, resolves pre-signed download URLs, and returns Tauri-format JSON.
4

Desktop downloads and installs

Tauri downloads the installer from the pre-signed URL (no auth needed), verifies the Ed25519 signature, installs, and relaunches.
5

Android opens the APK URL

Android checks the Odoo endpoint via JS, then opens the APK download URL in the system browser for manual install.

Desktop update endpoint

The desktop client does not talk to GitHub directly. A Cloudflare Worker (workers/update-proxy/) acts as a proxy that handles authentication for the private repo.

How the Worker proxy works

Tauri app                  Cloudflare Worker              GitHub API
   │                            │                            │
   │  GET /update/check         │                            │
   │──────────────────────────►│                            │
   │                            │  GET /releases/latest      │
   │                            │  (with PAT)               │
   │                            │──────────────────────────►│
   │                            │◄──────────────────────────│
   │                            │                            │
   │                            │  Resolve pre-signed URLs   │
   │                            │  Fetch .sig contents       │
   │                            │──────────────────────────►│
   │                            │◄──────────────────────────│
   │                            │                            │
   │  Tauri-format JSON         │                            │
   │  (pre-signed download URLs)│                            │
   │◄──────────────────────────│                            │
   │                            │                            │
   │  GET pre-signed URL        │                            │
   │──────────────────────────────────────────────────────►│
   │◄──────────────────────────────────────────────────────│
   │  (binary download, no auth needed)                      │
The Worker:
  1. Fetches the latest release from the GitHub API using a server-side PAT
  2. Maps installer filenames to Tauri platform keys (e.g. *_x64-setup.exewindows-x86_64)
  3. Resolves pre-signed CDN download URLs for each asset (no auth needed to download)
  4. Reads .sig file contents inline
  5. Returns Tauri-format JSON with version, notes, platforms, and signatures
No PAT or auth token is embedded in the app binary. The Worker stores the GitHub PAT as a Cloudflare secret.

Endpoint URL

The updater plugin reads the endpoint from tauri.conf.json:
https://pos-update-proxy.jespinal.workers.dev/update/check

Fallback endpoint

If the Worker endpoint is unreachable, the desktop client falls back to a dynamic Odoo endpoint (if POS_API_BASE_URL is configured):
GET {POS_API_BASE_URL}/pos-api/v1/updates/check/{{target}}/{{arch}}/{{current_version}}
The fallback only runs when the primary check throws a network error — not when it succeeds with no update available.

Android update endpoint

Android uses the Odoo endpoint directly (the Tauri updater plugin does not support mobile):
GET /pos-api/v1/updates/check/android/aarch64/<current_version>
This endpoint is public (no JWT auth) because the client may not have valid tokens yet at launch.
ResponseMeaning
200 with JSON bodyUpdate available
204 No ContentNo update available

Release workflow

Publishing a new version

  1. Bump version.json and run npm run version:sync
  2. Commit and tag: git tag v1.1.0 && git push origin main --tags
  3. CI builds, signs, and creates a draft GitHub Release
  4. Review the draft on GitHub, edit release notes, then Publish
  5. The Worker picks up the new release within 5 minutes (cache TTL)
  6. Existing installations detect the update on next launch
See AUTO_UPDATE_GUIDE.md for the full step-by-step release process and checklist.

CI configuration

The release.yml workflow:
  • Validates (Ubuntu): lint, typecheck, unit tests
  • Builds (Windows): Tauri NSIS installer with tauri-apps/tauri-action
  • Signs with TAURI_SIGNING_PRIVATE_KEY from GitHub secrets
  • Uploads installer + signature to GitHub Release
A separate cache-warm.yml workflow runs cargo check on main branch pushes to warm the Rust build cache for faster release builds.

Required GitHub secrets

SecretPurpose
TAURI_SIGNING_PRIVATE_KEYEd25519 private key for signing update artifacts
TAURI_SIGNING_PRIVATE_KEY_PASSWORDPassword for the signing key

Required Cloudflare secrets

SecretSet viaPurpose
GITHUB_PATwrangler secret put GITHUB_PATFine-grained GitHub PAT with Contents: read on the repo

Signing

Tauri requires Ed25519 signatures to verify update artifacts. Generate a signing keypair:
cd nu_pos_react && npx @tauri-apps/cli signer generate -w ~/.tauri/nupos.key
The public key is embedded in tauri.conf.json under plugins.updater.pubkey. The private key is stored as a GitHub secret for CI builds.
Never commit the private key (~/.tauri/nupos.key) to the repository. Only the public key in tauri.conf.json is safe to commit.
The public key in tauri.conf.json must match the private key in the TAURI_SIGNING_PRIVATE_KEY GitHub secret. If you regenerate keys, update both — a mismatch causes “The signature was created with a different key than the one provided” errors on the client.
Set signing secrets via CLI (gh secret set TAURI_SIGNING_PRIVATE_KEY < ~/.tauri/nupos.key), not the GitHub web UI. The web UI can corrupt base64 values during copy-paste.

Updater artifacts

tauri.conf.json has "createUpdaterArtifacts": true, which tells the bundler to generate signed .exe installers with companion .sig signature files during CI builds.

Worker proxy

Source code

The Worker lives in workers/update-proxy/ in the repo:
workers/update-proxy/
├── wrangler.toml       # Worker config (name, repo var)
├── package.json        # Dependencies (wrangler, types)
├── tsconfig.json       # TypeScript config
└── src/
    └── index.ts        # Worker implementation

Endpoints

PathMethodResponse
/update/checkGETTauri-format JSON with version, platforms, pre-signed URLs, and signatures
/healthGET{"ok": true}

Caching

The Worker returns Cache-Control: public, max-age=300 (5 minutes). After publishing a new release, it takes up to 5 minutes for the Worker to serve the new version.

Deploying changes

cd workers/update-proxy
wrangler deploy

Frontend components

UpdateGate

src/features/updates/UpdateGate.tsx wraps the entire app outside AuthProvider. It:
  • Detects the platform via getPlatformInfo() from runtime config flags (IS_DESKTOP, IS_ANDROID)
  • On web: passes through immediately (no update check)
  • On desktop: tries the Tauri updater plugin first (Worker proxy endpoint), falls back to the Odoo endpoint only if the plugin check fails
  • On Android: calls the Odoo endpoint directly, then prompts to download
  • On error: shows “Continue Anyway” (the min_client_version system parameter acts as a backstop)

UpdateScreen

src/features/updates/UpdateScreen.tsx is a full-screen blocking component with i18n support (updateScreen namespace in en.json/es.json):
PhaseWhat the user sees
checkingSpinner with “Checking for updates…”
availableVersion info + “Install Update” + “Later” buttons
downloadingProgress bar with percentage
installingSpinner with “Installing…” + restart message
android-promptVersion info + “Download Update” button
errorError message + “Retry” and “Continue Anyway” buttons
The “Later” button defers the update — the overlay will return on next app launch. During download/install, the overlay cannot be dismissed.

Platform detection

src/lib/updater.ts exports getPlatformInfo() which reads window.__POS_RUNTIME_CONFIG__:
FlagSet byPurpose
IS_TAURIlib.rsApp is running in Tauri shell
IS_DESKTOPlib.rsDesktop platform (macOS, Windows, Linux)
IS_ANDROIDlib.rsAndroid platform

Testing

Manual testing checklist

1

Worker endpoint

Verify the Worker returns valid JSON:
curl -s https://pos-update-proxy.jespinal.workers.dev/update/check | python3 -m json.tool
2

Desktop

Run npm run desktop:dev. Verify the update check runs on launch. If a newer version is published, the overlay should appear.
3

Local test server

Build with version 0.0.1, create a fake update JSON for 0.0.2, serve with npx serve, and temporarily point the endpoint to localhost. See AUTO_UPDATE_GUIDE.md for details.
4

Android

Run npm run tauri android dev. Verify the “Download Update” prompt appears and opens the URL in the browser.
5

Web

Run npm run dev and verify the app loads normally with no update screen.
6

Offline

Disconnect from network. Verify the error screen appears with “Continue Anyway”, and tapping it proceeds to the app.

Unit tests

cd nu_pos_react && npm run test:run -- src/lib/__tests__/updater.test.ts
Covers semver parsing, version comparison, and platform detection.

Error handling

The update system is designed to be non-blocking on failure:
  • Network errors during the update check show an error screen with “Continue Anyway”
  • The existing min_client_version system parameter in Odoo acts as a backstop — clients below that version get a CLIENT_OUTDATED error after login
  • Signature verification failures on desktop prevent installation (security requirement)
  • Android download failures are handled by the system browser, not the app

File map

FilePurpose
workers/update-proxy/src/index.tsCloudflare Worker: proxies GitHub API, returns Tauri-format JSON
workers/update-proxy/wrangler.tomlWorker config (name, repo, GITHUB_PAT secret)
.github/workflows/release.ymlCI: build, sign, create GitHub Release
.github/workflows/cache-warm.ymlCI: warm Rust build cache on main pushes
nu_pos_react/src-tauri/tauri.conf.jsonUpdater endpoint URL (Worker), pubkey, createUpdaterArtifacts
nu_pos_react/src-tauri/src/lib.rsPlugin registration, fallback check_for_update command
nu_pos_react/src-tauri/capabilities/default.jsonPlugin permissions
nu_pos_react/src/lib/updater.tsPlatform detection, version comparison, update checks
nu_pos_react/src/features/updates/UpdateGate.tsxApp-level update gate wrapper
nu_pos_react/src/features/updates/UpdateScreen.tsxFull-screen update UI (i18n)
nu_pos_react/src/i18n/locales/{en,es}.jsonupdateScreen namespace translations
nu_restaurant_pos/models/app_release.pynu.pos.app.release model (Android fallback)
nu_restaurant_pos/controllers/api_v1.pyOdoo update check endpoint (Android + desktop fallback)