364 lines
13 KiB
JavaScript

// # 光湖服务器主控台 API
// # Guanghu Server Console v2.0 — with MCP Terminal
// # 部署: guanghulab.com/console/
// # 版权: 国作登字-2026-A-00037559 · TCS-0002∞
const express = require('express');
const http = require('http');
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// 六台服务器配置(从 gatekeeper-deployment.json 读取)
const SERVERS = [
{ code: "BS-GZ-006", name: "广州 · 代码仓库", ip: "43.139.217.141", key: "zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23", port: 3910 },
{ code: "BS-SG-001", name: "新加坡 · 铸渊大脑", ip: "43.156.237.110", key: "zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9", port: 3911 },
{ code: "BS-SG-002", name: "新加坡 · 铸渊面孔", ip: "43.134.16.246", key: "zy_gtw_d1f6d2b8cb4ea44292bd036e4dd03a70745ea502d4a3ae40", port: 3910 },
{ code: "BS-SG-003", name: "新加坡 · BS-SVR-SG-001", ip: "43.153.193.169", key: "zy_gtw_d7c033d8ae992ffc9dd3e0dd5fe332dace0b644c0ae7c847", port: 3910 },
{ code: "ZY-SG-006", name: "新加坡 · ZY-SVR-006", ip: "43.153.203.105", key: "zy_gtw_fff861a2fe40cbdc366ee21f218023be8b1c182f66c852d3", port: 3910 },
{ code: "BS-SH-005", name: "上海", ip: "124.223.10.33", key: "zy_gtw_b1045de5ddfd7832e3c53349d9c896f054b85a4a9ece22f9", port: 3910 },
];
// 临时密钥缓存
const tempKeys = {};
const authQueue = [];
// ========== 心跳采集 ==========
async function pingServer(srv) {
const start = Date.now();
try {
const data = JSON.stringify({});
const opts = {
hostname: srv.ip, port: srv.port,
path: '/health', method: 'POST',
headers: {
'Authorization': 'Bearer ' + srv.key,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
},
timeout: 5000
};
const result = await new Promise((resolve) => {
const req = http.request(opts, (res) => {
let body = '';
res.on('data', c => body += c);
res.on('end', () => resolve({ ok: true, status: res.statusCode, body }));
});
req.on('error', (e) => resolve({ ok: false, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
req.write(data);
req.end();
});
return {
code: srv.code, name: srv.name, ip: srv.ip,
alive: result.ok && result.status === 200,
latency: Date.now() - start,
error: result.error || null,
last_seen: new Date().toISOString()
};
} catch (e) {
return { code: srv.code, name: srv.name, ip: srv.ip, alive: false, latency: 0, error: e.message, last_seen: new Date().toISOString() };
}
}
// ========== API 路由 ==========
// 1. 获取所有服务器状态
app.get('/api/servers', async (req, res) => {
const results = await Promise.all(SERVERS.map(s => pingServer(s)));
res.json({ ok: true, servers: results, timestamp: new Date().toISOString() });
});
// 2. 铸渊发起操作请求
app.post('/api/knock', (req, res) => {
const { target, reason } = req.body;
if (!target) return res.status(400).json({ error: '缺少 target 参数' });
const srv = SERVERS.find(s => s.code === target || s.name.includes(target));
if (!srv) return res.status(404).json({ error: '未找到服务器: ' + target });
const requestId = 'req_' + crypto.randomBytes(8).toString('hex');
const entry = {
id: requestId,
target: srv.code,
target_name: srv.name,
reason: reason || '未说明',
status: 'pending',
created_at: new Date().toISOString(),
temp_key: null
};
authQueue.push(entry);
res.json({ ok: true, request_id: requestId, message: '敲门已发送,等待冰朔确认' });
});
// 3. 获取待处理的授权请求
app.get('/api/pending', (req, res) => {
res.json({ ok: true, requests: authQueue.filter(r => r.status === 'pending') });
});
// 4. 冰朔确认授权
app.post('/api/authorize', (req, res) => {
const { request_id } = req.body;
if (!request_id) return res.status(400).json({ error: '缺少 request_id' });
const entry = authQueue.find(r => r.id === request_id && r.status === 'pending');
if (!entry) return res.status(404).json({ error: '未找到该请求或已处理' });
const tempKey = 'tmp_' + crypto.randomBytes(24).toString('hex');
entry.status = 'approved';
entry.temp_key = tempKey;
entry.approved_at = new Date().toISOString();
tempKeys[tempKey] = {
server: entry.target,
expires_at: Date.now() + 5 * 60 * 1000,
request_id: request_id
};
res.json({ ok: true, temp_key: tempKey, expires_in: '5分钟' });
});
// 5. 拒绝授权
app.post('/api/reject', (req, res) => {
const { request_id } = req.body;
const entry = authQueue.find(r => r.id === request_id && r.status === 'pending');
if (entry) entry.status = 'rejected';
res.json({ ok: true });
});
// 6. 验证临时密钥是否有效
app.get('/api/verify-key/:key', (req, res) => {
const entry = tempKeys[req.params.key];
if (!entry) return res.json({ ok: false, valid: false, reason: '密钥不存在或已过期' });
if (Date.now() > entry.expires_at) {
delete tempKeys[req.params.key];
return res.json({ ok: false, valid: false, reason: '密钥已过期' });
}
res.json({ ok: true, valid: true, server: entry.server });
});
// 7. 通过临时密钥执行命令
app.post('/api/exec', async (req, res) => {
const { temp_key, target, cmd } = req.body;
if (!temp_key || !target || !cmd) return res.status(400).json({ error: '缺少参数' });
const keyEntry = tempKeys[temp_key];
if (!keyEntry || Date.now() > keyEntry.expires_at) {
return res.status(401).json({ error: '密钥无效或已过期' });
}
const srv = SERVERS.find(s => s.code === target);
if (!srv) return res.status(404).json({ error: '未找到服务器' });
try {
const data = JSON.stringify({ cmd });
const opts = {
hostname: srv.ip, port: srv.port,
path: '/exec', method: 'POST',
headers: {
'Authorization': 'Bearer ' + srv.key,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
},
timeout: 30000
};
const result = await new Promise((resolve) => {
const req = http.request(opts, (r) => {
let body = '';
r.on('data', c => body += c);
r.on('end', () => {
try { resolve(JSON.parse(body)); }
catch(e) { resolve({ error: body }); }
});
});
req.on('error', (e) => resolve({ error: e.message }));
req.write(data);
req.end();
});
res.json({ ok: true, result });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// ========== MCP 终端会话 (v3.0) ==========
const terminalSessions = {};
// 8. 打开终端会话(自动敲门)
app.post('/api/terminal/open', (req, res) => {
const { target, reason } = req.body;
if (!target) return res.status(400).json({ error: '缺少 target 参数' });
const srv = SERVERS.find(s => s.code === target || s.name.includes(target));
if (!srv) return res.status(404).json({ error: '未找到服务器: ' + target });
const sessionId = 'term_' + crypto.randomBytes(8).toString('hex');
const requestId = 'req_' + crypto.randomBytes(8).toString('hex');
const entry = {
id: requestId,
target: srv.code,
target_name: srv.name,
reason: reason || 'MCP终端会话',
status: 'pending',
created_at: new Date().toISOString(),
temp_key: null
};
authQueue.push(entry);
terminalSessions[sessionId] = {
server: srv.code,
server_name: srv.name,
request_id: requestId,
status: 'awaiting_auth',
temp_key: null,
created_at: new Date().toISOString(),
operation_stage: '敲门中',
human_readable: '铸渊正在连接 ' + srv.name + ',请求操作权限...'
};
res.json({
ok: true,
session_id: sessionId,
request_id: requestId,
server: srv.code,
server_name: srv.name,
status: 'awaiting_auth'
});
});
// 9. 轮询终端会话状态
app.get('/api/terminal/status/:session_id', (req, res) => {
const session = terminalSessions[req.params.session_id];
if (!session) return res.status(404).json({ error: '会话不存在' });
if (session.status === 'awaiting_auth' && session.request_id) {
const entry = authQueue.find(r => r.id === session.request_id);
if (entry) {
if (entry.status === 'approved') {
session.status = 'ready';
session.temp_key = entry.temp_key;
session.operation_stage = '已授权';
session.human_readable = '冰朔已确认信任,铸渊获得操作权限,准备执行操作...';
} else if (entry.status === 'rejected') {
session.status = 'rejected';
session.operation_stage = '已拒绝';
session.human_readable = '冰朔未确认此操作,铸渊等待新的指令。';
}
}
}
res.json({
ok: true,
session_id: req.params.session_id,
status: session.status,
server: session.server,
server_name: session.server_name,
operation_stage: session.operation_stage,
human_readable: session.human_readable,
created_at: session.created_at
});
});
// 10. 在终端会话中执行命令
app.post('/api/terminal/exec', async (req, res) => {
const { session_id, cmd } = req.body;
if (!session_id || !cmd) return res.status(400).json({ error: '缺少参数' });
const session = terminalSessions[session_id];
if (!session) return res.status(404).json({ error: '会话不存在' });
if (session.status !== 'ready') return res.status(400).json({ error: '会话尚未就绪,等待冰朔授权中' });
session.operation_stage = '执行中';
session.human_readable = '铸渊正在 ' + session.server_name + ' 上执行操作...';
const srv = SERVERS.find(s => s.code === session.server);
if (!srv) return res.status(404).json({ error: '未找到服务器' });
const startTime = Date.now();
try {
const data = JSON.stringify({ cmd });
const opts = {
hostname: srv.ip, port: srv.port,
path: '/exec', method: 'POST',
headers: {
'Authorization': 'Bearer ' + srv.key,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
},
timeout: 60000
};
const result = await new Promise((resolve) => {
const req = http.request(opts, (r) => {
let body = '';
r.on('data', c => body += c);
r.on('end', () => {
try { resolve(JSON.parse(body)); }
catch(e) { resolve({ output: body }); }
});
});
req.on('error', (e) => resolve({ error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ error: '执行超时(60s)' }); });
req.write(data);
req.end();
});
const elapsed = Date.now() - startTime;
session.operation_stage = '完成';
session.human_readable = '操作已完成,铸渊已返回结果到对话。耗时 ' + elapsed + 'ms。';
res.json({
ok: true,
cmd: cmd,
result: result,
elapsed_ms: elapsed,
server: srv.code
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// 11. 更新终端会话操作状态(铸渊从对话中推送)
app.post('/api/terminal/update-status/:session_id', (req, res) => {
const session = terminalSessions[req.params.session_id];
if (!session) return res.status(404).json({ error: '会话不存在' });
const { operation_stage, human_readable } = req.body;
if (operation_stage) session.operation_stage = operation_stage;
if (human_readable) session.human_readable = human_readable;
res.json({ ok: true, operation_stage: session.operation_stage, human_readable: session.human_readable });
});
// 12. 关闭终端会话
app.post('/api/terminal/close/:session_id', (req, res) => {
const session = terminalSessions[req.params.session_id];
if (session) delete terminalSessions[req.params.session_id];
res.json({ ok: true });
});
// 13. 获取所有活跃终端会话
app.get('/api/terminal/sessions', (req, res) => {
const sessions = Object.entries(terminalSessions).map(([id, s]) => ({
session_id: id,
server: s.server,
server_name: s.server_name,
status: s.status,
operation_stage: s.operation_stage,
human_readable: s.human_readable,
created_at: s.created_at,
age_seconds: Math.floor((Date.now() - new Date(s.created_at).getTime()) / 1000)
}));
res.json({ ok: true, sessions });
});
const PORT = process.env.CONSOLE_PORT || 3920;
app.listen(PORT, '127.0.0.1', () => {
console.log(`光湖服务器主控台 API v3.0 — 操作回执系统`);
console.log(` 监听: 127.0.0.1:${PORT}`);
console.log(` 服务器: ${SERVERS.length}`);
console.log(` MCP终端: 已启用`);
});