guanghulab/_deploy/console-server/push-heartbeat.js
铸渊 ICE-GL-ZY001 80bfaca576 feat: push-heartbeat 推送模式 — 各服主动上报非扫描
- 新增 push-heartbeat.js: 每台服务器部署,采集本机数据POST到主控台
- server.js v3.5: 新增 /api/heartbeat 接收端点 + /api/heartbeat-summary
- 架构改进: 冰朔指出扫描模式像攻击 → 改为push模式
2026-05-26 02:40:56 +08:00

117 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 光湖 push-heartbeat v1.0 — 每台服务器主动上报本机状态
// 部署: 各服务器 PM2 cron模式每5分钟运行一次
// 原理: 不扫描别人,只报自己。主控台(BS-GZ-006:3920)集中收。
// 冰朔: "不要扫描,让每个服务器自己主动往外发"
'use strict';
const http = require('http');
const os = require('os');
const fs = require('fs');
// ═══════════════════════════════════════
// 配置 — 部署时修改
// ═══════════════════════════════════════
const MASTER_HOST = process.env.MASTER_HOST || '43.139.217.141';
const MASTER_PORT = parseInt(process.env.MASTER_PORT || '3920');
const SERVER_CODE = process.env.SERVER_CODE || 'BS-UNKNOWN';
const SERVER_NAME = process.env.SERVER_NAME || '未命名';
const SERVER_KEY = process.env.SERVER_KEY || '';
const INTERVAL_MS = 5 * 60 * 1000; // 5分钟
// ═══════════════════════════════════════
// 硬件采集(本机,不连外网)
// ═══════════════════════════════════════
function collectHardware() {
const cpuCores = os.cpus().length;
const loadAvg = os.loadavg();
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const uptime = os.uptime();
return {
cpu_cores: cpuCores,
cpu_load: parseFloat(loadAvg[0].toFixed(2)),
cpu_load5: parseFloat(loadAvg[1].toFixed(2)),
cpu_load15: parseFloat(loadAvg[2].toFixed(2)),
mem_total_mb: Math.floor(totalMem / 1024 / 1024),
mem_used_mb: Math.floor(usedMem / 1024 / 1024),
mem_avail_mb: Math.floor(freeMem / 1024 / 1024),
mem_used_pct: parseFloat((usedMem / totalMem * 100).toFixed(1)),
uptime_h: Math.floor(uptime / 3600),
hostname: os.hostname(),
};
}
// ═══════════════════════════════════════
// 上报到主控台只发POST不扫描
// ═══════════════════════════════════════
function reportToMaster(data) {
return new Promise((resolve) => {
const payload = JSON.stringify(data);
const opts = {
hostname: MASTER_HOST,
port: MASTER_PORT,
path: '/api/heartbeat',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
'X-Server-Code': SERVER_CODE,
'X-Server-Key': SERVER_KEY,
},
timeout: 8000,
};
const req = http.request(opts, (res) => {
let body = '';
res.on('data', (c) => (body += c));
res.on('end', () => resolve({ ok: res.statusCode === 200, 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(payload);
req.end();
});
}
// ═══════════════════════════════════════
// 主逻辑
// ═══════════════════════════════════════
async function push() {
const hw = collectHardware();
const data = {
server_code: SERVER_CODE,
server_name: SERVER_NAME,
timestamp: new Date().toISOString(),
hardware: hw,
};
console.log(`[${data.timestamp}] ${SERVER_CODE}${MASTER_HOST}:${MASTER_PORT}`);
const result = await reportToMaster(data);
if (result.ok) {
console.log(` ✅ 上报成功 · CPU ${hw.cpu_load} · 内存 ${hw.mem_used_pct}% · 运行 ${hw.uptime_h}h`);
} else {
console.log(` ❌ 上报失败: ${result.error || result.status}`);
}
}
// 立即发一次
push();
// 定时发
if (process.argv.includes('--once')) {
// 一次性模式 — PM2 cron 使用
} else {
setInterval(push, INTERVAL_MS);
}