feat(linux): bundle Python CLI and fix Electron packaged build

- contrib/linux/Dockerfile: add libpython3.11 + PyInstaller to build
  standalone cli binary; fix venv isolation via .dockerignore
- frontend/electron/main.cjs: use bundled cli binary in prod via
  process.resourcesPath, fallback to venv/python3 in dev
- frontend/package.json: add extraResources for cli binary,
  set output dir to release/
- frontend/vite.config.js: set base './' for file:// protocol in prod
- .dockerignore: exclude venv/, node_modules/, dist/, .git/
- .gitignore: ignore release/ output directories
This commit is contained in:
2026-03-10 09:00:27 +01:00
parent 9475e444ba
commit 0a81f0db23
8 changed files with 85 additions and 14 deletions

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
venv/
.venv/
__pycache__/
*.pyc
node_modules/
frontend/node_modules/
frontend/dist/
frontend/release/
release/
.git/

2
.gitignore vendored
View File

@@ -20,3 +20,5 @@ __pycache__/
node_modules/
frontend/node_modules/
frontend/dist/
frontend/release/
release/

31
contrib/linux/Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
FROM node:22.14.0-bookworm
# System dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv libpython3.11 \
binutils \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Copy repo
COPY . .
# Python venv + dependencies
RUN python3 -m venv venv && \
venv/bin/pip install --no-cache-dir -r requirements.txt
# Compile Python CLI into a standalone binary
RUN venv/bin/pip install --no-cache-dir pyinstaller && \
venv/bin/pyinstaller --onefile --name cli src/cli.py && \
mkdir -p frontend/resources && \
cp dist/cli frontend/resources/cli
# JS dependencies + electron-builder
RUN cd frontend && npm ci && npm install --no-save electron-builder
# Build
RUN cd frontend && npx vite build && npx electron-builder --linux AppImage --publish never
# Export AppImage
CMD cp frontend/release/*.AppImage /out/

18
contrib/linux/build.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT=$(cd "$(dirname "$0")/../.." && pwd)
OUT_DIR="$REPO_ROOT/release"
mkdir -p "$OUT_DIR"
docker build -t wallet-generator-builder \
-f "$REPO_ROOT/contrib/linux/Dockerfile" \
"$REPO_ROOT"
docker run --rm \
-v "$OUT_DIR:/out" \
wallet-generator-builder
echo "AppImage saved to: $OUT_DIR"
ls "$OUT_DIR"/*.AppImage

View File

@@ -48,21 +48,24 @@ function resolveWalletFile(kind, filename) {
return filePath
}
// ── Resolve Python executable ──────────────────────────────────────────────────
function findPython() {
// ── Resolve CLI command (dev: python + cli.py, prod: bundled binary) ──────────
function getCliCommand() {
if (!isDev) {
const bundled = path.join(process.resourcesPath, 'cli')
if (fs.existsSync(bundled)) return { exec: bundled, baseArgs: [] }
}
const repoRoot = path.join(__dirname, '..', '..')
const venvPython = path.join(repoRoot, 'venv', 'bin', 'python')
return fs.existsSync(venvPython) ? venvPython : 'python3'
const python = fs.existsSync(path.join(repoRoot, 'venv', 'bin', 'python'))
? path.join(repoRoot, 'venv', 'bin', 'python')
: 'python3'
return { exec: python, baseArgs: [path.join(repoRoot, 'src', 'cli.py')] }
}
// ── Call Python CLI ────────────────────────────────────────────────────────────
function callPython(command, args = {}) {
return new Promise((resolve, reject) => {
const python = findPython()
const repoRoot = path.join(__dirname, '..', '..')
const cliPath = path.join(repoRoot, 'src', 'cli.py')
execFile(python, [cliPath, command, JSON.stringify(args)], { cwd: repoRoot }, (err, stdout, stderr) => {
const { exec, baseArgs } = getCliCommand()
execFile(exec, [...baseArgs, command, JSON.stringify(args)], (err, stdout, stderr) => {
if (err) { reject(new Error(stderr || err.message)); return }
try {
const result = JSON.parse(stdout.trim())
@@ -139,7 +142,7 @@ function createWindow() {
height: 750,
minWidth: 320,
minHeight: 480,
title: 'Wallet Generator',
title: 'wallet-gen',
backgroundColor: '#0f1117',
icon: path.join(__dirname, '..', 'public', 'icons', 'icon.png'),
webPreferences: {

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icons/bitcoin.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wallet Generator</title>
<title>wallet-gen</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,5 +1,5 @@
{
"name": "wallet-generator",
"name": "wallet-gen",
"private": true,
"version": "1.0.0",
"type": "module",
@@ -12,10 +12,16 @@
"dist": "vite build && electron-builder"
},
"build": {
"appId": "com.walletgenerator.app",
"productName": "Wallet Generator",
"appId": "com.walletgen.app",
"productName": "wallet-gen",
"icon": "public/icons/icon.png",
"files": ["dist/**", "electron/**"],
"extraResources": [
{ "from": "resources/cli", "to": "cli" }
],
"directories": {
"output": "release"
},
"linux": {
"target": "AppImage",
"category": "Finance"

View File

@@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: './',
server: {
port: 5173,
},