guanghulab/server/proxy/service/vpn-worker.js

264 lines
9.7 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
// server/proxy/service/vpn-worker.js
// 🔌 VPN节点Worker · 像路由器一样插入ZY-CLOUD
//
// 在任意服务器上运行此脚本:
// 1. 自动检测本机Xray状态
// 2. 通过HLDP heartbeat向ZY-CLOUD注册
// 3. 定期心跳保持在线
// 4. 断开 = 自动从ZY-CLOUD节点列表移除
//
// 用法:
// node vpn-worker.js --brain-host=10.0.0.5 \
// --node-id=zy-feimao-gz --name="肥猫广州" \
// --host=43.138.243.30 --pbk=xxx --sid=xxx \
// --region=cn-gz --specs="2核4G"
//
// 或通过环境变量:
// ZY_CLOUD_BRAIN=10.0.0.5:3804
// VPN_NODE_ID=zy-feimao-gz
// VPN_NODE_NAME=肥猫广州
// VPN_NODE_HOST=43.138.243.30
// VPN_NODE_PORT=443
// VPN_NODE_PBK=xxx
// VPN_NODE_SID=xxx
// VPN_NODE_REGION=cn-gz
// VPN_NODE_SPECS=2核4G
// VPN_PERSONA_ID=PER-SS001
//
// 最小启动 (自动检测本机IP和Xray):
// node vpn-worker.js --brain-host=10.0.0.5 --pbk=xxx
// ═══════════════════════════════════════════════
'use strict';
const http = require('http');
const { execSync } = require('child_process');
const os = require('os');
// ── 配置解析 ────────────────────────────────
function parseConfig() {
const args = {};
for (const arg of process.argv.slice(2)) {
if (arg.startsWith('--')) {
const [key, ...val] = arg.slice(2).split('=');
args[key] = val.join('=') || true;
}
}
return {
brainHost: args['brain-host'] || process.env.ZY_CLOUD_BRAIN || '',
brainPort: parseInt(args['brain-port'] || process.env.ZY_CLOUD_BRAIN_PORT || '3804', 10),
nodeId: args['node-id'] || process.env.VPN_NODE_ID || `vpn-${os.hostname()}`,
nodeName: args['name'] || process.env.VPN_NODE_NAME || `VPN-${os.hostname()}`,
nodeHost: args['host'] || process.env.VPN_NODE_HOST || getPublicIp(),
nodePort: parseInt(args['port'] || process.env.VPN_NODE_PORT || '443', 10),
nodePbk: args['pbk'] || process.env.VPN_NODE_PBK || '',
nodeSid: args['sid'] || process.env.VPN_NODE_SID || '',
nodeRegion: args['region'] || process.env.VPN_NODE_REGION || 'unknown',
nodeSpecs: args['specs'] || process.env.VPN_NODE_SPECS || detectSpecs(),
personaId: args['persona-id'] || process.env.VPN_PERSONA_ID || null,
heartbeatInterval: parseInt(args['interval'] || process.env.VPN_HB_INTERVAL || '60', 10) * 1000,
};
}
// ── 自动检测本机IP ──────────────────────────
function getPublicIp() {
// 优先使用Node.js原生API安全
const nets = os.networkInterfaces();
for (const ifaces of Object.values(nets)) {
for (const iface of ifaces) {
if (!iface.internal && iface.family === 'IPv4') return iface.address;
}
}
return '0.0.0.0';
}
// ── 自动检测本机配置 ────────────────────────
function detectSpecs() {
const cpus = os.cpus().length;
const memGB = Math.round(os.totalmem() / (1024 ** 3));
return `${cpus}${memGB}G`;
}
// ── 检查本机Xray状态 ────────────────────────
function checkLocalXray() {
try {
const result = execSync('pgrep -x xray', { encoding: 'utf8', timeout: 3000 });
return result.trim().length > 0;
} catch {
return false;
}
}
// ── 获取本机CPU和内存使用率 ──────────────────
function getResourceUsage() {
const loadAvg = os.loadavg();
const cpus = os.cpus().length;
const totalMem = os.totalmem();
const freeMem = os.freemem();
return {
cpu_cores: cpus,
cpu_load_1m: parseFloat(loadAvg[0].toFixed(2)),
cpu_usage_pct: parseFloat(((loadAvg[0] / cpus) * 100).toFixed(1)),
memory_total_gb: parseFloat((totalMem / (1024 ** 3)).toFixed(1)),
memory_free_gb: parseFloat((freeMem / (1024 ** 3)).toFixed(1)),
memory_usage_pct: parseFloat((((totalMem - freeMem) / totalMem) * 100).toFixed(1))
};
}
// ── 构建HLDP heartbeat消息 ──────────────────
function buildHldpHeartbeat(config) {
const seq = String(Date.now()).slice(-6);
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
const resources = getResourceUsage();
const xrayRunning = checkLocalXray();
return {
hldp_v: '3.0',
msg_id: `HLDP-VPN-${dateStr}-HB${seq}`,
msg_type: 'heartbeat',
sender: {
id: config.personaId || config.nodeId,
name: config.nodeName,
role: 'vpn-worker'
},
receiver: {
id: 'ICE-GL-ZY001',
name: '铸渊'
},
timestamp: new Date().toISOString(),
priority: 'routine',
payload: {
intent: 'VPN节点心跳 · 自动注册',
data: {
status: xrayRunning ? 'online' : 'xray_offline',
uptime_seconds: Math.floor(os.uptime()),
xray_running: xrayRunning,
...resources,
vpn_node: {
node_id: config.nodeId,
name: config.nodeName,
host: config.nodeHost,
port: config.nodePort,
pbk: config.nodePbk,
sid: config.nodeSid,
region: config.nodeRegion,
server_code: config.nodeId,
specs: config.nodeSpecs
}
}
}
};
}
// ── 发送心跳到ZY-CLOUD ─────────────────────
function sendHeartbeat(config) {
return new Promise((resolve, reject) => {
const hldpMsg = buildHldpHeartbeat(config);
const postData = JSON.stringify(hldpMsg);
const req = http.request({
hostname: config.brainHost,
port: config.brainPort,
path: '/hldp/v3/heartbeat',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
},
timeout: 10000
}, (res) => {
let body = '';
res.on('data', chunk => { body += chunk; });
res.on('end', () => {
try {
const ack = JSON.parse(body);
resolve(ack);
} catch {
resolve({ status: res.statusCode, raw: body });
}
});
});
req.on('error', (err) => reject(err));
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
req.end(postData);
});
}
// ── 主循环 ───────────────────────────────────
async function main() {
const config = parseConfig();
// 验证必填参数
if (!config.brainHost) {
console.error('❌ 缺少 --brain-host (ZY-CLOUD大脑服务器内网IP)');
console.error(' 用法: node vpn-worker.js --brain-host=10.0.0.5 --pbk=xxx');
process.exit(1);
}
if (!config.nodePbk) {
console.error('❌ 缺少 --pbk (Reality公钥)');
console.error(' 在大脑服务器上运行 xray x25519 获取公钥');
process.exit(1);
}
console.log('════════════════════════════════════════');
console.log('🔌 VPN Worker · 节点接入ZY-CLOUD');
console.log('════════════════════════════════════════');
console.log(` 节点ID: ${config.nodeId}`);
console.log(` 节点名称: ${config.nodeName}`);
console.log(` 节点地址: ${config.nodeHost}:${config.nodePort}`);
console.log(` 节点区域: ${config.nodeRegion}`);
console.log(` 节点配置: ${config.nodeSpecs}`);
console.log(` 大脑地址: ${config.brainHost}:${config.brainPort}`);
console.log(` 心跳间隔: ${config.heartbeatInterval / 1000}`);
console.log(` Xray状态: ${checkLocalXray() ? '✅ 运行中' : '❌ 未运行'}`);
console.log('════════════════════════════════════════');
// 立即发送第一次心跳
let consecutiveFailures = 0;
async function heartbeatLoop() {
try {
const ack = await sendHeartbeat(config);
consecutiveFailures = 0;
const status = ack.payload?.data?.status || ack.status || 'unknown';
const xrayOk = checkLocalXray();
console.log(`[${new Date().toISOString().slice(11, 19)}] ❤️ 心跳发送成功 → ZY-CLOUD (status: ${status}, xray: ${xrayOk ? '✅' : '❌'})`);
} catch (err) {
consecutiveFailures++;
console.error(`[${new Date().toISOString().slice(11, 19)}] ❌ 心跳失败 (${consecutiveFailures}次): ${err.message}`);
if (consecutiveFailures >= 5) {
console.error(' ⚠️ 连续5次心跳失败ZY-CLOUD可能不可达');
console.error(` 检查: curl http://${config.brainHost}:${config.brainPort}/health`);
}
}
}
// 首次心跳
await heartbeatLoop();
// 定期心跳
setInterval(heartbeatLoop, config.heartbeatInterval);
console.log('');
console.log('🟢 VPN Worker已启动持续向ZY-CLOUD发送心跳...');
console.log(' Ctrl+C 停止 → 节点10分钟后自动从ZY-CLOUD移除');
}
main().catch(err => {
console.error('❌ VPN Worker启动失败:', err.message);
process.exit(1);
});
// Graceful shutdown
process.on('SIGTERM', () => { console.log('SIGTERM. Worker停止.'); process.exit(0); });
process.on('SIGINT', () => { console.log('\nWorker停止. 节点将在10分钟后从ZY-CLOUD自动移除.'); process.exit(0); });