Skip to main content

Overview

Building UNS for production involves two main steps:
  1. Compile the Python backend into a standalone binary
  2. Package the Electron app with electron-builder
The final distributable includes the React frontend, Electron runtime, and Python engine bundled together.
Build times vary by platform. Expect 2-5 minutes for a full build on modern hardware.

Build Architecture

The production build structure:
UNS.app / UNS.exe / UNS
├── Electron App (ASAR)
│   ├── main.js
│   ├── preload.js
│   └── frontend/dist/       (React build)
└── Resources/bin/
    └── engine               (Python binary)
The Python engine is placed in the app’s resource directory and launched by Electron at runtime.

Building the Python Engine

The Python backend must be compiled before building the Electron app.
1

Activate Virtual Environment

cd backend
source venv/bin/activate
Always activate the venv before running PyInstaller. Otherwise, it may fail to bundle dependencies.
2

Run PyInstaller

Compile the FastAPI app into a standalone binary:
python -m PyInstaller --onefile --windowed --name engine api.py
Flags explained:
  • --onefile - Bundles everything into a single executable
  • --windowed - Suppresses console window (important for desktop apps)
  • --name engine - Names the output binary “engine” (or “engine.exe”)
Output location: backend/dist/engine
3

Verify the Binary

Test the compiled engine manually:
cd dist
chmod +x engine  # macOS only
./engine /tmp/test_output
You should see:
🚀 Engine starting... Data home: /tmp/test_output
INFO:     Started server process
INFO:     Uvicorn running on http://127.0.0.1:8000
Press Ctrl+C to stop it.
4

Use engine.spec for Faster Rebuilds

PyInstaller generates engine.spec on the first run. Use it for faster subsequent builds:
python -m PyInstaller engine.spec
This skips dependency analysis and uses cached data.

Troubleshooting PyInstaller

PyInstaller may miss some imports. Add them manually to engine.spec:
engine.spec
hiddenimports=[
    'fastapi',
    'uvicorn.logging',
    'uvicorn.loops.auto',
    'ebooklib.epub',
],
This is normal for Python apps. The binary includes:
  • Python runtime
  • All pip packages (FastAPI, Uvicorn, ebooklib, etc.)
  • Standard library modules
To reduce size, use --exclude-module for unused packages:
--exclude-module matplotlib --exclude-module numpy
Sign the binary with an Apple Developer certificate or instruct users to:
  1. Right-click the app → Open
  2. Go to System Preferences → Security & Privacy
  3. Click Open Anyway

Building the Electron App

Once the Python engine is compiled, build the full desktop app.
1

Build the Frontend

From the project root:
npm run build:frontend
This runs:
  1. cd frontend && npm install - Ensures dependencies are installed
  2. npm run build - Builds React app with Vite
Output: frontend/dist/ (HTML, CSS, JS)
2

Package with Electron Builder

Build the platform-specific installer:
npm run build:electron
Output directory: dist_electron/
3

Full Build (One Command)

To build everything at once:
npm run build
This runs:
  1. npm run build:frontend
  2. npm run build:electron
This does NOT rebuild the Python engine. You must rebuild backend/dist/engine separately if you changed api.py.

Platform-Specific Builds

macOS

Requirements:
  • macOS 10.13+ (to build)
  • Xcode Command Line Tools
Build targets:
  • dmg - Disk image installer (recommended)
  • pkg - macOS installer package
  • zip - Zipped app bundle
Build command:
npm run build:electron -- --mac dmg
Output:
  • dist_electron/UNS-1.1.0.dmg
  • dist_electron/mac/UNS.app (unpacked)
Code signing (optional):
package.json
"build": {
  "mac": {
    "identity": "Developer ID Application: Your Name (TEAM_ID)",
    "hardenedRuntime": true,
    "gatekeeperAssess": false,
    "entitlements": "build/entitlements.mac.plist"
  }
}

Windows

Requirements:
  • Windows 7+ (to build)
  • Node.js with build tools
Build targets:
  • nsis - NSIS installer (default)
  • portable - Portable executable
  • zip - Zipped app
Build command:
npm run build:electron -- --win nsis
Output:
  • dist_electron/UNS Setup 1.1.0.exe
  • dist_electron/win-unpacked/ (unpacked)
