Claude Skill

desktop-packaging-tauri

Tauri 2.x bundling, code signing, auto-updater, platform installers, CI/CD

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_desktop-packaging-tauri_skills_desktop-packaging-tauri-3a51ef5.zip · 21 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/desktop-packaging-tauri/skills/desktop-packaging-tauri
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Tauri 2.x Bundling & Distribution

Quick Guide: Configure bundling in tauri.conf.json under bundle. Platform targets: NSIS/MSI (Windows), DMG/app bundle (macOS), deb/rpm/AppImage (Linux). Code signing is required for macOS distribution (Apple notarization) and recommended for Windows (SmartScreen). The auto-updater uses Ed25519 (Minisign) signatures -- generate keys with cargo tauri signer generate, set TAURI_SIGNING_PRIVATE_KEY at build time. Cross-platform CI uses tauri-apps/tauri-action with a matrix strategy. Optimize binary size with [profile.release] settings in Cargo.toml.

Current version: Tauri 2.x (stable). Updater artifacts use createUpdaterArtifacts: true (not the v1 "v1Compatible" unless migrating).


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST set TAURI_SIGNING_PRIVATE_KEY as an environment variable at build time for updater artifacts -- never commit the private key)

(You MUST configure code signing for macOS distribution -- unsigned apps are blocked by Gatekeeper)

(You MUST use bundle.identifier as a valid reverse-domain string -- it is used for code signing, app data paths, and store submissions)

(You MUST build platform-specific installers on their native OS -- cross-compilation is limited to NSIS via cargo-xwin)

(You MUST set createUpdaterArtifacts: true in bundle to generate .sig files alongside installers)

</critical_requirements>


Auto-detection: tauri.conf.json bundle, cargo tauri build, bundle targets, NSIS, MSI, DMG, AppImage, deb, rpm, code signing, notarization, APPLE_SIGNING_IDENTITY, certificateThumbprint, tauri-plugin-updater, createUpdaterArtifacts, TAURI_SIGNING_PRIVATE_KEY, sidecar, externalBin, tauri-action, cargo tauri signer, Minisign, installer hooks

When to use:

  • Configuring tauri.conf.json bundle settings (targets, icons, resources, identifier)
  • Building platform-specific installers (NSIS, MSI, DMG, deb, rpm, AppImage)
  • Setting up macOS code signing and Apple notarization
  • Setting up Windows code signing (OV/EV certificates, Azure Trusted Signing)
  • Configuring the auto-updater plugin with Ed25519 signature verification
  • Optimizing Tauri app binary size (Rust release profile, frontend bundle)
  • Bundling sidecar binaries or extra resources
  • Creating GitHub Actions CI/CD for cross-platform builds
  • Customizing NSIS installers with hooks or templates

When NOT to use:

  • Tauri command/IPC bridge, permissions, plugins, window management (use the Tauri framework skill)
  • Frontend framework or build tool configuration (separate skills)
  • General Rust programming or Cargo configuration not specific to Tauri bundling
  • Mobile distribution to App Store / Google Play (different workflow)

Key patterns covered:

Detailed resources:




<decision_framework>

Decision Framework

Which Installer Format?

Target platform?
|-- Windows
|   +-- Need MSI for enterprise deployment? -> msi (WiX, Windows-only build)
|   +-- General distribution? -> nsis (recommended, cross-compilable)
|-- macOS
|   +-- App Store? -> app bundle + App Store signing
|   +-- Direct download? -> dmg + Developer ID + notarization
|-- Linux
|   +-- Targeting Debian/Ubuntu? -> deb
|   +-- Targeting Fedora/RHEL? -> rpm
|   +-- Maximum portability? -> appimage (larger, ~70+ MB)
|   +-- Sandboxed distribution? -> snap or flatpak (manual setup)
+-- All platforms? -> Use "all" target with CI matrix

Code Signing Decision

Distributing publicly?
|-- macOS
|   +-- App Store? -> Apple Distribution certificate
|   +-- Direct download? -> Developer ID Application + notarization (REQUIRED)
|   +-- Internal/testing only? -> Ad-hoc signing (signingIdentity: "-")
|-- Windows
|   +-- Microsoft Store? -> Store signing
|   +-- Direct download? -> OV or EV certificate (prevents SmartScreen warnings)
|   +-- Internal only? -> Optional but recommended
+-- Linux
    +-- Code signing is not required for Linux distribution

Updater Strategy

Need auto-updates?
|-- YES -> tauri-plugin-updater
|   +-- Simple static hosting? -> Static JSON endpoint (GitHub Releases, S3)
|   +-- Dynamic update logic? -> Dynamic endpoint (returns 200/204)
|   +-- Need update UI? -> JS-side check() + downloadAndInstall()
|   +-- Background updates? -> Rust-side updater with AppHandle
+-- NO -> Skip updater config, omit createUpdaterArtifacts

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Committing TAURI_SIGNING_PRIVATE_KEY to source control -- store as CI secret, never in repo
  • Missing createUpdaterArtifacts: true when using the updater -- no .sig files generated, updates fail
  • Building macOS installer on Linux/Windows -- cross-compilation not supported for DMG/app bundle
  • Using "targets": "all" in CI without a matrix strategy -- builds all formats for the current OS only
  • Missing bundle.identifier or using an invalid format -- breaks code signing, app data paths, and store submissions
  • Distributing unsigned macOS app -- Gatekeeper blocks it, users cannot open it

Medium Priority Issues:

  • AppImage on Ubuntu 22.04+ targeting older distros -- higher glibc requirement breaks compatibility
  • Missing strip = true in release profile -- debug symbols inflate binary by 10-20%
  • Using opt-level = 3 instead of "s" or "z" when binary size matters -- optimizes for speed, not size
  • WebView2 skip install mode without guarantee runtime is present -- app crashes on startup
  • NSIS perUser mode when app needs system-wide installation -- installs to %LOCALAPPDATA%, not Program Files

Gotchas & Edge Cases:

  • opt-level = "s" vs "z" -- sometimes "z" produces smaller binaries, sometimes "s" does. Test both.
  • macOS ad-hoc signing (signingIdentity: "-") still triggers Gatekeeper warnings -- only useful for development
  • NSIS is the only format supporting cross-compilation from Linux/macOS to Windows (via cargo-xwin)
  • Sidecar binary filenames must include the Rust target triple suffix -- Tauri resolves the correct one at runtime
  • Updater endpoint template variables ({{target}}, {{arch}}, {{current_version}}) are Tauri-specific, not environment variables
  • AppImage bundles are ~70+ MB because they include all dependencies -- deb/rpm are 2-6 MB but require system packages
  • Windows WebView2 runtime is bundled by default with embedBootstrapper -- older downloadBootstrapper mode requires internet at install time
  • Snap/Flatpak packages run in a sandbox -- DBus communication is blocked unless declared in the manifest
  • removeUnusedCommands: true (Tauri 2.4+) strips commands not in capability files -- ensure all needed commands are listed in ACL
  • Free Apple Developer accounts cannot notarize apps -- a paid $99/year account is required for distribution

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST set TAURI_SIGNING_PRIVATE_KEY as an environment variable at build time for updater artifacts -- never commit the private key)

(You MUST configure code signing for macOS distribution -- unsigned apps are blocked by Gatekeeper)

(You MUST use bundle.identifier as a valid reverse-domain string -- it is used for code signing, app data paths, and store submissions)

(You MUST build platform-specific installers on their native OS -- cross-compilation is limited to NSIS via cargo-xwin)

(You MUST set createUpdaterArtifacts: true in bundle to generate .sig files alongside installers)

Failure to follow these rules will produce unsigned binaries, missing update signatures, or broken cross-platform builds.

</critical_reminders>

