chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:12 +08:00
commit d8dcd5f6d1
8604 changed files with 2479390 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
import { loadSqliteRuntime } from "./sqliteRuntime.mjs";
let warmed = false;
/**
* Pre-resolves native runtimes at startup so the first DB access is fast
* and EBUSY-resilient on Windows.
*
* Tray runtime (systray2) is warmed lazily by initTray() in bin/cli/tray/.
*/
export async function warmUpRuntimes() {
if (warmed) return;
warmed = true;
try {
await loadSqliteRuntime();
} catch {}
}
export { loadSqliteRuntime };
+47
View File
@@ -0,0 +1,47 @@
import { readFileSync, existsSync } from "node:fs";
/**
* Validates that a file starts with a known native-binary magic number.
* Returns the platform label ("elf" | "macho" | "macho-le" | "macho-fat" | "pe")
* or null if unrecognized / missing / unreadable.
*
*/
export function validateBinaryMagic(path) {
if (!existsSync(path)) return null;
let buf;
try {
buf = readFileSync(path, { encoding: null }).subarray(0, 16);
} catch {
return null;
}
if (buf.length < 4) return null;
// ELF: 7F 45 4C 46
if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) return "elf";
// Mach-O big-endian: FE ED FA CF (64-bit) / FE ED FA CE (32-bit)
if (buf[0] === 0xfe && buf[1] === 0xed && buf[2] === 0xfa && buf[3] === 0xcf) return "macho";
if (buf[0] === 0xfe && buf[1] === 0xed && buf[2] === 0xfa && buf[3] === 0xce) return "macho";
// Mach-O little-endian: CF FA ED FE (64-bit) / CE FA ED FE (32-bit)
if (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) return "macho-le";
if (buf[0] === 0xce && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) return "macho-le";
// Mach-O fat (universal binary): CA FE BA BE
if (buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe) return "macho-fat";
// PE (Windows .node — DLL): MZ at offset 0
if (buf[0] === 0x4d && buf[1] === 0x5a) return "pe";
return null;
}
/**
* Returns the expected magic label for the current platform.
* Used to validate that a runtime-installed binary matches this OS.
*/
export function platformBinaryLabel() {
if (process.platform === "win32") return "pe";
if (process.platform === "darwin") return "macho";
return "elf";
}
+137
View File
@@ -0,0 +1,137 @@
import {
existsSync,
readFileSync,
writeFileSync,
openSync,
readSync,
closeSync,
mkdirSync,
} from "node:fs";
import { join, sep } from "node:path";
import { spawnSync } from "node:child_process";
import { platform } from "node:os";
import { resolveDataDir } from "../data-dir.mjs";
const BETTER_SQLITE3_VERSION = "12.10.1";
function runtimeDir() {
return join(resolveDataDir(), "runtime");
}
function runtimeModules() {
return join(runtimeDir(), "node_modules");
}
export function ensureRuntimeDir() {
const dir = runtimeDir();
mkdirSync(dir, { recursive: true });
const pkgPath = join(dir, "package.json");
if (!existsSync(pkgPath)) {
writeFileSync(
pkgPath,
JSON.stringify(
{
name: "omniroute-runtime",
version: "1.0.0",
private: true,
description: "User-writable runtime deps for OmniRoute (native binaries)",
},
null,
2
)
);
}
return dir;
}
export function getRuntimeNodeModules() {
return runtimeModules();
}
export function hasModule(name) {
return existsSync(join(runtimeModules(), name, "package.json"));
}
export function isBetterSqliteBinaryValid() {
const binary = join(
runtimeModules(),
"better-sqlite3",
"build",
"Release",
"better_sqlite3.node"
);
if (!existsSync(binary)) return false;
try {
const fd = openSync(binary, "r");
const buf = Buffer.alloc(4);
readSync(fd, buf, 0, 4, 0);
closeSync(fd);
const magic = buf.toString("hex");
const os = platform();
if (os === "linux") return magic.startsWith("7f454c46"); // ELF
if (os === "darwin") return magic.startsWith("cffaedfe") || magic.startsWith("cefaedfe"); // Mach-O
if (os === "win32") return magic.startsWith("4d5a"); // PE/MZ
return true;
} catch {
return false;
}
}
export function npmInstallRuntime(pkgs, opts = {}) {
const cwd = ensureRuntimeDir();
// Persist to the runtime package.json (exact version) instead of --no-save so a later
// install of a sibling runtime dep (e.g. systray2 from trayRuntime.ts, which writes to the
// same runtime dir) does not prune this package as "extraneous" — that pruning otherwise
// reproduces "No SQLite driver available" after a tray install removes better-sqlite3.
const npmArgs = [
"install",
...pkgs,
"--no-audit",
"--no-fund",
"--prefer-online",
"--save-exact",
];
// On Windows .cmd files cannot be executed without a shell; use cmd.exe /c explicitly
// so we never set shell:true (which would propagate env and enable injection).
const isWin = platform() === "win32";
const [exe, args] = isWin ? ["cmd.exe", ["/c", "npm", ...npmArgs]] : ["npm", npmArgs];
if (!opts.silent) {
process.stdout.write(`[omniroute][runtime] npm ${npmArgs.join(" ")}\n`);
}
const res = spawnSync(exe, args, {
cwd,
stdio: opts.silent ? "ignore" : "inherit",
timeout: opts.timeout ?? 180_000,
shell: false,
env: { ...process.env },
});
return res.status === 0;
}
/**
* Ensure better-sqlite3 is installed and valid in the runtime dir.
* Returns { betterSqlite: boolean }.
*/
export function ensureBetterSqliteRuntime({ silent = false, force = false } = {}) {
ensureRuntimeDir();
const valid = hasModule("better-sqlite3") && isBetterSqliteBinaryValid();
if (valid && !force) {
if (!silent) process.stdout.write("[omniroute][runtime] better-sqlite3 OK\n");
return { betterSqlite: true };
}
const ok = npmInstallRuntime([`better-sqlite3@${BETTER_SQLITE3_VERSION}`], { silent });
if (!ok && !silent) {
process.stderr.write("[omniroute][runtime] better-sqlite3 install failed\n");
}
return { betterSqlite: ok && hasModule("better-sqlite3") && isBetterSqliteBinaryValid() };
}
/**
* Build an env object with NODE_PATH extended to include the runtime node_modules.
*/
export function buildEnvWithRuntime(baseEnv = process.env) {
const runtimeNm = runtimeModules();
const existing = baseEnv.NODE_PATH || "";
const parts = [runtimeNm, existing].filter(Boolean);
return { ...baseEnv, NODE_PATH: parts.join(sep === "\\" ? ";" : ":") };
}
+136
View File
@@ -0,0 +1,136 @@
import { spawn } from "node:child_process";
import { dirname } from "node:path";
import { writePidFile, cleanupPidFile, killAllSubprocesses } from "../utils/pid.mjs";
import {
RESTART_RESET_MS,
DEFAULT_MAX_RESTARTS,
shouldExitInsteadOfRestart,
computeRestartDelayMs,
waitUntilPortFree,
} from "./supervisorPolicy.mjs";
import { buildNodeHeapArgs } from "../../../scripts/build/runtime-env.mjs";
const CRASH_LOG_LINES = 50;
export class ServerSupervisor {
constructor({ serverPath, env, maxRestarts = DEFAULT_MAX_RESTARTS, memoryLimit = 512, onCrashCallback }) {
this.serverPath = serverPath;
this.env = env;
this.maxRestarts = maxRestarts;
this.memoryLimit = memoryLimit;
this.onCrashCallback = onCrashCallback;
this.restartCount = 0;
this.startedAt = 0;
this.crashLog = [];
this.child = null;
this.isShuttingDown = false;
}
start() {
this.startedAt = Date.now();
this.crashLog = [];
const showLog = process.env.OMNIROUTE_SHOW_LOG === "1";
// #5238: skip the explicit CLI --max-old-space-size when the user pinned the
// heap via NODE_OPTIONS (a CLI arg would shadow/override their value). The
// calibrated heap is already carried by env.NODE_OPTIONS either way.
const heapArgs = buildNodeHeapArgs(process.env, this.memoryLimit);
this.child = spawn("node", [...heapArgs, this.serverPath], {
cwd: dirname(this.serverPath),
env: this.env,
stdio: showLog ? "inherit" : ["ignore", "ignore", "pipe"],
});
writePidFile("server", this.child.pid);
if (this.child.stderr) {
this.child.stderr.on("data", (data) => {
const lines = data.toString().split("\n").filter(Boolean);
this.crashLog.push(...lines);
if (this.crashLog.length > CRASH_LOG_LINES) {
this.crashLog = this.crashLog.slice(-CRASH_LOG_LINES);
}
});
}
this.child.on("error", (err) => this.handleExit(-1, err));
this.child.on("exit", (code) => this.handleExit(code));
return this.child;
}
handleExit(code) {
// Node.js v24+ requires process.exit() to receive a number. Spawn-error events
// deliver err.code (a string like 'ENOENT') via the 'error' listener; normalise here.
const exitCode = typeof code === "number" ? code : null;
cleanupPidFile("server");
// #4425: only exit on an intentional shutdown. A spontaneous code-0 exit (e.g. a
// systemd MemoryMax cgroup kill, which reports the process exited cleanly) is anomalous
// and must be restarted, not treated as a graceful stop that leaves the gateway dead.
if (shouldExitInsteadOfRestart(this.isShuttingDown)) {
process.exit(exitCode ?? 0);
return;
}
const aliveMs = Date.now() - this.startedAt;
if (aliveMs >= RESTART_RESET_MS) this.restartCount = 0;
if (this.restartCount >= this.maxRestarts) {
console.error(`\n⚠ Server crashed ${this.maxRestarts} times in <30s.`);
if (this.onCrashCallback) {
const action = this.onCrashCallback(this.crashLog);
if (action === "disable-mitm-and-retry") {
console.error("⚠ Disabling MITM and retrying...\n");
this.restartCount = 0;
this.start();
return;
}
}
this.dumpCrashLog();
process.exit(exitCode ?? 1);
return;
}
this.restartCount++;
const delay = computeRestartDelayMs(this.restartCount);
console.error(
`\n⚠ Server exited (code=${code ?? "?"}). Restarting in ${delay / 1000}s... (${this.restartCount}/${this.maxRestarts})`
);
if (this.crashLog.length) this.dumpCrashLog();
// #4425: after a crash the OS may not have released the listen socket yet — restarting
// immediately produced the EADDRINUSE cascade that exhausted the restart budget. Wait
// (bounded) for the port to free up before respawning.
setTimeout(async () => {
await waitUntilPortFree(process.env.PORT || 20128);
this.start();
}, delay);
}
dumpCrashLog() {
console.error("\n--- Server crash log ---");
this.crashLog.forEach((l) => console.error(l));
console.error("--- End crash log ---\n");
}
stop() {
this.isShuttingDown = true;
if (this.child?.pid) {
try {
process.kill(this.child.pid, "SIGTERM");
} catch {}
setTimeout(() => {
try {
process.kill(this.child.pid, "SIGKILL");
} catch {}
}, 5000);
}
killAllSubprocesses();
}
}
export function detectMitmCrash(crashLog) {
const text = crashLog.join("\n").toLowerCase();
const signals = ["mitm", "tls socket", "certificate", "hosts", "eaccess"];
return signals.filter((s) => text.includes(s)).length >= 2;
}
+129
View File
@@ -0,0 +1,129 @@
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { homedir } from "node:os";
import { execSync } from "node:child_process";
import { pathToFileURL } from "node:url";
import { validateBinaryMagic, platformBinaryLabel } from "./magicBytes.mjs";
const RUNTIME_DIR = join(homedir(), ".omniroute", "runtime");
const BETTER_SQLITE3_VERSION = "better-sqlite3@^12.10.1";
let resolvedCached = null;
/**
* Resolves a SQLite driver through a 5-step fallback chain:
* 1. Bundled better-sqlite3 (optionalDependency)
* 2. Runtime-installed better-sqlite3 in ~/.omniroute/runtime/
* 3. Lazy npm install into runtime dir
* 4. node:sqlite (Node ≥22.5 stdlib)
* 5. sql.js (bundled WASM, always available)
*
* Returns { driver, source } where source is one of:
* "bundled" | "runtime" | "runtime-installed-now" | "node-sqlite" | "sql-js"
*/
export async function loadSqliteRuntime() {
if (resolvedCached) return resolvedCached;
const bundled = await tryLoadBundled();
if (bundled) {
resolvedCached = { driver: bundled, source: "bundled" };
return resolvedCached;
}
const runtimeInstalled = await tryLoadRuntimeInstalled();
if (runtimeInstalled) {
resolvedCached = { driver: runtimeInstalled, source: "runtime" };
return resolvedCached;
}
try {
await installRuntime();
const after = await tryLoadRuntimeInstalled();
if (after) {
resolvedCached = { driver: after, source: "runtime-installed-now" };
return resolvedCached;
}
} catch (err) {
console.warn(`[omniroute] runtime install failed: ${err.message}`);
}
try {
const nodeSqlite = await import("node:sqlite");
resolvedCached = {
driver: { kind: "node-sqlite", DatabaseSync: nodeSqlite.DatabaseSync },
source: "node-sqlite",
};
return resolvedCached;
} catch {}
const sqljs = await import("sql.js");
resolvedCached = {
driver: { kind: "sql-js", initSqlJs: sqljs.default ?? sqljs.initSqlJs },
source: "sql-js",
};
return resolvedCached;
}
async function tryLoadBundled() {
try {
const mod = await import("better-sqlite3");
return { kind: "better-sqlite3", Database: mod.default ?? mod };
} catch {
return null;
}
}
async function tryLoadRuntimeInstalled() {
const runtimeNodeModules = resolve(RUNTIME_DIR, "node_modules");
const pkgRoot = resolve(runtimeNodeModules, "better-sqlite3");
if (!pkgRoot.startsWith(`${runtimeNodeModules}/`)) return null;
if (!existsSync(join(pkgRoot, "package.json"))) return null;
const buildDir = join(pkgRoot, "build", "Release");
if (existsSync(buildDir)) {
const nodeFile = readdirSync(buildDir).find((f) => f.endsWith(".node"));
if (nodeFile) {
const magic = validateBinaryMagic(join(buildDir, nodeFile));
const expected = platformBinaryLabel();
if (!magic || (magic !== expected && magic !== "macho-le" && magic !== "macho-fat")) {
console.warn(
`[omniroute] runtime sqlite binary magic mismatch (${magic}${expected}) — skipping`
);
return null;
}
}
}
try {
const mod = await import(/* webpackIgnore: true */ pathToFileURL(pkgRoot).href);
return { kind: "better-sqlite3", Database: mod.default ?? mod };
} catch {
return null;
}
}
function ensureRuntimeDir() {
if (!existsSync(RUNTIME_DIR)) mkdirSync(RUNTIME_DIR, { recursive: true });
const pkg = join(RUNTIME_DIR, "package.json");
if (!existsSync(pkg)) {
writeFileSync(
pkg,
JSON.stringify({ name: "omniroute-runtime", private: true, type: "commonjs" }),
"utf-8"
);
}
}
async function installRuntime() {
ensureRuntimeDir();
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
execSync(
`${npm} install --prefix "${RUNTIME_DIR}" ${BETTER_SQLITE3_VERSION} --no-audit --no-fund --silent`,
{ stdio: ["ignore", "ignore", "pipe"], timeout: 180_000 }
);
}
/** Clears the cached resolved driver (for testing). */
export function clearRuntimeCache() {
resolvedCached = null;
}
+56
View File
@@ -0,0 +1,56 @@
import net from "node:net";
// #4425: bumped from 30s — the old window reset the crash counter too quickly, so during
// an EADDRINUSE cascade the supervisor kept "recovering" then crashing within the window
// and exhausted its restart budget. A longer window keeps the counter meaningful.
export const RESTART_RESET_MS = 60_000;
// #4425: bumped from 2 — more recovery headroom before the supervisor gives up.
export const DEFAULT_MAX_RESTARTS = 3;
/**
* #4425: a clean child exit (code 0) is only intentional when the supervisor itself is
* shutting down. A spontaneous code-0 exit is anomalous — e.g. a systemd `MemoryMax`
* cgroup kill reports the process exited with code 0 — and MUST be restarted, not treated
* as a graceful stop (which left the gateway dead with `Restart=on-failure`).
*/
export function shouldExitInsteadOfRestart(isShuttingDown) {
return isShuttingDown === true;
}
/** Exponential backoff (1s, 2s, 4s, …) capped at 10s, matching the prior inline formula. */
export function computeRestartDelayMs(restartCount) {
return Math.min(1000 * 2 ** (Math.max(1, restartCount) - 1), 10_000);
}
/** Resolve true when nothing is listening on `port` (so a restart won't hit EADDRINUSE). */
export function isPortFree(port, host = "127.0.0.1") {
return new Promise((resolve) => {
const tester = net.createServer();
tester.once("error", (err) => {
// EADDRINUSE = something is bound → not free. Any other error → treat as free.
resolve(!(err && err.code === "EADDRINUSE"));
});
tester.once("listening", () => {
tester.close(() => resolve(true));
});
tester.listen(port, host);
});
}
/**
* #4425: wait until `port` is free before respawning. After a crash the OS may not have
* released the listen socket yet; restarting immediately produced the EADDRINUSE cascade
* that exhausted the restart budget. Polls up to `timeoutMs`, then proceeds anyway so a
* stuck port never blocks recovery forever.
*/
export async function waitUntilPortFree(port, timeoutMs = 10_000, intervalMs = 250) {
const p = Number(port);
if (!Number.isFinite(p) || p <= 0) return true;
const deadline = Date.now() + timeoutMs;
for (;;) {
if (await isPortFree(p)) return true;
if (Date.now() >= deadline) return false;
await new Promise((r) => setTimeout(r, intervalMs));
}
}
+94
View File
@@ -0,0 +1,94 @@
import { existsSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { execSync } from "node:child_process";
const RUNTIME_DIR = join(homedir(), ".omniroute", "runtime");
// systray2 is a maintained fork with prebuilt binaries — installed lazily at runtime,
// not in dependencies, to avoid npm install overhead for users who don't use --tray.
//
// Pin: stay on the 2.x line. The original `systray@1.0.5` package bundles a 2017
// x86_64 Go binary whose Mach-O headers modern dyld (macOS 14+) rejects, so the
// tray silently fails to register on Apple Silicon. systray2 2.x ships newer
// getlantern/systray-portable binaries that work under Rosetta. Inherited from
// upstream decolua/9router#1080.
export const SYSTRAY_PACKAGE = "systray2";
export const SYSTRAY_VERSION = "2.1.4";
const SYSTRAY_SPEC = `${SYSTRAY_PACKAGE}@${SYSTRAY_VERSION}`;
export function resolveSystrayBinName(platform: NodeJS.Platform): string | null {
if (platform === "win32") return null;
if (platform === "darwin") return "tray_darwin_release";
return "tray_linux_release";
}
export interface ChmodResult {
changed: boolean;
reason?: "win32-skip" | "missing" | "chmod-failed";
}
// systray2's npm tarball sometimes ships the bundled Go binary without the
// executable bit set on macOS/Linux, causing spawn() to fail with EACCES.
// Set +x best-effort so the tray actually starts. Inherited from
// upstream decolua/9router#1080.
export function chmodSystrayBinAt(runtimeRoot: string, platform: NodeJS.Platform): ChmodResult {
const binName = resolveSystrayBinName(platform);
if (!binName) return { changed: false, reason: "win32-skip" };
const binPath = join(runtimeRoot, "node_modules", SYSTRAY_PACKAGE, "traybin", binName);
if (!existsSync(binPath)) return { changed: false, reason: "missing" };
try {
chmodSync(binPath, 0o755);
return { changed: true };
} catch {
return { changed: false, reason: "chmod-failed" };
}
}
export async function loadSystray(): Promise<(new (...args: unknown[]) => unknown) | null> {
if (process.platform === "win32") return null; // Windows uses tray.ps1 instead
ensureRuntimeDir();
if (!isInstalled()) {
try {
installSystray();
} catch (err) {
// Surface failures to stderr instead of staying silent — anyone hitting
// a tray problem otherwise has zero diagnostic. (PR #1080)
console.warn(`[omniroute] tray runtime install failed: ${(err as Error).message}`);
return null;
}
}
// Best-effort: ensure the bundled Go binary is executable. Some npm tarballs
// drop the +x bit on extraction (observed on macOS).
chmodSystrayBinAt(RUNTIME_DIR, process.platform);
try {
const modPath = join(RUNTIME_DIR, "node_modules", SYSTRAY_PACKAGE);
const mod = await import(modPath);
return (mod.default ?? mod.SysTray ?? mod) as (new (...args: unknown[]) => unknown) | null;
} catch (err) {
console.warn(`[omniroute] tray runtime import failed: ${(err as Error).message}`);
return null;
}
}
function ensureRuntimeDir(): void {
if (!existsSync(RUNTIME_DIR)) mkdirSync(RUNTIME_DIR, { recursive: true });
const pkg = join(RUNTIME_DIR, "package.json");
if (!existsSync(pkg)) {
writeFileSync(pkg, JSON.stringify({ name: "omniroute-runtime", private: true }), "utf-8");
}
}
function isInstalled(): boolean {
return existsSync(join(RUNTIME_DIR, "node_modules", SYSTRAY_PACKAGE, "package.json"));
}
function installSystray(): void {
// --save-exact persists systray2 to the runtime package.json so installing it does not
// prune a sibling runtime dep (e.g. better-sqlite3 from nativeDeps.mjs, which writes to the
// same runtime dir) as "extraneous", and so the tray dep survives a later sibling install.
// Without it, a sibling install reproduces "No SQLite driver available".
execSync(
`npm install --prefix "${RUNTIME_DIR}" ${SYSTRAY_SPEC} --no-audit --no-fund --save-exact --silent`,
{ stdio: ["ignore", "ignore", "pipe"], timeout: 120_000 }
);
}