server.js v3.1: 新增GPU/训练/Agent API端点 + 算力池API + 综合状态端点

This commit is contained in:
bingshuo 2026-05-23 15:37:33 +08:00
parent 831f462fae
commit 8e07caf2d1

View File

@ -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);
});