mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 21:00:42 -07:00
Merge pull request #5 from Snowy7/fix/dev-mode-sidecar
Fix dev mode sidecar and cross-platform HuggingFace cache paths
This commit is contained in:
+12
-5
@@ -78,25 +78,32 @@ Thank you for your interest in contributing to Voicebox! This document provides
|
||||
This creates the SQLite database at `data/voicebox.db`.
|
||||
|
||||
5. **Start development servers**
|
||||
|
||||
**Terminal 1: Backend server**
|
||||
|
||||
Development requires two terminals: one for the Python backend, one for the Tauri app.
|
||||
|
||||
**Terminal 1: Backend server** (start this first)
|
||||
```bash
|
||||
cd backend
|
||||
source venv/bin/activate # Activate venv if not already active
|
||||
bun run dev:server
|
||||
# Or manually: uvicorn main:app --reload --port 8000
|
||||
# Or manually: uvicorn main:app --reload --port 17493
|
||||
```
|
||||
Backend will be available at `http://localhost:8000`
|
||||
|
||||
Backend will be available at `http://localhost:17493`
|
||||
|
||||
**Terminal 2: Desktop app**
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
This will:
|
||||
- Create a placeholder sidecar binary (for Tauri compilation)
|
||||
- Start Vite dev server on port 5173
|
||||
- Launch Tauri window pointing to localhost:5173
|
||||
- Connect to the Python server you started in Terminal 1
|
||||
- Enable hot reload
|
||||
|
||||
> **Note:** In dev mode, the app connects to your manually-started Python server.
|
||||
> The bundled server binary is only used in production builds.
|
||||
|
||||
**Optional: Web app**
|
||||
```bash
|
||||
bun run dev:web
|
||||
|
||||
+3
-2
@@ -9,10 +9,11 @@
|
||||
"landing"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "cd tauri && bun run tauri dev",
|
||||
"dev": "bun run setup:dev && cd tauri && bun run tauri dev",
|
||||
"dev:web": "cd web && bun run dev",
|
||||
"dev:landing": "cd landing && bun run dev",
|
||||
"dev:server": "uvicorn backend.main:app --reload --port 8000",
|
||||
"dev:server": "uvicorn backend.main:app --reload --port 17493",
|
||||
"setup:dev": "bun run scripts/setup-dev-sidecar.js",
|
||||
"build": "cd tauri && bun run tauri build",
|
||||
"build:web": "cd web && bun run build",
|
||||
"build:landing": "cd landing && bun run build",
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Creates placeholder sidecar binaries for development mode.
|
||||
*
|
||||
* In dev mode, Tauri requires the sidecar binary files to exist at compile time,
|
||||
* even though developers typically run the Python server manually.
|
||||
*
|
||||
* This script creates minimal placeholder binaries that allow Tauri to compile.
|
||||
* The actual server should be started separately with `bun run dev:server`.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, writeFileSync, statSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const BINARIES_DIR = join(__dirname, '..', 'tauri', 'src-tauri', 'binaries');
|
||||
|
||||
// Minimum size to consider a binary "real" (placeholder is ~256 bytes, real is MBs)
|
||||
const MIN_REAL_BINARY_SIZE = 10000;
|
||||
|
||||
// Get the current platform's target triple
|
||||
function getTargetTriple() {
|
||||
try {
|
||||
const triple = execSync('rustc --print host-tuple', { encoding: 'utf-8' }).trim();
|
||||
return triple;
|
||||
} catch {
|
||||
// Fallback detection
|
||||
const platform = process.platform;
|
||||
const arch = process.arch;
|
||||
|
||||
if (platform === 'win32') {
|
||||
return arch === 'x64' ? 'x86_64-pc-windows-msvc' : 'i686-pc-windows-msvc';
|
||||
} else if (platform === 'darwin') {
|
||||
return arch === 'arm64' ? 'aarch64-apple-darwin' : 'x86_64-apple-darwin';
|
||||
} else if (platform === 'linux') {
|
||||
return arch === 'x64' ? 'x86_64-unknown-linux-gnu' : 'aarch64-unknown-linux-gnu';
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported platform: ${platform}/${arch}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a minimal executable for the platform
|
||||
function createPlaceholderBinary(targetTriple) {
|
||||
const isWindows = targetTriple.includes('windows');
|
||||
const binaryName = `voicebox-server-${targetTriple}${isWindows ? '.exe' : ''}`;
|
||||
const binaryPath = join(BINARIES_DIR, binaryName);
|
||||
|
||||
// Check if real binary already exists (larger than our placeholder)
|
||||
if (existsSync(binaryPath)) {
|
||||
try {
|
||||
const stats = statSync(binaryPath);
|
||||
if (stats.size > MIN_REAL_BINARY_SIZE) {
|
||||
console.log(`Real binary already exists: ${binaryName} (${(stats.size / 1024 / 1024).toFixed(1)} MB)`);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// File exists but can't stat - try to replace it
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure binaries directory exists
|
||||
if (!existsSync(BINARIES_DIR)) {
|
||||
mkdirSync(BINARIES_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
// Create a minimal valid Windows PE executable that exits with code 1
|
||||
// This is the smallest valid PE that Windows will accept
|
||||
const minimalPE = Buffer.from([
|
||||
// DOS Header
|
||||
0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
|
||||
0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||
// DOS Stub
|
||||
0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68,
|
||||
0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F,
|
||||
0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20,
|
||||
0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// PE Signature
|
||||
0x50, 0x45, 0x00, 0x00,
|
||||
// COFF Header (x64)
|
||||
0x64, 0x86, // Machine: AMD64
|
||||
0x01, 0x00, // NumberOfSections: 1
|
||||
0x00, 0x00, 0x00, 0x00, // TimeDateStamp
|
||||
0x00, 0x00, 0x00, 0x00, // PointerToSymbolTable
|
||||
0x00, 0x00, 0x00, 0x00, // NumberOfSymbols
|
||||
0xF0, 0x00, // SizeOfOptionalHeader
|
||||
0x22, 0x00, // Characteristics: EXECUTABLE_IMAGE | LARGE_ADDRESS_AWARE
|
||||
// Optional Header (PE32+)
|
||||
0x0B, 0x02, // Magic: PE32+
|
||||
0x00, 0x00, // Linker version
|
||||
0x00, 0x00, 0x00, 0x00, // SizeOfCode
|
||||
0x00, 0x00, 0x00, 0x00, // SizeOfInitializedData
|
||||
0x00, 0x00, 0x00, 0x00, // SizeOfUninitializedData
|
||||
0x00, 0x10, 0x00, 0x00, // AddressOfEntryPoint
|
||||
0x00, 0x00, 0x00, 0x00, // BaseOfCode
|
||||
0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, // ImageBase
|
||||
0x00, 0x10, 0x00, 0x00, // SectionAlignment
|
||||
0x00, 0x02, 0x00, 0x00, // FileAlignment
|
||||
0x06, 0x00, 0x00, 0x00, // OS version
|
||||
0x00, 0x00, 0x00, 0x00, // Image version
|
||||
0x06, 0x00, 0x00, 0x00, // Subsystem version
|
||||
0x00, 0x00, 0x00, 0x00, // Win32VersionValue
|
||||
0x00, 0x20, 0x00, 0x00, // SizeOfImage
|
||||
0x00, 0x02, 0x00, 0x00, // SizeOfHeaders
|
||||
0x00, 0x00, 0x00, 0x00, // CheckSum
|
||||
0x03, 0x00, // Subsystem: CONSOLE
|
||||
0x60, 0x01, // DllCharacteristics
|
||||
// Stack/Heap sizes (8 bytes each for PE32+)
|
||||
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // LoaderFlags
|
||||
0x10, 0x00, 0x00, 0x00, // NumberOfRvaAndSizes
|
||||
]);
|
||||
|
||||
// Pad to 512 bytes minimum for valid PE
|
||||
const paddedPE = Buffer.alloc(512);
|
||||
minimalPE.copy(paddedPE);
|
||||
writeFileSync(binaryPath, paddedPE);
|
||||
} else {
|
||||
// Create a minimal shell script for Unix-like systems
|
||||
const script = `#!/bin/sh
|
||||
echo "[voicebox-server] Dev mode placeholder - start the real server with: bun run dev:server"
|
||||
exit 1
|
||||
`;
|
||||
writeFileSync(binaryPath, script, { mode: 0o755 });
|
||||
}
|
||||
|
||||
console.log(`Created dev placeholder: ${binaryName}`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log('Setting up development sidecar...');
|
||||
console.log('');
|
||||
|
||||
const targetTriple = getTargetTriple();
|
||||
console.log(`Platform: ${targetTriple}`);
|
||||
|
||||
createPlaceholderBinary(targetTriple);
|
||||
|
||||
console.log('');
|
||||
console.log('Sidecar setup complete.');
|
||||
console.log('For development, start the Python server in a separate terminal:');
|
||||
console.log(' bun run dev:server');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
main();
|
||||
+126
-21
@@ -178,14 +178,41 @@ async fn start_server(
|
||||
println!("Data directory: {:?}", data_dir);
|
||||
println!("Remote mode: {}", remote.unwrap_or(false));
|
||||
|
||||
let mut sidecar = app
|
||||
.shell()
|
||||
.sidecar("voicebox-server")
|
||||
.map_err(|e| {
|
||||
let sidecar_result = app.shell().sidecar("voicebox-server");
|
||||
|
||||
let mut sidecar = match sidecar_result {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get sidecar: {}", e);
|
||||
eprintln!("This usually means the binary is not bundled correctly or doesn't have execute permissions");
|
||||
format!("Failed to get sidecar: {}", e)
|
||||
})?;
|
||||
|
||||
// In dev mode, check if the server is already running (started manually)
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
eprintln!("Dev mode: Checking if server is already running on port {}...", SERVER_PORT);
|
||||
|
||||
// Try to connect to the server port
|
||||
use std::net::TcpStream;
|
||||
if TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", SERVER_PORT).parse().unwrap(),
|
||||
std::time::Duration::from_secs(1),
|
||||
).is_ok() {
|
||||
println!("Found server already running on port {}", SERVER_PORT);
|
||||
return Ok(format!("http://127.0.0.1:{}", SERVER_PORT));
|
||||
}
|
||||
|
||||
eprintln!("");
|
||||
eprintln!("=================================================================");
|
||||
eprintln!("DEV MODE: No server found on port {}", SERVER_PORT);
|
||||
eprintln!("");
|
||||
eprintln!("Start the Python server in a separate terminal:");
|
||||
eprintln!(" bun run dev:server");
|
||||
eprintln!("=================================================================");
|
||||
eprintln!("");
|
||||
}
|
||||
|
||||
return Err(format!("Failed to start server. In dev mode, run 'bun run dev:server' in a separate terminal."));
|
||||
}
|
||||
};
|
||||
|
||||
println!("Sidecar command created successfully");
|
||||
|
||||
@@ -204,17 +231,47 @@ async fn start_server(
|
||||
}
|
||||
|
||||
println!("Spawning server process...");
|
||||
let (mut rx, child) = sidecar
|
||||
.spawn()
|
||||
.map_err(|e| {
|
||||
let spawn_result = sidecar.spawn();
|
||||
|
||||
let (mut rx, child) = match spawn_result {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to spawn server process: {}", e);
|
||||
eprintln!("This could be due to:");
|
||||
eprintln!(" - Missing or corrupted binary");
|
||||
eprintln!(" - Missing execute permissions");
|
||||
eprintln!(" - Code signing issues on macOS");
|
||||
eprintln!(" - Missing dependencies");
|
||||
format!("Failed to spawn: {}", e)
|
||||
})?;
|
||||
|
||||
// In dev mode, check if a manually-started server is available
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use std::net::TcpStream;
|
||||
if TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", SERVER_PORT).parse().unwrap(),
|
||||
std::time::Duration::from_secs(1),
|
||||
).is_ok() {
|
||||
println!("Found manually-started server on port {}", SERVER_PORT);
|
||||
return Ok(format!("http://127.0.0.1:{}", SERVER_PORT));
|
||||
}
|
||||
|
||||
eprintln!("");
|
||||
eprintln!("=================================================================");
|
||||
eprintln!("DEV MODE: Server binary failed to start");
|
||||
eprintln!("");
|
||||
eprintln!("Start the Python server in a separate terminal:");
|
||||
eprintln!(" bun run dev:server");
|
||||
eprintln!("=================================================================");
|
||||
eprintln!("");
|
||||
return Err("Dev mode: Start server manually with 'bun run dev:server'".to_string());
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
eprintln!("This could be due to:");
|
||||
eprintln!(" - Missing or corrupted binary");
|
||||
eprintln!(" - Missing execute permissions");
|
||||
eprintln!(" - Code signing issues on macOS");
|
||||
eprintln!(" - Missing dependencies");
|
||||
return Err(format!("Failed to spawn: {}", e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("Server process spawned, waiting for ready signal...");
|
||||
println!("=================================================================");
|
||||
@@ -239,6 +296,22 @@ async fn start_server(
|
||||
eprintln!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
// In dev mode, check if a manual server came up during the wait
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use std::net::TcpStream;
|
||||
if TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", SERVER_PORT).parse().unwrap(),
|
||||
std::time::Duration::from_secs(1),
|
||||
).is_ok() {
|
||||
// Kill the placeholder process
|
||||
let _ = state.child.lock().unwrap().take();
|
||||
println!("Found manually-started server on port {}", SERVER_PORT);
|
||||
return Ok(format!("http://127.0.0.1:{}", SERVER_PORT));
|
||||
}
|
||||
}
|
||||
|
||||
return Err("Server startup timeout - check Console.app for detailed logs".to_string());
|
||||
}
|
||||
|
||||
@@ -273,10 +346,42 @@ async fn start_server(
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
eprintln!("Server process ended unexpectedly during startup!");
|
||||
eprintln!("The server binary may have crashed or exited with an error.");
|
||||
eprintln!("Check Console.app logs for more details (search for 'voicebox')");
|
||||
return Err("Server process ended unexpectedly".to_string());
|
||||
// In dev mode, this is expected when using the placeholder binary
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use std::net::TcpStream;
|
||||
eprintln!("Server process ended (dev mode placeholder detected)");
|
||||
|
||||
// Check if a manually-started server is available
|
||||
if TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", SERVER_PORT).parse().unwrap(),
|
||||
std::time::Duration::from_secs(1),
|
||||
).is_ok() {
|
||||
// Clean up state
|
||||
let _ = state.child.lock().unwrap().take();
|
||||
let _ = state.server_pid.lock().unwrap().take();
|
||||
println!("Found manually-started server on port {}", SERVER_PORT);
|
||||
return Ok(format!("http://127.0.0.1:{}", SERVER_PORT));
|
||||
}
|
||||
|
||||
eprintln!("");
|
||||
eprintln!("=================================================================");
|
||||
eprintln!("DEV MODE: No bundled server binary available");
|
||||
eprintln!("");
|
||||
eprintln!("Start the Python server in a separate terminal:");
|
||||
eprintln!(" bun run dev:server");
|
||||
eprintln!("=================================================================");
|
||||
eprintln!("");
|
||||
return Err("Dev mode: Start server manually with 'bun run dev:server'".to_string());
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
eprintln!("Server process ended unexpectedly during startup!");
|
||||
eprintln!("The server binary may have crashed or exited with an error.");
|
||||
eprintln!("Check Console.app logs for more details (search for 'voicebox')");
|
||||
return Err("Server process ended unexpectedly".to_string());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Timeout on this recv, continue loop
|
||||
|
||||
Reference in New Issue
Block a user