// OpenWA portable launcher — compile with build-launcher.ps1 (csc.exe). // Layout expected next to this EXE: // OpenWA.exe // node\node.exe // app\dist\main.js // app\.env using System; using System.Diagnostics; using System.IO; using System.Threading; internal static class Program { private const string Title = "OpenWA"; private static int Main() { Console.Title = Title; string root = AppDomain.CurrentDomain.BaseDirectory.TrimEnd( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar ); string nodeExe = Path.Combine(root, "node", "node.exe"); string appDir = Path.Combine(root, "app"); string mainJs = Path.Combine(appDir, "dist", "main.js"); string dataDir = Path.Combine(appDir, "data"); string pidFile = Path.Combine(dataDir, "openwa.pid"); string puppeteerCache = Path.Combine(dataDir, "puppeteer"); if (!File.Exists(nodeExe)) { Fail( "Portable Node missing: node\\node.exe\n" + "Rebuild the package with scripts\\windows-portable\\pack.ps1" ); return 1; } if (!File.Exists(mainJs)) { Fail( "App build missing: app\\dist\\main.js\n" + "Rebuild the package on the build PC (npm run portable:windows)." ); return 1; } Directory.CreateDirectory(dataDir); Directory.CreateDirectory(puppeteerCache); Directory.CreateDirectory(Path.Combine(dataDir, "sessions")); Directory.CreateDirectory(Path.Combine(dataDir, "media")); Directory.CreateDirectory(Path.Combine(dataDir, "plugins")); if (IsAlreadyRunning(pidFile)) { Fail( "OpenWA is already running (see app\\data\\openwa.pid).\n" + "Close that window or end the node process, then try again." ); return 2; } var psi = new ProcessStartInfo { FileName = nodeExe, Arguments = "dist\\main.js", WorkingDirectory = appDir, UseShellExecute = false, RedirectStandardOutput = false, RedirectStandardError = false, }; // Environment for child process psi.EnvironmentVariables["NODE_ENV"] = "production"; psi.EnvironmentVariables["PUPPETEER_CACHE_DIR"] = puppeteerCache; // Prefer portable node for any child tools string nodeDir = Path.Combine(root, "node"); string path = psi.EnvironmentVariables["PATH"] ?? ""; psi.EnvironmentVariables["PATH"] = nodeDir + Path.PathSeparator + path; Console.WriteLine("========================================"); Console.WriteLine(" OpenWA portable"); Console.WriteLine(" Dashboard/API: http://localhost:2785"); Console.WriteLine(" API key file: app\\data\\.api-key"); Console.WriteLine(" Ctrl+C to stop"); Console.WriteLine("========================================"); Console.WriteLine(); Process process; try { process = Process.Start(psi); } catch (Exception ex) { Fail("Failed to start Node: " + ex.Message); return 1; } if (process == null) { Fail("Failed to start Node (null process)."); return 1; } try { File.WriteAllText(pidFile, process.Id.ToString()); } catch { // non-fatal } Console.CancelKeyPress += (sender, e) => { e.Cancel = true; try { if (!process.HasExited) { Console.WriteLine("Stopping OpenWA..."); process.Kill(); } } catch { // ignore } }; process.WaitForExit(); TryDelete(pidFile); return process.ExitCode; } private static bool IsAlreadyRunning(string pidFile) { if (!File.Exists(pidFile)) { return false; } string text; try { text = File.ReadAllText(pidFile).Trim(); } catch { return false; } int pid; if (!int.TryParse(text, out pid) || pid <= 0) { TryDelete(pidFile); return false; } try { Process existing = Process.GetProcessById(pid); if (existing != null && !existing.HasExited) { string name = (existing.ProcessName ?? "").ToLowerInvariant(); if (name.Contains("node") || name.Contains("openwa")) { return true; } } } catch (ArgumentException) { // process gone } catch { // treat as not running } TryDelete(pidFile); return false; } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { // ignore } } private static void Fail(string message) { Console.Error.WriteLine(); Console.Error.WriteLine("ERROR: " + message); Console.Error.WriteLine(); Console.Error.WriteLine("Press Enter to close..."); try { Console.ReadLine(); } catch { Thread.Sleep(5000); } } }