mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-18 14:30:29 +00:00
Support preferred WebUI port via environment variable
Adds support for specifying a preferred WebUI port using the NAPCAT_WEBUI_PREFERRED_PORT environment variable. The shell and backend now coordinate to pass and honor this port during worker restarts, falling back to the default port if the preferred one is unavailable.
This commit is contained in:
parent
7216755430
commit
8a232d8c68
@ -4,6 +4,7 @@ import { LogWrapper } from '@/napcat-core/helper/log';
|
|||||||
import { connectToNamedPipe } from './pipe';
|
import { connectToNamedPipe } from './pipe';
|
||||||
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
|
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
|
||||||
import { AuthHelper } from '@/napcat-webui-backend/src/helper/SignToken';
|
import { AuthHelper } from '@/napcat-webui-backend/src/helper/SignToken';
|
||||||
|
import { webUiRuntimePort } from '@/napcat-webui-backend/index';
|
||||||
import { createProcessManager, type IProcessManager, type IWorkerProcess } from './process-api';
|
import { createProcessManager, type IProcessManager, type IWorkerProcess } from './process-api';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
@ -19,6 +20,13 @@ const ENV = {
|
|||||||
isPipeDisabled: process.env['NAPCAT_DISABLE_PIPE'] === '1',
|
isPipeDisabled: process.env['NAPCAT_DISABLE_PIPE'] === '1',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// Worker 消息类型
|
||||||
|
interface WorkerMessage {
|
||||||
|
type: 'restart' | 'restart-prepare' | 'shutdown';
|
||||||
|
secretKey?: string;
|
||||||
|
port?: number;
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化日志
|
// 初始化日志
|
||||||
const pathWrapper = new NapCatPathWrapper();
|
const pathWrapper = new NapCatPathWrapper();
|
||||||
const logger = new LogWrapper(pathWrapper.logsPath);
|
const logger = new LogWrapper(pathWrapper.logsPath);
|
||||||
@ -81,7 +89,7 @@ function forceKillProcess (pid: number): void {
|
|||||||
/**
|
/**
|
||||||
* 重启 Worker 进程
|
* 重启 Worker 进程
|
||||||
*/
|
*/
|
||||||
export async function restartWorker (secretKey?: string): Promise<void> {
|
export async function restartWorker (secretKey?: string, port?: number): Promise<void> {
|
||||||
isRestarting = true;
|
isRestarting = true;
|
||||||
|
|
||||||
if (!currentWorker) {
|
if (!currentWorker) {
|
||||||
@ -129,16 +137,18 @@ export async function restartWorker (secretKey?: string): Promise<void> {
|
|||||||
// 4. 等待后启动新进程
|
// 4. 等待后启动新进程
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
|
||||||
// 5. 启动新进程(重启模式不传递快速登录参数,传递密钥)
|
// 5. 启动新进程(重启模式不传递快速登录参数,传递密钥和端口)
|
||||||
await startWorker(false, secretKey);
|
await startWorker(false, secretKey, port);
|
||||||
isRestarting = false;
|
isRestarting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动 Worker 进程
|
* 启动 Worker 进程
|
||||||
* @param passQuickLogin 是否传递快速登录参数,默认为 true,重启时为 false
|
* @param passQuickLogin 是否传递快速登录参数,默认为 true,重启时为 false
|
||||||
|
* @param secretKey WebUI JWT 密钥
|
||||||
|
* @param preferredPort 优先使用的 WebUI 端口
|
||||||
*/
|
*/
|
||||||
async function startWorker (passQuickLogin: boolean = true, secretKey?: string): Promise<void> {
|
async function startWorker (passQuickLogin: boolean = true, secretKey?: string, preferredPort?: number): Promise<void> {
|
||||||
if (!processManager) {
|
if (!processManager) {
|
||||||
throw new Error('进程管理器未初始化');
|
throw new Error('进程管理器未初始化');
|
||||||
}
|
}
|
||||||
@ -165,6 +175,7 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string):
|
|||||||
...process.env,
|
...process.env,
|
||||||
NAPCAT_WORKER_PROCESS: '1',
|
NAPCAT_WORKER_PROCESS: '1',
|
||||||
...(secretKey ? { NAPCAT_WEBUI_JWT_SECRET_KEY: secretKey } : {}),
|
...(secretKey ? { NAPCAT_WEBUI_JWT_SECRET_KEY: secretKey } : {}),
|
||||||
|
...(preferredPort ? { NAPCAT_WEBUI_PREFERRED_PORT: String(preferredPort) } : {}),
|
||||||
},
|
},
|
||||||
stdio: isElectron ? 'pipe' : ['inherit', 'pipe', 'pipe', 'ipc'],
|
stdio: isElectron ? 'pipe' : ['inherit', 'pipe', 'pipe', 'ipc'],
|
||||||
});
|
});
|
||||||
@ -188,12 +199,14 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string):
|
|||||||
// 监听子进程消息
|
// 监听子进程消息
|
||||||
child.on('message', (msg: unknown) => {
|
child.on('message', (msg: unknown) => {
|
||||||
// 处理重启请求
|
// 处理重启请求
|
||||||
if (typeof msg === 'object' && msg !== null && 'type' in msg && msg.type === 'restart') {
|
if (typeof msg === 'object' && msg !== null && 'type' in msg) {
|
||||||
const secretKey = 'secretKey' in msg ? (msg as any).secretKey : undefined;
|
const message = msg as WorkerMessage;
|
||||||
restartWorker(secretKey).catch(e => {
|
if (message.type === 'restart') {
|
||||||
|
restartWorker(message.secretKey, message.port).catch(e => {
|
||||||
logger.logError(`[NapCat] [${processType}] 重启Worker进程失败:`, e);
|
logger.logError(`[NapCat] [${processType}] 重启Worker进程失败:`, e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听子进程退出
|
// 监听子进程退出
|
||||||
@ -282,8 +295,11 @@ async function startWorkerProcess (): Promise<void> {
|
|||||||
// 注册重启进程函数到 WebUI
|
// 注册重启进程函数到 WebUI
|
||||||
WebUiDataRuntime.setRestartProcessCall(async () => {
|
WebUiDataRuntime.setRestartProcessCall(async () => {
|
||||||
try {
|
try {
|
||||||
const success = processManager!.sendToParent({ type: 'restart', secretKey: AuthHelper.getSecretKey() });
|
const success = processManager!.sendToParent({
|
||||||
|
type: 'restart',
|
||||||
|
secretKey: AuthHelper.getSecretKey(),
|
||||||
|
port: webUiRuntimePort,
|
||||||
|
});
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
return { result: true, message: '进程重启请求已发送' };
|
return { result: true, message: '进程重启请求已发送' };
|
||||||
|
|||||||
@ -72,7 +72,19 @@ export function setPendingTokenToSend (token: string | null) {
|
|||||||
export async function InitPort (parsedConfig: WebUiConfigType): Promise<[string, number, string]> {
|
export async function InitPort (parsedConfig: WebUiConfigType): Promise<[string, number, string]> {
|
||||||
try {
|
try {
|
||||||
await tryUseHost(parsedConfig.host);
|
await tryUseHost(parsedConfig.host);
|
||||||
const port = await tryUsePort(parsedConfig.port, parsedConfig.host);
|
const preferredPort = parseInt(process.env['NAPCAT_WEBUI_PREFERRED_PORT'] || '', 10);
|
||||||
|
|
||||||
|
let port: number;
|
||||||
|
if (preferredPort > 0) {
|
||||||
|
try {
|
||||||
|
port = await tryUsePort(preferredPort, parsedConfig.host, 0, true);
|
||||||
|
} catch {
|
||||||
|
port = await tryUsePort(parsedConfig.port, parsedConfig.host);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port = await tryUsePort(parsedConfig.port, parsedConfig.host);
|
||||||
|
}
|
||||||
|
|
||||||
return [parsedConfig.host, port, parsedConfig.token];
|
return [parsedConfig.host, port, parsedConfig.token];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('host或port不可用', error);
|
console.log('host或port不可用', error);
|
||||||
@ -356,7 +368,7 @@ async function tryUseHost (host: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryUsePort (port: number, host: string, tryCount: number = 0): Promise<number> {
|
async function tryUsePort (port: number, host: string, tryCount: number = 0, singleTry: boolean = false): Promise<number> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const server = net.createServer();
|
const server = net.createServer();
|
||||||
@ -367,9 +379,12 @@ async function tryUsePort (port: number, host: string, tryCount: number = 0): Pr
|
|||||||
|
|
||||||
server.on('error', (err: any) => {
|
server.on('error', (err: any) => {
|
||||||
if (err.code === 'EADDRINUSE') {
|
if (err.code === 'EADDRINUSE') {
|
||||||
if (tryCount < MAX_PORT_TRY) {
|
if (singleTry) {
|
||||||
// 使用循环代替递归
|
// 只尝试一次,端口被占用则直接失败
|
||||||
resolve(tryUsePort(port + 1, host, tryCount + 1));
|
reject(new Error(`端口 ${port} 已被占用`));
|
||||||
|
} else if (tryCount < MAX_PORT_TRY) {
|
||||||
|
// 递归尝试下一个端口
|
||||||
|
resolve(tryUsePort(port + 1, host, tryCount + 1, false));
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`));
|
reject(new Error(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user