import { existsSync, writeFileSync, unlinkSync, mkdirSync, realpathSync } from "node:fs"; import { execFileSync, execSync } from "node:child_process"; import { homedir } from "node:os"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const APP_LABEL = "com.omniroute.autostart"; const WIN_REG_VALUE = "OmniRoute"; const LINUX_SERVICE_NAME = "omniroute.service"; const LINUX_DESKTOP_NAME = "omniroute.desktop"; function resolveCliPath() { const candidates = []; if (process.argv[1]) candidates.push(process.argv[1]); try { const which = execSync("command -v omniroute 2>/dev/null", { encoding: "utf8" }).trim(); if (which) candidates.push(which); } catch { // command -v unavailable } candidates.push(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "omniroute.mjs")); for (const candidate of candidates) { if (!candidate) continue; try { const resolved = realpathSync(candidate); if (resolved.endsWith("omniroute.mjs") && existsSync(resolved)) return resolved; } catch { // try next candidate } } const fallback = candidates[candidates.length - 1]; return existsSync(fallback) ? fallback : null; } function quoteExecArg(value) { if (!/[ \t"'\\]/.test(value)) return value; return `"${value.replace(/(["\\])/g, "\\$1")}"`; } function buildServeExecLine(cliPath, { tray = false } = {}) { const parts = [quoteExecArg(process.execPath), quoteExecArg(cliPath), "serve", "--no-open"]; if (tray) parts.push("--tray"); return parts.join(" "); } function isGraphicalLinuxSession() { if (process.env.DISPLAY || process.env.WAYLAND_DISPLAY) return true; if (process.env.XDG_CURRENT_DESKTOP) return true; return false; } function userHomeDir() { return process.env.HOME || homedir(); } function linuxSystemdUnitPath() { return join(userHomeDir(), ".config", "systemd", "user", LINUX_SERVICE_NAME); } function linuxDesktopPath() { return join(userHomeDir(), ".config", "autostart", LINUX_DESKTOP_NAME); } function runUserSystemctl(args, { ignoreFailure = true } = {}) { try { execFileSync("systemctl", ["--user", ...args], { stdio: "ignore" }); return true; } catch (err) { if (!ignoreFailure) throw err; return false; } } function isSystemdUserAvailable() { try { execFileSync("systemctl", ["--user", "--version"], { stdio: "ignore" }); return true; } catch { return false; } } function isSystemdServiceEnabled() { if (!existsSync(linuxSystemdUnitPath())) return false; try { execFileSync("systemctl", ["--user", "is-enabled", LINUX_SERVICE_NAME], { stdio: "ignore" }); return true; } catch { // systemctl --user can't query the bus (headless environments / CI runners). // Treat the presence of the unit file as the source of truth, matching the // fallback used in enableLinux() where unit-file existence counts as success. return true; } } function tryEnableLinger() { try { const user = process.env.USER || process.env.LOGNAME || execFileSync("whoami", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); if (!user) return false; execFileSync("loginctl", ["enable-linger", user], { stdio: "ignore" }); return true; } catch { return false; } } function writeLinuxSystemdUnit(cliPath) { const unitDir = dirname(linuxSystemdUnitPath()); mkdirSync(unitDir, { recursive: true }); const envFile = join(userHomeDir(), ".omniroute", ".env"); const lines = [ "[Unit]", "Description=OmniRoute AI proxy router", "After=network-online.target", "Wants=network-online.target", "", "[Service]", "Type=simple", `ExecStart=${buildServeExecLine(cliPath, { tray: false })}`, "Restart=on-failure", "RestartSec=5", ]; if (existsSync(envFile)) lines.push(`EnvironmentFile=-${envFile}`); lines.push("", "[Install]", "WantedBy=default.target", ""); writeFileSync(linuxSystemdUnitPath(), `${lines.join("\n")}\n`, { mode: 0o644 }); } function writeLinuxDesktopEntry(cliPath) { const dir = dirname(linuxDesktopPath()); mkdirSync(dir, { recursive: true }); const desktop = [ "[Desktop Entry]", "Type=Application", "Name=OmniRoute", "Comment=AI proxy router with auto fallback", `Exec=${buildServeExecLine(cliPath, { tray: true })}`, "Terminal=false", "Hidden=false", "X-GNOME-Autostart-enabled=true", ].join("\n") + "\n"; writeFileSync(linuxDesktopPath(), desktop, { mode: 0o644 }); } export function getAutostartStatus() { if (process.platform === "linux") { const systemdUnit = linuxSystemdUnitPath(); const desktopFile = linuxDesktopPath(); const systemdUnitExists = existsSync(systemdUnit); const systemdEnabled = isSystemdServiceEnabled() || systemdUnitExists; const desktopEnabled = existsSync(desktopFile); const enabled = systemdEnabled || desktopEnabled; let mechanism = null; if (systemdEnabled) mechanism = "systemd-user"; else if (desktopEnabled) mechanism = "xdg-desktop"; return { enabled, mechanism, systemdUnit: systemdUnitExists ? systemdUnit : null, desktopFile: desktopEnabled ? desktopFile : null, linger: tryReadLingerEnabled(), }; } return { enabled: isAutostartEnabled(), mechanism: null }; } function tryReadLingerEnabled() { try { const user = process.env.USER || process.env.LOGNAME; if (!user) return null; const out = execFileSync("loginctl", ["show-user", user, "-p", "Linger"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }); return out.includes("Linger=yes"); } catch { return null; } } export function enable() { if (process.platform === "darwin") return enableMac(); if (process.platform === "win32") return enableWin(); if (process.platform === "linux") return enableLinux(); return false; } export function disable() { if (process.platform === "darwin") return disableMac(); if (process.platform === "win32") return disableWin(); if (process.platform === "linux") return disableLinux(); return false; } export function isAutostartEnabled() { if (process.platform === "darwin") return isEnabledMac(); if (process.platform === "win32") return isEnabledWin(); if (process.platform === "linux") return isEnabledLinux(); return false; } /** * Pure parse of `launchctl list