MITM Warning

This commit is contained in:
decolua
2026-05-16 11:39:39 +07:00
parent 18f87f43ca
commit b5979dfbd6
37 changed files with 137 additions and 64 deletions
+7 -2
View File
@@ -698,6 +698,9 @@ function startServer(latestVersion) {
// old NSStatusItem released before a new tray process can register;
// otherwise the bgProcess tray silently fails ("works sometimes").
try { await require("./src/cli/tray/tray").killTray(); } catch (e) { }
// Extra delay so macOS NSStatusBar fully removes the old icon before
// bgProcess spawns a new one. Without this, two icons appear briefly.
await new Promise((r) => setTimeout(r, 400));
// Enable auto startup on OS boot
try {
@@ -731,8 +734,10 @@ function startServer(latestVersion) {
console.log(` • Open Dashboard`);
console.log(` • Quit\n`);
// Exit current process - background process takes over
cleanup();
// Exit current process - background process takes over.
// Don't call cleanup() here: tray already killed above, and cleanup()
// would kill the server which bgProcess relies on staying alive.
isShuttingDown = true;
process.exit(0);
} else if (choice === "exit") {
isShuttingDown = true;
+37 -12
View File
@@ -60,19 +60,45 @@ function isBetterSqliteBinaryValid() {
} catch { return false; }
}
function npmInstall(pkgs, opts = {}) {
const cwd = ensureRuntimeDir();
const args = ["install", ...pkgs, "--no-audit", "--no-fund", "--prefer-online"];
if (opts.optional) args.push("--no-save");
// Extract a short, user-friendly reason from npm stderr.
function summarizeNpmError(stderr = "") {
const text = String(stderr);
if (/ENOTFOUND|ETIMEDOUT|EAI_AGAIN|network|getaddrinfo/i.test(text)) return "No internet connection or registry unreachable";
if (/EACCES|EPERM|permission denied/i.test(text)) return "Permission denied (check folder permissions)";
if (/ENOSPC|no space/i.test(text)) return "Not enough disk space";
if (/node-gyp|gyp ERR|python|MSBuild|Visual Studio|Xcode/i.test(text)) return "Missing build tools (Xcode CLT / Python / VS Build Tools)";
if (/ETARGET|version.*not found/i.test(text)) return "Package version not found on registry";
const m = text.match(/npm ERR! (.+)/);
if (m) return m[1].slice(0, 200);
const lastLine = text.trim().split(/\r?\n/).filter(Boolean).pop();
return lastLine ? lastLine.slice(0, 200) : "Unknown error";
}
function runNpmInstall({ cwd, pkgs, extraArgs = [], timeout = 180000 }) {
const args = ["install", ...pkgs, "--no-audit", "--no-fund", "--prefer-online", ...extraArgs];
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
console.log(`[9router][runtime] ${npmCmd} ${args.join(" ")} (cwd: ${cwd})`);
const res = spawnSync(npmCmd, args, {
cwd,
stdio: opts.silent ? "ignore" : "inherit",
timeout: opts.timeout || 180000,
stdio: ["ignore", "pipe", "pipe"],
timeout,
shell: process.platform === "win32",
encoding: "utf8",
});
return res.status === 0;
return { ok: res.status === 0, code: res.status, stderr: res.stderr || "", stdout: res.stdout || "" };
}
function npmInstall(pkgs, opts = {}) {
const cwd = ensureRuntimeDir();
const extra = opts.optional ? ["--no-save"] : [];
if (!opts.silent) console.log("⏳ Installing SQLite engine (first run)...");
const res = runNpmInstall({ cwd, pkgs, extraArgs: extra, timeout: opts.timeout || 180000 });
if (!res.ok && !opts.silent) {
const reason = summarizeNpmError(res.stderr);
console.warn("⚠️ SQLite engine install failed — using fallback");
console.warn(` Reason: ${reason}`);
console.warn(` Retry: cd "${cwd}" && npm install ${pkgs.join(" ")}`);
}
return res.ok;
}
// Public: ensure better-sqlite3 native module is installed in user-writable
@@ -83,14 +109,11 @@ function ensureSqliteRuntime({ silent = false } = {}) {
const needBetterSqlite = !hasModule("better-sqlite3") || !isBetterSqliteBinaryValid();
if (!needBetterSqlite) {
if (!silent) console.log("[9router][runtime] better-sqlite3 OK");
if (!silent) console.log("✅ SQLite engine ready");
return { betterSqlite: true };
}
const ok = npmInstall([`better-sqlite3@${BETTER_SQLITE3_VERSION}`], { optional: true, silent });
if (!ok && !silent) {
console.warn("[9router][runtime] better-sqlite3 install failed (will use node:sqlite or sql.js fallback)");
}
return {
betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid(),
};
@@ -111,4 +134,6 @@ module.exports = {
buildEnvWithRuntime,
getRuntimeDir,
getRuntimeNodeModules,
runNpmInstall,
summarizeNpmError,
};
+11 -15
View File
@@ -9,7 +9,7 @@
const { spawnSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const { getRuntimeDir, getRuntimeNodeModules } = require("./sqliteRuntime");
const { getRuntimeDir, getRuntimeNodeModules, runNpmInstall, summarizeNpmError } = require("./sqliteRuntime");
const SYSTRAY_PKG = "systray2";
const SYSTRAY_VERSION = "2.1.4";
@@ -73,16 +73,15 @@ function ensureRuntimeDir() {
function npmInstall(pkgs, { silent = false } = {}) {
const cwd = ensureRuntimeDir();
const args = ["install", ...pkgs, "--no-audit", "--no-fund", "--no-save", "--prefer-online"];
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
if (!silent) console.log(`[9router][runtime] ${npmCmd} ${args.join(" ")} (cwd: ${cwd})`);
const res = spawnSync(npmCmd, args, {
cwd,
stdio: silent ? "ignore" : "inherit",
timeout: 120000,
shell: process.platform === "win32"
});
return res.status === 0;
if (!silent) console.log("⏳ Installing system tray (first run)...");
const res = runNpmInstall({ cwd, pkgs, extraArgs: ["--no-save"], timeout: 120000 });
if (!res.ok && !silent) {
const reason = summarizeNpmError(res.stderr);
console.warn("⚠️ System tray install failed — tray disabled");
console.warn(` Reason: ${reason}`);
console.warn(` Retry: cd "${cwd}" && npm install ${pkgs.join(" ")}`);
}
return res.ok;
}
// Public: ensure systray2 is installed on macOS/Linux only.
@@ -97,14 +96,11 @@ function ensureTrayRuntime({ silent = false } = {}) {
}
if (hasSystray()) {
chmodSystrayBin({ silent });
if (!silent) console.log("[9router][runtime] systray2 OK");
if (!silent) console.log("✅ System tray ready");
return { systray: true };
}
const ok = npmInstall([`${SYSTRAY_PKG}@${SYSTRAY_VERSION}`], { silent });
if (ok) chmodSystrayBin({ silent });
if (!ok && !silent) {
console.warn("[9router][runtime] systray2 install failed (tray will be disabled)");
}
return { systray: ok && hasSystray() };
}
+11 -3
View File
@@ -260,16 +260,20 @@ function killTray() {
return Promise.resolve();
}
// Unix: get the Go tray child process handle, SIGKILL it, await "exit"
// Unix: get the Go tray child process handle.
let proc = null;
try {
proc = instance._process || (typeof instance.process === "function" ? instance.process() : null);
} catch (e) {}
// Always close IPC (best-effort, may throw if pipe already broken)
// Graceful shutdown: send {type:"exit"} via IPC so the Go binary can call
// systray.Quit() and release NSStatusItem. SIGKILL leaves a ghost icon on
// the macOS menubar until logout, causing duplicate icons after re-spawn.
const gracefulQuit = () => { try { instance.kill(true); } catch (e) {} };
const closeIpc = () => { try { instance.kill(false); } catch (e) {} };
if (!proc || !proc.pid) {
gracefulQuit();
closeIpc();
return Promise.resolve();
}
@@ -279,7 +283,11 @@ function killTray() {
const finish = () => { if (done) return; done = true; closeIpc(); resolve(); };
proc.once("exit", finish);
try { proc.kill("SIGKILL"); } catch (e) {}
gracefulQuit();
// Escalate: SIGTERM after 800ms, SIGKILL after 1600ms if still alive.
setTimeout(() => { try { process.kill(proc.pid, 0); proc.kill("SIGTERM"); } catch (e) {} }, 800);
setTimeout(() => { try { process.kill(proc.pid, 0); proc.kill("SIGKILL"); } catch (e) {} }, 1600);
// Fallback poll in case "exit" never fires (detached child, pipe closed)
const deadline = Date.now() + 3000;