Skip to content

NSIS installer doesn't replace externalBin sidecar on reinstall; path resolution undocumented #15134

Description

@vandorsey

Tauri v2 NSIS Installer: externalBin Sidecar Not Replaced on Reinstall

Summary

When using Tauri v2's externalBin feature with NSIS installers, the sidecar binary may not be replaced during reinstalls or upgrades. This leads to the installed application running a stale sidecar while the main Tauri app binary is correctly updated. The behavior is silent — no errors are reported during install — making it difficult to diagnose.

Environment

  • Tauri: v2 (CLI and bundler)
  • Platform: Windows 11 (x64)
  • Installer: NSIS (via bundle.targets: ["nsis"])
  • Sidecar: PyInstaller-built Python exe (~71MB), configured via bundle.externalBin

Configuration

{
  "bundle": {
    "active": true,
    "targets": ["nsis"],
    "externalBin": ["quote-sidecar"]
  }
}

The sidecar binary is placed at:

src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe

The Problem

Issue 1: Stale sidecar bundled from wrong path

What happened: After rebuilding the sidecar binary and running tauri build, the NSIS installer continued to bundle the old sidecar. The main Tauri app (Rust binary) was correctly rebuilt, but the sidecar inside the installer was stale.

Root cause: Tauri copies the sidecar to src-tauri/target/release/quote-sidecar.exe (stripping the target triple) during the first build. On subsequent builds, if this cached copy exists and the build system doesn't detect the source has changed, it reuses the stale cached copy.

Evidence: The generated NSIS script (target/release/nsis/x64/installer.nsi) correctly references src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe — but by the time NSIS runs, Tauri's bundler has already resolved the binary path. In our case, a stale 188MB binary persisted while the rebuilt binary was 71MB.

Fix: We had to manually ensure the file at src-tauri/quote-sidecar-{target-triple}.exe was the current build. A binaries/ subdirectory we had been using was not the path Tauri resolves — externalBin resolves relative to src-tauri/, not src-tauri/binaries/.

Issue 2: NSIS installer doesn't replace sidecar on same-version reinstall

What happened: Even when the correct sidecar was bundled in the installer, reinstalling the same version (0.1.00.1.0) did not overwrite the installed sidecar binary. The main app exe was replaced, but the sidecar was left untouched.

Root cause: NSIS file replacement behavior during same-version installs. The sidecar exe has no Windows version resource (it's a PyInstaller-built binary), so NSIS's version comparison logic may skip it. Additionally, if the sidecar process is still running when the installer copies files, the copy silently fails.

Partial mitigation: We added NSIS installer hooks to kill the running sidecar before install:

; src-tauri/installer-hooks.nsh
!macro NSIS_HOOK_PREINSTALL
  nsExec::Exec 'taskkill /F /IM quote-sidecar.exe'
  nsExec::Exec 'taskkill /F /IM coda-quote.exe'
  Sleep 1000
  Delete "$INSTDIR\quote-sidecar.exe"
!macroend
{
  "bundle": {
    "windows": {
      "nsis": {
        "installerHooks": "installer-hooks.nsh"
      }
    }
  }
}

This helps with the "file in use" problem but doesn't fully solve the version-comparison issue.

Issue 3: CSP blocks sidecar API in production builds

What happened: The sidecar API (http://127.0.0.1:8000) worked fine in dev mode but returned no data in the installed production app. No errors were visible.

Root cause: In dev mode, the frontend runs on http://localhost:1420. In production, Tauri serves from https://tauri.localhost. The default CSP allowed connect-src to http://127.0.0.1:8000, but the production origin (https://tauri.localhost) treating requests to http:// as mixed content.

Fix: Added https://tauri.localhost to CSP and dangerousDisableAssetCspModification:

{
  "app": {
    "security": {
      "csp": "default-src 'self'; connect-src 'self' http://localhost:8000 http://127.0.0.1:8000 https://tauri.localhost; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'",
      "dangerousDisableAssetCspModification": true
    }
  }
}

Documentation Gaps

  1. externalBin path resolution is not clearly documented. The docs don't explicitly state that externalBin: ["quote-sidecar"] resolves to src-tauri/quote-sidecar-{target-triple}.exe. Users may assume it resolves from a binaries/ subdirectory (which some Tauri v1 examples showed).

  2. No mention of sidecar caching in target/release/. When rebuilding a sidecar outside of Cargo (e.g., PyInstaller, Go, etc.), the cached copy in target/release/ is not automatically invalidated. The docs should warn about this.

  3. NSIS reinstall behavior with unversioned binaries is not documented. The docs describe installerHooks but don't explain that sidecars without Windows version resources may not be replaced on reinstall.

  4. CSP for production sidecar communication is not covered in the sidecar documentation. The dev → production origin change (http://localhosthttps://tauri.localhost) is a common gotcha for sidecar architectures.

Recommendations for Tauri

For the build system:

  1. Always re-copy externalBin files to target/release/ before bundling, comparing checksums rather than relying on timestamp/existence checks.
  2. Log the sidecar path and size during tauri build so developers can verify the correct binary is being bundled.
  3. Warn if target/release/{sidecar}.exe is older than src-tauri/{sidecar}-{triple}.exe.

For NSIS installer:

  1. Default to SetOverwrite on for sidecar binaries, or always delete-then-copy to handle unversioned executables.
  2. Include process termination for known sidecar binaries in the default NSIS template (pre-install hook).

For documentation:

  1. Explicitly document externalBin path resolution: src-tauri/{name}-{target-triple}{.exe}.
  2. Add a "Rebuilding Sidecars" section covering the target/release/ cache issue for non-Cargo sidecars.
  3. Add CSP guidance for sidecar communication in production builds.
  4. Document installerHooks with a practical sidecar example (kill process, delete old binary).

What Finally Worked

  1. Place sidecar at src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe (not in a subdirectory)
  2. Use --clean flag with PyInstaller and clear __pycache__ before building
  3. Use NSIS installerHooks to kill running processes and delete the old binary pre-install
  4. Add dangerousDisableAssetCspModification: true and https://tauri.localhost to CSP
  5. Bump the version number when making sidecar changes to force NSIS to treat it as an upgrade
  6. Add npm run build:sidecar script to standardize the sidecar build with correct output path

Build Commands (Working)

# 1. Build the sidecar (outputs to src-tauri/)
npm run build:sidecar

# 2. Build the Tauri app + installer
npm run tauri:build

# Installer output:
# src-tauri/target/release/bundle/nsis/

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-slopLow effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions