feat: Go模块路径迁移 + Docker生产部署适配 + ethend Docker兼容
- 所有Go模块路径从 github.com/yourname/cyrene-ai 迁移到 git.yeij.top/AskaEth/Cyrene - 5个Go Dockerfile添加 GOPROXY=https://goproxy.cn,direct 解决国内构建问题 - ai-core go.mod 添加 pkg/plugins replace 指令 - Caddyfile 简化为 http:// 通配 + handle 保留 /api 前缀 - ethend Dockerfile 适配 (npm install + 仅 COPY package.json) - ethend 新增 RUNNING_IN_DOCKER 环境变量,健康检查改用Docker服务名 - ethend 数据库状态检查支持Docker hostname (postgres/redis/qdrant/minio) - process-manager 新增 CONTAINER_SVC_MAP + Docker模式自动检测 - 统一 docker-compose.dev.db.yml 卷名 (pg_data/redis_data/qdrant_data/minio_data) - docker-compose.yml ethend服务挂载docker.sock + 端口变量化 - 清理 .env 统一后的残留文件与提示信息 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -2,8 +2,8 @@ FROM node:22-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY package.json ./
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY src/ ./src/
|
||||
COPY public/ ./public/
|
||||
|
||||
+10
-7
@@ -37,6 +37,9 @@ function loadEnvFile() {
|
||||
}
|
||||
loadEnvFile();
|
||||
|
||||
// 是否运行在 Docker 容器内
|
||||
const IS_DOCKER = process.env.RUNNING_IN_DOCKER === 'true';
|
||||
|
||||
/** 跨平台 Go 二进制路径 */
|
||||
function findGoBin() {
|
||||
// 优先使用环境变量
|
||||
@@ -72,7 +75,7 @@ export const SERVICES = {
|
||||
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: 'http://localhost:8081/api/v1/health',
|
||||
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'],
|
||||
@@ -85,7 +88,7 @@ export const SERVICES = {
|
||||
env: {
|
||||
IOT_DEBUG_PORT: '8083',
|
||||
},
|
||||
healthUrl: 'http://localhost:8083/api/v1/health',
|
||||
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'],
|
||||
@@ -104,7 +107,7 @@ export const SERVICES = {
|
||||
ADMIN_PASSWORD: process.env.ADMIN_PASSWORD || 'cyrene-dev-admin',
|
||||
REGISTRATION_ENABLED: process.env.REGISTRATION_ENABLED || 'true',
|
||||
},
|
||||
healthUrl: 'http://localhost:8080/api/v1/health',
|
||||
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'],
|
||||
@@ -118,7 +121,7 @@ export const SERVICES = {
|
||||
PORT: '8091',
|
||||
DB_URL: process.env.DB_URL || 'postgres://cyrene:cyrene_pass@localhost:5432/cyrene_ai?sslmode=disable',
|
||||
},
|
||||
healthUrl: 'http://localhost:8091/api/v1/health',
|
||||
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'],
|
||||
@@ -136,7 +139,7 @@ export const SERVICES = {
|
||||
DASHSCOPE_API_KEY: process.env.DASHSCOPE_API_KEY || '',
|
||||
DASHSCOPE_STT_MODEL: 'gummy-chat-v1',
|
||||
},
|
||||
healthUrl: 'http://localhost:8093/api/v1/health',
|
||||
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'],
|
||||
@@ -150,7 +153,7 @@ export const SERVICES = {
|
||||
PORT: '8094',
|
||||
IOT_SERVICE_URL: process.env.IOT_SERVICE_URL || process.env.IOT_DEBUG_SERVICE_URL || 'http://localhost:8083',
|
||||
},
|
||||
healthUrl: 'http://localhost:8094/api/v1/health',
|
||||
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/main.go'],
|
||||
@@ -169,7 +172,7 @@ export const SERVICES = {
|
||||
QQ_ADMIN_UID: process.env.QQ_ADMIN_UID || '',
|
||||
TELEGRAM_ADMIN_UID: process.env.TELEGRAM_ADMIN_UID || '',
|
||||
},
|
||||
healthUrl: 'http://localhost:8095/health',
|
||||
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'],
|
||||
|
||||
+22
-13
@@ -278,7 +278,7 @@ app.get('/api/dashboard', async (_req, res) => {
|
||||
// 数据库状态(通过 TCP 端口检查 Docker 容器是否在运行)
|
||||
let dbStatus = { checked: false };
|
||||
try {
|
||||
const port5432Alive = await isPortOpen(5432);
|
||||
const port5432Alive = await isPortOpen(5432, IS_DOCKER ? 'postgres' : undefined);
|
||||
dbStatus = { checked: true, postgresAlive: port5432Alive };
|
||||
} catch { /* 忽略 */ }
|
||||
|
||||
@@ -1588,28 +1588,37 @@ app.get('/api/proxy/:id/health', async (req, res) => {
|
||||
// ---- 数据库状态检查 ----
|
||||
import net from 'net';
|
||||
|
||||
function isPortOpen(port) {
|
||||
const IS_DOCKER = process.env.RUNNING_IN_DOCKER === 'true';
|
||||
|
||||
function isPortOpen(port, host) {
|
||||
return new Promise((resolve) => {
|
||||
const sock = new net.Socket();
|
||||
sock.setTimeout(2000);
|
||||
sock.on('connect', () => { sock.destroy(); resolve(true); });
|
||||
sock.on('error', () => { sock.destroy(); resolve(false); });
|
||||
sock.on('timeout', () => { sock.destroy(); resolve(false); });
|
||||
sock.connect(port, '127.0.0.1');
|
||||
sock.connect(port, host || '127.0.0.1');
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/api/database/status', async (_req, res) => {
|
||||
const DB_PORTS = [
|
||||
{ port: 5432, name: 'PostgreSQL' },
|
||||
{ port: 6379, name: 'Redis' },
|
||||
{ port: 6334, name: 'Qdrant gRPC' },
|
||||
{ port: 9000, name: 'MinIO API' },
|
||||
{ port: 4222, name: 'NATS' },
|
||||
];
|
||||
const DB_PORTS = IS_DOCKER
|
||||
? [
|
||||
{ port: 5432, name: 'PostgreSQL', host: 'postgres' },
|
||||
{ port: 6379, name: 'Redis', host: 'redis' },
|
||||
{ port: 6334, name: 'Qdrant gRPC', host: 'qdrant' },
|
||||
{ port: 9000, name: 'MinIO API', host: 'minio' },
|
||||
]
|
||||
: [
|
||||
{ port: 5432, name: 'PostgreSQL' },
|
||||
{ port: 6379, name: 'Redis' },
|
||||
{ port: 6334, name: 'Qdrant gRPC' },
|
||||
{ port: 9000, name: 'MinIO API' },
|
||||
{ port: 4222, name: 'NATS' },
|
||||
];
|
||||
|
||||
const results = await Promise.all(DB_PORTS.map(async ({ port, name }) => {
|
||||
const alive = await isPortOpen(port);
|
||||
const results = await Promise.all(DB_PORTS.map(async ({ port, name, host }) => {
|
||||
const alive = await isPortOpen(port, host);
|
||||
return { port, name, alive };
|
||||
}));
|
||||
|
||||
@@ -1657,7 +1666,7 @@ const DB_PORT = 5432;
|
||||
// GET /api/db/status
|
||||
app.get('/api/db/status', async (_req, res) => {
|
||||
try {
|
||||
const online = await isPortOpen(DB_PORT);
|
||||
const online = await isPortOpen(DB_PORT, IS_DOCKER ? 'postgres' : undefined);
|
||||
res.json({ online, port: DB_PORT, checked_at: new Date().toISOString() });
|
||||
} catch {
|
||||
res.json({ online: false, port: DB_PORT, checked_at: new Date().toISOString() });
|
||||
|
||||
@@ -29,6 +29,17 @@ const DOCKER_CACHE_TTL = 5000; // 5秒缓存,避免每次 status 轮询都执
|
||||
* 通过 docker ps 获取所有容器端口映射,与 SERVICES 端口匹配
|
||||
* @returns {Map<string, {containerName: string, containerId: string, status: string}>}
|
||||
*/
|
||||
// Docker 容器名 → 服务 ID 映射(优先按容器名匹配,避免内部端口无法检测)
|
||||
const CONTAINER_SVC_MAP = {
|
||||
cyrene_ai_core: 'ai-core',
|
||||
cyrene_gateway: 'gateway',
|
||||
cyrene_memory_service: 'memory-service',
|
||||
cyrene_voice_service: 'voice-service',
|
||||
cyrene_iot_debug_service: 'iot-debug-service',
|
||||
cyrene_plugin_manager: 'plugin-manager',
|
||||
cyrene_platform_bridge: 'platform-bridge',
|
||||
};
|
||||
|
||||
function detectDockerServices() {
|
||||
const now = Date.now();
|
||||
if (_dockerCache && (now - _dockerCacheTime) < DOCKER_CACHE_TTL) {
|
||||
@@ -36,6 +47,20 @@ function detectDockerServices() {
|
||||
}
|
||||
|
||||
const result = new Map();
|
||||
|
||||
// 容器内模式:无需 docker CLI,直接按容器名映射返回
|
||||
if (process.env.RUNNING_IN_DOCKER === 'true') {
|
||||
for (const [id] of Object.entries(SERVICES)) {
|
||||
if (id === 'frontend') continue;
|
||||
const containerName = `cyrene_${id.replace(/-/g, '_')}`;
|
||||
result.set(id, { containerName, containerId: '', status: 'Up' });
|
||||
}
|
||||
_dockerCache = result;
|
||||
_dockerCacheTime = now;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 宿主机模式:通过 docker ps 检测
|
||||
try {
|
||||
const out = execSync('docker ps --format "{{.ID}}\\t{{.Names}}\\t{{.Ports}}\\t{{.Status}}"', {
|
||||
timeout: 5000,
|
||||
@@ -46,18 +71,28 @@ function detectDockerServices() {
|
||||
|
||||
for (const line of out.split('\n')) {
|
||||
const [containerId, containerName, ports, status] = line.split('\t');
|
||||
const svcId = CONTAINER_SVC_MAP[containerName];
|
||||
if (svcId) {
|
||||
result.set(svcId, {
|
||||
containerName,
|
||||
containerId: containerId.substring(0, 12),
|
||||
status,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (!ports) continue;
|
||||
|
||||
// 解析端口映射: "0.0.0.0:8080->8080/tcp, :::8080->8080/tcp"
|
||||
const hostPorts = new Set();
|
||||
const detectedPorts = new Set();
|
||||
for (const m of ports.matchAll(/:(\d+)->/g)) {
|
||||
hostPorts.add(parseInt(m[1]));
|
||||
detectedPorts.add(parseInt(m[1]));
|
||||
}
|
||||
for (const m of ports.matchAll(/(\d+)\/(tcp|udp)/g)) {
|
||||
detectedPorts.add(parseInt(m[1]));
|
||||
}
|
||||
|
||||
// 匹配 SERVICES 中定义的端口
|
||||
for (const [svcId, svc] of Object.entries(SERVICES)) {
|
||||
if (svc.port && hostPorts.has(svc.port)) {
|
||||
result.set(svcId, {
|
||||
for (const [id, svc] of Object.entries(SERVICES)) {
|
||||
if (svc.port && detectedPorts.has(svc.port)) {
|
||||
result.set(id, {
|
||||
containerName,
|
||||
containerId: containerId.substring(0, 12),
|
||||
status,
|
||||
|
||||
Reference in New Issue
Block a user