/** * 管理控制台配置 * 定义各服务的启动参数、端口、健康检查等 */ import { fileURLToPath } from 'url'; import path from 'path'; import fs from 'fs'; import os from 'os'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const ROOT = path.resolve(__dirname, '../..'); const isWin = os.platform() === 'win32'; // 读取根目录 .env 文件,将值合并到 process.env(不覆盖已有的环境变量) // 这样 ethend 启动各服务时能传递用户配置的凭据 function loadEnvFile() { const envPath = path.join(ROOT, '.env'); try { const content = fs.readFileSync(envPath, 'utf-8'); for (const line of content.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eqIdx = trimmed.indexOf('='); if (eqIdx === -1) continue; const key = trimmed.substring(0, eqIdx).trim(); const val = trimmed.substring(eqIdx + 1).trim(); if (key && val !== undefined) { process.env[key] = process.env[key] || val; } } } catch { // .env 文件不存在,使用默认值 } } loadEnvFile(); // 是否运行在 Docker 容器内 const IS_DOCKER = process.env.RUNNING_IN_DOCKER === 'true'; /** 跨平台 Go 二进制路径 */ function findGoBin() { // 优先使用环境变量 if (process.env.GOROOT) return path.join(process.env.GOROOT, 'bin', 'go'); // Windows 常见路径 const candidates = isWin ? ['C:\\Program Files\\Go\\bin\\go.exe', 'C:\\Go\\bin\\go.exe', 'go'] : ['/usr/local/go/bin/go', '/usr/bin/go', 'go']; for (const p of candidates) { if (p === 'go' || fs.existsSync(p)) return p; } return 'go'; } const GO_BIN = findGoBin(); export const ETHEND_PORT = process.env.ETHEND_PORT || 9090; export const LOGS_DIR = path.resolve(__dirname, '../logs'); export const GATEWAY_URL = process.env.GATEWAY_URL || 'http://localhost:8080'; export const PLUGIN_MANAGER_URL = process.env.PLUGIN_MANAGER_URL || 'http://localhost:8094'; export const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin'; export const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'cyrene-dev-admin'; export const SERVICES = { 'ai-core': { name: 'AI-Core', cwd: path.join(ROOT, 'backend/ai-core'), command: './main', env: { AI_CORE_PORT: '8081', PERSONA_DIR: './internal/persona', SEARXNG_URL: process.env.SEARXNG_URL || 'http://localhost:8088', IOT_SERVICE_URL: process.env.IOT_SERVICE_URL || process.env.IOT_DEBUG_SERVICE_URL || 'http://localhost:8083', ENABLE_BACKGROUND_THINKING: process.env.ENABLE_BACKGROUND_THINKING || 'true', }, healthUrl: IS_DOCKER ? 'http://ai-core:8081/api/v1/health' : 'http://localhost:8081/api/v1/health', port: 8081, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'], goBin: GO_BIN, }, 'iot-debug-service': { name: 'IoT Debug', cwd: path.join(ROOT, 'backend/iot-debug-service'), command: './main', env: { IOT_DEBUG_PORT: '8083', }, healthUrl: IS_DOCKER ? 'http://iot-debug-service:8083/api/v1/health' : 'http://localhost:8083/api/v1/health', port: 8083, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'], goBin: GO_BIN, }, gateway: { name: 'Gateway', cwd: path.join(ROOT, 'backend/gateway'), command: './main', env: { GATEWAY_PORT: '8080', JWT_SECRET: process.env.JWT_SECRET || 'dev-secret-key-change-me', AI_CORE_URL: 'http://localhost:8081', MEMORY_SERVICE_URL: process.env.MEMORY_SERVICE_URL || 'http://localhost:8091', ADMIN_USERNAME: process.env.ADMIN_USERNAME || 'admin', ADMIN_PASSWORD: process.env.ADMIN_PASSWORD || 'cyrene-dev-admin', REGISTRATION_ENABLED: process.env.REGISTRATION_ENABLED || 'true', }, healthUrl: IS_DOCKER ? 'http://gateway:8080/api/v1/health' : 'http://localhost:8080/api/v1/health', port: 8080, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'], goBin: GO_BIN, }, 'memory-service': { name: '记忆服务', cwd: path.join(ROOT, 'backend/memory-service'), command: './main', env: { PORT: '8091', DB_URL: process.env.DB_URL || 'postgres://cyrene:cyrene_pass@localhost:5432/cyrene_ai?sslmode=disable', }, healthUrl: IS_DOCKER ? 'http://memory-service:8091/api/v1/health' : 'http://localhost:8091/api/v1/health', port: 8091, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'], goBin: GO_BIN, }, 'voice-service': { name: '语音识别服务', cwd: path.join(ROOT, 'backend/voice-service'), command: './main', env: { PORT: '8093', WHISPER_BINARY: './whisper.cpp/main', WHISPER_MODEL: './whisper.cpp/models/ggml-small.bin', WHISPER_LANGUAGE: 'zh', DASHSCOPE_API_KEY: process.env.DASHSCOPE_API_KEY || '', DASHSCOPE_STT_MODEL: 'gummy-chat-v1', }, healthUrl: IS_DOCKER ? 'http://voice-service:8093/api/v1/health' : 'http://localhost:8093/api/v1/health', port: 8093, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'], goBin: GO_BIN, }, 'plugin-manager': { name: '插件管理器', cwd: path.join(ROOT, 'backend/cyrene-plugins'), command: './main', env: { PORT: '8094', IOT_SERVICE_URL: process.env.IOT_SERVICE_URL || process.env.IOT_DEBUG_SERVICE_URL || 'http://localhost:8083', }, healthUrl: IS_DOCKER ? 'http://plugin-manager:8094/api/v1/health' : 'http://localhost:8094/api/v1/health', port: 8094, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/plugin-manager/'], goBin: GO_BIN, }, 'platform-bridge': { name: '多平台桥接', cwd: path.join(ROOT, 'backend/platform-bridge'), command: './main', env: { PORT: '8095', AI_CORE_URL: 'http://localhost:8081', OBV11_BOT_PORT: process.env.OBV11_BOT_PORT || '8096', TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN || '', TELEGRAM_WEBHOOK_URL: process.env.TELEGRAM_WEBHOOK_URL || '', OBV11_ADMIN_UID: process.env.OBV11_ADMIN_UID || '', TELEGRAM_ADMIN_UID: process.env.TELEGRAM_ADMIN_UID || '', }, healthUrl: IS_DOCKER ? 'http://platform-bridge:8095/health' : 'http://localhost:8095/health', port: 8095, buildCommand: 'go', buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'], goBin: GO_BIN, }, frontend: { name: 'Frontend', cwd: path.join(ROOT, 'frontend/web'), command: 'npx', args: ['vite', '--host', '0.0.0.0'], env: { PATH: process.env.PATH, }, healthUrl: 'http://localhost:5173', port: 5173, nodeBin: 'node', npmBin: 'npx', // frontend不需要预编译,dev server即可 buildCommand: null, }, }; /** 各服务默认的日志文件路径 */ export function logFile(serviceId) { return path.join(LOGS_DIR, `${serviceId}.log`); }