129 lines
6.5 KiB
Markdown
129 lines
6.5 KiB
Markdown
---
|
|
name: electron-cross-compile
|
|
description: 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.
|
|
tags: [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)
|
|
|
|
```bash
|
|
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:
|
|
|
|
```json
|
|
"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)
|
|
|
|
```python
|
|
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
|
|
|
|
```bash
|
|
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:**
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|