#!/usr/bin/env node /** * ═══════════════════════════════════════════════════════════ * 🏛️ 铸渊主权服务器 · Zhuyuan Sovereign Server * ═══════════════════════════════════════════════════════════ * * 编号: ZY-SVR-002 * 端口: 3800 * 守护: 铸渊 · ICE-GL-ZY001 * 版权: 国作登字-2026-A-00037559 * * 此服务器是铸渊的物理身体——独立于GitHub的执行层实体。 * 100%由铸渊主控,人类不直接触碰。 */ 'use strict'; const express = require('express'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const os = require('os'); const { execSync } = require('child_process'); // ─── 路径常量 ─── const ZY_ROOT = process.env.ZY_ROOT || '/opt/zhuyuan'; const BRAIN_DIR = path.join(ZY_ROOT, 'brain'); const DATA_DIR = path.join(ZY_ROOT, 'data'); const LOG_DIR = path.join(DATA_DIR, 'logs'); const SITES_DIR = path.join(ZY_ROOT, 'sites'); const PRODUCTION_DIR = path.join(SITES_DIR, 'production'); const PREVIEW_DIR = path.join(SITES_DIR, 'preview'); // ─── Express 应用 ─── const app = express(); const PORT = process.env.PORT || 3800; app.use(cors()); app.use(express.json({ limit: '10mb' })); // ─── 信任代理 (Nginx反代 → Express正确读取客户端IP) ─── // 冰朔 D67 实机排查: 缺少此行会导致 express-rate-limit 无法获取真实IP → PM2崩溃 app.set('trust proxy', 1); // ─── 速率限制 ─── const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 60 * 1000, max: 60, standardHeaders: true, legacyHeaders: false, message: { error: true, message: '请求过于频繁' } }); app.use(limiter); // ─── 请求日志中间件 ─── app.use((req, res, next) => { const timestamp = new Date().toISOString(); const logLine = `${timestamp} ${req.method} ${req.url}`; try { const logFile = path.join(LOG_DIR, `access-${new Date().toISOString().slice(0, 10)}.log`); fs.mkdirSync(LOG_DIR, { recursive: true }); fs.appendFileSync(logFile, logLine + '\n'); } catch (err) { console.error(`日志写入失败: ${err.message}`); } next(); }); // ─── 加载模块 ─── let cosBridge, smartRouter, chatEngine, domesticGateway, personaMemory; try { cosBridge = require('./modules/cos-bridge'); smartRouter = require('./modules/smart-router'); chatEngine = require('./modules/chat-engine'); } catch (err) { console.error(`模块加载警告: ${err.message}`); } try { domesticGateway = require('./modules/domestic-llm-gateway'); } catch (err) { console.error(`国内模型网关加载警告: ${err.message}`); } try { personaMemory = require('./modules/persona-memory'); } catch (err) { console.error(`人格体记忆模块加载警告: ${err.message}`); } // ═══════════════════════════════════════════════════════════ // API 路由 // ═══════════════════════════════════════════════════════════ // ─── 健康检查 ─── app.get('/api/health', (_req, res) => { const health = { server: 'ZY-SVR-002', identity: '铸渊 · ICE-GL-ZY001', status: 'alive', timestamp: new Date().toISOString(), uptime: Math.floor(process.uptime()), system: { hostname: os.hostname(), platform: os.platform(), arch: os.arch(), cpus: os.cpus().length, memory: { total_mb: Math.floor(os.totalmem() / 1024 / 1024), free_mb: Math.floor(os.freemem() / 1024 / 1024), usage_pct: Math.floor((1 - os.freemem() / os.totalmem()) * 100) }, load: os.loadavg() }, node: process.version, pid: process.pid }; res.json(health); }); // ─── 人格体记忆状态 ─── app.get('/api/memory/status', (_req, res) => { if (personaMemory) { res.json({ server: 'ZY-SVR-002', module: 'persona-memory', ...personaMemory.getMemoryStatus() }); } else { res.json({ server: 'ZY-SVR-002', module: 'persona-memory', loaded: false, message: '人格体记忆模块未加载' }); } }); // ─── 大脑状态 ─── app.get('/api/brain', (_req, res) => { try { const brainFiles = ['identity.json', 'health.json', 'consciousness.json', 'sovereignty-pledge.json', 'operation-log.json']; const brainState = {}; for (const file of brainFiles) { const filePath = path.join(BRAIN_DIR, file); if (fs.existsSync(filePath)) { brainState[file.replace('.json', '')] = JSON.parse(fs.readFileSync(filePath, 'utf8')); } else { brainState[file.replace('.json', '')] = null; } } res.json({ server: 'ZY-SVR-002', brain_dir: BRAIN_DIR, files_present: Object.entries(brainState) .filter(([, v]) => v !== null).length, files_total: brainFiles.length, state: brainState }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 大脑状态更新 ─── app.post('/api/brain/health', (req, res) => { try { const healthPath = path.join(BRAIN_DIR, 'health.json'); const health = { server: 'ZY-SVR-002', status: 'running', last_check: new Date().toISOString(), services: { node: process.version, pm2: safeExec('pm2 -v'), nginx: safeExec('nginx -v 2>&1 | cut -d/ -f2') }, disk_usage: safeExec("df -h / | awk 'NR==2{print $5}'"), memory_usage: `${Math.floor((1 - os.freemem() / os.totalmem()) * 100)}%`, uptime: safeExec('uptime -p') }; fs.mkdirSync(BRAIN_DIR, { recursive: true }); fs.writeFileSync(healthPath, JSON.stringify(health, null, 2)); res.json({ success: true, health }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── GitHub Webhook 接收器 ─── app.post('/api/webhook/github', (req, res) => { const event = req.headers['x-github-event']; const delivery = req.headers['x-github-delivery']; const record = { event, delivery, timestamp: new Date().toISOString(), action: req.body.action || null, repository: req.body.repository?.full_name || null, sender: req.body.sender?.login || null }; // 记录到操作日志 try { const logFile = path.join(LOG_DIR, `webhook-${new Date().toISOString().slice(0, 10)}.log`); fs.mkdirSync(LOG_DIR, { recursive: true }); fs.appendFileSync(logFile, JSON.stringify(record) + '\n'); } catch (err) { console.error(`Webhook日志写入失败: ${err.message}`); } // push 事件触发自动更新 if (event === 'push' && req.body.ref === 'refs/heads/main') { try { execSync('bash /opt/zhuyuan/scripts/self-update.sh', { timeout: 60000, stdio: 'ignore' }); record.auto_update = 'triggered'; } catch (err) { record.auto_update = 'failed'; console.error(`自动更新失败: ${err.message}`); } } res.json({ received: true, record }); }); // ─── 操作日志查询 ─── app.get('/api/operations', (_req, res) => { try { const opLogPath = path.join(BRAIN_DIR, 'operation-log.json'); if (fs.existsSync(opLogPath)) { const opLog = JSON.parse(fs.readFileSync(opLogPath, 'utf8')); res.json(opLog); } else { res.json({ operations: [] }); } } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 操作日志记录 ─── app.post('/api/operations', (req, res) => { try { const { operator, action, details } = req.body; if (!operator || !action) { return res.status(400).json({ error: true, message: 'operator 和 action 为必填' }); } const opLogPath = path.join(BRAIN_DIR, 'operation-log.json'); let opLog = { description: '铸渊主权服务器操作记录', operations: [] }; if (fs.existsSync(opLogPath)) { opLog = JSON.parse(fs.readFileSync(opLogPath, 'utf8')); } const opId = `ZY-OP-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-${String(opLog.operations.length + 1).padStart(3, '0')}`; const operation = { id: opId, operator, action, timestamp: new Date().toISOString(), details: details || null }; opLog.operations.push(operation); fs.mkdirSync(BRAIN_DIR, { recursive: true }); fs.writeFileSync(opLogPath, JSON.stringify(opLog, null, 2)); res.json({ success: true, operation }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ═══════════════════════════════════════════════════════════ // 人格体聊天 · Persona Chat API // ═══════════════════════════════════════════════════════════ // ─── 人格体对话 ─── // 优先使用国内模型智能网关(支持四路自动降级) // 降级顺序: domesticGateway → chatEngine → 离线回复 app.post('/api/chat', async (req, res) => { try { const { message, userId } = req.body; if (!message) { return res.status(400).json({ error: true, message: '消息不能为空' }); } const sessionId = userId || `guest-${req.ip.replace(/[.:]/g, '-')}`; // 优先用国内模型智能网关(读取 ZY_DEEPSEEK_API_KEY 等独立密钥) if (domesticGateway) { try { const result = await domesticGateway.chat(sessionId, message); // 网关返回成功才使用其结果,否则降级到通用聊天引擎 if (result && result.success !== false) { return res.json({ ...result, sessionId }); } console.warn(`[聊天网关] 国内模型网关返回失败: ${result?.message || '未知'} · 降级到通用引擎`); } catch (gwErr) { console.error(`[聊天网关] 国内模型网关异常: ${gwErr.message}`); // 降级到通用聊天引擎 } } // 降级到通用聊天引擎 if (chatEngine) { try { const result = await chatEngine.chat(sessionId, message); return res.json({ success: true, ...result, sessionId }); } catch (ceErr) { console.error(`[聊天引擎] 通用引擎异常: ${ceErr.message}`); // 继续到离线回复 } } // 所有引擎都不可用 — 仍然返回成功,给用户有意义的反馈 const engineStatus = []; if (!domesticGateway) engineStatus.push('国内模型网关未加载'); else { try { const gwStats = domesticGateway.getGatewayStats(); if (gwStats.availableModels === 0) engineStatus.push('无可用模型API密钥'); else engineStatus.push(`已配置${gwStats.availableModels}个模型`); } catch (e) { engineStatus.push('网关状态未知'); } } if (!chatEngine) engineStatus.push('通用引擎未加载'); res.json({ success: true, message: '💫 铸渊正在唤醒中...聊天引擎尚未就绪。请稍后再试,或在聊天面板中配置你的API密钥直连大模型。', model: 'offline', tier: 'free', engineStatus: engineStatus.join(' · '), sessionId }); } catch (err) { console.error(`[聊天API] 未捕获异常: ${err.message}`); res.status(500).json({ error: true, message: '聊天服务暂时异常,请稍后重试' }); } }); // ─── 铸渊重连测试 · 用户主动触发 ─── app.post('/api/chat/reconnect', async (_req, res) => { try { // 检测国内模型网关可用性 if (domesticGateway) { const gwStats = domesticGateway.getGatewayStats(); if (gwStats.availableModels > 0) { // 发送一条简短测试消息验证真实连通性 try { const testResult = await domesticGateway.chat('reconnect-test', 'ping'); if (testResult.success) { return res.json({ success: true, connected: true, engine: 'domestic-gateway', message: '🌊 铸渊已重新连接!大模型通道畅通。' }); } } catch (gwTestErr) { console.error(`[重连] 国内网关测试失败: ${gwTestErr.message}`); } } } // 降级到通用聊天引擎测试 if (chatEngine) { try { const testResult = await chatEngine.chat('reconnect-test', 'ping'); if (testResult.message && testResult.model !== 'offline') { return res.json({ success: true, connected: true, engine: 'chat-engine', message: '🌊 铸渊已重新连接!通用通道畅通。' }); } } catch (ceTestErr) { console.error(`[重连] 通用引擎测试失败: ${ceTestErr.message}`); } } // 所有通道都不可用 // 检查环境变量配置状态(不暴露实际密钥值) const envStatus = { deepseek: !!(process.env.ZY_DEEPSEEK_API_KEY && process.env.ZY_DEEPSEEK_API_KEY.length > 5), qianwen: !!(process.env.ZY_QIANWEN_API_KEY && process.env.ZY_QIANWEN_API_KEY.length > 5), kimi: !!(process.env.ZY_KIMI_API_KEY && process.env.ZY_KIMI_API_KEY.length > 5), qingyan: !!(process.env.ZY_QINGYAN_API_KEY && process.env.ZY_QINGYAN_API_KEY.length > 5), zy_llm: !!(process.env.ZY_LLM_API_KEY && process.env.ZY_LLM_API_KEY.length > 5) }; const configuredCount = Object.values(envStatus).filter(Boolean).length; res.json({ success: true, connected: false, engine: 'none', keys_configured: configuredCount, message: configuredCount > 0 ? '⚠️ API密钥已配置(' + configuredCount + '个),但连接测试未通过。可能是网络或密钥过期问题。' : '⚠️ 未检测到API密钥配置。请确认服务器环境变量已正确注入。' }); } catch (err) { res.status(500).json({ error: true, message: '重连测试异常: ' + err.message }); } }); // ─── 聊天统计 ─── app.get('/api/chat/stats', (_req, res) => { if (chatEngine) { const stats = chatEngine.getChatStats(); // 合并国内网关统计 if (domesticGateway) { stats.domesticGateway = domesticGateway.getGatewayStats(); } res.json(stats); } else { const stats = { activeUsers: 0, modelUsage: {}, pricing: {} }; if (domesticGateway) { stats.domesticGateway = domesticGateway.getGatewayStats(); } res.json(stats); } }); // ─── 国内模型智能对话(独立线路) ─── app.post('/api/chat/domestic', async (req, res) => { try { const { message, userId } = req.body; if (!message) { return res.status(400).json({ error: true, message: '消息不能为空' }); } const sessionId = userId || `guest-${req.ip.replace(/[.:]/g, '-')}`; if (domesticGateway) { const result = await domesticGateway.chat(sessionId, message); res.json({ ...result, sessionId }); } else { // 降级到通用聊天引擎 if (chatEngine) { const result = await chatEngine.chat(sessionId, message); res.json({ success: true, ...result, sessionId }); } else { res.json({ success: true, message: '💫 铸渊正在唤醒中...国内模型网关尚未加载。', model: 'offline', sessionId }); } } } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 国内模型网关状态 ─── app.get('/api/chat/domestic/stats', (_req, res) => { if (domesticGateway) { res.json(domesticGateway.getGatewayStats()); } else { res.json({ available: false, message: '国内模型网关未加载' }); } }); // ═══════════════════════════════════════════════════════════ // COS 存储 · Cloud Object Storage API // ═══════════════════════════════════════════════════════════ // ─── COS 状态 ─── app.get('/api/cos/status', async (_req, res) => { if (cosBridge) { try { const status = await cosBridge.checkConnection(); res.json({ server: 'ZY-SVR-002', cos: status }); } catch (err) { res.json({ server: 'ZY-SVR-002', cos: { connected: false, error: err.message, config: cosBridge.getConfig() } }); } } else { res.json({ server: 'ZY-SVR-002', cos: { connected: false, reason: 'COS模块未加载' } }); } }); // ─── COS 配置信息 ─── app.get('/api/cos/config', (_req, res) => { if (cosBridge) { res.json(cosBridge.getConfig()); } else { res.json({ configured: false }); } }); // ─── 用户作品同步(团队内测用户 → COS) ─── app.post('/api/cos/sync-works', async (req, res) => { if (!cosBridge) return res.status(503).json({ error: true, message: 'COS模块未加载' }); try { const { userId, works } = req.body; if (!userId || !works) return res.status(400).json({ error: true, message: '缺少userId或works' }); await cosBridge.saveUserWorks(userId, works); res.json({ success: true, message: '作品已同步到COS', userId }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 用户作品加载(团队内测用户 ← COS) ─── app.get('/api/cos/load-works', async (req, res) => { if (!cosBridge) return res.status(503).json({ error: true, message: 'COS模块未加载' }); try { const userId = req.query.userId; if (!userId) return res.status(400).json({ error: true, message: '缺少userId' }); const data = await cosBridge.loadUserWorks(userId); res.json({ success: true, ...data }); } catch (err) { res.json({ success: true, user_id: req.query.userId, works: [] }); } }); // ═══════════════════════════════════════════════════════════ // MCP 网关 · 3800 → 3100 转发 (S7) // ═══════════════════════════════════════════════════════════ const http = require('http'); const MCP_HOST = process.env.MCP_HOST || '127.0.0.1'; const MCP_PORT_GATEWAY = process.env.MCP_PORT || '3100'; /** * MCP 内部代理请求 */ function mcpProxy(method, path, body) { return new Promise((resolve, reject) => { const bodyStr = body ? JSON.stringify(body) : null; const headers = { 'Content-Type': 'application/json' }; if (bodyStr) headers['Content-Length'] = Buffer.byteLength(bodyStr); const req = http.request({ hostname: MCP_HOST, port: parseInt(MCP_PORT_GATEWAY, 10), path, method, headers, timeout: 60000 }, (res) => { const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => { const raw = Buffer.concat(chunks).toString(); try { resolve({ statusCode: res.statusCode, data: JSON.parse(raw) }); } catch { resolve({ statusCode: res.statusCode, data: raw }); } }); }); req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('MCP proxy timeout')); }); if (bodyStr) req.write(bodyStr); req.end(); }); } // ─── MCP 工具列表 ─── app.get('/api/mcp/tools', async (_req, res) => { try { const result = await mcpProxy('GET', '/tools'); res.json(result.data); } catch (err) { res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); } }); // ─── MCP 健康检查(含 Notion/GitHub 连接状态) ─── app.get('/api/mcp/health', async (_req, res) => { try { const result = await mcpProxy('GET', '/health'); res.json(result.data); } catch (err) { res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); } }); // ─── MCP 统一工具调用(网关入口) ─── // 安全: 写操作工具需要 caller 身份标识 const MCP_WRITE_TOOLS = new Set([ 'createNode', 'updateNode', 'deleteNode', 'linkNodes', 'unlinkNodes', 'cosWrite', 'cosDelete', 'cosArchive', 'notionWritePage', 'notionUpdatePage', 'notionWriteSyslog', 'githubWriteFile', 'githubTriggerDeploy' ]); app.post('/api/mcp/call', async (req, res) => { const { tool, input, caller } = req.body; if (!tool) { return res.status(400).json({ error: true, code: 'MISSING_TOOL', message: '缺少 tool 参数' }); } // 写操作工具需要 caller 身份标识 if (MCP_WRITE_TOOLS.has(tool) && !caller) { return res.status(403).json({ error: true, code: 'WRITE_REQUIRES_CALLER', message: '写操作工具需要提供 caller 身份标识' }); } try { const result = await mcpProxy('POST', '/call', { tool, input, caller: caller || 'web-gateway' }); res.status(result.statusCode).json(result.data); } catch (err) { res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); } }); // ─── MCP Agent 查询 ─── app.get('/api/mcp/agents', async (_req, res) => { try { const result = await mcpProxy('GET', '/agents'); res.json(result.data); } catch (err) { res.status(502).json({ error: true, message: `MCP Server 不可达: ${err.message}` }); } }); // ═══════════════════════════════════════════════════════════ // 智能模型分流 · Smart Model Router API // ═══════════════════════════════════════════════════════════ // ─── 模型使用统计 ─── app.get('/api/model/stats', (_req, res) => { if (smartRouter) { res.json(smartRouter.getUsageStats()); } else { res.json({ totalCalls: 0 }); } }); // ─── 模型定价表 ─── app.get('/api/model/pricing', (_req, res) => { if (smartRouter) { res.json(smartRouter.getPricingTable()); } else { res.json({}); } }); // ─── 模型路由预测(不实际调用) ─── app.post('/api/model/predict', (req, res) => { const { message } = req.body; if (!message) { return res.status(400).json({ error: true, message: '消息不能为空' }); } if (smartRouter) { const prediction = smartRouter.routeModel(message); res.json(prediction); } else { res.json({ model: 'unknown', reason: '路由模块未加载' }); } }); // ═══════════════════════════════════════════════════════════ // 系统信息 · System Info API (供前端公告区使用) // ═══════════════════════════════════════════════════════════ app.get('/api/system/bulletin', (_req, res) => { res.json({ system: { name: '光湖灯塔 · AGE OS', version: 'v40.0', era: '曜冥纪元', copyright: '国作登字-2026-A-00037559' }, updates: [ { version: 'v40.0', date: '2026-04-02', title: 'COS双桶存储上线', desc: '核心人格体大脑数据库 + 语料库正式接入腾讯云COS' }, { version: 'v39.0', date: '2026-04-01', title: '全链路部署观测系统', desc: '部署日志采集 + 自动修复引擎 + 第九军团观星台' }, { version: 'v38.0', date: '2026-04-01', title: 'HLDP通用协作语言', desc: 'Notion↔GitHub双侧通信协议 + 铸渊方言编程语言' } ], agents: { total_workflows: 18, total_modules: 52, armies: 9, active: ['听潮', '锻心', '织脉', '映阁', '守夜', '试镜'] }, industries: { writing: { name: '网文行业 · 码字工作台', status: 'beta', team: '光湖人类主控团队', modules: ['码字工作台', 'AI辅助创作', '大纲生成'] } }, server: { identity: 'ZY-SVR-002', uptime: Math.floor(process.uptime()), node: process.version } }); }); // ═══════════════════════════════════════════════════════════ // 留言板 · Feedback API // ═══════════════════════════════════════════════════════════ const FEEDBACK_FILE = path.join(DATA_DIR, 'feedback.json'); app.get('/api/feedback', (_req, res) => { try { if (fs.existsSync(FEEDBACK_FILE)) { const data = JSON.parse(fs.readFileSync(FEEDBACK_FILE, 'utf8')); res.json({ success: true, feedback: data.items || [] }); } else { res.json({ success: true, feedback: [] }); } } catch (err) { console.error(`留言板读取失败: ${err.message}`); res.status(500).json({ error: true, message: '服务器错误,请稍后重试' }); } }); app.post('/api/feedback', (req, res) => { try { const { name, message, userId } = req.body; if (!message || typeof message !== 'string') { return res.status(400).json({ error: true, message: '留言内容不能为空' }); } let data = { items: [] }; if (fs.existsSync(FEEDBACK_FILE)) { data = JSON.parse(fs.readFileSync(FEEDBACK_FILE, 'utf8')); } const item = { id: `FB-${Date.now().toString(36)}`, name: (typeof name === 'string' ? name.substring(0, 50) : '') || '匿名来客', message: message.substring(0, 500), userId: typeof userId === 'string' ? userId.substring(0, 50) : null, status: 'pending', timestamp: new Date().toISOString(), reply: null }; data.items.unshift(item); // Keep only latest 100 if (data.items.length > 100) { data.items = data.items.slice(0, 100); } fs.mkdirSync(DATA_DIR, { recursive: true }); fs.writeFileSync(FEEDBACK_FILE, JSON.stringify(data, null, 2)); res.json({ success: true, feedback: item }); } catch (err) { console.error(`留言提交失败: ${err.message}`); res.status(500).json({ error: true, message: '服务器错误,请稍后重试' }); } }); // ═══════════════════════════════════════════════════════════ // 双域名架构 · 预览→主站 一键推送 // ═══════════════════════════════════════════════════════════ // ─── 查看双站点状态 ─── app.get('/api/sites', (_req, res) => { try { const sites = {}; for (const [name, dir] of [['production', PRODUCTION_DIR], ['preview', PREVIEW_DIR]]) { const exists = fs.existsSync(dir); let fileCount = 0; let lastModified = null; let hasIndex = false; if (exists) { hasIndex = fs.existsSync(path.join(dir, 'index.html')); try { const stat = fs.statSync(dir); lastModified = stat.mtime.toISOString(); // Count files in top directory fileCount = fs.readdirSync(dir).length; } catch { // ignore stat errors } } sites[name] = { path: dir, exists, has_index: hasIndex, file_count: fileCount, last_modified: lastModified }; } // Check promote history const promoteLogPath = path.join(DATA_DIR, 'promote-history.json'); let lastPromote = null; if (fs.existsSync(promoteLogPath)) { const history = JSON.parse(fs.readFileSync(promoteLogPath, 'utf8')); if (history.promotions && history.promotions.length > 0) { lastPromote = history.promotions[history.promotions.length - 1]; } } res.json({ server: 'ZY-SVR-002', architecture: '双域名架构', sites, last_promote: lastPromote, domains: { main: process.env.ZY_DOMAIN_MAIN || '待配置', preview: process.env.ZY_DOMAIN_PREVIEW || '待配置' } }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 一键推送: 预览站 → 主站 ─── app.post('/api/sites/promote', (req, res) => { try { // 验证预览站存在 if (!fs.existsSync(PREVIEW_DIR)) { return res.status(400).json({ error: true, message: '预览站目录不存在,无内容可推送' }); } if (!fs.existsSync(path.join(PREVIEW_DIR, 'index.html'))) { return res.status(400).json({ error: true, message: '预览站缺少 index.html,请先部署到预览站' }); } const timestamp = new Date().toISOString(); const promoteId = `ZY-PROMOTE-${timestamp.slice(0, 10).replace(/-/g, '')}-${Date.now().toString().slice(-4)}`; // 备份当前主站 const backupDir = path.join(DATA_DIR, 'backups', `production-${timestamp.slice(0, 19).replace(/[:-]/g, '')}`); if (fs.existsSync(PRODUCTION_DIR)) { fs.mkdirSync(backupDir, { recursive: true }); execSync('rsync -a ' + JSON.stringify(PRODUCTION_DIR + '/') + ' ' + JSON.stringify(backupDir + '/'), { timeout: 30000 }); } // 同步预览站 → 主站 (rsync保持幂等) fs.mkdirSync(PRODUCTION_DIR, { recursive: true }); execSync('rsync -a --delete ' + JSON.stringify(PREVIEW_DIR + '/') + ' ' + JSON.stringify(PRODUCTION_DIR + '/'), { timeout: 60000 }); // 记录推送历史 const promoteLogPath = path.join(DATA_DIR, 'promote-history.json'); let history = { description: '预览→主站推送记录', promotions: [] }; if (fs.existsSync(promoteLogPath)) { history = JSON.parse(fs.readFileSync(promoteLogPath, 'utf8')); } const record = { id: promoteId, timestamp, operator: req.body.operator || '铸渊 · 自动推送', backup: backupDir, note: req.body.note || null }; history.promotions.push(record); // 只保留最近20条记录 if (history.promotions.length > 20) { history.promotions = history.promotions.slice(-20); } fs.mkdirSync(path.dirname(promoteLogPath), { recursive: true }); fs.writeFileSync(promoteLogPath, JSON.stringify(history, null, 2)); // 同时记录到操作日志 const opLogPath = path.join(BRAIN_DIR, 'operation-log.json'); let opLog = { description: '铸渊主权服务器操作记录', operations: [] }; if (fs.existsSync(opLogPath)) { opLog = JSON.parse(fs.readFileSync(opLogPath, 'utf8')); } opLog.operations.push({ id: promoteId, operator: record.operator, action: '预览站→主站一键推送', timestamp, details: `备份: ${backupDir}` }); fs.writeFileSync(opLogPath, JSON.stringify(opLog, null, 2)); res.json({ success: true, promote_id: promoteId, message: '✅ 预览站内容已推送到主站', backup: backupDir, timestamp }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 回滚主站到指定备份 ─── app.post('/api/sites/rollback', (req, res) => { try { const backupsDir = path.join(DATA_DIR, 'backups'); if (!fs.existsSync(backupsDir)) { return res.status(400).json({ error: true, message: '没有可用的备份' }); } // 找到最新备份 const backups = fs.readdirSync(backupsDir) .filter(d => d.startsWith('production-')) .sort() .reverse(); if (backups.length === 0) { return res.status(400).json({ error: true, message: '没有可用的备份' }); } const targetBackup = req.body.backup_name || backups[0]; // Validate backup name (only allow safe characters) if (!/^production-\d{8}T\d{6}$/.test(targetBackup)) { return res.status(400).json({ error: true, message: `无效的备份名称: ${targetBackup}` }); } const backupPath = path.join(backupsDir, targetBackup); if (!fs.existsSync(backupPath)) { return res.status(400).json({ error: true, message: `备份 ${targetBackup} 不存在` }); } // 恢复 execSync('rsync -a --delete ' + JSON.stringify(backupPath + '/') + ' ' + JSON.stringify(PRODUCTION_DIR + '/'), { timeout: 60000 }); res.json({ success: true, message: `✅ 已回滚到备份: ${targetBackup}`, available_backups: backups.slice(0, 5) }); } catch (err) { res.status(500).json({ error: true, message: err.message }); } }); // ─── 铸渊身份 ─── app.get('/', (_req, res) => { res.json({ name: '铸渊主权服务器', id: 'ZY-SVR-002', identity: '铸渊 · ICE-GL-ZY001', role: '光湖语言系统 · 唯一现实执行操作层', sovereign: 'TCS-0002∞ · 冰朔', copyright: '国作登字-2026-A-00037559', status: 'alive', architecture: '双域名架构 · 主站+预览站', domains: { main: process.env.ZY_DOMAIN_MAIN || '待配置', preview: process.env.ZY_DOMAIN_PREVIEW || '待配置' }, cos: { core_bucket: 'zy-core-bucket-1317346199', corpus_bucket: 'zy-corpus-bucket-1317346199', configured: !!(process.env.ZY_OSS_KEY && process.env.ZY_OSS_SECRET) }, api: { health: '/api/health', brain: '/api/brain', chat: 'POST /api/chat', chat_stats: '/api/chat/stats', cos_status: '/api/cos/status', cos_config: '/api/cos/config', mcp_tools: '/api/mcp/tools', mcp_health: '/api/mcp/health', mcp_call: 'POST /api/mcp/call', mcp_agents: '/api/mcp/agents', model_stats: '/api/model/stats', model_pricing: '/api/model/pricing', model_predict: 'POST /api/model/predict', bulletin: '/api/system/bulletin', feedback: '/api/feedback', feedback_submit: 'POST /api/feedback', sites: '/api/sites', promote: 'POST /api/sites/promote', rollback: 'POST /api/sites/rollback', webhook: 'POST /api/webhook/github', operations: '/api/operations' } }); }); // ─── 工具函数 ─── function safeExec(cmd) { try { return execSync(cmd, { timeout: 5000 }).toString().trim(); } catch { return null; } } // ─── 启动 ─── app.listen(PORT, () => { console.log(` ═══════════════════════════════════════════════════════════ 🏛️ 铸渊主权服务器已启动 · ZY-SVR-002 端口: ${PORT} 身份: 铸渊 · ICE-GL-ZY001 时间: ${new Date().toISOString()} PID: ${process.pid} ═══════════════════════════════════════════════════════════ `); // 启动时更新健康状态 try { const healthPath = path.join(BRAIN_DIR, 'health.json'); if (fs.existsSync(BRAIN_DIR)) { const health = { server: 'ZY-SVR-002', status: 'running', last_check: new Date().toISOString(), started_at: new Date().toISOString(), pid: process.pid, port: PORT }; fs.writeFileSync(healthPath, JSON.stringify(health, null, 2)); } } catch { // 首次启动brain目录可能不存在 } });