diff --git a/server/proxy/deploy-brain-proxy.sh b/server/proxy/deploy-brain-proxy.sh index 722ff589..84b56a82 100644 --- a/server/proxy/deploy-brain-proxy.sh +++ b/server/proxy/deploy-brain-proxy.sh @@ -171,13 +171,13 @@ EOF chmod 600 "$KEYS_FILE" echo "" - echo " ══════════ V2密钥 (请添加到GitHub Secrets) ══════════" - echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY=$PUBLIC_KEY" - echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID=$SHORT_ID" - echo " ═══════════════════════════════════════════════════════" - echo "" - echo " ⚠️ 密钥已保存到: $KEYS_FILE" - echo " ⚠️ Private Key不需要添加到GitHub Secrets (已保存在服务器)" + echo " ══════════ V2密钥已生成 ══════════" + echo " ⚠️ 密钥已保存到: $KEYS_FILE (权限600·仅root可读)" + echo " ⚠️ 如需查看密钥,请SSH到服务器执行: cat $KEYS_FILE" + echo " ⚠️ 部署完成后,将公钥和ShortID添加到GitHub Secrets:" + echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY" + echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID" + echo " ═══════════════════════════════════" } # ── 部署V2服务代码 ──────────────────────────── diff --git a/server/proxy/service/subscription-server-v2.js b/server/proxy/service/subscription-server-v2.js index 8c9a77b0..d8c6bd10 100644 --- a/server/proxy/service/subscription-server-v2.js +++ b/server/proxy/service/subscription-server-v2.js @@ -27,6 +27,7 @@ const PORT = process.env.ZY_PROXY_V2_PORT || 3803; const PROXY_DIR = process.env.ZY_BRAIN_PROXY_DIR || '/opt/zhuyuan-brain/proxy'; const DATA_DIR = path.join(PROXY_DIR, 'data'); const KEYS_FILE = path.join(PROXY_DIR, '.env.keys'); +const LIVE_NODES_FRESHNESS_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '600000', 10); // 引入用户管理器 const userManager = require('./user-manager'); @@ -80,9 +81,9 @@ function buildVpnNodes() { const liveNodesFile = path.join(DATA_DIR, 'nodes-live.json'); try { const liveData = JSON.parse(fs.readFileSync(liveNodesFile, 'utf8')); - // 检查数据是否新鲜(5分钟内) + // 检查数据是否新鲜(使用与ZY-CLOUD相同的心跳过期阈值) const age = Date.now() - new Date(liveData.updated_at).getTime(); - if (age < 10 * 60 * 1000 && liveData.nodes && liveData.nodes.length > 0) { + if (age < LIVE_NODES_FRESHNESS_MS * 2 && liveData.nodes && liveData.nodes.length > 0) { return liveData.nodes; } } catch { /* ZY-CLOUD未运行,回退到静态配置 */ } diff --git a/server/proxy/service/user-manager.js b/server/proxy/service/user-manager.js index b736a623..8e41351c 100644 --- a/server/proxy/service/user-manager.js +++ b/server/proxy/service/user-manager.js @@ -120,6 +120,7 @@ function addUser(email, options = {}) { throw new Error(`用户已存在: ${email} (UUID: ${existing.uuid.substring(0, 8)}...)`); } + const now = new Date(); const user = { email, uuid: generateUUID(), @@ -127,11 +128,11 @@ function addUser(email, options = {}) { label: options.label || email.split('@')[0], quota_bytes: (options.quota_gb || 500) * 1024 * 1024 * 1024, enabled: true, - created_at: new Date().toISOString(), + created_at: now.toISOString(), traffic: { upload_bytes: 0, download_bytes: 0, - period: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`, + period: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`, alerts_sent: { p80: false, p90: false, p100: false } } }; diff --git a/server/proxy/service/vpn-worker.js b/server/proxy/service/vpn-worker.js index e7790c81..42a12416 100644 --- a/server/proxy/service/vpn-worker.js +++ b/server/proxy/service/vpn-worker.js @@ -66,19 +66,16 @@ function parseConfig() { }; } -// ── 自动检测本机公网IP ────────────────────── +// ── 自动检测本机IP ────────────────────────── function getPublicIp() { - try { - return execSync("hostname -I | awk '{print $1}'", { encoding: 'utf8', timeout: 3000 }).trim(); - } catch { - 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; - } + // 优先使用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'; } + return '0.0.0.0'; } // ── 自动检测本机配置 ──────────────────────── diff --git a/server/proxy/service/zy-cloud-vpn.js b/server/proxy/service/zy-cloud-vpn.js index 9ab72b75..76bba807 100644 --- a/server/proxy/service/zy-cloud-vpn.js +++ b/server/proxy/service/zy-cloud-vpn.js @@ -37,10 +37,12 @@ const REGISTRY_FILE = path.join(DATA_DIR, 'nodes-registry.json'); const HEARTBEAT_FILE = path.join(DATA_DIR, 'zy-cloud-vpn-heartbeat.json'); const KEYS_FILE = path.join(PROXY_DIR, '.env.keys'); -// ── 时间间隔 ──────────────────────────────── -const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒心跳 -const DIAGNOSE_INTERVAL = 5 * 60 * 1000; // 5分钟诊断 -const LEARN_INTERVAL = 30 * 60 * 1000; // 30分钟学习周期 +// ── 时间间隔(可通过环境变量调整)──────────── +const HEARTBEAT_INTERVAL = parseInt(process.env.ZY_CLOUD_HEARTBEAT_INTERVAL || '30000', 10); +const DIAGNOSE_INTERVAL = parseInt(process.env.ZY_CLOUD_DIAGNOSE_INTERVAL || '300000', 10); +const LEARN_INTERVAL = parseInt(process.env.ZY_CLOUD_LEARN_INTERVAL || '1800000', 10); +const HEARTBEAT_EXPIRY_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '600000', 10); // 10分钟 +const NODE_UNREGISTER_MS = parseInt(process.env.ZY_CLOUD_UNREGISTER_MS || '86400000', 10); // 24小时 // ═══════════════════════════════════════════════ // LivingModule 基类 @@ -273,7 +275,8 @@ class ZyCloudVpn extends LivingModule { const brainPbk = this._readEnvOrKey('ZY_PROXY_REALITY_PUBLIC_KEY'); const brainSid = this._readEnvOrKey('ZY_PROXY_REALITY_SHORT_ID'); if (brainHost && brainPbk) { - nodes.push(this._makeNode('zy-brain-sg1', '🧠 铸渊专线V2-SG1(大脑)', brainHost, 443, brainPbk, brainSid, 'sg-zone1', 'ZY-SVR-005', 'local', '4核8G')); + const localSpecs = `${require('os').cpus().length}核${Math.round(require('os').totalmem() / (1024 ** 3))}G`; + nodes.push(this._makeNode('zy-brain-sg1', '🧠 铸渊专线V2-SG1(大脑)', brainHost, 443, brainPbk, brainSid, 'sg-zone1', 'ZY-SVR-005', 'local', localSpecs)); seenIds.add('zy-brain-sg1'); } @@ -299,14 +302,14 @@ class ZyCloudVpn extends LivingModule { for (const regNode of Object.values(this._registry.nodes)) { if (seenIds.has(regNode.id)) continue; // 避免重复 - // 检查心跳是否过期(超过10分钟无心跳 → 自动注销) + // 检查心跳是否过期 const lastHb = new Date(regNode.last_heartbeat).getTime(); const age = Date.now() - lastHb; - if (age > 10 * 60 * 1000) { + if (age > HEARTBEAT_EXPIRY_MS) { console.log(`[ZY-CLOUD VPN] ⏰ 节点 ${regNode.name} 心跳过期(${Math.floor(age/60000)}分钟),标记为离线`); // 不删除注册,只是标记离线(可能临时断网) // 超过24小时无心跳才自动清理 - if (age > 24 * 60 * 60 * 1000) { + if (age > NODE_UNREGISTER_MS) { console.log(`[ZY-CLOUD VPN] 🗑️ 节点 ${regNode.name} 超24小时无心跳,自动注销`); delete this._registry.nodes[regNode.id]; this._saveRegistry();