Auto-Update & CI

Kalam uses Tauri v2's built-in updater (tauri-plugin-updater) for automatic updates.

Architecture

GitHub Releases latest.json  →  updater plugin  →  useUpdateChecker hook  →  UpdateBanner component

The latest.json is auto-generated by tauri-apps/tauri-action@v0 during CI (via includeUpdaterJson: true) and uploaded as a release asset — no manual manifest management needed.

Signing Keys (One-Time Setup)

Generate an Ed25519 keypair for update verification:

npm run tauri signer generate -- -w $HOME\.tauri\kalam.key

Creates:

  • $HOME\.tauri\kalam.keyPRIVATE KEY. Store in GitHub Secrets. Never commit.
  • $HOME\.tauri\kalam.key.pubPUBLIC KEY. Embedded in tauri.conf.json plugins.updater.pubkey.

Building with Signing (Local)

$env:TAURI_SIGNING_PRIVATE_KEY = "C:\Users\You\.tauri\kalam.key"
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "your-password"
npm run tauri build -- --bundles msi
.env files do NOT work — you must use real environment variables.

GitHub Secrets Needed

SecretValue
TAURI_SIGNING_PRIVATE_KEYBase64-encoded content of kalam.key (use [Convert]::ToBase64String(...)) — avoids YAML newline mangling
TAURI_SIGNING_PRIVATE_KEY_PASSWORDPassword chosen during key generation
GITHUB_TOKENAuto-provided by GitHub Actions

CI Signing Fix (Important)

Problem: GitHub YAML strips newlines when expanding ${{ secrets.X }} directly in the env: block. The PEM key gets truncated to just the first line.

Fix: Store the key as base64, decode to a temp file in a workflow step:

- name: Decode signing key
  shell: pwsh
  run: |
    $bytes = [Convert]::FromBase64String("${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}")
    $path = Join-Path $env:RUNNER_TEMP "kalam.key"
    [IO.File]::WriteAllBytes($path, $bytes)
    echo "TAURI_SIGNING_PRIVATE_KEY=$path" >> $env:GITHUB_ENV

Also add these fallbacks for a known Tauri bug:

TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}

Testing the Update UI Locally

  1. Create a latest.json and serve it locally: python -m http.server 8765
  2. Temporarily change the endpoint in tauri.conf.json to http://localhost:8765/latest.json
  3. Bump the version in latest.json above the local tauri.conf.json version
  4. Run npm run tauri dev — the UpdateBanner should appear
HTTP is only allowed in dev mode. Release builds require HTTPS.

Release Workflow

Triggers: workflow_dispatch (manual version input) or push tag v*.

Steps:

  1. Extract version from tag / manual input
  2. Write version into tauri.conf.json
  3. Decode base64 signing key to temp file
  4. Rust dependency cache (Swatinem/rust-cache@v2) — cuts compile from ~20 min to ~2-3 min
  5. Single tauri-action step — builds, creates draft GitHub Release, uploads MSI + .sig, generates latest.json

Release Process

  1. Update version in app/src-tauri/tauri.conf.json and app/package.json
  2. Commit: git commit -m "v0.2.0"
  3. Tag and push: git tag v0.2.0 && git push origin v0.2.0
  4. GitHub Actions handles the rest
  5. Manually publish the draft on GitHub Releases page

Common Pitfalls

IssueCause / Fix
No .sig filesTAURI_SIGNING_PRIVATE_KEY not set during build, or createUpdaterArtifacts missing
Signature rejectedPublic key in config doesn't match the private key used to sign
Update not detectedEndpoint must match https://github.com/Comp-Tech-Guy/Kalam/releases/latest/download/latest.json
Lost private keyAll existing installs permanently unable to receive updates. Store in password manager + GitHub secrets.
Private repoThe endpoint URL is unauthenticated — make repo public or host latest.json elsewhere
CI "Missing comment in secret key"Key newlines lost in YAML env: block. Use base64+temp file workaround.

Tauri Configuration

{
  "bundle": { "createUpdaterArtifacts": true },
  "plugins": {
    "updater": {
      "active": true,
      "endpoints": ["https://github.com/Comp-Tech-Guy/Kalam/releases/latest/download/latest.json"],
      "dialog": false,
      "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZFM0FGNkNBM0QzRTg3NkUKUldSdWh6NDl5dlk2L3BmanBHRGNLa3I2N2tKQUUvam1TWWZiYitOUllBVzFSWmVGL2QrRE93b2QK"
    }
  }
}