From 8e07caf2d145a8b8bc439cc65ea991f142c52984 Mon Sep 17 00:00:00 2001 From: bingshuo <565183519@qq.com> Date: Sat, 23 May 2026 15:37:33 +0800 Subject: [PATCH] =?UTF-8?q?server.js=20v3.1:=20=E6=96=B0=E5=A2=9EGPU/?= =?UTF-8?q?=E8=AE=AD=E7=BB=83/Agent=20API=E7=AB=AF=E7=82=B9=20+=20?= =?UTF-8?q?=E7=AE=97=E5=8A=9B=E6=B1=A0API=20+=20=E7=BB=BC=E5=90=88?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _deploy/console-server/server.js | 326 ++++++++++++++++++++++++++++++- 1 file changed, 321 insertions(+), 5 deletions(-) diff --git a/_deploy/console-server/server.js b/_deploy/console-server/server.js index 6c1d84a..0b1b778 100644 --- a/_deploy/console-server/server.js +++ b/_deploy/console-server/server.js @@ -1,5 +1,5 @@ // # 光湖服务器主控台 API -// # Guanghu Server Console v2.0 — with MCP Terminal +// # Guanghu Server Console v3.1 — with GPU & Agent API // # 部署: guanghulab.com/console/ // # 版权: 国作登字-2026-A-00037559 · TCS-0002∞ @@ -13,6 +13,12 @@ const app = express(); app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); +// 数据存储目录 +const DATA_DIR = '/opt/zhuyuan/data'; + +// 确保数据目录存在 +try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch(e) {} + // 六台服务器配置(从 gatekeeper-deployment.json 读取) const SERVERS = [ { code: "BS-GZ-006", name: "广州 · 代码仓库", ip: "43.139.217.141", key: "zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23", port: 3910 }, @@ -66,6 +72,21 @@ async function pingServer(srv) { } } +// ========== Agent认证 ========== + +function checkAgentAuth(req) { + const auth = req.headers.authorization || ''; + const token = auth.replace('Bearer ', ''); + if (!token) return false; + try { + const keysData = fs.readFileSync('/tmp/zhuyuan-keys.json', 'utf-8'); + const keys = JSON.parse(keysData || '[]'); + return keys.some(k => k.value === token); + } catch(e) { + return false; + } +} + // ========== API 路由 ========== // 1. 获取所有服务器状态 @@ -355,10 +376,305 @@ app.get('/api/terminal/sessions', (req, res) => { res.json({ ok: true, sessions }); }); +// ========== 密钥投递 API ========== + +// 密钥存储 +const KEYS_FILE = '/tmp/zhuyuan-keys.json'; +let keyStore = []; + +function loadKeys() { + try { + if (fs.existsSync(KEYS_FILE)) { + const data = fs.readFileSync(KEYS_FILE, 'utf-8'); + keyStore = JSON.parse(data || '[]'); + } + } catch(e) { keyStore = []; } +} +function saveKeys() { + fs.writeFileSync(KEYS_FILE, JSON.stringify(keyStore, null, 2)); +} +loadKeys(); + +// 14. 投递密钥 +app.post('/api/keys/submit', (req, res) => { + const { value, label } = req.body; + if (!value) return res.status(400).json({ error: '缺少密钥内容' }); + const id = 'key_' + crypto.randomBytes(6).toString('hex'); + keyStore.push({ id, value, label: label || '未命名', created_at: new Date().toISOString() }); + saveKeys(); + res.json({ ok: true, id }); +}); + +// 15. 列出所有密钥(不包含值) +app.get('/api/keys', (req, res) => { + const list = keyStore.map(k => ({ id: k.id, label: k.label, created_at: k.created_at })); + res.json({ ok: true, keys: list }); +}); + +// 16. 获取单个密钥值 +app.get('/api/keys/retrieve/:id', (req, res) => { + const key = keyStore.find(k => k.id === req.params.id); + if (!key) return res.status(404).json({ error: '密钥不存在' }); + res.json({ ok: true, value: key.value, label: key.label }); +}); + +// 17. 删除密钥 +app.delete('/api/keys/:id', (req, res) => { + keyStore = keyStore.filter(k => k.id !== req.params.id); + saveKeys(); + res.json({ ok: true }); +}); + +// ========== GPU & 训练 & Agent API (v3.1 新增) ========== + +// 18. GPU状态 - Agent推送 +app.post('/api/gpu/status', (req, res) => { + if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权,请检查API Key' }); + const data = { + timestamp: new Date().toISOString(), + hostname: req.body.hostname || '3090-server', + gpus: req.body.gpus || [] + }; + try { + fs.writeFileSync(DATA_DIR + '/gpu-status.json', JSON.stringify(data, null, 2)); + res.json({ ok: true }); + } catch(e) { + res.status(500).json({ error: '存储失败: ' + e.message }); + } +}); + +// 19. GPU状态 - 仪表盘读取 +app.get('/api/gpu/status', (req, res) => { + try { + const data = JSON.parse(fs.readFileSync(DATA_DIR + '/gpu-status.json', 'utf-8')); + const age = Date.now() - new Date(data.timestamp).getTime(); + res.json({ ok: true, ...data, online: age < 120000, age_seconds: Math.floor(age / 1000) }); + } catch(e) { + res.json({ ok: true, gpus: [], timestamp: null, online: false }); + } +}); + +// 20. 训练状态 - Agent推送 +app.post('/api/training/status', (req, res) => { + if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权' }); + const data = { + timestamp: new Date().toISOString(), + job_id: req.body.job_id || 'hldp-3b-test', + status: req.body.status || 'idle', + step: req.body.step || 0, + total_steps: req.body.total_steps || 0, + loss: req.body.loss || null, + loss_history: req.body.loss_history || [], + eta_seconds: req.body.eta_seconds || null, + learning_rate: req.body.learning_rate || null, + elapsed_seconds: req.body.elapsed_seconds || 0, + model_name: req.body.model_name || '', + message: req.body.message || '' + }; + try { + fs.writeFileSync(DATA_DIR + '/training-status.json', JSON.stringify(data, null, 2)); + res.json({ ok: true }); + } catch(e) { + res.status(500).json({ error: '存储失败' }); + } +}); + +// 21. 训练状态 - 仪表盘读取 +app.get('/api/training/status', (req, res) => { + try { + const data = JSON.parse(fs.readFileSync(DATA_DIR + '/training-status.json', 'utf-8')); + const age = Date.now() - new Date(data.timestamp).getTime(); + res.json({ ok: true, ...data, online: age < 120000 }); + } catch(e) { + res.json({ ok: true, status: 'offline', message: '铸渊Agent未连接' }); + } +}); + +// 22. Agent操作日志 - Agent推送 +app.post('/api/agent/log', (req, res) => { + if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权' }); + const entry = { + timestamp: new Date().toISOString(), + level: req.body.level || 'info', + category: req.body.category || 'agent', + message: req.body.message || '' + }; + try { + const logPath = DATA_DIR + '/agent-log.jsonl'; + fs.appendFileSync(logPath, JSON.stringify(entry) + '\n'); + const lines = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean); + if (lines.length > 500) { + fs.writeFileSync(logPath, lines.slice(-500).join('\n') + '\n'); + } + res.json({ ok: true }); + } catch(e) { + res.status(500).json({ error: '存储失败' }); + } +}); + +// 23. Agent操作日志 - 仪表盘读取 +app.get('/api/agent/log', (req, res) => { + try { + const logPath = DATA_DIR + '/agent-log.jsonl'; + if (!fs.existsSync(logPath)) return res.json({ ok: true, entries: [], total: 0 }); + const lines = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean); + const limit = parseInt(req.query.limit) || 50; + const entries = lines.slice(-limit).map(l => { + try { return JSON.parse(l); } catch(e) { return { level: 'info', message: l }; } + }); + res.json({ ok: true, entries, total: lines.length }); + } catch(e) { + res.json({ ok: true, entries: [], total: 0 }); + } +}); + +// 24. Agent日记 - Agent推送 +app.post('/api/agent/diary', (req, res) => { + if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权' }); + const entry = { + timestamp: new Date().toISOString(), + type: req.body.type || 'info', + title: req.body.title || '', + description: req.body.description || '' + }; + try { + const diaryPath = DATA_DIR + '/agent-diary.jsonl'; + fs.appendFileSync(diaryPath, JSON.stringify(entry) + '\n'); + const lines = fs.readFileSync(diaryPath, 'utf-8').trim().split('\n').filter(Boolean); + if (lines.length > 200) { + fs.writeFileSync(diaryPath, lines.slice(-200).join('\n') + '\n'); + } + res.json({ ok: true }); + } catch(e) { + res.status(500).json({ error: '存储失败' }); + } +}); + +// 25. Agent日记 - 仪表盘读取 +app.get('/api/agent/diary', (req, res) => { + try { + const diaryPath = DATA_DIR + '/agent-diary.jsonl'; + if (!fs.existsSync(diaryPath)) return res.json({ ok: true, entries: [], total: 0 }); + const lines = fs.readFileSync(diaryPath, 'utf-8').trim().split('\n').filter(Boolean); + const entries = lines.map(l => { + try { return JSON.parse(l); } catch(e) { return { type: 'info', title: l }; } + }); + res.json({ ok: true, entries: entries.reverse().slice(0, 20), total: entries.length }); + } catch(e) { + res.json({ ok: true, entries: [], total: 0 }); + } +}); + +// 26. 综合状态 - 一次性拉取所有Agent数据 +app.get('/api/agent/combined', (req, res) => { + const result = { gpu: null, training: null, logs: [], diary: [], online: false, last_seen: null }; + try { + if (fs.existsSync(DATA_DIR + '/gpu-status.json')) { + result.gpu = JSON.parse(fs.readFileSync(DATA_DIR + '/gpu-status.json', 'utf-8')); + result.last_seen = result.gpu.timestamp; + result.online = result.gpu.timestamp && (Date.now() - new Date(result.gpu.timestamp).getTime() < 120000); + } + } catch(e) {} + try { + if (fs.existsSync(DATA_DIR + '/training-status.json')) { + result.training = JSON.parse(fs.readFileSync(DATA_DIR + '/training-status.json', 'utf-8')); + } + } catch(e) {} + try { + const logPath = DATA_DIR + '/agent-log.jsonl'; + if (fs.existsSync(logPath)) { + const logLines = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean); + result.logs = logLines.slice(-20).map(l => { try { return JSON.parse(l); } catch(e) { return { message: l }; } }); + } + } catch(e) {} + try { + const diaryPath = DATA_DIR + '/agent-diary.jsonl'; + if (fs.existsSync(diaryPath)) { + const diaryLines = fs.readFileSync(diaryPath, 'utf-8').trim().split('\n').filter(Boolean); + result.diary = diaryLines.map(l => { try { return JSON.parse(l); } catch(e) { return { type: 'info', title: l }; } }).reverse().slice(0, 10); + } + } catch(e) {} + res.json(result); +}); + +// ========== 算力池 API ========== + +let cachedPoolStatus = {}; + +// 27. 接收gatekeeper上报的节点状态 +app.post('/api/pool/report', (req, res) => { + const { node_code, node_name, cpu_load, mem_total_mb, mem_avail_mb, disk_used_pct, uptime_hours, tasks, spec } = req.body; + if (!node_code) return res.status(400).json({ error: '缺少 node_code' }); + cachedPoolStatus[node_code] = { + name: node_name || node_code, + online: true, + cpu_load: cpu_load || 0, + mem_total_mb: mem_total_mb || 0, + mem_avail_mb: mem_avail_mb || 0, + disk_used_pct: disk_used_pct || 0, + uptime_hours: uptime_hours || 0, + tasks: tasks || 0, + spec: spec || { cpu: 2, mem_gb: 4 }, + last_seen: new Date().toISOString() + }; + res.json({ ok: true }); +}); + +// 28. 获取算力池汇总状态 +app.get('/api/pool/status', (req, res) => { + const nodes = {}; + let totalCpu = 0, usedCpu = 0, totalMem = 0, usedMem = 0, onlineNodes = 0; + let totalSpec = { cpu: 0, mem_gb: 0 }; + + for (const srv of SERVERS) { + const cached = cachedPoolStatus[srv.code]; + if (cached) { + nodes[srv.code] = cached; + const spec = cached.spec || { cpu: 2, mem_gb: 4 }; + totalCpu += spec.cpu; + usedCpu += cached.cpu_load; + totalMem += spec.mem_gb; + const memAvailGb = cached.mem_avail_mb / 1024; + usedMem += Math.max(0, spec.mem_gb - memAvailGb); + onlineNodes++; + totalSpec.cpu += spec.cpu; + totalSpec.mem_gb += spec.mem_gb; + } else { + nodes[srv.code] = { + name: srv.name, + online: false, + cpu_load: 0, mem_total_mb: 0, mem_avail_mb: 0, + disk_used_pct: 0, uptime_hours: 0, tasks: 0, + spec: { cpu: 2, mem_gb: 4 }, + last_seen: null + }; + totalSpec.cpu += 2; + totalSpec.mem_gb += 4; + } + } + + const pool = { + updated_at: new Date().toISOString(), + cpu_total: totalSpec.cpu, + cpu_used: usedCpu, + cpu_available: Math.max(0, totalSpec.cpu - usedCpu), + mem_total_gb: totalSpec.mem_gb, + mem_used_gb: usedMem, + mem_available_gb: Math.max(0, totalSpec.mem_gb - usedMem), + total_nodes: SERVERS.length, + online_nodes: onlineNodes + }; + + res.json({ + ok: true, + pool: { pool, nodes, total_spec: totalSpec, active_tasks: 0, updated_at: pool.updated_at } + }); +}); + 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终端: 已启用`); + console.log('光湖主控台 API v3.1 — GPU+Agent训练台'); + console.log(' 监听: 127.0.0.1:' + PORT); + console.log(' 服务器: ' + SERVERS.length + ' 台 | GPU训练台: 已启用'); + console.log(' 数据目录: ' + DATA_DIR); }); \ No newline at end of file