Files

183 lines
4.9 KiB
JavaScript
Raw Permalink Normal View History

2026-06-30 20:26:33 +08:00
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';
2026-06-30 20:26:33 +08:00
import { parseCookies } from './local-auth.mjs';
import { createPlanSyncService } from './plan-sync.mjs';
ensureArgon2Sync();
import { bootstrapAdminServices } from './bootstrap.mjs';
import { createAdminApp } from './app.mjs';
2026-06-30 20:26:33 +08:00
const projectRoot = loadProjectEnv();
const pidFile = path.join(projectRoot, '.adm-api.pid');
const port = Number(process.env.ADM_API_PORT ?? 8085);
2026-06-30 20:26:33 +08:00
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();
2026-06-30 20:26:33 +08:00
const planSyncService = createPlanSyncService(console);
const services = {
ready,
2026-06-20 16:52:13 +08:00
parseCookies,
2026-06-30 20:26:33 +08:00
planSyncService,
};
ready
2026-06-30 20:26:33 +08:00
.then((bootstrapped) => {
const {
pool,
userAuth,
llmProviderService,
assetGatewayConfigService,
2026-06-30 20:26:33 +08:00
plazaOps,
createOpsApi,
wechatAdmin,
loadMindSpaceConfig,
updateMindSpaceConfig,
2026-07-03 09:34:42 +08:00
memoryV2ConfigService,
2026-07-15 13:46:22 +08:00
mindSearchConfigService,
personalMemoryCandidateStore,
skillRuntimeConfigService,
2026-07-07 12:10:21 +08:00
wechatScheduleLlmConfigService,
adminSystemTestService,
systemTestAccountService,
2026-06-30 20:26:33 +08:00
wordFilterService,
planCatalogService,
subscriptionService,
planSyncService,
USER_COOKIE,
userLoginCookies,
clearUserSessionCookie,
resolveCookieDomainForRequest,
} = bootstrapped;
2026-06-20 16:52:13 +08:00
Object.assign(services, {
pool,
userAuth,
llmProviderService,
assetGatewayConfigService,
2026-06-20 16:52:13 +08:00
plazaOps,
createOpsApi,
wechatAdmin,
2026-06-30 20:26:33 +08:00
loadMindSpaceConfig,
updateMindSpaceConfig,
2026-07-03 09:34:42 +08:00
memoryV2ConfigService,
2026-07-15 13:46:22 +08:00
mindSearchConfigService,
personalMemoryCandidateStore,
skillRuntimeConfigService,
2026-07-07 12:10:21 +08:00
wechatScheduleLlmConfigService,
adminSystemTestService,
systemTestAccountService,
2026-06-30 20:26:33 +08:00
wordFilterService,
planCatalogService,
subscriptionService,
planSyncService,
USER_COOKIE,
userLoginCookies,
clearUserSessionCookie,
resolveCookieDomainForRequest,
2026-06-20 16:52:13 +08:00
});
const app = createAdminApp(services);
2026-06-30 20:26:33 +08:00
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));
});
2026-06-20 16:52:13 +08:00
globalThis.__tkmindAdminServer = server;
})
.catch((err) => {
console.error('Admin server bootstrap failed:', err);
process.exit(1);
});