Files

6.5 KiB

name, description, tags
name description tags
electron-cross-compile Cross-compile Electron desktop apps for Windows (exe/msi) on a Linux server. Covers wine setup, code signing bypass, disk space pitfalls, and COS upload for delivery.
electron
windows
cross-compile
nsis
portable
exe
msi
wine

Electron Cross-Compile: Linux → Windows

Build Windows exe/msi/installer from an Electron project on a Linux host.

Prerequisites

  • Node.js 18+ and npm
  • Disk space: at least 300MB free per build (electron binary ~115MB + app + NSIS output ~160MB)
  • wine for NSIS installer signing step (even without a certificate, makensis needs wine)

Step-by-Step

1. Install Wine (required for NSIS on Linux)

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y wine32:i386 wine64

Verify: wine --version — wine32 warnings about display driver are harmless for CLI usage.

2. Disable Code Signing (no certificate scenario)

Edit package.json build config:

"win": {
  "target": ["nsis", "portable"],
  "icon": "assets/icon.ico",
  "signAndEditExecutable": false,
  "sign": null
}

Key pitfalls:

  • "sign": false does NOT work — must be null
  • "signingHashAlgorithms": [] causes schema validation error — use sign: null instead
  • Without wine installed, electron-builder fails with ERR_ELECTRON_BUILDER_CANNOT_EXECUTE even with signing disabled

3. Generate Icon Files (if missing)

from PIL import Image, ImageDraw
img = Image.new('RGB', (256, 256), '#1a1a2e')
draw = ImageDraw.Draw(img)
draw.rectangle([40, 40, 216, 216], fill='#16213e', outline='#0f3460', width=3)
img.save('assets/icon.png')
img.save('assets/icon.ico', format='ICO', sizes=[(16,16),(32,32),(48,48),(64,64),(128,128),(256,256)])

Both .ico (Windows) and .png (Linux) needed depending on targets.

4. Install Dependencies & Build

cd /path/to/electron-project
npm install
# If using electron-vite, build the renderer/main/preload first:
npx electron-vite build   # produces out/ directory
# Then package:
npx electron-builder --win --x64

If rcedit fails with USER32.dll not found, the root cause is that wine32 prefix was initialized without an X display server. wine cannot create its internal DLL stubs (user32.dll etc.) without X11, even for headless CLI usage. Fix: start Xvfb before initializing the wine prefix:

# Step 1: Install Xvfb (usually pre-installed on Ubuntu server)
sudo apt-get install -y xvfb

# Step 2: Start Xvfb in background
Xvfb :99 -screen 0 1024x768x24 &

# Step 3: (Re)create the wine32 prefix WITH display available
rm -rf ~/.wine32
DISPLAY=:99 WINEARCH=win32 WINEPREFIX=~/.wine32 wineboot --init

# Step 4: Verify user32.dll was generated
find ~/.wine32 -name "user32.dll"  # should find it

# Step 5: Run electron-builder with the wine32 prefix and display
DISPLAY=:99 WINEARCH=win32 WINEPREFIX=~/.wine32 npx electron-builder --win --x64

Key insight: sudo apt install wine32:i386 alone is NOT sufficient — the wine prefix must be initialized with DISPLAY set, or user32.dll and other system DLL stubs won't be generated.

5. Output Files

File Description
dist/AppName Setup X.X.X.exe NSIS installer (per-user by default)
dist/AppName X.X.X.exe Portable standalone (no install needed)
dist/win-unpacked/ Unpacked directory (can be deleted to save space)

6. Upload for Download

# COS upload example
source ~/.hermes/custom_services.env
coscmd config -a "$COS_SECRET_ID" -s "$COS_SECRET_KEY" -b "$COS_BUCKET_NAME" -r "$COS_REGION"
coscmd upload "dist/AppName X.X.X.exe" tools/AppName-X.X.X-Portable.exe

COS URL pattern: https://{bucket}.cos.{region}.myqcloud.com/tools/{filename}

Pitfalls

  1. Disk space exhaustion: NSIS needs ~160MB temp space. If disk is >95% full, makensis crashes with can't write N bytes to output — BUT the portable exe may already be written successfully before the error. Check dist/ for valid PE32 files.
  2. Wine display driver errors: nodrv_CreateWindow errors are harmless — wine works for CLI signing steps without X11.
  3. sign: false vs sign: null: false is ignored by electron-builder; must use null to fully skip signing.
  4. extraResources paths: Relative paths like ../extension are resolved from the package.json directory. Missing source dirs cause silent failures.
  5. No code signing = SmartScreen warning: Windows users will see "Unknown publisher" — instruct them to click "More info" → "Run anyway".
  6. wine rcedit-ia32.exe USER32.dll not found: When running electron-builder --win on Linux, it uses wine to run rcedit-ia32.exe (from ~/.cache/electron-builder/winCodeSign/) to set exe metadata/icon. Error: Library USER32.dll not found. Root cause: wine32 prefix was initialized without an X display server, so wine couldn't generate system DLL stubs. Fix: Start Xvfb (Xvfb :99 -screen 0 1024x768x24 &), then re-create the wine32 prefix with DISPLAY=:99 WINEARCH=win32 WINEPREFIX=~/.wine32 wineboot --init, then run electron-builder with DISPLAY=:99 WINEPREFIX=~/.wine32. Simply installing wine32:i386 is NOT enough — the prefix needs X11 to properly initialize. As a last resort, zip dist/win-unpacked/ and run the exe directly.
  7. electron-vite projects: If the project uses electron-vite, run npx electron-vite build first (produces out/ directory), then npx electron-builder --win --x64. Do NOT skip the vite build step.
  8. 🔥 npmRebuild: false packages Linux native .node into Windows build: If electron-builder.yml has npmRebuild: false (common for speed), any native Node addon (e.g. better-sqlite3) compiled on Linux will be packaged as a Linux ELF .node binary. On Windows, loading this crashes the app silently. Verify after build: file dist/win-unpacked/resources/app.asar.unpacked/**/better_sqlite3.node — should say PE32+ executable not ELF. Fix: Either set npmRebuild: true (slow, needs Windows toolchain on Linux via wine), or exclude the native module and use a pure-JS alternative (like sql.js for SQLite). For projects that only use native modules in optional code paths, you can also exclude them from files in electron-builder.yml and handle the import failure gracefully at runtime.

User Instructions Template

When delivering unsigned exe to user:

  • Warn about "Unknown publisher" SmartScreen warning
  • Recommend Portable exe for quick test, Setup exe for permanent install
  • Remind about Chrome Extension manual loading if applicable