Files

187 lines
5.0 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { ensureArgon2Sync } from './argon2-polyfill.mjs';
import { loadProjectEnv } from './load-env.mjs';
import { parseCookies } from './local-auth.mjs';
import { createPlanSyncService } from './plan-sync.mjs';
ensureArgon2Sync();
import { bootstrapAdminServices } from './bootstrap.mjs';
import { createAdminApp } from './app.mjs';
const projectRoot = loadProjectEnv();
const pidFile = path.join(projectRoot, '.adm-api.pid');
const port = Number(process.env.ADM_API_PORT ?? 8085);
function readPidFile() {
try {
const raw = fs.readFileSync(pidFile, 'utf8').trim();
if (!raw) return null;
const pid = Number(raw);
return Number.isInteger(pid) && pid > 0 ? pid : null;
} catch {
return null;
}
}
function isProcessAlive(pid) {
if (!Number.isInteger(pid) || pid <= 0) return false;
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
function cleanupStalePidFile() {
const existingPid = readPidFile();
if (!existingPid || existingPid === process.pid) return;
if (isProcessAlive(existingPid)) return;
try {
fs.unlinkSync(pidFile);
console.warn(`Removed stale PID file ${pidFile} (pid ${existingPid})`);
} catch {}
}
function writePidFile() {
try {
fs.writeFileSync(pidFile, `${process.pid}\n`, 'utf8');
} catch (err) {
console.warn(`Failed to write PID file ${pidFile}:`, err);
}
}
function removePidFile() {
const existingPid = readPidFile();
if (existingPid !== process.pid) return;
try {
fs.unlinkSync(pidFile);
} catch {}
}
function lookupPortOwner(listenPort) {
try {
const out = execFileSync('lsof', ['-nP', '-iTCP:' + listenPort, '-sTCP:LISTEN', '-Fpc'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
});
let pid = null;
let command = null;
for (const line of out.split('\n')) {
if (line.startsWith('p')) pid = line.slice(1);
if (line.startsWith('c')) command = line.slice(1);
if (pid && command) break;
}
if (!pid) return null;
return command ? `${command} (pid ${pid})` : `pid ${pid}`;
} catch {
return null;
}
}
cleanupStalePidFile();
const ready = bootstrapAdminServices();
const planSyncService = createPlanSyncService(console);
const services = {
ready,
parseCookies,
planSyncService,
};
ready
.then((bootstrapped) => {
const {
pool,
userAuth,
llmProviderService,
assetGatewayConfigService,
plazaOps,
createOpsApi,
wechatAdmin,
loadMindSpaceConfig,
updateMindSpaceConfig,
memoryV2ConfigService,
mindSearchConfigService,
orchestratorConfigService,
orchestratorObservabilityService,
personalMemoryCandidateStore,
skillRuntimeConfigService,
wechatScheduleLlmConfigService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
planCatalogService,
subscriptionService,
planSyncService,
USER_COOKIE,
userLoginCookies,
clearUserSessionCookie,
resolveCookieDomainForRequest,
} = bootstrapped;
Object.assign(services, {
pool,
userAuth,
llmProviderService,
assetGatewayConfigService,
plazaOps,
createOpsApi,
wechatAdmin,
loadMindSpaceConfig,
updateMindSpaceConfig,
memoryV2ConfigService,
mindSearchConfigService,
orchestratorConfigService,
orchestratorObservabilityService,
personalMemoryCandidateStore,
skillRuntimeConfigService,
wechatScheduleLlmConfigService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
planCatalogService,
subscriptionService,
planSyncService,
USER_COOKIE,
userLoginCookies,
clearUserSessionCookie,
resolveCookieDomainForRequest,
});
const app = createAdminApp(services);
const host = process.env.ADM_API_HOST?.trim() || '127.0.0.1';
const server = app.listen(port, host, () => {
writePidFile();
console.log(`TKMind Admin API @ http://${host}:${port}`);
});
server.on('close', removePidFile);
server.on('error', (err) => {
if (err?.code === 'EADDRINUSE') {
const owner = lookupPortOwner(port);
console.error(
`Admin API 无法启动:${host}:${port} 已被占用${owner ? `,当前占用者是 ${owner}` : ''}`,
);
console.error(`如需重启,可先停止旧进程,或修改 .env 中的 ADM_API_PORT。`);
} else {
console.error('Admin API listen failed:', err);
}
process.exit(1);
});
process.on('exit', removePidFile);
process.on('SIGINT', () => {
removePidFile();
server.close(() => process.exit(0));
});
process.on('SIGTERM', () => {
removePidFile();
server.close(() => process.exit(0));
});
globalThis.__tkmindAdminServer = server;
})
.catch((err) => {
console.error('Admin server bootstrap failed:', err);
process.exit(1);
});