diff --git a/backend/server.py b/backend/server.py index 2dfb0cd9..15f94a67 100644 --- a/backend/server.py +++ b/backend/server.py @@ -5,45 +5,80 @@ This module provides an entry point that works with PyInstaller by using absolute imports instead of relative imports. """ -import argparse -import uvicorn +import sys +import logging -# Import the FastAPI app from the backend package -from backend.main import app -from backend import config, database +# Set up logging FIRST, before any imports that might fail +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + stream=sys.stderr, # Log to stderr so it's captured by Tauri +) +logger = logging.getLogger(__name__) + +# Log startup immediately to confirm binary execution +logger.info("=" * 60) +logger.info("voicebox-server starting up...") +logger.info(f"Python version: {sys.version}") +logger.info(f"Executable: {sys.executable}") +logger.info(f"Arguments: {sys.argv}") +logger.info("=" * 60) + +try: + import argparse + import uvicorn + logger.info("Standard library imports successful") + + # Import the FastAPI app from the backend package + logger.info("Importing backend modules...") + from backend.main import app + from backend import config, database + logger.info("Backend imports successful") +except Exception as e: + logger.error(f"Failed to import required modules: {e}", exc_info=True) + sys.exit(1) if __name__ == "__main__": - parser = argparse.ArgumentParser(description="voicebox backend server") - parser.add_argument( - "--host", - type=str, - default="127.0.0.1", - help="Host to bind to (use 0.0.0.0 for remote access)", - ) - parser.add_argument( - "--port", - type=int, - default=8000, - help="Port to bind to", - ) - parser.add_argument( - "--data-dir", - type=str, - default=None, - help="Data directory for database, profiles, and generated audio", - ) - args = parser.parse_args() + try: + parser = argparse.ArgumentParser(description="voicebox backend server") + parser.add_argument( + "--host", + type=str, + default="127.0.0.1", + help="Host to bind to (use 0.0.0.0 for remote access)", + ) + parser.add_argument( + "--port", + type=int, + default=8000, + help="Port to bind to", + ) + parser.add_argument( + "--data-dir", + type=str, + default=None, + help="Data directory for database, profiles, and generated audio", + ) + args = parser.parse_args() + logger.info(f"Parsed arguments: host={args.host}, port={args.port}, data_dir={args.data_dir}") - # Set data directory if provided - if args.data_dir: - config.set_data_dir(args.data_dir) + # Set data directory if provided + if args.data_dir: + logger.info(f"Setting data directory to: {args.data_dir}") + config.set_data_dir(args.data_dir) - # Initialize database after data directory is set - database.init_db() + # Initialize database after data directory is set + logger.info("Initializing database...") + database.init_db() + logger.info("Database initialized successfully") - uvicorn.run( - app, - host=args.host, - port=args.port, - log_level="info", - ) + logger.info(f"Starting uvicorn server on {args.host}:{args.port}...") + uvicorn.run( + app, + host=args.host, + port=args.port, + log_level="info", + ) + except Exception as e: + logger.error(f"Server startup failed: {e}", exc_info=True) + sys.exit(1) diff --git a/tauri/src-tauri/Entitlements.plist b/tauri/src-tauri/Entitlements.plist new file mode 100644 index 00000000..46f67566 --- /dev/null +++ b/tauri/src-tauri/Entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + diff --git a/tauri/src-tauri/gen/Assets.car b/tauri/src-tauri/gen/Assets.car index 0795abd4..ce0088a7 100644 Binary files a/tauri/src-tauri/gen/Assets.car and b/tauri/src-tauri/gen/Assets.car differ diff --git a/tauri/src-tauri/src/main.rs b/tauri/src-tauri/src/main.rs index 68a4c29e..13f0894c 100644 --- a/tauri/src-tauri/src/main.rs +++ b/tauri/src-tauri/src/main.rs @@ -31,10 +31,21 @@ async fn start_server( std::fs::create_dir_all(&data_dir) .map_err(|e| format!("Failed to create data dir: {}", e))?; + println!("================================================================="); + println!("Starting voicebox-server sidecar"); + println!("Data directory: {:?}", data_dir); + println!("Remote mode: {}", remote.unwrap_or(false)); + let mut sidecar = app .shell() .sidecar("voicebox-server") - .map_err(|e| format!("Failed to get sidecar: {}", e))?; + .map_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) + })?; + + println!("Sidecar command created successfully"); // Pass data directory to Python server sidecar = sidecar.args([ @@ -48,9 +59,21 @@ async fn start_server( sidecar = sidecar.args(["--host", "0.0.0.0"]); } + println!("Spawning server process..."); let (mut rx, child) = sidecar .spawn() - .map_err(|e| format!("Failed to spawn: {}", e))?; + .map_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) + })?; + + println!("Server process spawned, waiting for ready signal..."); + println!("================================================================="); // Store child process *state.child.lock().unwrap() = Some(child); @@ -58,10 +81,18 @@ async fn start_server( // Wait for server to be ready by listening for startup log let timeout = tokio::time::Duration::from_secs(30); let start_time = tokio::time::Instant::now(); + let mut error_output = Vec::new(); loop { if start_time.elapsed() > timeout { - return Err("Server startup timeout".to_string()); + eprintln!("Server startup timeout after 30 seconds"); + if !error_output.is_empty() { + eprintln!("Collected error output:"); + for line in &error_output { + eprintln!(" {}", line); + } + } + return Err("Server startup timeout - check Console.app for detailed logs".to_string()); } match tokio::time::timeout(tokio::time::Duration::from_millis(100), rx.recv()).await { @@ -77,9 +108,14 @@ async fn start_server( } } tauri_plugin_shell::process::CommandEvent::Stderr(line) => { - let line_str = String::from_utf8_lossy(&line); + let line_str = String::from_utf8_lossy(&line).to_string(); eprintln!("Server: {}", line_str); + // Collect error lines for debugging + if line_str.contains("ERROR") || line_str.contains("Error") || line_str.contains("Failed") { + error_output.push(line_str.clone()); + } + // Uvicorn logs to stderr, so check there too if line_str.contains("Uvicorn running") || line_str.contains("Application startup complete") { println!("Server is ready!"); @@ -90,6 +126,9 @@ 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()); } Err(_) => { diff --git a/tauri/src-tauri/tauri.conf.json b/tauri/src-tauri/tauri.conf.json index 96cd02a2..a02c5f5f 100644 --- a/tauri/src-tauri/tauri.conf.json +++ b/tauri/src-tauri/tauri.conf.json @@ -24,7 +24,8 @@ "macOS": { "frameworks": [], "minimumSystemVersion": "11.0", - "infoPlist": "Info.plist" + "infoPlist": "Info.plist", + "entitlements": "Entitlements.plist" }, "resources": { "gen/Assets.car": "./",