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, 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, 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); });