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:
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
node_modules/
|
||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
frontend/release/
|
||||||
|
release/
|
||||||
|
.git/
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,3 +20,5 @@ __pycache__/
|
|||||||
node_modules/
|
node_modules/
|
||||||
frontend/node_modules/
|
frontend/node_modules/
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
|
frontend/release/
|
||||||
|
release/
|
||||||
|
|||||||
31
contrib/linux/Dockerfile
Normal file
31
contrib/linux/Dockerfile
Normal 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
18
contrib/linux/build.sh
Executable 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
|
||||||
@@ -48,21 +48,24 @@ function resolveWalletFile(kind, filename) {
|
|||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Resolve Python executable ──────────────────────────────────────────────────
|
// ── Resolve CLI command (dev: python + cli.py, prod: bundled binary) ──────────
|
||||||
function findPython() {
|
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 repoRoot = path.join(__dirname, '..', '..')
|
||||||
const venvPython = path.join(repoRoot, 'venv', 'bin', 'python')
|
const python = fs.existsSync(path.join(repoRoot, 'venv', 'bin', 'python'))
|
||||||
return fs.existsSync(venvPython) ? venvPython : 'python3'
|
? path.join(repoRoot, 'venv', 'bin', 'python')
|
||||||
|
: 'python3'
|
||||||
|
return { exec: python, baseArgs: [path.join(repoRoot, 'src', 'cli.py')] }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Call Python CLI ────────────────────────────────────────────────────────────
|
// ── Call Python CLI ────────────────────────────────────────────────────────────
|
||||||
function callPython(command, args = {}) {
|
function callPython(command, args = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const python = findPython()
|
const { exec, baseArgs } = getCliCommand()
|
||||||
const repoRoot = path.join(__dirname, '..', '..')
|
execFile(exec, [...baseArgs, command, JSON.stringify(args)], (err, stdout, stderr) => {
|
||||||
const cliPath = path.join(repoRoot, 'src', 'cli.py')
|
|
||||||
|
|
||||||
execFile(python, [cliPath, command, JSON.stringify(args)], { cwd: repoRoot }, (err, stdout, stderr) => {
|
|
||||||
if (err) { reject(new Error(stderr || err.message)); return }
|
if (err) { reject(new Error(stderr || err.message)); return }
|
||||||
try {
|
try {
|
||||||
const result = JSON.parse(stdout.trim())
|
const result = JSON.parse(stdout.trim())
|
||||||
@@ -139,7 +142,7 @@ function createWindow() {
|
|||||||
height: 750,
|
height: 750,
|
||||||
minWidth: 320,
|
minWidth: 320,
|
||||||
minHeight: 480,
|
minHeight: 480,
|
||||||
title: 'Wallet Generator',
|
title: 'wallet-gen',
|
||||||
backgroundColor: '#0f1117',
|
backgroundColor: '#0f1117',
|
||||||
icon: path.join(__dirname, '..', 'public', 'icons', 'icon.png'),
|
icon: path.join(__dirname, '..', 'public', 'icons', 'icon.png'),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/icons/bitcoin.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icons/bitcoin.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Wallet Generator</title>
|
<title>wallet-gen</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "wallet-generator",
|
"name": "wallet-gen",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -12,10 +12,16 @@
|
|||||||
"dist": "vite build && electron-builder"
|
"dist": "vite build && electron-builder"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.walletgenerator.app",
|
"appId": "com.walletgen.app",
|
||||||
"productName": "Wallet Generator",
|
"productName": "wallet-gen",
|
||||||
"icon": "public/icons/icon.png",
|
"icon": "public/icons/icon.png",
|
||||||
"files": ["dist/**", "electron/**"],
|
"files": ["dist/**", "electron/**"],
|
||||||
|
"extraResources": [
|
||||||
|
{ "from": "resources/cli", "to": "cli" }
|
||||||
|
],
|
||||||
|
"directories": {
|
||||||
|
"output": "release"
|
||||||
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": "AppImage",
|
"target": "AppImage",
|
||||||
"category": "Finance"
|
"category": "Finance"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react'
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
base: './',
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user