mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
Refactor execSync and spawn calls to include windowsHide option for better compatibility on Windows environments.
This commit is contained in:
@@ -6,7 +6,7 @@ import { isTailscaleInstalled, isTailscaleLoggedIn, TAILSCALE_SOCKET } from "@/l
|
||||
const EXTENDED_PATH = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${process.env.PATH || ""}`;
|
||||
|
||||
function hasBrew() {
|
||||
try { execSync("which brew", { stdio: "ignore", env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
|
||||
try { execSync("which brew", { stdio: "ignore", windowsHide: true, env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
function isDaemonRunning() {
|
||||
@@ -14,6 +14,7 @@ function isDaemonRunning() {
|
||||
// Use custom socket + --json; exit 0 even when not logged in
|
||||
execSync(`tailscale --socket ${TAILSCALE_SOCKET} status --json`, {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
env: { ...process.env, PATH: EXTENDED_PATH },
|
||||
timeout: 3000
|
||||
});
|
||||
@@ -21,7 +22,7 @@ function isDaemonRunning() {
|
||||
} catch {
|
||||
// Fallback: check if tailscaled process is alive
|
||||
try {
|
||||
execSync("pgrep -x tailscaled", { stdio: "ignore", timeout: 2000 });
|
||||
execSync("pgrep -x tailscaled", { stdio: "ignore", windowsHide: true, timeout: 2000 });
|
||||
return true;
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ initDbHooks(getSettings, updateSettings);
|
||||
const EXTENDED_PATH = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${process.env.PATH || ""}`;
|
||||
|
||||
function hasBrew() {
|
||||
try { execSync("which brew", { stdio: "ignore", env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
|
||||
try { execSync("which brew", { stdio: "ignore", windowsHide: true, env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
export async function POST(request) {
|
||||
|
||||
@@ -103,7 +103,7 @@ export async function ensureCloudflared() {
|
||||
await downloadFile(url, downloadDest);
|
||||
|
||||
if (isArchive) {
|
||||
execSync(`tar -xzf "${downloadDest}" -C "${BIN_DIR}"`, { stdio: "pipe" });
|
||||
execSync(`tar -xzf "${downloadDest}" -C "${BIN_DIR}"`, { stdio: "pipe", windowsHide: true });
|
||||
fs.unlinkSync(downloadDest);
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ export function killCloudflared() {
|
||||
|
||||
// Kill any remaining cloudflared processes
|
||||
try {
|
||||
execSync("pkill -f cloudflared 2>/dev/null || true", { stdio: "ignore" });
|
||||
execSync("pkill -f cloudflared 2>/dev/null || true", { stdio: "ignore", windowsHide: true });
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const SOCKET_FLAG = IS_WINDOWS ? [] : ["--socket", TAILSCALE_SOCKET];
|
||||
// Prefer system tailscale, fallback to local bin
|
||||
function getTailscaleBin() {
|
||||
try {
|
||||
const systemPath = execSync("which tailscale 2>/dev/null || where tailscale 2>nul", { encoding: "utf8" }).trim();
|
||||
const systemPath = execSync("which tailscale 2>/dev/null || where tailscale 2>nul", { encoding: "utf8", windowsHide: true }).trim();
|
||||
if (systemPath) return systemPath;
|
||||
} catch (e) { /* not in PATH */ }
|
||||
if (fs.existsSync(TAILSCALE_BIN)) return TAILSCALE_BIN;
|
||||
@@ -41,6 +41,7 @@ export function isTailscaleLoggedIn() {
|
||||
try {
|
||||
const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, {
|
||||
encoding: "utf8",
|
||||
windowsHide: true,
|
||||
env: { ...process.env, PATH: EXTENDED_PATH },
|
||||
timeout: 5000
|
||||
});
|
||||
@@ -56,7 +57,7 @@ export function isTailscaleRunning() {
|
||||
const bin = getTailscaleBin();
|
||||
if (!bin) return false;
|
||||
try {
|
||||
const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel status --json 2>/dev/null`, { encoding: "utf8" });
|
||||
const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel status --json 2>/dev/null`, { encoding: "utf8", windowsHide: true });
|
||||
const json = JSON.parse(out);
|
||||
return Object.keys(json.AllowFunnel || {}).length > 0;
|
||||
} catch (e) {
|
||||
@@ -69,7 +70,7 @@ export function getTailscaleFunnelUrl(port) {
|
||||
const bin = getTailscaleBin();
|
||||
if (!bin) return null;
|
||||
try {
|
||||
const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, { encoding: "utf8" });
|
||||
const out = execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, { encoding: "utf8", windowsHide: true });
|
||||
const json = JSON.parse(out);
|
||||
const dnsName = json.Self?.DNSName?.replace(/\.$/, "");
|
||||
if (dnsName) return `https://${dnsName}`;
|
||||
@@ -102,7 +103,7 @@ export async function installTailscale(sudoPassword, hostname, onProgress) {
|
||||
const EXTENDED_PATH = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${process.env.PATH || ""}`;
|
||||
|
||||
function hasBrew() {
|
||||
try { execSync("which brew", { stdio: "ignore", env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
|
||||
try { execSync("which brew", { stdio: "ignore", windowsHide: true, env: { ...process.env, PATH: EXTENDED_PATH } }); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
async function installTailscaleMac(sudoPassword, log) {
|
||||
@@ -111,6 +112,7 @@ async function installTailscaleMac(sudoPassword, log) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("brew", ["install", "tailscale"], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
env: { ...process.env, PATH: EXTENDED_PATH }
|
||||
});
|
||||
child.stdout.on("data", (d) => {
|
||||
@@ -137,7 +139,8 @@ async function installTailscaleMac(sudoPassword, log) {
|
||||
log("Downloading Tailscale package...");
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("curl", ["-fL", "--progress-bar", pkgUrl, "-o", pkgPath], {
|
||||
stdio: ["ignore", "pipe", "pipe"]
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true
|
||||
});
|
||||
child.stderr.on("data", (d) => {
|
||||
const line = d.toString().trim();
|
||||
@@ -153,7 +156,8 @@ async function installTailscaleMac(sudoPassword, log) {
|
||||
log("Installing package...");
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("sudo", ["-S", "installer", "-pkg", pkgPath, "-target", "/"], {
|
||||
stdio: ["pipe", "pipe", "pipe"]
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true
|
||||
});
|
||||
let stderr = "";
|
||||
child.stderr.on("data", (d) => { stderr += d.toString(); });
|
||||
@@ -162,7 +166,7 @@ async function installTailscaleMac(sudoPassword, log) {
|
||||
if (line) log(line);
|
||||
});
|
||||
child.on("close", (c) => {
|
||||
try { execSync(`rm -f ${pkgPath}`, { stdio: "ignore" }); } catch { /* ignore */ }
|
||||
try { execSync(`rm -f ${pkgPath}`, { stdio: "ignore", windowsHide: true }); } catch { /* ignore */ }
|
||||
if (c === 0) resolve();
|
||||
else {
|
||||
const msg = (stderr.includes("incorrect password") || stderr.includes("Sorry"))
|
||||
@@ -181,7 +185,8 @@ async function installTailscaleLinux(sudoPassword, log) {
|
||||
log("Downloading install script...");
|
||||
return new Promise((resolve, reject) => {
|
||||
const curlChild = spawn("curl", ["-fsSL", "https://tailscale.com/install.sh"], {
|
||||
stdio: ["ignore", "pipe", "pipe"]
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true
|
||||
});
|
||||
let scriptContent = "";
|
||||
let curlErr = "";
|
||||
@@ -190,7 +195,7 @@ async function installTailscaleLinux(sudoPassword, log) {
|
||||
curlChild.on("exit", (code) => {
|
||||
if (code !== 0) return reject(new Error(`Failed to download install script: ${curlErr}`));
|
||||
log("Running install script...");
|
||||
const child = spawn("sudo", ["-S", "sh"], { stdio: ["pipe", "pipe", "pipe"] });
|
||||
const child = spawn("sudo", ["-S", "sh"], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (d) => {
|
||||
const line = d.toString().trim();
|
||||
@@ -225,7 +230,7 @@ async function installTailscaleWindows(log) {
|
||||
const child = spawn("powershell", [
|
||||
"-NoProfile", "-NonInteractive", "-Command",
|
||||
`Invoke-WebRequest -Uri '${msiUrl}' -OutFile '${msiPath}'`
|
||||
], { stdio: ["ignore", "pipe", "pipe"] });
|
||||
], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
||||
child.stdout.on("data", (d) => { const l = d.toString().trim(); if (l) log(l); });
|
||||
child.stderr.on("data", (d) => { const l = d.toString().trim(); if (l) log(l); });
|
||||
child.on("close", (c) => c === 0 ? resolve() : reject(new Error("Download failed")));
|
||||
@@ -236,7 +241,8 @@ async function installTailscaleWindows(log) {
|
||||
log("Installing Tailscale (UAC prompt may appear)...");
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("msiexec", ["/i", msiPath, "/quiet", "/norestart"], {
|
||||
stdio: ["ignore", "pipe", "pipe"]
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true
|
||||
});
|
||||
child.stdout.on("data", (d) => { const l = d.toString().trim(); if (l) log(l); });
|
||||
child.stderr.on("data", (d) => { const l = d.toString().trim(); if (l) log(l); });
|
||||
@@ -259,6 +265,7 @@ export async function startDaemonWithPassword(sudoPassword) {
|
||||
const bin = getTailscaleBin() || "tailscale";
|
||||
execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} status --json`, {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
env: { ...process.env, PATH: EXTENDED_PATH },
|
||||
timeout: 3000
|
||||
});
|
||||
@@ -307,7 +314,8 @@ export function startLogin(hostname) {
|
||||
if (hostname) args.push(`--hostname=${hostname}`);
|
||||
const child = spawn(bin, args, {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
detached: true
|
||||
detached: true,
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
let resolved = false;
|
||||
@@ -368,11 +376,12 @@ export async function startFunnel(port) {
|
||||
if (!bin) throw new Error("Tailscale not installed");
|
||||
|
||||
// Reset any existing funnel
|
||||
try { execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel --bg reset`, { stdio: "ignore" }); } catch (e) { /* ignore */ }
|
||||
try { execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel --bg reset`, { stdio: "ignore", windowsHide: true }); } catch (e) { /* ignore */ }
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(bin, tsArgs("funnel", "--bg", `${port}`), {
|
||||
stdio: ["ignore", "pipe", "pipe"]
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
let resolved = false;
|
||||
@@ -442,16 +451,16 @@ export async function startFunnel(port) {
|
||||
export function stopFunnel() {
|
||||
const bin = getTailscaleBin();
|
||||
if (!bin) return;
|
||||
try { execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel --bg reset`, { stdio: "ignore" }); } catch (e) { /* ignore */ }
|
||||
try { execSync(`"${bin}" ${SOCKET_FLAG.join(" ")} funnel --bg reset`, { stdio: "ignore", windowsHide: true }); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
/** Kill tailscaled daemon (runs as root, needs sudo) */
|
||||
export async function stopDaemon(sudoPassword) {
|
||||
// Try non-sudo first
|
||||
try { execSync("pkill -x tailscaled", { stdio: "ignore", timeout: 3000 }); } catch { /* ignore */ }
|
||||
try { execSync("pkill -x tailscaled", { stdio: "ignore", windowsHide: true, timeout: 3000 }); } catch { /* ignore */ }
|
||||
|
||||
// Check if still alive
|
||||
try { execSync("pgrep -x tailscaled", { stdio: "ignore", timeout: 2000 }); } catch { return; } // Dead, done
|
||||
try { execSync("pgrep -x tailscaled", { stdio: "ignore", windowsHide: true, timeout: 2000 }); } catch { return; } // Dead, done
|
||||
|
||||
// Kill with sudo password
|
||||
if (!IS_WINDOWS) {
|
||||
|
||||
@@ -29,10 +29,10 @@ function checkCertInstalledMac(certPath) {
|
||||
try {
|
||||
const fingerprint = getCertFingerprint(certPath).replace(/:/g, "");
|
||||
// security verify-cert returns 0 only if cert is trusted by system policy
|
||||
exec(`security verify-cert -c "${certPath}" -p ssl -k /Library/Keychains/System.keychain 2>/dev/null`, (error) => {
|
||||
exec(`security verify-cert -c "${certPath}" -p ssl -k /Library/Keychains/System.keychain 2>/dev/null`, { windowsHide: true }, (error) => {
|
||||
if (!error) return resolve(true);
|
||||
// Fallback: check if fingerprint appears in System keychain with trust
|
||||
exec(`security dump-trust-settings -d 2>/dev/null | grep -i "${fingerprint}"`, (err2, stdout2) => {
|
||||
exec(`security dump-trust-settings -d 2>/dev/null | grep -i "${fingerprint}"`, { windowsHide: true }, (err2, stdout2) => {
|
||||
resolve(!err2 && !!stdout2?.trim());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ function executeElevatedPowerShell(psScriptPath, timeoutMs = 30000) {
|
||||
function isSudoAvailable() {
|
||||
if (IS_WIN) return false;
|
||||
try {
|
||||
execSync("command -v sudo", { stdio: "ignore" });
|
||||
execSync("command -v sudo", { stdio: "ignore", windowsHide: true });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -78,8 +78,8 @@ function execWithPassword(command, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const useSudo = isSudoAvailable();
|
||||
const child = useSudo
|
||||
? spawn("sudo", ["-S", "sh", "-c", command], { stdio: ["pipe", "pipe", "pipe"] })
|
||||
: spawn("sh", ["-c", command], { stdio: ["ignore", "pipe", "pipe"] });
|
||||
? spawn("sudo", ["-S", "sh", "-c", command], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true })
|
||||
: spawn("sh", ["-c", command], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
@@ -76,7 +76,7 @@ function getProcessUsingPort443() {
|
||||
if (processMatch) return processMatch[1].replace(".exe", "");
|
||||
}
|
||||
} else {
|
||||
const result = execSync("lsof -i :443", { encoding: "utf8" });
|
||||
const result = execSync("lsof -i :443", { encoding: "utf8", windowsHide: true });
|
||||
const lines = result.trim().split("\n");
|
||||
if (lines.length > 1) return lines[1].split(/\s+/)[0];
|
||||
}
|
||||
@@ -110,9 +110,9 @@ function killProcess(pid, force = false, sudoPassword = null) {
|
||||
const cmd = `pkill -${sig} -P ${pid} 2>/dev/null; kill -${sig} ${pid} 2>/dev/null`;
|
||||
if (sudoPassword) {
|
||||
const { execWithPassword } = require("./dns/dnsConfig");
|
||||
execWithPassword(cmd, sudoPassword).catch(() => exec(cmd, () => { }));
|
||||
execWithPassword(cmd, sudoPassword).catch(() => exec(cmd, { windowsHide: true }, () => { }));
|
||||
} else {
|
||||
exec(cmd, () => { });
|
||||
exec(cmd, { windowsHide: true }, () => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,7 @@ function getPort443Owner(sudoPassword) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
exec(`ps aux | grep "[s]erver.js"`, (err, stdout) => {
|
||||
exec(`ps aux | grep "[s]erver.js"`, { windowsHide: true }, (err, stdout) => {
|
||||
if (!stdout?.trim()) return resolve(null);
|
||||
for (const line of stdout.split("\n")) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
@@ -252,7 +252,7 @@ async function killLeftoverMitm(sudoPassword) {
|
||||
const { execWithPassword } = require("./dns/dnsConfig");
|
||||
await execWithPassword(`pkill -SIGKILL -f "${escaped}" 2>/dev/null || true`, sudoPassword).catch(() => { });
|
||||
} else {
|
||||
exec(`pkill -SIGKILL -f "${escaped}" 2>/dev/null || true`, () => { });
|
||||
exec(`pkill -SIGKILL -f "${escaped}" 2>/dev/null || true`, { windowsHide: true }, () => { });
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
} catch { /* ignore */ }
|
||||
@@ -489,7 +489,7 @@ async function startServer(apiKey, sudoPassword) {
|
||||
].join(" ");
|
||||
serverProcess = spawn(
|
||||
"sudo", ["-S", "-E", "sh", "-c", inlineCmd],
|
||||
{ detached: false, stdio: ["pipe", "pipe", "pipe"] }
|
||||
{ detached: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"] }
|
||||
);
|
||||
serverProcess.stdin.write(`${sudoPassword}\n`);
|
||||
serverProcess.stdin.end();
|
||||
@@ -497,6 +497,7 @@ async function startServer(apiKey, sudoPassword) {
|
||||
// Docker/minimal images: no sudo — same as Windows-style direct spawn
|
||||
serverProcess = spawn(process.execPath, [SERVER_PATH], {
|
||||
detached: false,
|
||||
windowsHide: true,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: {
|
||||
...process.env,
|
||||
|
||||
@@ -222,7 +222,7 @@ const server = https.createServer(sslOptions, async (req, res) => {
|
||||
// Kill any process occupying LOCAL_PORT before binding
|
||||
function killPort(port) {
|
||||
try {
|
||||
const pids = execSync(`lsof -ti :${port}`, { encoding: "utf-8" }).trim();
|
||||
const pids = execSync(`lsof -ti :${port}`, { encoding: "utf-8", windowsHide: true }).trim();
|
||||
if (!pids) return;
|
||||
const pidList = pids.split("\n");
|
||||
pidList.forEach(pid => {
|
||||
|
||||
Reference in New Issue
Block a user