Files
creator-hub/scripts/dev-gateway.mjs
T

114 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// gateway: 直接运行开发用 native browser gateway;不依赖 systemd。
import { spawn, execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import net from "node:net";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
function readEnvFile(filePath) {
if (!existsSync(filePath)) return {};
const values = {};
for (const line of readFileSync(filePath, "utf8").split("\n")) {
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line.trim());
if (match) values[match[1]] = match[2];
}
return values;
}
const rootEnv = readEnvFile(path.join(root, ".env"));
const gatewayEnvPath = process.env.CREATORHUB_BROWSER_GATEWAY_ENV
?? path.join(os.homedir(), ".config", "creatorhub", "browser-gateway.env");
const gatewayEnv = readEnvFile(gatewayEnvPath);
const env = { ...process.env, ...rootEnv, ...gatewayEnv };
if (!env.LISTEN_ADDR) env.LISTEN_ADDR = "127.0.0.1:28187";
if (!env.GATEWAY_TOKEN) env.GATEWAY_TOKEN = "dev-creatorhub-gateway-token";
// 与 Python 侧 split_listen_address 对齐。
function splitListenAddr(value) {
let rest = value;
let host = "";
if (rest.startsWith(":")) {
rest = rest.slice(1);
} else if (rest.startsWith("[")) {
const closing = rest.indexOf("]:");
if (closing <= 1) throw new Error(`LISTEN_ADDR 必须是 host:port:${value}`);
host = rest.slice(1, closing);
rest = rest.slice(closing + 2);
} else {
const sep = rest.lastIndexOf(":");
if (sep < 0) throw new Error(`LISTEN_ADDR 必须是 host:port:${value}`);
host = rest.slice(0, sep);
rest = rest.slice(sep + 1);
}
const port = Number(rest);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error(`LISTEN_ADDR 端口必须是 1..65535:${value}`);
}
return { host, port };
}
function probePort(host, port) {
return new Promise((resolve) => {
const server = net.createServer();
server.once("error", (err) => resolve(err));
server.listen({ host: host || undefined, port, exclusive: true }, () => {
server.close(() => resolve(null));
});
});
}
// 通过 ss -ltnp 找出占用端口的进程;失败或拿不到进程信息时返回 []。
function findOccupants(port) {
try {
const out = execFileSync("ss", ["-ltnp"], { encoding: "utf8" });
const occupants = [];
for (const line of out.split("\n")) {
if (!line.includes(`:${port} `)) continue;
for (const match of line.matchAll(/"([^"]+)",pid=(\d+)/g)) {
occupants.push(`${match[1]} (pid ${match[2]})`);
}
}
return [...new Set(occupants)];
} catch {
return [];
}
}
async function assertListenAddrFree(value) {
const { host, port } = splitListenAddr(value);
const shown = host || "0.0.0.0";
const err = await probePort(host, port);
if (!err) return;
if (err.code !== "EADDRINUSE") return; // 其余错误交给 Python 侧原样报出。
const occupants = findOccupants(port);
console.error(`[gateway] 监听地址 ${shown}:${port} 已被占用,gateway 无法启动。`);
if (occupants.length > 0) {
console.error(`[gateway] 占用进程:${occupants.join("、")}(常见原因:上一个 gateway 实例还在运行)。`);
console.error(`[gateway] 处理:先停掉占用进程,或用 LISTEN_ADDR 换一个端口(优先级:~/.config/creatorhub/browser-gateway.env > .env)。`);
} else {
console.error(`[gateway] 无法识别占用进程(可能需要 root 才能查看),可用命令:ss -ltnp | grep :${port}`);
}
process.exit(1);
}
await assertListenAddrFree(env.LISTEN_ADDR);
const gateway = spawn("python3", ["-m", "browser_gateway.server.http"], {
cwd: root,
env,
stdio: "inherit",
});
for (const signal of ["SIGINT", "SIGTERM"]) {
process.on(signal, () => gateway.kill(signal));
}
gateway.on("exit", (code, signal) => {
if (signal) process.exit(1);
process.exit(code ?? 0);
});