Files (skills)
  • examples
    • ci-cd.md 7.7 KB
      # Tauri Bundling - CI/CD
      
      > GitHub Actions cross-platform build workflow, matrix strategy, code signing in CI, updater artifact publishing. See [SKILL.md](../SKILL.md) for decision frameworks. See [code-signing.md](code-signing.md) for signing details.
      
      ---
      
      ## GitHub Actions Workflow
      
      ### Complete Cross-Platform Build
      
      ```yaml
      name: "Build & Release"
      
      on:
        workflow_dispatch:
        push:
          branches:
            - release
      
      jobs:
        publish-tauri:
          permissions:
            contents: write
          strategy:
            fail-fast: false
            matrix:
              include:
                - platform: "macos-latest"
                  args: "--target aarch64-apple-darwin"
                - platform: "macos-latest"
                  args: "--target x86_64-apple-darwin"
                - platform: "ubuntu-22.04"
                  args: ""
                - platform: "ubuntu-22.04-arm"
                  args: ""
                - platform: "windows-latest"
                  args: ""
      
          runs-on: ${{ matrix.platform }}
      
          steps:
            - uses: actions/checkout@v4
      
            # Install Linux dependencies
            - name: Install Linux dependencies
              if: runner.os == 'Linux'
              run: |
                sudo apt-get update
                sudo apt-get install -y \
                  libwebkit2gtk-4.1-dev \
                  libappindicator3-dev \
                  librsvg2-dev \
                  patchelf
      
            # Setup Node.js
            - name: Setup Node.js
              uses: actions/setup-node@v4
              with:
                node-version: lts/*
                cache: "npm"
      
            # Setup Rust
            - name: Setup Rust
              uses: dtolnay/rust-toolchain@stable
              with:
                targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
      
            # Cache Rust build artifacts
            - name: Cache Rust
              uses: swatinem/rust-cache@v2
      
            # Install frontend dependencies
            - name: Install dependencies
              run: npm ci
      
            # Import Windows certificate
            - name: Import Windows certificate
              if: matrix.platform == 'windows-latest'
              env:
                WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
                WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
              run: |
                $bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)
                [IO.File]::WriteAllBytes("cert.pfx", $bytes)
                Import-PfxCertificate -FilePath cert.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force)
                Remove-Item cert.pfx
      
            # Build and release
            - name: Build Tauri app
              uses: tauri-apps/tauri-action@v0
              env:
                GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                # macOS signing
                APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
                APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
                APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
                APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
                APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
                APPLE_API_KEY_PATH: ${{ secrets.APPLE_API_KEY_PATH }}
                # Updater signing
                TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
                TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
              with:
                tagName: app-v__VERSION__
                releaseName: "App v__VERSION__"
                releaseBody: "See the assets to download and install this version."
                releaseDraft: true
                prerelease: false
                args: ${{ matrix.args }}
      ```
      
      ### Key Workflow Details
      
      **Matrix strategy:**
      
      - `fail-fast: false` -- one platform failure does not cancel other builds
      - macOS builds target both Intel and Apple Silicon separately
      - Ubuntu 22.04 for Linux (22.04-arm for ARM64 builds)
      - `tauri-action` handles the build, bundling, and release upload
      
      **Platform dependencies:**
      
      - Linux requires `libwebkit2gtk-4.1-dev`, `libappindicator3-dev`, `librsvg2-dev`, `patchelf`
      - macOS and Windows have no additional system dependencies
      
      **Secrets required:**
      
      | Secret                               | Platform | Purpose                      |
      | ------------------------------------ | -------- | ---------------------------- |
      | `APPLE_CERTIFICATE`                  | macOS    | Base64-encoded `.p12`        |
      | `APPLE_CERTIFICATE_PASSWORD`         | macOS    | `.p12` password              |
      | `APPLE_SIGNING_IDENTITY`             | macOS    | Signing identity string      |
      | `APPLE_API_ISSUER`                   | macOS    | Notarization API issuer      |
      | `APPLE_API_KEY`                      | macOS    | Notarization API key ID      |
      | `APPLE_API_KEY_PATH`                 | macOS    | Path to `.p8` key file       |
      | `WINDOWS_CERTIFICATE`                | Windows  | Base64-encoded `.pfx`        |
      | `WINDOWS_CERTIFICATE_PASSWORD`       | Windows  | `.pfx` password              |
      | `TAURI_SIGNING_PRIVATE_KEY`          | All      | Ed25519 key for updater sigs |
      | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | All      | Key password (optional)      |
      
      ---
      
      ## Tauri Action Configuration
      
      ### Common Options
      
      ```yaml
      - uses: tauri-apps/tauri-action@v0
        with:
          # Release settings
          tagName: app-v__VERSION__ # __VERSION__ is replaced with app version
          releaseName: "App v__VERSION__"
          releaseBody: "Release notes here"
          releaseDraft: true # Create as draft (review before publishing)
          prerelease: false
      
          # Build settings
          args: "--target aarch64-apple-darwin" # Rust target
          tauriScript: "npx tauri" # Custom tauri command
      
          # Updater
          updaterJsonPreferNsis: true # Prefer NSIS over MSI for Windows
      ```
      
      ### macOS Universal Binary
      
      To create a universal binary (Intel + Apple Silicon in one file), build both architectures and combine:
      
      ```yaml
      matrix:
        include:
          - platform: "macos-latest"
            args: "--target universal-apple-darwin"
      ```
      
      Or build separately and use `lipo`:
      
      ```yaml
      - platform: "macos-latest"
        args: "--target aarch64-apple-darwin"
      - platform: "macos-latest"
        args: "--target x86_64-apple-darwin"
      ```
      
      ---
      
      ## Separated Build and Bundle
      
      For complex pipelines, split the build and bundle steps:
      
      ```yaml
      # Step 1: Build without bundling
      - name: Build binary
        run: npx tauri build --no-bundle
      
      # Step 2: Bundle separately (can be a different job)
      - name: Bundle
        run: npx tauri bundle
      ```
      
      This is useful when you need to run additional steps between compilation and packaging (e.g., signing, testing the binary).
      
      ---
      
      ## Caching Strategy
      
      ```yaml
      # Rust build cache (significant speedup)
      - uses: swatinem/rust-cache@v2
      
      # Node.js dependency cache
      - uses: actions/setup-node@v4
        with:
          cache: "npm"
      ```
      
      **Impact:** Rust caching reduces subsequent build times from 10-30 minutes to 2-5 minutes. Node.js caching saves 30-60 seconds on dependency installation.
      
      ---
      
      ## Common CI Pitfalls
      
      - **macOS notarization timeout** -- Notarization can take 5-60 minutes. Ensure your CI job timeout is sufficient (default GitHub timeout is 6 hours).
      - **ARM Linux builds** -- Use `ubuntu-22.04-arm` runners (available for public repos). Previous workarounds with emulation are no longer needed.
      - **Missing Linux dependencies** -- `libwebkit2gtk-4.1-dev` is the critical one. Missing it produces cryptic Rust compile errors.
      - **Certificate not found in Windows CI** -- The certificate import step must run before `tauri-action`. The PowerShell script imports into `Cert:\CurrentUser\My`.
      - **`GITHUB_TOKEN` permissions** -- The job needs `contents: write` permission to create releases.
      - **Version mismatch** -- `__VERSION__` in `tagName` is replaced with the version from `tauri.conf.json`. Ensure `tauri.conf.json` version matches your intended release.
      
      ---
      
      See [code-signing.md](code-signing.md) for detailed signing setup and [updater.md](updater.md) for auto-updater configuration.
      
    • code-signing.md 6.2 KB
      # Tauri Bundling - Code Signing
      
      > macOS code signing and notarization, Windows code signing, CI/CD signing setup. See [SKILL.md](../SKILL.md) for decision frameworks. See [reference.md](../reference.md) for environment variable reference.
      
      ---
      
      ## macOS Code Signing
      
      ### Certificate Types
      
      | Certificate                     | Distribution Method   | Notarization Required |
      | ------------------------------- | --------------------- | --------------------- |
      | Apple Distribution              | App Store             | No (Apple handles it) |
      | Developer ID Application        | Direct download (DMG) | Yes                   |
      | Ad-hoc (`signingIdentity: "-"`) | Development/testing   | No (Gatekeeper warns) |
      
      ### Local Setup
      
      1. Create a Certificate Signing Request from Keychain Access
      2. Upload CSR to Apple Developer > Certificates, IDs & Profiles
      3. Download and install the `.cer` file
      4. Find your signing identity:
      
      ```sh
      security find-identity -v -p codesigning
      ```
      
      5. Configure in `tauri.conf.json`:
      
      ```json
      {
        "bundle": {
          "macOS": {
            "signingIdentity": "Developer ID Application: My Company (TEAMID)"
          }
        }
      }
      ```
      
      Or set the `APPLE_SIGNING_IDENTITY` environment variable.
      
      ### Notarization Setup
      
      Required when using Developer ID Application certificates (apps distributed outside the App Store).
      
      **App Store Connect API (recommended for CI):**
      
      ```sh
      export APPLE_API_ISSUER="issuer-uuid-from-app-store-connect"
      export APPLE_API_KEY="key-id"
      export APPLE_API_KEY_PATH="/path/to/AuthKey_KEYID.p8"
      ```
      
      **Apple ID method (alternative):**
      
      ```sh
      export APPLE_ID="developer@example.com"
      export APPLE_PASSWORD="app-specific-password"
      export APPLE_TEAM_ID="TEAMID"
      ```
      
      **Key points:**
      
      - Generate an app-specific password at appleid.apple.com (not your Apple ID password)
      - Team ID is found in Apple Developer > Membership
      - App Store Connect API is preferred because it avoids 2FA issues in CI
      
      ### CI/CD Setup for macOS
      
      Export your certificate as base64 for use in CI secrets:
      
      ```sh
      # Export certificate from Keychain as .p12
      # Then encode as base64
      openssl base64 -A -in certificate.p12 -out certificate-base64.txt
      ```
      
      Required CI secrets:
      
      ```yaml
      # GitHub Actions secrets
      APPLE_CERTIFICATE: <base64-encoded .p12 file>
      APPLE_CERTIFICATE_PASSWORD: <password for .p12>
      APPLE_SIGNING_IDENTITY: "Developer ID Application: My Company (TEAMID)"
      APPLE_API_ISSUER: <App Store Connect API issuer>
      APPLE_API_KEY: <API key ID>
      APPLE_API_KEY_PATH: <path to .p8 key file>
      ```
      
      The certificate is imported into a temporary keychain during the build. The `tauri-action` handles this automatically when the environment variables are set.
      
      ---
      
      ## Windows Code Signing
      
      ### Certificate Types
      
      | Certificate Type            | SmartScreen Behavior             | Availability       |
      | --------------------------- | -------------------------------- | ------------------ |
      | EV (Extended Validation)    | Immediate reputation             | More expensive     |
      | OV (Organization Validated) | Builds reputation over time      | Pre-June 2023 only |
      | Azure Trusted Signing       | Immediate reputation             | Azure subscription |
      | None                        | SmartScreen warning to all users | Free               |
      
      ### Method 1: Certificate Thumbprint (Local/CI)
      
      1. Obtain a code signing certificate (not SSL) from a trusted CA
      2. Convert to `.pfx` if needed:
      
      ```sh
      openssl pkcs12 -export -in cert.cer -inkey private.key -out cert.pfx
      ```
      
      3. Import into Windows certificate store and find the thumbprint via `certmgr.msc`
      4. Configure in `tauri.conf.json`:
      
      ```json
      {
        "bundle": {
          "windows": {
            "certificateThumbprint": "A1B2C3D4E5F6...",
            "digestAlgorithm": "sha256",
            "timestampUrl": "http://timestamp.comodoca.com"
          }
        }
      }
      ```
      
      ### Method 2: Custom Sign Command (Azure, relic)
      
      For Azure Key Vault, Azure Trusted Signing, or other signing tools, use `signCommand`:
      
      ```json
      {
        "bundle": {
          "windows": {
            "signCommand": "trusted-signing-cli -e https://wus2.codesigning.azure.net -a MyAccount -c MyProfile -d \"My App\" %1"
          }
        }
      }
      ```
      
      The `%1` placeholder is replaced with the file path to sign.
      
      **Azure Trusted Signing setup:**
      
      ```sh
      # Install the CLI tool
      cargo install trusted-signing-cli
      
      # Set Azure credentials
      export AZURE_CLIENT_ID="..."
      export AZURE_CLIENT_SECRET="..."
      export AZURE_TENANT_ID="..."
      ```
      
      ### CI/CD Setup for Windows
      
      ```yaml
      # GitHub Actions -- import certificate before build
      - name: Import Windows certificate
        if: matrix.platform == 'windows-latest'
        env:
          WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
          WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
        run: |
          $bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)
          [IO.File]::WriteAllBytes("cert.pfx", $bytes)
          Import-PfxCertificate -FilePath cert.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force)
          Remove-Item cert.pfx
      ```
      
      **Key points:**
      
      - The certificate must be in the Windows certificate store before building
      - `timestampUrl` ensures the signature remains valid after the certificate expires
      - EV certificates provide immediate SmartScreen trust; OV certificates build reputation over time
      
      ---
      
      ## Common Signing Pitfalls
      
      - **macOS: "certificate not found"** -- The signing identity string must match exactly. Run `security find-identity -v -p codesigning` to verify.
      - **macOS: notarization stuck** -- Notarization can take 5-60 minutes. The Tauri CLI waits automatically but CI may time out.
      - **macOS: free account** -- Free Apple Developer accounts cannot notarize. A paid $99/year account is required.
      - **macOS: private key lost** -- The private key download from Apple is only available once. Export from Keychain before it is lost.
      - **Windows: "certificate not trusted"** -- The certificate must be from a CA trusted by Windows. Self-signed certificates will not work.
      - **Windows: SmartScreen warning** -- New OV certificates have no reputation. Microsoft offers a manual review process, or use an EV certificate.
      - **Timestamping:** Always configure a timestamp URL. Without it, signatures become invalid when the certificate expires.
      
      ---
      
      See [core.md](core.md) for bundle configuration and [ci-cd.md](ci-cd.md) for the full GitHub Actions workflow.
      
    • core.md 9.3 KB
      # Tauri Bundling - Core Patterns
      
      > Bundle configuration, platform-specific settings, size optimization, sidecars, and NSIS customization. See [SKILL.md](../SKILL.md) for decision frameworks. See [reference.md](../reference.md) for config field reference.
      
      ---
      
      ## Bundle Configuration
      
      ### Complete tauri.conf.json Bundle Section
      
      ```json
      {
        "productName": "My App",
        "version": "1.0.0",
        "identifier": "com.mycompany.myapp",
        "build": {
          "devUrl": "http://localhost:5173",
          "frontendDist": "../dist"
        },
        "bundle": {
          "active": true,
          "targets": "all",
          "icon": [
            "icons/32x32.png",
            "icons/128x128.png",
            "icons/128x128@2x.png",
            "icons/icon.icns",
            "icons/icon.ico"
          ],
          "resources": {
            "locales/*": "locales/",
            "data/defaults.json": "data/"
          },
          "createUpdaterArtifacts": true,
          "windows": {
            "certificateThumbprint": null,
            "digestAlgorithm": "sha256",
            "timestampUrl": "http://timestamp.digicert.com",
            "webviewInstallMode": {
              "type": "embedBootstrapper"
            },
            "nsis": {
              "installMode": "both",
              "displayLanguageSelector": true
            }
          },
          "macOS": {
            "signingIdentity": null,
            "entitlements": null,
            "minimumSystemVersion": "10.13",
            "frameworks": []
          },
          "linux": {
            "deb": {
              "depends": ["libwebkit2gtk-4.1-0"],
              "section": "utils"
            },
            "appimage": {
              "bundleMediaFramework": false
            }
          }
        }
      }
      ```
      
      **Key points:**
      
      - `identifier` must be unique reverse-domain -- used for app data paths, code signing, and store submissions
      - Generate icons from a single 1024x1024 PNG: `cargo tauri icon path/to/icon.png`
      - `createUpdaterArtifacts: true` produces `.sig` signature files alongside each installer
      - `resources` bundles extra files accessible at runtime via Tauri path resolver
      - Set `certificateThumbprint` and `signingIdentity` to `null` during development, configure for release via env vars or CI
      
      ---
      
      ## Platform-Specific Targets
      
      ### Building for Specific Formats
      
      ```sh
      # Build all formats for current platform
      cargo tauri build
      
      # Build specific format(s)
      cargo tauri build --bundles nsis
      cargo tauri build --bundles deb,appimage
      
      # Debug build (faster compile, includes debug symbols)
      cargo tauri build --debug
      
      # Cross-compile NSIS installer from Linux/macOS
      cargo tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
      
      # Build for specific architecture
      cargo tauri build --target aarch64-apple-darwin
      cargo tauri build --target x86_64-apple-darwin
      ```
      
      ### Targeting Specific Formats in Config
      
      ```json
      {
        "bundle": {
          "targets": ["nsis", "msi"]
        }
      }
      ```
      
      **Trade-offs by format:**
      
      - **NSIS** (Windows): Recommended. Supports `perUser`/`perMachine`/`both` modes, custom hooks, language selector. Cross-compilable via `cargo-xwin`.
      - **MSI** (Windows): WiX-based, Windows-only build. Better for enterprise deployment with Group Policy.
      - **DMG** (macOS): Standard drag-to-install disk image. Requires code signing + notarization for distribution.
      - **deb** (Linux): Small package (~2-6 MB), relies on system packages. Ideal for Debian/Ubuntu.
      - **rpm** (Linux): Small package, for Fedora/RHEL/openSUSE.
      - **AppImage** (Linux): Portable (~70+ MB), bundles all dependencies. No installation needed.
      
      ---
      
      ## Binary Size Optimization
      
      ### Rust Release Profile
      
      ```toml
      # src-tauri/Cargo.toml
      [profile.release]
      codegen-units = 1   # Better optimization (slower compile)
      lto = true           # Link-time optimization
      opt-level = "s"      # Optimize for size ("z" may also work -- test both)
      panic = "abort"      # Remove panic unwinding code
      strip = true         # Remove debug symbols
      ```
      
      **Impact:** These settings typically reduce binary size by 30-50% but increase clean build time to 10-30 minutes.
      
      ### Nightly Toolchain Additions (Optional)
      
      ```toml
      # Only with nightly Rust toolchain
      [profile.release]
      trim-paths = "all"
      ```
      
      ### Frontend Bundle Size
      
      The frontend JavaScript bundle is embedded in the binary. Minimize it:
      
      - Use a bundle analyzer to identify large dependencies
      - Tree-shake unused code (most bundlers do this by default)
      - Consider lighter alternatives for heavy dependencies
      - Lazy-load routes and heavy components
      
      ### Remove Unused Commands (Tauri 2.4+)
      
      ```json
      {
        "build": {
          "removeUnusedCommands": true
        }
      }
      ```
      
      This strips commands not listed in your capability files from the binary. Ensure all needed commands are explicitly allowed in your ACL.
      
      ---
      
      ## Sidecar Binaries
      
      ### Configuration
      
      ```json
      {
        "bundle": {
          "externalBin": ["binaries/ffmpeg"]
        }
      }
      ```
      
      ### File Naming Convention
      
      Sidecar filenames must include the Rust target triple. Tauri resolves the correct one at runtime.
      
      ```
      binaries/ffmpeg-x86_64-pc-windows-msvc.exe
      binaries/ffmpeg-x86_64-apple-darwin
      binaries/ffmpeg-x86_64-unknown-linux-gnu
      binaries/ffmpeg-aarch64-apple-darwin
      ```
      
      ### Executing a Sidecar from Rust
      
      ```rust
      use tauri_plugin_shell::ShellExt;
      
      #[tauri::command]
      async fn run_sidecar(app: tauri::AppHandle, input: String) -> Result<String, String> {
          let output = app.shell()
              .sidecar("ffmpeg")
              .map_err(|e| e.to_string())?
              .args(["-i", &input, "-o", "output.mp4"])
              .output()
              .await
              .map_err(|e| e.to_string())?;
      
          if output.status.success() {
              Ok(String::from_utf8_lossy(&output.stdout).to_string())
          } else {
              Err(String::from_utf8_lossy(&output.stderr).to_string())
          }
      }
      ```
      
      **Key points:**
      
      - The shell plugin (`tauri-plugin-shell`) is required for sidecar execution
      - Add `shell:allow-execute` or `shell:allow-spawn` permission to your capability file
      - The `externalBin` path is relative to `src-tauri/` and omits the target triple suffix
      
      ---
      
      ## NSIS Installer Customization
      
      ### Install Modes
      
      ```json
      {
        "bundle": {
          "windows": {
            "nsis": {
              "installMode": "both"
            }
          }
        }
      }
      ```
      
      - `"perUser"` (default): No admin required, installs to `%LOCALAPPDATA%`
      - `"perMachine"`: Requires admin, installs to `Program Files`
      - `"both"`: User chooses at install time, requires admin
      
      ### Installer Hooks
      
      Create a `.nsh` file with NSIS macros for the four lifecycle hooks:
      
      ```nsis
      ; hooks.nsh
      
      !macro NSIS_HOOK_PREINSTALL
        ; Runs before copying files, registry keys, and shortcuts
        DetailPrint "Preparing installation..."
      !macroend
      
      !macro NSIS_HOOK_POSTINSTALL
        ; Runs after all installation steps complete
        ; Example: register file association
        WriteRegStr HKCU "Software\Classes\.myext" "" "MyApp.Document"
        WriteRegStr HKCU "Software\Classes\MyApp.Document\shell\open\command" "" '"$INSTDIR\MyApp.exe" "%1"'
      !macroend
      
      !macro NSIS_HOOK_PREUNINSTALL
        ; Runs before removing files and registry entries
        DetailPrint "Cleaning up..."
      !macroend
      
      !macro NSIS_HOOK_POSTUNINSTALL
        ; Runs after removal operations finish
        DeleteRegKey HKCU "Software\Classes\.myext"
        DeleteRegKey HKCU "Software\Classes\MyApp.Document"
      !macroend
      ```
      
      Reference the hook file in config:
      
      ```json
      {
        "bundle": {
          "windows": {
            "nsis": {
              "installerHooks": "hooks.nsh"
            }
          }
        }
      }
      ```
      
      ### Custom NSIS Template
      
      For complete control over the installer, provide a custom `.nsi` template:
      
      ```json
      {
        "bundle": {
          "windows": {
            "nsis": {
              "template": "custom-installer.nsi"
            }
          }
        }
      }
      ```
      
      The template uses Handlebars syntax. Tauri injects variables from your `tauri.conf.json` into the template at build time.
      
      ---
      
      ## Resources and Extra Files
      
      ### Bundling Static Files
      
      ```json
      {
        "bundle": {
          "resources": {
            "assets/*": "assets/",
            "config/defaults.json": "config/",
            "models/weights.bin": "models/"
          }
        }
      }
      ```
      
      ### Accessing Resources at Runtime (Rust)
      
      ```rust
      use tauri::Manager;
      
      #[tauri::command]
      fn read_default_config(app: tauri::AppHandle) -> Result<String, String> {
          let resource_path = app.path()
              .resolve("config/defaults.json", tauri::path::BaseDirectory::Resource)
              .map_err(|e| e.to_string())?;
          std::fs::read_to_string(resource_path).map_err(|e| e.to_string())
      }
      ```
      
      **Key point:** Resource paths are resolved relative to the bundle's resource directory. In development (`cargo tauri dev`), they resolve relative to `src-tauri/`.
      
      ---
      
      ## Snap and Flatpak (Manual Setup)
      
      Snap and Flatpak are not built by `cargo tauri build`. They require manual manifest files.
      
      ### Snap Packaging
      
      Requires a `snapcraft.yaml` manifest:
      
      ```yaml
      name: my-app
      base: core22
      version: "1.0.0"
      summary: My Tauri app
      description: |
        A desktop application built with Tauri.
      grade: stable
      confinement: strict
      
      apps:
        my-app:
          command: usr/bin/my-app
          desktop: usr/share/applications/my-app.desktop
          extensions: [gnome]
      
      parts:
        my-app:
          plugin: dump
          source: src-tauri/target/release/bundle/deb/my-app_1.0.0_amd64.deb
          source-type: deb
      ```
      
      Build: `sudo snapcraft`
      
      ### Flatpak Packaging
      
      Requires a Flatpak manifest (e.g., `com.mycompany.myapp.yml`). Flatpak runs in a sandbox -- DBus communication must be explicitly declared.
      
      **Key constraints for sandboxed packages:**
      
      - Most DBus service communication is blocked by default
      - Declare `--talk-name` and `--own-name` finish args for needed services
      - Tray icon, notifications, and single-instance plugins may need additional manifest configuration
      
      ---
      
      See [code-signing.md](code-signing.md) for signing setup, [updater.md](updater.md) for auto-update configuration, and [ci-cd.md](ci-cd.md) for GitHub Actions workflows.
      
    • updater.md 8.4 KB
      # Tauri Bundling - Auto-Updater
      
      > Auto-updater plugin setup, Ed25519 key generation, endpoint format, JS and Rust usage, CI integration. See [SKILL.md](../SKILL.md) for decision frameworks. See [reference.md](../reference.md) for endpoint response format and platform target strings.
      
      ---
      
      ## Setup
      
      ### Install the Plugin
      
      ```sh
      # Using Tauri CLI (registers in Cargo.toml + lib.rs)
      cargo tauri add updater
      
      # Or manually:
      cargo add tauri-plugin-updater
      npm add @tauri-apps/plugin-updater
      ```
      
      ### Register in Rust
      
      ```rust
      // src-tauri/src/lib.rs
      #[cfg_attr(mobile, tauri::mobile_entry_point)]
      pub fn run() {
          tauri::Builder::default()
              .plugin(tauri_plugin_updater::Builder::new().build())
              .run(tauri::generate_context!())
              .expect("error while running tauri application");
      }
      ```
      
      **Note:** The updater is desktop-only. Use the `#[cfg(desktop)]` gate if your app targets both desktop and mobile:
      
      ```rust
      #[cfg(desktop)]
      app.handle().plugin(tauri_plugin_updater::Builder::new().build());
      ```
      
      ### Add Permissions
      
      ```json
      {
        "permissions": ["updater:default"]
      }
      ```
      
      Individual permissions: `updater:allow-check`, `updater:allow-download`, `updater:allow-install`, `updater:allow-download-and-install`.
      
      ---
      
      ## Key Generation
      
      Generate an Ed25519 (Minisign) key pair:
      
      ```sh
      cargo tauri signer generate -w ~/.tauri/myapp.key
      ```
      
      This produces:
      
      - **Private key** -- store securely, never commit to source control
      - **Public key** -- safe to commit, goes in `tauri.conf.json`
      
      ### Build-Time Environment Variables
      
      Set before running `cargo tauri build`:
      
      ```sh
      # Path to key file or raw key content
      export TAURI_SIGNING_PRIVATE_KEY="~/.tauri/myapp.key"
      
      # Optional password (if key is password-protected)
      export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""
      ```
      
      **CRITICAL:** Without `TAURI_SIGNING_PRIVATE_KEY`, the build will not generate `.sig` signature files even if `createUpdaterArtifacts: true` is set.
      
      ---
      
      ## Configuration
      
      ### tauri.conf.json
      
      ```json
      {
        "bundle": {
          "createUpdaterArtifacts": true
        },
        "plugins": {
          "updater": {
            "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6...",
            "endpoints": [
              "https://releases.example.com/{{target}}/{{arch}}/{{current_version}}"
            ]
          }
        }
      }
      ```
      
      **Template variables** (resolved at runtime):
      
      - `{{current_version}}` -- app version from `tauri.conf.json`
      - `{{target}}` -- `linux`, `windows`, or `darwin`
      - `{{arch}}` -- `x86_64`, `aarch64`, `i686`, `armv7`
      
      ### Windows Update Behavior
      
      ```json
      {
        "plugins": {
          "updater": {
            "windows": {
              "installMode": "passive"
            }
          }
        }
      }
      ```
      
      - `"passive"` (default): Shows progress bar, no user interaction
      - `"basicUi"`: Shows basic installer UI
      - `"quiet"`: Silent installation
      
      ---
      
      ## Build Artifacts
      
      When `createUpdaterArtifacts: true` and `TAURI_SIGNING_PRIVATE_KEY` is set:
      
      | Platform | Installer                 | Signature File                 |
      | -------- | ------------------------- | ------------------------------ |
      | Linux    | `.AppImage`               | `.AppImage.sig`                |
      | macOS    | `.app` (as `.app.tar.gz`) | `.app.tar.gz.sig`              |
      | Windows  | `-setup.exe` or `.msi`    | `-setup.exe.sig` or `.msi.sig` |
      
      ---
      
      ## JavaScript Usage
      
      ### Check and Install Updates
      
      ```typescript
      import { check } from "@tauri-apps/plugin-updater";
      import { relaunch } from "@tauri-apps/plugin-process";
      
      async function checkForUpdates(): Promise<void> {
        const update = await check();
      
        if (!update) {
          console.log("No update available");
          return;
        }
      
        console.log(`Update available: ${update.version}`);
        console.log(`Release notes: ${update.body}`);
      
        await update.downloadAndInstall((event) => {
          switch (event.event) {
            case "Started":
              console.log(
                `Downloading ${event.data.contentLength ?? "unknown"} bytes`,
              );
              break;
            case "Progress":
              console.log(`Downloaded chunk: ${event.data.chunkLength} bytes`);
              break;
            case "Finished":
              console.log("Download complete, installing...");
              break;
          }
        });
      
        // Restart the app to apply the update
        await relaunch();
      }
      ```
      
      ### Runtime Configuration (JS)
      
      ```typescript
      import { check } from "@tauri-apps/plugin-updater";
      
      const update = await check({
        proxy: "https://proxy.example.com",
        timeout: 30000,
        headers: {
          Authorization: "Bearer token",
        },
        target: "macos-universal",
      });
      ```
      
      ---
      
      ## Rust Usage
      
      ### Check and Install from Backend
      
      ```rust
      use tauri_plugin_updater::UpdaterExt;
      
      #[tauri::command]
      async fn check_for_update(app: tauri::AppHandle) -> Result<Option<String>, String> {
          let update = app.updater()
              .map_err(|e| e.to_string())?
              .check()
              .await
              .map_err(|e| e.to_string())?;
      
          match update {
              Some(update) => {
                  let version = update.version.clone();
      
                  update.download_and_install(
                      |chunk_length, content_length| {
                          println!("downloaded {chunk_length} of {content_length:?}");
                      },
                      || {
                          println!("download finished");
                      },
                  ).await.map_err(|e| e.to_string())?;
      
                  Ok(Some(version))
              }
              None => Ok(None),
          }
      }
      ```
      
      ### Runtime Configuration (Rust)
      
      ```rust
      use tauri_plugin_updater::UpdaterExt;
      
      // Custom endpoints at runtime
      let update = app.updater_builder()
          .endpoints(vec!["https://custom.endpoint.com/update/{{target}}/{{arch}}/{{current_version}}".parse().unwrap()])?
          .build()?
          .check()
          .await?;
      
      // Custom target (e.g., universal macOS binary)
      let update = app.updater_builder()
          .target("macos-universal")
          .build()?
          .check()
          .await?;
      
      // Custom version comparator (allow downgrades)
      let update = app.updater_builder()
          .version_comparator(|current, update| update.version != current)
          .build()?
          .check()
          .await?;
      ```
      
      ---
      
      ## Update Endpoint
      
      ### Static JSON (GitHub Releases, S3)
      
      Host a JSON file per platform, or a single JSON with all platforms:
      
      ```json
      {
        "version": "1.2.0",
        "notes": "Bug fixes and improvements",
        "pub_date": "2025-06-15T00:00:00Z",
        "platforms": {
          "linux-x86_64": {
            "signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
            "url": "https://github.com/myorg/myapp/releases/download/v1.2.0/my-app_1.2.0_amd64.AppImage"
          },
          "darwin-aarch64": {
            "signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
            "url": "https://github.com/myorg/myapp/releases/download/v1.2.0/my-app.app.tar.gz"
          },
          "windows-x86_64": {
            "signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
            "url": "https://github.com/myorg/myapp/releases/download/v1.2.0/my-app_1.2.0_x64-setup.exe"
          }
        }
      }
      ```
      
      Required fields: `version`, `platforms.[target].url`, `platforms.[target].signature`.
      
      ### Dynamic Endpoint
      
      A server that returns HTTP 204 (no update) or HTTP 200 with:
      
      ```json
      {
        "version": "1.2.0",
        "url": "https://cdn.example.com/my-app-setup.exe",
        "signature": "dW50cnVzdGVkIGNvbW1lbnQ6...",
        "notes": "Bug fixes",
        "pub_date": "2025-06-15T00:00:00Z"
      }
      ```
      
      Required fields: `version`, `url`, `signature`.
      
      ### Using GitHub Releases with tauri-action
      
      The `tauri-action` automatically generates the update JSON when configured:
      
      ```yaml
      - uses: tauri-apps/tauri-action@v0
        with:
          tagName: app-v__VERSION__
          releaseName: "App v__VERSION__"
          updaterJsonPreferNsis: true # Prefer NSIS over MSI for Windows updates
      ```
      
      The action creates a `latest.json` artifact compatible with the static endpoint format. Point your `endpoints` config at the release asset URL.
      
      ---
      
      ## Common Updater Pitfalls
      
      - **Missing `TAURI_SIGNING_PRIVATE_KEY`** at build time -- `.sig` files are not generated, updates will fail signature verification
      - **Lost private key** -- Users with the app already installed cannot receive updates. Generate a new key pair and distribute a fresh build.
      - **Windows: app exits during update** -- This is expected behavior on Windows. The installer takes over and restarts the app.
      - **HTTP endpoints** -- Set `dangerousInsecureTransportProtocol: true` for non-HTTPS endpoints (not recommended for production)
      - **Version format** -- Must be valid semver. The updater compares versions; non-semver strings cause parse errors.
      - **macOS update artifact** -- The updater expects `.app.tar.gz` (not `.dmg`). The build produces this automatically.
      
      ---
      
      See [core.md](core.md) for bundle configuration, [code-signing.md](code-signing.md) for signing setup, and [ci-cd.md](ci-cd.md) for the GitHub Actions workflow.
      
  • reference.md 9 KB
    # Tauri Bundling & Distribution Reference
    
    > Quick-lookup tables for bundle configuration, CLI commands, and platform targets. See [SKILL.md](SKILL.md) for decision frameworks and red flags. See [examples/](examples/) for full code examples.
    
    ---
    
    ## Build CLI Commands
    
    | Command                                          | Purpose                                     |
    | ------------------------------------------------ | ------------------------------------------- |
    | `cargo tauri build`                              | Build production binary + installers        |
    | `cargo tauri build --debug`                      | Debug build (faster compile, larger binary) |
    | `cargo tauri build --bundles nsis`               | Build specific installer format             |
    | `cargo tauri build --bundles deb,appimage`       | Build multiple formats                      |
    | `cargo tauri build --no-bundle`                  | Build binary without packaging              |
    | `cargo tauri build --target x86_64-apple-darwin` | Build for specific Rust target              |
    | `cargo tauri icon path/to/1024.png`              | Generate all icon sizes from source image   |
    | `cargo tauri signer generate -w key.key`         | Generate Ed25519 key pair for updater       |
    
    ---
    
    ## Bundle Config Field Reference
    
    ### Top-Level Bundle Fields
    
    | Field                           | Type       | Default | Purpose                                      |
    | ------------------------------- | ---------- | ------- | -------------------------------------------- |
    | `bundle.active`                 | `boolean`  | `true`  | Enable/disable bundling                      |
    | `bundle.targets`                | `string[]` | `"all"` | Installer formats to build                   |
    | `bundle.identifier`             | `string`   | --      | Reverse-domain app ID (required)             |
    | `bundle.icon`                   | `string[]` | `[]`    | Icon file paths                              |
    | `bundle.resources`              | `object`   | --      | Extra files/dirs to include in bundle        |
    | `bundle.externalBin`            | `string[]` | --      | Sidecar binary paths (without target triple) |
    | `bundle.createUpdaterArtifacts` | `boolean`  | `false` | Generate `.sig` files for updater            |
    
    ### Windows Bundle Fields
    
    | Field                                         | Purpose                                  |
    | --------------------------------------------- | ---------------------------------------- |
    | `bundle.windows.certificateThumbprint`        | Code signing certificate thumbprint      |
    | `bundle.windows.digestAlgorithm`              | Signing digest (typically `"sha256"`)    |
    | `bundle.windows.timestampUrl`                 | Timestamp server URL                     |
    | `bundle.windows.signCommand`                  | Custom sign command (Azure, relic, etc.) |
    | `bundle.windows.nsis.installMode`             | `"perUser"` / `"perMachine"` / `"both"`  |
    | `bundle.windows.nsis.installerHooks`          | Path to `.nsh` hook file                 |
    | `bundle.windows.nsis.template`                | Path to custom NSIS template             |
    | `bundle.windows.nsis.displayLanguageSelector` | Show language picker                     |
    | `bundle.windows.nsis.languages`               | NSIS language list                       |
    | `bundle.windows.nsis.minimumWebview2Version`  | Minimum WebView2 runtime version         |
    | `bundle.windows.webviewInstallMode`           | WebView2 bundling strategy               |
    
    ### macOS Bundle Fields
    
    | Field                               | Purpose                                     |
    | ----------------------------------- | ------------------------------------------- |
    | `bundle.macOS.signingIdentity`      | Code signing identity (or `"-"` for ad-hoc) |
    | `bundle.macOS.entitlements`         | Path to entitlements plist                  |
    | `bundle.macOS.frameworks`           | Native frameworks to bundle                 |
    | `bundle.macOS.minimumSystemVersion` | Minimum macOS version (default `"10.13"`)   |
    
    ### Linux Bundle Fields
    
    | Field                                        | Purpose                              |
    | -------------------------------------------- | ------------------------------------ |
    | `bundle.linux.deb.depends`                   | Debian package dependencies          |
    | `bundle.linux.deb.section`                   | Package section (e.g., `"utils"`)    |
    | `bundle.linux.appimage.bundleMediaFramework` | Include GStreamer for media playback |
    | `bundle.linux.appimage.files`                | Extra files to include in AppImage   |
    | `bundle.linux.rpm.epoch`                     | RPM epoch number                     |
    | `bundle.linux.rpm.release`                   | RPM release string                   |
    
    ---
    
    ## WebView2 Install Modes
    
    | Mode                   | Internet | Size Impact | Use Case                        |
    | ---------------------- | -------- | ----------- | ------------------------------- |
    | `downloadBootstrapper` | Yes      | +0 MB       | Default, smallest installer     |
    | `embedBootstrapper`    | Yes      | +1.8 MB     | Better Windows 7 support        |
    | `offlineInstaller`     | No       | +127 MB     | Offline/air-gapped environments |
    | `fixedVersion`         | No       | +180 MB     | Controlled runtime management   |
    | `skip`                 | No       | +0 MB       | Assumes runtime pre-installed   |
    
    ---
    
    ## NSIS Install Modes
    
    | Mode         | Admin Required | Install Path     | Use Case               |
    | ------------ | -------------- | ---------------- | ---------------------- |
    | `perUser`    | No             | `%LOCALAPPDATA%` | Default, no UAC prompt |
    | `perMachine` | Yes            | `Program Files`  | Enterprise/system-wide |
    | `both`       | Yes            | User chooses     | Flexible installer     |
    
    ---
    
    ## Code Signing Environment Variables
    
    ### macOS
    
    | Variable                     | Purpose                                |
    | ---------------------------- | -------------------------------------- |
    | `APPLE_CERTIFICATE`          | Base64-encoded `.p12` certificate      |
    | `APPLE_CERTIFICATE_PASSWORD` | Password for the `.p12` file           |
    | `APPLE_SIGNING_IDENTITY`     | Signing identity string                |
    | `APPLE_API_ISSUER`           | App Store Connect API issuer ID        |
    | `APPLE_API_KEY`              | App Store Connect API key ID           |
    | `APPLE_API_KEY_PATH`         | Path to `.p8` API key file             |
    | `APPLE_ID`                   | Apple account email (alternate auth)   |
    | `APPLE_PASSWORD`             | App-specific password (alternate auth) |
    | `APPLE_TEAM_ID`              | Apple Developer team ID                |
    
    ### Windows
    
    | Variable                       | Purpose                           |
    | ------------------------------ | --------------------------------- |
    | `WINDOWS_CERTIFICATE`          | Base64-encoded `.pfx` certificate |
    | `WINDOWS_CERTIFICATE_PASSWORD` | Password for the `.pfx` file      |
    
    ### Updater
    
    | Variable                             | Purpose                               |
    | ------------------------------------ | ------------------------------------- |
    | `TAURI_SIGNING_PRIVATE_KEY`          | Ed25519 private key (path or content) |
    | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Private key password (optional)       |
    
    ---
    
    ## Updater Endpoint Response Format
    
    ### Static JSON (GitHub Releases, S3)
    
    ```json
    {
      "version": "1.2.0",
      "notes": "Bug fixes and performance improvements",
      "pub_date": "2025-01-15T00:00:00Z",
      "platforms": {
        "linux-x86_64": {
          "signature": "MINISIGN_SIGNATURE",
          "url": "https://cdn.example.com/app_1.2.0_amd64.AppImage"
        },
        "windows-x86_64": {
          "signature": "MINISIGN_SIGNATURE",
          "url": "https://cdn.example.com/app_1.2.0_x64-setup.exe"
        },
        "darwin-x86_64": {
          "signature": "MINISIGN_SIGNATURE",
          "url": "https://cdn.example.com/app.app.tar.gz"
        },
        "darwin-aarch64": {
          "signature": "MINISIGN_SIGNATURE",
          "url": "https://cdn.example.com/app-aarch64.app.tar.gz"
        }
      }
    }
    ```
    
    ### Dynamic Server Response
    
    - **No update available:** HTTP 204 (No Content)
    - **Update available:** HTTP 200 with:
    
    ```json
    {
      "version": "1.2.0",
      "url": "https://cdn.example.com/bundle",
      "signature": "MINISIGN_SIGNATURE",
      "notes": "Release notes",
      "pub_date": "2025-01-15T00:00:00Z"
    }
    ```
    
    Required fields: `version`, `url`, `signature`.
    
    ---
    
    ## Platform Target Strings (Updater)
    
    | `{{target}}` | `{{arch}}` | Platform            |
    | ------------ | ---------- | ------------------- |
    | `linux`      | `x86_64`   | Linux x64           |
    | `linux`      | `aarch64`  | Linux ARM64         |
    | `windows`    | `x86_64`   | Windows x64         |
    | `windows`    | `i686`     | Windows x86         |
    | `windows`    | `aarch64`  | Windows ARM64       |
    | `darwin`     | `x86_64`   | macOS Intel         |
    | `darwin`     | `aarch64`  | macOS Apple Silicon |
    
    ---
    
    ## See Also
    
    - [Tauri v2 Distribute Guide](https://v2.tauri.app/distribute/)
    - [Tauri v2 Config Reference](https://v2.tauri.app/reference/config/)
    - [Tauri GitHub Action](https://github.com/tauri-apps/tauri-action)
    - [Tauri App Size Guide](https://v2.tauri.app/concept/size/)
    
  • SKILL.md 16.5 KB
    ---
    name: desktop-packaging-tauri
    description: Tauri 2.x bundling, code signing, auto-updater, platform installers, CI/CD
    ---
    
    # Tauri 2.x Bundling & Distribution
    
    > **Quick Guide:** Configure bundling in `tauri.conf.json` under `bundle`. Platform targets: NSIS/MSI (Windows), DMG/app bundle (macOS), deb/rpm/AppImage (Linux). Code signing is required for macOS distribution (Apple notarization) and recommended for Windows (SmartScreen). The auto-updater uses Ed25519 (Minisign) signatures -- generate keys with `cargo tauri signer generate`, set `TAURI_SIGNING_PRIVATE_KEY` at build time. Cross-platform CI uses `tauri-apps/tauri-action` with a matrix strategy. Optimize binary size with `[profile.release]` settings in `Cargo.toml`.
    >
    > **Current version:** Tauri 2.x (stable). Updater artifacts use `createUpdaterArtifacts: true` (not the v1 `"v1Compatible"` unless migrating).
    
    ---
    
    <critical_requirements>
    
    ## CRITICAL: Before Using This Skill
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST set `TAURI_SIGNING_PRIVATE_KEY` as an environment variable at build time for updater artifacts -- never commit the private key)**
    
    **(You MUST configure code signing for macOS distribution -- unsigned apps are blocked by Gatekeeper)**
    
    **(You MUST use `bundle.identifier` as a valid reverse-domain string -- it is used for code signing, app data paths, and store submissions)**
    
    **(You MUST build platform-specific installers on their native OS -- cross-compilation is limited to NSIS via `cargo-xwin`)**
    
    **(You MUST set `createUpdaterArtifacts: true` in `bundle` to generate `.sig` files alongside installers)**
    
    </critical_requirements>
    
    ---
    
    **Auto-detection:** tauri.conf.json bundle, cargo tauri build, bundle targets, NSIS, MSI, DMG, AppImage, deb, rpm, code signing, notarization, APPLE_SIGNING_IDENTITY, certificateThumbprint, tauri-plugin-updater, createUpdaterArtifacts, TAURI_SIGNING_PRIVATE_KEY, sidecar, externalBin, tauri-action, cargo tauri signer, Minisign, installer hooks
    
    **When to use:**
    
    - Configuring `tauri.conf.json` bundle settings (targets, icons, resources, identifier)
    - Building platform-specific installers (NSIS, MSI, DMG, deb, rpm, AppImage)
    - Setting up macOS code signing and Apple notarization
    - Setting up Windows code signing (OV/EV certificates, Azure Trusted Signing)
    - Configuring the auto-updater plugin with Ed25519 signature verification
    - Optimizing Tauri app binary size (Rust release profile, frontend bundle)
    - Bundling sidecar binaries or extra resources
    - Creating GitHub Actions CI/CD for cross-platform builds
    - Customizing NSIS installers with hooks or templates
    
    **When NOT to use:**
    
    - Tauri command/IPC bridge, permissions, plugins, window management (use the Tauri framework skill)
    - Frontend framework or build tool configuration (separate skills)
    - General Rust programming or Cargo configuration not specific to Tauri bundling
    - Mobile distribution to App Store / Google Play (different workflow)
    
    **Key patterns covered:**
    
    - Bundle configuration in `tauri.conf.json` ([examples/core.md](examples/core.md))
    - Platform-specific installer options ([examples/core.md](examples/core.md))
    - macOS code signing and notarization ([examples/code-signing.md](examples/code-signing.md))
    - Windows code signing ([examples/code-signing.md](examples/code-signing.md))
    - Auto-updater setup with Ed25519 signatures ([examples/updater.md](examples/updater.md))
    - Binary size optimization ([examples/core.md](examples/core.md))
    - Sidecar binaries and resources ([examples/core.md](examples/core.md))
    - GitHub Actions cross-platform CI/CD ([examples/ci-cd.md](examples/ci-cd.md))
    - NSIS installer hooks and customization ([examples/core.md](examples/core.md))
    
    **Detailed resources:**
    
    - [examples/core.md](examples/core.md) - Bundle config, platform targets, size optimization, sidecars, NSIS hooks
    - [examples/code-signing.md](examples/code-signing.md) - macOS notarization, Windows signing, CI/CD signing setup
    - [examples/updater.md](examples/updater.md) - Auto-updater plugin, key generation, endpoint format, JS/Rust usage
    - [examples/ci-cd.md](examples/ci-cd.md) - GitHub Actions workflow, matrix strategy, secrets
    - [reference.md](reference.md) - Bundle config field reference, CLI commands, platform target table
    
    ---
    
    <philosophy>
    
    ## Philosophy
    
    Tauri's bundling system produces small, native installers by leveraging the OS system webview instead of bundling a browser engine. The typical binary is 5-15 MB compared to 150+ MB for alternatives. Distribution requires platform-specific steps: code signing and notarization for macOS, SmartScreen-friendly signing for Windows, and package manager formats for Linux.
    
    **The bundling workflow:**
    
    1. Configure `tauri.conf.json` bundle section (identifier, icons, targets)
    2. Set up code signing for target platforms
    3. Configure the updater plugin with Ed25519 keys
    4. Build with `cargo tauri build` (produces installer + `.sig` files)
    5. Distribute via CI/CD pipeline with platform matrix
    
    **Key constraints:**
    
    - Cross-compilation is limited -- build macOS on macOS, Windows on Windows (NSIS is the exception via `cargo-xwin`)
    - Code signing requires platform-specific certificates and accounts (Apple Developer, Windows code signing cert)
    - The updater requires Ed25519 signatures -- this cannot be disabled
    - AppImage bundles all dependencies (~70+ MB) while deb/rpm rely on system packages (~2-6 MB)
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## Core Patterns
    
    ### Pattern 1: Bundle Configuration
    
    The `bundle` section in `tauri.conf.json` controls all packaging behavior. The `identifier` is the most important field -- it must be a unique reverse-domain string used for app data paths, code signing, and store submissions.
    
    ```json
    {
      "bundle": {
        "active": true,
        "targets": "all",
        "identifier": "com.mycompany.myapp",
        "icon": [
          "icons/32x32.png",
          "icons/128x128.png",
          "icons/128x128@2x.png",
          "icons/icon.icns",
          "icons/icon.ico"
        ],
        "resources": {
          "locales/*": "locales/"
        },
        "createUpdaterArtifacts": true
      }
    }
    ```
    
    **Key points:** `"targets": "all"` builds all formats for the current OS. Use `cargo tauri icon path/to/1024x1024.png` to generate all icon sizes. Set `createUpdaterArtifacts: true` to produce `.sig` signature files alongside installers. See [examples/core.md](examples/core.md) for full config with platform-specific sections.
    
    ---
    
    ### Pattern 2: Platform-Specific Installers
    
    Each platform has distinct installer formats with different trade-offs.
    
    | Platform | Format     | Output        | Size    | Notes                      |
    | -------- | ---------- | ------------- | ------- | -------------------------- |
    | Windows  | `nsis`     | `-setup.exe`  | ~2-5 MB | Recommended, cross-compile |
    | Windows  | `msi`      | `.msi`        | ~2-5 MB | WiX, Windows-only build    |
    | macOS    | `dmg`      | `.dmg`        | ~5-8 MB | Drag-to-install disk image |
    | macOS    | `app`      | `.app` bundle | ~5-8 MB | Raw app, no installer      |
    | Linux    | `deb`      | `.deb`        | ~2-6 MB | Debian/Ubuntu              |
    | Linux    | `rpm`      | `.rpm`        | ~2-6 MB | Fedora/RHEL                |
    | Linux    | `appimage` | `.AppImage`   | ~70+ MB | Portable, bundles all deps |
    
    ```sh
    # Build all formats for current platform
    cargo tauri build
    
    # Build specific format
    cargo tauri build --bundles nsis
    
    # Debug build (faster compile, larger binary)
    cargo tauri build --debug
    ```
    
    **Key point:** You cannot build `.msi` on Linux or `.dmg` on Windows. NSIS is the only cross-compilable format (via `cargo-xwin`). See [examples/core.md](examples/core.md) for platform-specific config sections.
    
    ---
    
    ### Pattern 3: Code Signing
    
    macOS requires both code signing and notarization for distribution outside the App Store. Windows signing prevents SmartScreen warnings.
    
    **macOS** -- set environment variables for CI:
    
    - `APPLE_CERTIFICATE` (base64-encoded `.p12`)
    - `APPLE_CERTIFICATE_PASSWORD`
    - `APPLE_SIGNING_IDENTITY`
    - Notarization: `APPLE_API_ISSUER`, `APPLE_API_KEY`, `APPLE_API_KEY_PATH` (App Store Connect API)
    
    **Windows** -- configure in `tauri.conf.json` or use `signCommand`:
    
    ```json
    {
      "bundle": {
        "windows": {
          "certificateThumbprint": "YOUR_THUMBPRINT",
          "digestAlgorithm": "sha256",
          "timestampUrl": "http://timestamp.comodoca.com"
        }
      }
    }
    ```
    
    **Key point:** macOS notarization is mandatory for apps distributed outside the App Store -- without it, Gatekeeper blocks the app. See [examples/code-signing.md](examples/code-signing.md) for full setup and CI integration.
    
    ---
    
    ### Pattern 4: Auto-Updater
    
    The updater plugin uses Ed25519 (Minisign) signatures to verify update authenticity. Signature verification cannot be disabled.
    
    ```sh
    # Generate key pair (store private key securely)
    cargo tauri signer generate -w ~/.tauri/myapp.key
    ```
    
    ```json
    {
      "plugins": {
        "updater": {
          "pubkey": "YOUR_ED25519_PUBLIC_KEY",
          "endpoints": [
            "https://releases.example.com/{{target}}/{{arch}}/{{current_version}}"
          ]
        }
      }
    }
    ```
    
    **Key points:** Set `TAURI_SIGNING_PRIVATE_KEY` at build time (never commit it). The endpoint template variables `{{target}}`, `{{arch}}`, `{{current_version}}` are resolved at runtime. The server returns HTTP 204 for no update, HTTP 200 with update JSON for available updates. See [examples/updater.md](examples/updater.md) for endpoint response format and JS/Rust usage.
    
    ---
    
    ### Pattern 5: Binary Size Optimization
    
    Tauri binaries are already small (5-15 MB) but can be further optimized with Rust release profile settings.
    
    ```toml
    # src-tauri/Cargo.toml
    [profile.release]
    codegen-units = 1
    lto = true
    opt-level = "s"
    panic = "abort"
    strip = true
    ```
    
    | Setting             | Impact              | Trade-off                |
    | ------------------- | ------------------- | ------------------------ |
    | `strip = true`      | ~10-20% smaller     | No debug symbols         |
    | `lto = true`        | ~10-20% smaller     | Slower compile           |
    | `opt-level = "s"`   | Optimize for size   | May be slower at runtime |
    | `codegen-units = 1` | Better optimization | Slower compile           |
    | `panic = "abort"`   | Smaller binary      | No panic unwinding       |
    
    **Key point:** These settings significantly increase compile time (10-30 min clean builds). Use `cargo tauri build --debug` during development. Tauri 2.4+ also supports `removeUnusedCommands: true` in `build` config. See [examples/core.md](examples/core.md) for frontend optimization tips.
    
    ---
    
    ### Pattern 6: Sidecar Binaries
    
    Bundle external executables that run alongside your app. Filenames must include the Rust target triple.
    
    ```json
    {
      "bundle": {
        "externalBin": ["binaries/ffmpeg"]
      }
    }
    ```
    
    ```
    binaries/ffmpeg-x86_64-pc-windows-msvc.exe
    binaries/ffmpeg-x86_64-apple-darwin
    binaries/ffmpeg-x86_64-unknown-linux-gnu
    binaries/ffmpeg-aarch64-apple-darwin
    ```
    
    **Key point:** Tauri resolves the correct platform binary at runtime. The shell plugin (`tauri-plugin-shell`) is required for sidecar execution. See [examples/core.md](examples/core.md) for the Rust sidecar execution pattern.
    
    ---
    
    ### Pattern 7: NSIS Installer Customization
    
    Extend NSIS installers with hooks or replace the template entirely.
    
    Four lifecycle hooks: `NSIS_HOOK_PREINSTALL`, `NSIS_HOOK_POSTINSTALL`, `NSIS_HOOK_PREUNINSTALL`, `NSIS_HOOK_POSTUNINSTALL`.
    
    ```json
    {
      "bundle": {
        "windows": {
          "nsis": {
            "installerHooks": "hooks.nsh",
            "installMode": "both",
            "displayLanguageSelector": true
          }
        }
      }
    }
    ```
    
    **Key point:** Use hooks for targeted changes (registry keys, file associations). Use a custom template (`nsis.template`) only if hooks are insufficient. See [examples/core.md](examples/core.md) for hook examples.
    
    </patterns>
    
    ---
    
    <decision_framework>
    
    ## Decision Framework
    
    ### Which Installer Format?
    
    ```
    Target platform?
    |-- Windows
    |   +-- Need MSI for enterprise deployment? -> msi (WiX, Windows-only build)
    |   +-- General distribution? -> nsis (recommended, cross-compilable)
    |-- macOS
    |   +-- App Store? -> app bundle + App Store signing
    |   +-- Direct download? -> dmg + Developer ID + notarization
    |-- Linux
    |   +-- Targeting Debian/Ubuntu? -> deb
    |   +-- Targeting Fedora/RHEL? -> rpm
    |   +-- Maximum portability? -> appimage (larger, ~70+ MB)
    |   +-- Sandboxed distribution? -> snap or flatpak (manual setup)
    +-- All platforms? -> Use "all" target with CI matrix
    ```
    
    ### Code Signing Decision
    
    ```
    Distributing publicly?
    |-- macOS
    |   +-- App Store? -> Apple Distribution certificate
    |   +-- Direct download? -> Developer ID Application + notarization (REQUIRED)
    |   +-- Internal/testing only? -> Ad-hoc signing (signingIdentity: "-")
    |-- Windows
    |   +-- Microsoft Store? -> Store signing
    |   +-- Direct download? -> OV or EV certificate (prevents SmartScreen warnings)
    |   +-- Internal only? -> Optional but recommended
    +-- Linux
        +-- Code signing is not required for Linux distribution
    ```
    
    ### Updater Strategy
    
    ```
    Need auto-updates?
    |-- YES -> tauri-plugin-updater
    |   +-- Simple static hosting? -> Static JSON endpoint (GitHub Releases, S3)
    |   +-- Dynamic update logic? -> Dynamic endpoint (returns 200/204)
    |   +-- Need update UI? -> JS-side check() + downloadAndInstall()
    |   +-- Background updates? -> Rust-side updater with AppHandle
    +-- NO -> Skip updater config, omit createUpdaterArtifacts
    ```
    
    </decision_framework>
    
    ---
    
    <red_flags>
    
    ## RED FLAGS
    
    **High Priority Issues:**
    
    - Committing `TAURI_SIGNING_PRIVATE_KEY` to source control -- store as CI secret, never in repo
    - Missing `createUpdaterArtifacts: true` when using the updater -- no `.sig` files generated, updates fail
    - Building macOS installer on Linux/Windows -- cross-compilation not supported for DMG/app bundle
    - Using `"targets": "all"` in CI without a matrix strategy -- builds all formats for the current OS only
    - Missing `bundle.identifier` or using an invalid format -- breaks code signing, app data paths, and store submissions
    - Distributing unsigned macOS app -- Gatekeeper blocks it, users cannot open it
    
    **Medium Priority Issues:**
    
    - AppImage on Ubuntu 22.04+ targeting older distros -- higher glibc requirement breaks compatibility
    - Missing `strip = true` in release profile -- debug symbols inflate binary by 10-20%
    - Using `opt-level = 3` instead of `"s"` or `"z"` when binary size matters -- optimizes for speed, not size
    - WebView2 `skip` install mode without guarantee runtime is present -- app crashes on startup
    - NSIS `perUser` mode when app needs system-wide installation -- installs to `%LOCALAPPDATA%`, not Program Files
    
    **Gotchas & Edge Cases:**
    
    - `opt-level = "s"` vs `"z"` -- sometimes `"z"` produces smaller binaries, sometimes `"s"` does. Test both.
    - macOS ad-hoc signing (`signingIdentity: "-"`) still triggers Gatekeeper warnings -- only useful for development
    - NSIS is the only format supporting cross-compilation from Linux/macOS to Windows (via `cargo-xwin`)
    - Sidecar binary filenames must include the Rust target triple suffix -- Tauri resolves the correct one at runtime
    - Updater endpoint template variables (`{{target}}`, `{{arch}}`, `{{current_version}}`) are Tauri-specific, not environment variables
    - AppImage bundles are ~70+ MB because they include all dependencies -- deb/rpm are 2-6 MB but require system packages
    - Windows WebView2 runtime is bundled by default with `embedBootstrapper` -- older `downloadBootstrapper` mode requires internet at install time
    - Snap/Flatpak packages run in a sandbox -- DBus communication is blocked unless declared in the manifest
    - `removeUnusedCommands: true` (Tauri 2.4+) strips commands not in capability files -- ensure all needed commands are listed in ACL
    - Free Apple Developer accounts cannot notarize apps -- a paid $99/year account is required for distribution
    
    </red_flags>
    
    ---
    
    <critical_reminders>
    
    ## CRITICAL REMINDERS
    
    > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
    
    **(You MUST set `TAURI_SIGNING_PRIVATE_KEY` as an environment variable at build time for updater artifacts -- never commit the private key)**
    
    **(You MUST configure code signing for macOS distribution -- unsigned apps are blocked by Gatekeeper)**
    
    **(You MUST use `bundle.identifier` as a valid reverse-domain string -- it is used for code signing, app data paths, and store submissions)**
    
    **(You MUST build platform-specific installers on their native OS -- cross-compilation is limited to NSIS via `cargo-xwin`)**
    
    **(You MUST set `createUpdaterArtifacts: true` in `bundle` to generate `.sig` files alongside installers)**
    
    **Failure to follow these rules will produce unsigned binaries, missing update signatures, or broken cross-platform builds.**
    
    </critical_reminders>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related