Code signing (optional):
package.json
"build": {
  "win": {
    "certificateFile": "path/to/cert.pfx",
    "certificatePassword": "your-password",
    "signingHashAlgorithms": ["sha256"]
  }
}

Linux

Requirements:
  • Linux (any distro)
  • Build essentials installed
Build targets:
  • AppImage - Universal Linux package (recommended)
  • snap - Snap package
  • deb - Debian/Ubuntu package
  • rpm - RedHat/Fedora package
Build command:
npm run build:electron -- --linux AppImage
Output:
  • dist_electron/UNS-1.1.0.AppImage
  • dist_electron/linux-unpacked/ (unpacked)

electron-builder Configuration

The build configuration is in package.json:
package.json
{
  "build": {
    "appId": "com.universalnovelscraper.app",
    "productName": "UNS",
    "directories": {
      "output": "dist_electron"
    },
    "files": [
      "main.js",
      "preload.js",
      "assets/icon.png",
      "frontend/dist/**/*"
    ],
    "asar": true,
    "extraResources": [
      {
        "from": "backend/dist",
        "to": "bin",
        "filter": ["engine", "engine.exe"]
      }
    ]
  }
}
Key settings:
  • files - What gets packaged in the ASAR archive
  • extraResources - Files placed outside ASAR (like the Python engine)
  • asar: true - Compresses app files for faster loading

Accessing the Engine Path

The engine location differs between development and production:
main.js
const isPackaged = app.isPackaged;
const enginePath = isPackaged
  ? path.join(process.resourcesPath, 'bin', 'engine')
  : path.join(__dirname, 'backend', 'dist', 'engine');

const finalPath = (isPackaged && process.platform === 'win32') 
  ? `${enginePath}.exe` 
  : enginePath;
EnvironmentPath
Development<project>/backend/dist/engine
Production (macOS)UNS.app/Contents/Resources/bin/engine
Production (Windows)resources/bin/engine.exe
Production (Linux)resources/bin/engine

Common Build Issues

Check the extraResources config in package.json:
"extraResources": [
  {
    "from": "backend/dist",
    "to": "bin",
    "filter": ["engine", "engine.exe"]
  }
]
Ensure backend/dist/engine exists before building.
  1. Check if the Python engine starts correctly:
    • Open the app
    • Check Activity Monitor (macOS) or Task Manager (Windows)
    • Look for a process named “engine”
  2. Enable logging in main.js:
    pythonProcess.stderr?.on('data', (data) => {
      console.error(`🐍 Python Error: ${data}`);
    });
    
  3. Run the app from terminal to see logs:
    # macOS
    ./dist_electron/mac/UNS.app/Contents/MacOS/UNS
    
    # Linux
    ./dist_electron/linux-unpacked/uns
    
    # Windows
    .\dist_electron\win-unpacked\UNS.exe
    
Ensure all dependencies are installed:
npm install
cd frontend && npm install && cd ..
cd backend && pip install -r requirements.txt && cd ..
This happens when the app isn’t code-signed. Users must:
  1. Right-click → Open
  2. Click “Open” in the security dialog
Or remove the quarantine flag:
xattr -cr /Applications/UNS.app
Unsigned Windows apps trigger SmartScreen. Users should:
  1. Click “More info”
  2. Click “Run anyway”
To fix permanently, sign the app with an EV code signing certificate.

Testing the Build

Before distributing, test the packaged app:
1

Test Basic Functionality

  1. Launch the app
  2. Check if the UI loads
  3. Navigate to Library, Download, History pages
  4. Verify the Python engine is running (check Activity Monitor/Task Manager)
2

Test a Full Scrape

  1. Add a provider (or use a pre-installed one)
  2. Search for a novel
  3. Start a download
  4. Verify chapters are being scraped
  5. Check if the EPUB is generated in the output folder
3

Test on a Clean Machine

The best test is on a machine without development tools:
  • No Node.js, Python, or dev tools installed
  • Fresh user account (no leftover config)
  • Different OS version (e.g., macOS 12 if you built on macOS 14)

Next Steps

Development Setup

Return to setup guide for development mode

Project Structure

Learn about the codebase organization

Build docs developers (and LLMs) love