From 68d737fc451c33e59e5b68dfcb1dd6e14f421f8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:39:42 +0000 Subject: [PATCH 1/4] =?UTF-8?q?V3=E5=8D=87=E7=BA=A7Phase1:=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9Ellm-router.js=20+=20reverse-boost-agent.js,=20zy-cloud?= =?UTF-8?q?-vpn.js=E5=A2=9E=E5=8A=A0consultAI=E7=AC=AC6=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/226aae4f-48b7-4668-850c-7858729e6aa0 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/proxy/service/llm-router.js | 206 ++++++++++++++ server/proxy/service/reverse-boost-agent.js | 293 ++++++++++++++++++++ server/proxy/service/zy-cloud-vpn.js | 102 ++++++- 3 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 server/proxy/service/llm-router.js create mode 100644 server/proxy/service/reverse-boost-agent.js diff --git a/server/proxy/service/llm-router.js b/server/proxy/service/llm-router.js new file mode 100644 index 00000000..c38fa446 --- /dev/null +++ b/server/proxy/service/llm-router.js @@ -0,0 +1,206 @@ +#!/usr/bin/env node +// ═══════════════════════════════════════════════ +// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +// 📜 Copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════════════ +// server/proxy/service/llm-router.js +// 🤖 AI大模型动态路由器 +// +// 核心原则 (冰朔指令): +// - 不写死模型,系统自动检测+按需调用 +// - 调用失败自动切换下一个 +// - 所有模型都失败 → 推送邮件给冰朔 +// +// 使用仓库已配置的 ZY_LLM_API_KEY + ZY_LLM_BASE_URL +// secrets-manifest.json 确认此KEY支持多模型动态路由 +// ═══════════════════════════════════════════════ + +'use strict'; + +const https = require('https'); +const http = require('http'); + +// ── 模型优先级列表(自动降级)────────────────── +// API网关会根据可用性自动路由,但我们仍按优先级尝试 +const MODEL_PRIORITY = [ + 'deepseek-chat', + 'qwen-turbo', + 'moonshot-v1-8k', + 'glm-4-flash', + 'claude-3-haiku-20240307', + 'gpt-4o-mini' +]; + +// ── 路由器状态 ────────────────────────────── +const routerState = { + total_calls: 0, + successful_calls: 0, + failed_calls: 0, + last_success: null, + last_error: null, + model_stats: {} // 每个模型的调用统计 +}; + +/** + * 调用LLM API(自动模型降级) + * @param {string} prompt - 用户/系统提示 + * @param {Object} options - 可选配置 + * @param {string} options.systemPrompt - 系统提示 + * @param {number} options.maxTokens - 最大token数 + * @param {number} options.timeout - 超时时间(ms) + * @param {string[]} options.preferModels - 优先尝试的模型列表 + * @returns {Promise<{content: string, model: string, usage: Object}|null>} + */ +async function callLLM(prompt, options = {}) { + const apiKey = process.env.ZY_LLM_API_KEY || ''; + const baseUrl = process.env.ZY_LLM_BASE_URL || ''; + + if (!apiKey || !baseUrl) { + console.log('[LLM路由器] API未配置 (ZY_LLM_API_KEY / ZY_LLM_BASE_URL)'); + return null; + } + + const { + systemPrompt = '你是光湖语言世界VPN系统的AI助手。请简洁、准确地回答问题。', + maxTokens = 500, + timeout = 30000, + preferModels = null + } = options; + + const modelsToTry = preferModels || MODEL_PRIORITY; + routerState.total_calls++; + + for (const model of modelsToTry) { + try { + const result = await _callSingleModel(baseUrl, apiKey, model, prompt, systemPrompt, maxTokens, timeout); + if (result) { + routerState.successful_calls++; + routerState.last_success = { + model, + time: new Date().toISOString() + }; + + // 更新模型统计 + if (!routerState.model_stats[model]) { + routerState.model_stats[model] = { success: 0, fail: 0 }; + } + routerState.model_stats[model].success++; + + return result; + } + } catch (err) { + console.log(`[LLM路由器] 模型 ${model} 调用失败: ${err.message},尝试下一个...`); + + if (!routerState.model_stats[model]) { + routerState.model_stats[model] = { success: 0, fail: 0 }; + } + routerState.model_stats[model].fail++; + } + } + + // 所有模型都失败 + routerState.failed_calls++; + routerState.last_error = { + time: new Date().toISOString(), + message: `所有模型(${modelsToTry.length}个)均调用失败` + }; + + console.error(`[LLM路由器] ❌ 所有模型均不可用`); + return null; +} + +/** + * 调用单个模型 + */ +function _callSingleModel(baseUrl, apiKey, model, prompt, systemPrompt, maxTokens, timeout) { + return new Promise((resolve, reject) => { + let urlObj; + try { + urlObj = new URL(baseUrl); + } catch { + // 如果baseUrl不含协议,加上https + urlObj = new URL(`https://${baseUrl}`); + } + + const postData = JSON.stringify({ + model, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: prompt } + ], + max_tokens: maxTokens, + temperature: 0.3 + }); + + // 构建路径:如果baseUrl包含路径则使用,否则追加 /v1/chat/completions + let apiPath = urlObj.pathname; + if (apiPath === '/' || apiPath === '') { + apiPath = '/v1/chat/completions'; + } else if (!apiPath.endsWith('/chat/completions')) { + apiPath = apiPath.replace(/\/$/, '') + '/v1/chat/completions'; + } + + const options = { + hostname: urlObj.hostname, + port: urlObj.port || (urlObj.protocol === 'http:' ? 80 : 443), + path: apiPath, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + 'Content-Length': Buffer.byteLength(postData) + }, + timeout + }; + + const httpModule = urlObj.protocol === 'http:' ? http : https; + + const req = httpModule.request(options, (res) => { + let data = ''; + res.on('data', (d) => { data += d; }); + res.on('end', () => { + try { + if (res.statusCode >= 400) { + reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)); + return; + } + const json = JSON.parse(data); + const content = json.choices?.[0]?.message?.content; + if (!content) { + reject(new Error('响应中无content字段')); + return; + } + resolve({ + content, + model: json.model || model, + usage: json.usage || null + }); + } catch (e) { + reject(new Error(`JSON解析失败: ${e.message}`)); + } + }); + }); + + req.on('error', (err) => reject(err)); + req.on('timeout', () => { + req.destroy(); + reject(new Error(`超时(${timeout}ms)`)); + }); + + req.write(postData); + req.end(); + }); +} + +/** + * 获取路由器状态 + */ +function getRouterStatus() { + return { + ...routerState, + api_configured: !!(process.env.ZY_LLM_API_KEY && process.env.ZY_LLM_BASE_URL), + models_available: MODEL_PRIORITY + }; +} + +module.exports = { callLLM, getRouterStatus, MODEL_PRIORITY }; diff --git a/server/proxy/service/reverse-boost-agent.js b/server/proxy/service/reverse-boost-agent.js new file mode 100644 index 00000000..e469a33e --- /dev/null +++ b/server/proxy/service/reverse-boost-agent.js @@ -0,0 +1,293 @@ +#!/usr/bin/env node +// ═══════════════════════════════════════════════ +// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +// 📜 Copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════════════ +// server/proxy/service/reverse-boost-agent.js +// 🚀 反向加速Agent · 服务端网络优化活模块 +// +// 核心理念 (冰朔定根): +// 服务器只是桥梁,用户的光纤才是主引擎。 +// 通过减少服务器瓶颈,让用户100M/300M/500M +// 的光纤能力尽可能穿透到外网。 +// +// 优化策略: +// 1. BBR拥塞控制检测与优化 +// 2. MTU自动探测 +// 3. TCP连接参数优化 +// 4. 系统级网络栈调优 +// 5. Xray连接池配置优化 +// +// 运行方式: PM2 managed (zy-reverse-boost) +// 检查间隔: 每15分钟 +// ═══════════════════════════════════════════════ + +'use strict'; + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const PROXY_DIR = process.env.ZY_BRAIN_PROXY_DIR || '/opt/zhuyuan-brain/proxy'; +const DATA_DIR = path.join(PROXY_DIR, 'data'); +const BOOST_STATUS_FILE = path.join(DATA_DIR, 'reverse-boost-status.json'); +const CHECK_INTERVAL = 15 * 60 * 1000; // 15分钟 + +// ── 安全执行命令 ────────────────────────────── +function runCmd(cmd, timeout = 10000) { + try { + return { ok: true, output: execSync(cmd, { encoding: 'utf8', timeout }).trim() }; + } catch (err) { + return { ok: false, output: err.message }; + } +} + +// ── 检查BBR状态 ─────────────────────────────── +function checkBBR() { + const result = runCmd('sysctl net.ipv4.tcp_congestion_control'); + if (result.ok) { + const algo = result.output.split('=').pop().trim(); + return { + ok: true, + algorithm: algo, + is_bbr: algo === 'bbr', + detail: `当前拥塞控制: ${algo}` + }; + } + return { ok: false, algorithm: 'unknown', is_bbr: false, detail: '无法检查BBR' }; +} + +// ── 检查可用拥塞控制算法 ────────────────────── +function getAvailableAlgorithms() { + const result = runCmd('sysctl net.ipv4.tcp_available_congestion_control'); + if (result.ok) { + return result.output.split('=').pop().trim().split(/\s+/); + } + return []; +} + +// ── 检查当前MTU ─────────────────────────────── +function checkMTU() { + const result = runCmd("ip link show | grep 'mtu' | head -5"); + if (result.ok) { + const mtuMatch = result.output.match(/mtu\s+(\d+)/); + return { + ok: true, + mtu: mtuMatch ? parseInt(mtuMatch[1], 10) : 1500, + detail: result.output.split('\n')[0] + }; + } + return { ok: false, mtu: 1500, detail: '无法检查MTU' }; +} + +// ── 检查TCP参数 ─────────────────────────────── +function checkTcpParams() { + const params = {}; + + // TCP缓冲区大小 + const rmem = runCmd('sysctl net.ipv4.tcp_rmem'); + if (rmem.ok) params.tcp_rmem = rmem.output.split('=').pop().trim(); + + const wmem = runCmd('sysctl net.ipv4.tcp_wmem'); + if (wmem.ok) params.tcp_wmem = wmem.output.split('=').pop().trim(); + + // TCP快速打开 + const tfo = runCmd('sysctl net.ipv4.tcp_fastopen'); + if (tfo.ok) params.tcp_fastopen = tfo.output.split('=').pop().trim(); + + // TCP keep-alive + const keepalive = runCmd('sysctl net.ipv4.tcp_keepalive_time'); + if (keepalive.ok) params.tcp_keepalive_time = keepalive.output.split('=').pop().trim(); + + // 连接跟踪 + const conntrack = runCmd('sysctl net.netfilter.nf_conntrack_max 2>/dev/null'); + if (conntrack.ok) params.conntrack_max = conntrack.output.split('=').pop().trim(); + + return params; +} + +// ── 检查网络负载 ────────────────────────────── +function checkNetworkLoad() { + // 获取主网卡的流量统计 + const result = runCmd("cat /proc/net/dev | grep -E 'eth0|ens' | head -1"); + if (result.ok) { + const parts = result.output.trim().split(/\s+/); + if (parts.length >= 10) { + return { + ok: true, + interface: parts[0].replace(':', ''), + rx_bytes: parseInt(parts[1], 10), + tx_bytes: parseInt(parts[9], 10), + rx_packets: parseInt(parts[2], 10), + tx_packets: parseInt(parts[10], 10) + }; + } + } + return { ok: false }; +} + +// ── 优化建议生成 ────────────────────────────── +function generateOptimizations() { + const optimizations = []; + const applied = []; + + // 1. 检查BBR + const bbr = checkBBR(); + if (!bbr.is_bbr) { + const available = getAvailableAlgorithms(); + if (available.includes('bbr')) { + optimizations.push({ + type: 'bbr', + priority: 'high', + current: bbr.algorithm, + recommended: 'bbr', + description: '启用BBR拥塞控制可显著提升吞吐量', + command: 'sysctl -w net.ipv4.tcp_congestion_control=bbr' + }); + } + } else { + applied.push('BBR拥塞控制已启用'); + } + + // 2. 检查TCP快速打开 + const tcpParams = checkTcpParams(); + if (tcpParams.tcp_fastopen !== '3') { + optimizations.push({ + type: 'tcp_fastopen', + priority: 'medium', + current: tcpParams.tcp_fastopen || 'unknown', + recommended: '3', + description: 'TCP Fast Open可减少握手延迟', + command: 'sysctl -w net.ipv4.tcp_fastopen=3' + }); + } else { + applied.push('TCP Fast Open已启用'); + } + + // 3. 检查TCP缓冲区 + // 推荐: rmem = 4096 131072 67108864, wmem = 4096 16384 67108864 + if (tcpParams.tcp_rmem) { + const maxRmem = parseInt(tcpParams.tcp_rmem.split(/\s+/).pop(), 10); + if (maxRmem < 67108864) { + optimizations.push({ + type: 'tcp_rmem', + priority: 'medium', + current: tcpParams.tcp_rmem, + recommended: '4096 131072 67108864', + description: '增大TCP接收缓冲区可提升大文件传输速度', + command: 'sysctl -w net.ipv4.tcp_rmem="4096 131072 67108864"' + }); + } else { + applied.push('TCP接收缓冲区已优化'); + } + } + + return { optimizations, applied, tcp_params: tcpParams }; +} + +// ── 应用安全优化(仅无风险的参数)────────────── +function applySafeOptimizations(optimizations) { + const results = []; + + for (const opt of optimizations) { + // 只自动应用低风险优化 + if (opt.type === 'tcp_fastopen' || opt.type === 'bbr') { + const result = runCmd(opt.command, 5000); + results.push({ + type: opt.type, + applied: result.ok, + detail: result.ok ? `✅ 已应用: ${opt.description}` : `❌ 失败: ${result.output}` + }); + if (result.ok) { + console.log(`[反向加速] ✅ ${opt.description}`); + } + } else { + results.push({ + type: opt.type, + applied: false, + detail: `⏭️ 跳过(需手动): ${opt.description}` + }); + } + } + + return results; +} + +// ── 读取/保存状态 ───────────────────────────── +function readBoostStatus() { + try { + return JSON.parse(fs.readFileSync(BOOST_STATUS_FILE, 'utf8')); + } catch { + return { + checks: 0, + optimizations_applied: 0, + last_check: null, + status: 'initializing' + }; + } +} + +function saveBoostStatus(status) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + fs.writeFileSync(BOOST_STATUS_FILE, JSON.stringify(status, null, 2)); +} + +// ── 主巡检 ──────────────────────────────────── +function boost() { + console.log('[反向加速] 🚀 开始网络优化巡检...'); + + const status = readBoostStatus(); + status.checks++; + status.last_check = new Date().toISOString(); + + // 检查系统网络状态 + const bbr = checkBBR(); + const mtu = checkMTU(); + const networkLoad = checkNetworkLoad(); + const { optimizations, applied, tcp_params } = generateOptimizations(); + + console.log(`[反向加速] BBR: ${bbr.is_bbr ? '✅ 已启用' : '❌ 未启用'} (${bbr.algorithm})`); + console.log(`[反向加速] MTU: ${mtu.mtu}`); + console.log(`[反向加速] 已优化项: ${applied.length}个`); + + // 应用安全优化 + if (optimizations.length > 0) { + console.log(`[反向加速] 发现${optimizations.length}个可优化项`); + const results = applySafeOptimizations(optimizations); + const appliedCount = results.filter(r => r.applied).length; + status.optimizations_applied += appliedCount; + status.last_optimizations = results; + } + + // 更新状态 + status.status = 'active'; + status.current = { + bbr: bbr, + mtu: mtu, + tcp_params: tcp_params, + network_load: networkLoad.ok ? { + interface: networkLoad.interface, + rx_bytes: networkLoad.rx_bytes, + tx_bytes: networkLoad.tx_bytes + } : null, + already_optimized: applied + }; + + saveBoostStatus(status); + + console.log(`[反向加速] 巡检完成 (第${status.checks}次)`); +} + +// ── 启动巡检循环 ────────────────────────────── +console.log('🚀 光湖语言世界 · 反向加速Agent启动'); +console.log(` 巡检间隔: ${CHECK_INTERVAL / 1000}秒`); +console.log(` 优化策略: BBR拥塞控制 + TCP Fast Open + 缓冲区调优`); +console.log(` 核心理念: 服务器只是桥梁,用户光纤才是主引擎`); + +// 立即执行一次 +try { boost(); } catch (err) { console.error('首次巡检失败:', err.message); } + +// 定期执行 +setInterval(() => { + try { boost(); } catch (err) { console.error('巡检异常:', err.message); } +}, CHECK_INTERVAL); diff --git a/server/proxy/service/zy-cloud-vpn.js b/server/proxy/service/zy-cloud-vpn.js index 76bba807..4dc465e5 100644 --- a/server/proxy/service/zy-cloud-vpn.js +++ b/server/proxy/service/zy-cloud-vpn.js @@ -13,8 +13,8 @@ // - 服务器越多 → 节点越多 → 系统越强 → 不输商业VPN // - 所有服务器VPN能力汇聚到一个活的人格模块上 // - 动态感知空闲、动态选路、自我修复、自我学习 -// - 活模块5接口: heartbeat / selfDiagnose / selfHeal / -// alertZhuyuan / learnFromRun +// - 活模块6接口: heartbeat / selfDiagnose / selfHeal / +// alertZhuyuan / learnFromRun / consultAI // // 运行在大脑服务器 (ZY-SVR-005) // 管理所有VPN节点的生命周期 @@ -124,6 +124,7 @@ class LivingModule { async selfHeal(problem) { throw new Error('selfHeal() 未实现'); } async alertZhuyuan(alert) { throw new Error('alertZhuyuan() 未实现'); } async learnFromRun() { throw new Error('learnFromRun() 未实现'); } + async consultAI(context) { throw new Error('consultAI() 未实现'); } } // ═══════════════════════════════════════════════ @@ -582,13 +583,37 @@ class ZyCloudVpn extends LivingModule { console.log(` 🚨 所有节点不可用!尝试修复本机节点...`); const localNode = this._nodes.find(n => n.type === 'local'); if (localNode) { - return this.selfHeal({ type: 'node_dead', node: localNode }); + const healed = await this.selfHeal({ type: 'node_dead', node: localNode }); + if (healed) return true; } - // 修复失败 → 升级到alertZhuyuan + + // 自动修复失败 → 第2次重试: 调用AI推理 + console.log(' 🤖 自动修复失败,启动AI推理...'); + const aiResult = await this.consultAI({ + type: 'all_nodes_dead', + nodes: this._nodes.map(n => ({ + id: n.id, status: n.status, host: n.host, port: n.port, + consecutive_failures: n.consecutive_failures + })), + attempted_fix: 'xray_restart_failed' + }); + + if (aiResult) { + // AI给出了建议,记录并尝试第3次修复 + console.log(` 🤖 AI建议已记录,再次尝试修复...`); + if (localNode) { + const retryHeal = await this.selfHeal({ type: 'node_dead', node: localNode }); + if (retryHeal) return true; + } + } + + // 第3次: 通知冰朔 await this.alertZhuyuan({ level: 'critical', - message: '所有VPN节点不可用!需要人工干预', - nodes: this._nodes.map(n => ({ id: n.id, status: n.status })) + message: '所有VPN节点不可用!自动修复+AI推理均失败,需要人工干预', + nodes: this._nodes.map(n => ({ id: n.id, status: n.status })), + ai_advice: aiResult ? aiResult.content.slice(0, 500) : '(AI推理未返回结果)', + attempted_fixes: ['xray_restart', 'ai_diagnosis'] }); return false; } @@ -703,6 +728,69 @@ class ZyCloudVpn extends LivingModule { } } + // ═══ 接口6: consultAI() — "我请教AI" (V3新增) ═══ + // 遇到复杂问题时调用LLM路由器进行深度推理 + // 三次重试机制: + // 第1次: selfHeal 自动修复 + // 第2次: consultAI 调用LLM + // 第3次: alertZhuyuan 邮件通知冰朔 + async consultAI(context) { + let llmRouter; + try { + llmRouter = require('./llm-router'); + } catch { + console.log('[ZY-CLOUD VPN] ⚠️ LLM路由器未加载,跳过AI推理'); + return null; + } + + const prompt = `你是光湖语言世界VPN系统的AI诊断助手。 +当前系统状态: +- 模块: ${this._moduleName} +- 状态: ${this._state} +- 总节点: ${this._nodes.length} +- 存活节点: ${this._liveNodes.length} +- 连续错误: ${this._consecutiveErrors} + +问题上下文: +${JSON.stringify(context, null, 2)} + +请分析问题原因并给出具体的修复建议。要求: +1. 判断是网络层面还是服务层面的问题 +2. 给出可执行的修复步骤 +3. 评估问题严重程度(low/medium/high/critical) +回答要简洁明确。`; + + console.log('[ZY-CLOUD VPN] 🤖 调用AI推理...'); + const result = await llmRouter.callLLM(prompt, { + systemPrompt: '你是光湖语言世界VPN系统的AI诊断引擎。你负责分析网络和代理服务的异常,给出精准的修复建议。', + maxTokens: 800, + timeout: 45000 + }); + + if (result) { + console.log(`[ZY-CLOUD VPN] 🤖 AI推理完成 (模型: ${result.model})`); + console.log(` 建议: ${result.content.slice(0, 200)}...`); + + // 记录AI咨询历史 + this._history.push({ + type: 'consultAI', + model: result.model, + context_summary: context.type || 'unknown', + response_preview: result.content.slice(0, 100), + timestamp: new Date().toISOString() + }); + + // 只保留最近20条历史 + if (this._history.length > 20) { + this._history = this._history.slice(-20); + } + } else { + console.log('[ZY-CLOUD VPN] ⚠️ AI推理未返回结果'); + } + + return result; + } + // ── 应用学习数据优化节点排序 ──────────────── _applyLearnedOptimization() { const now = new Date(); @@ -917,6 +1005,8 @@ const mgmtServer = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ module: vpnModule._moduleId, + brand: '光湖语言世界 — 冰朔开发维护', + version: '3.0.0', state: vpnModule._state, started_at: vpnModule._startedAt, uptime_seconds: Math.floor((Date.now() - new Date(vpnModule._startedAt).getTime()) / 1000), From 5a42b0a5e38adc7155c290892d3094491e5c9251 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:45:06 +0000 Subject: [PATCH 2/4] =?UTF-8?q?V3=E5=8D=87=E7=BA=A7Phase2:=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BAsubscription-server-v3.js(=E5=93=81=E7=89=8C=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D/=E7=A1=AC=E5=88=87/=E4=BB=AA=E8=A1=A8?= =?UTF-8?q?=E7=9B=98/=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE),=20=E5=8D=87?= =?UTF-8?q?=E7=BA=A7proxy-guardian.js,=20=E6=96=B0=E5=A2=9EPM2+Nginx=20V3?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/226aae4f-48b7-4668-850c-7858729e6aa0 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .../config/nginx-brain-proxy-v3-snippet.conf | 34 + .../proxy/ecosystem.brain-proxy-v3.config.js | 64 ++ server/proxy/service/proxy-guardian.js | 70 +- .../proxy/service/subscription-server-v3.js | 853 ++++++++++++++++++ server/proxy/service/traffic-monitor-v2.js | 33 + 5 files changed, 1003 insertions(+), 51 deletions(-) create mode 100644 server/proxy/config/nginx-brain-proxy-v3-snippet.conf create mode 100644 server/proxy/ecosystem.brain-proxy-v3.config.js create mode 100644 server/proxy/service/subscription-server-v3.js diff --git a/server/proxy/config/nginx-brain-proxy-v3-snippet.conf b/server/proxy/config/nginx-brain-proxy-v3-snippet.conf new file mode 100644 index 00000000..eedc369b --- /dev/null +++ b/server/proxy/config/nginx-brain-proxy-v3-snippet.conf @@ -0,0 +1,34 @@ +# ═══════════════════════════════════════════════ +# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +# 📜 Copyright: 国作登字-2026-A-00037559 +# ═══════════════════════════════════════════════ +# 光湖语言世界 V3 · 大脑服务器 Nginx反向代理配置 +# 添加到 /etc/nginx/sites-enabled/ 中的server块内 +# +# 测试阶段: /api/proxy-v3/ → 3805 (V2继续运行) +# 切换阶段: 修改 /api/proxy-v2/ 的 proxy_pass 为 3805 +# ═══════════════════════════════════════════════ + +# §4 光湖语言世界V3订阅服务 (测试路径) +# 通过 /api/proxy-v3/ 路径访问 +# 实际由 subscription-server-v3.js (port 3805) 处理 +# ⚠️ 测试通过后: +# 方法1: 将上方V2的 proxy_pass 改为 http://127.0.0.1:3805/ +# 方法2: 或停掉V2进程,将V3端口改为3803 + +location /api/proxy-v3/ { + proxy_pass http://127.0.0.1:3805/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 10s; + proxy_read_timeout 30s; + proxy_send_timeout 30s; + + # 订阅服务安全头 + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; +} diff --git a/server/proxy/ecosystem.brain-proxy-v3.config.js b/server/proxy/ecosystem.brain-proxy-v3.config.js new file mode 100644 index 00000000..960becac --- /dev/null +++ b/server/proxy/ecosystem.brain-proxy-v3.config.js @@ -0,0 +1,64 @@ +// ═══════════════════════════════════════════════ +// 光湖语言世界 V3 · PM2 大脑服务器代理配置 +// 部署在 ZY-SVR-005 (43.156.237.110) · 大脑服务器 +// +// V3独立于V2运行,测试通过后切换Nginx即可 +// V2进程 (ecosystem.brain-proxy.config.js) 继续运行 +// +// 切换方式: +// 测试中: /api/proxy-v3/ → 3805 +// 切换后: /api/proxy-v2/ → 3805 (Nginx改一行) +// ═══════════════════════════════════════════════ + +module.exports = { + apps: [ + { + name: 'zy-proxy-v3-sub', + version: '3.0.0', + script: '/opt/zhuyuan-brain/proxy/service/subscription-server-v3.js', + instances: 1, + exec_mode: 'fork', + env: { + NODE_ENV: 'production', + ZY_PROXY_V3_PORT: 3805, + ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy' + }, + max_memory_restart: '128M', + log_file: '/opt/zhuyuan-brain/proxy/logs/subscription-v3.log', + error_file: '/opt/zhuyuan-brain/proxy/logs/subscription-v3-error.log', + time: true + }, + { + name: 'zy-proxy-guardian', + version: '3.0.0', + script: '/opt/zhuyuan-brain/proxy/service/proxy-guardian.js', + instances: 1, + exec_mode: 'fork', + env: { + NODE_ENV: 'production', + ZY_PROXY_DATA_DIR: '/opt/zhuyuan-brain/proxy/data', + ZY_PROXY_LOG_DIR: '/opt/zhuyuan-brain/proxy/logs', + ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy' + }, + max_memory_restart: '64M', + log_file: '/opt/zhuyuan-brain/proxy/logs/guardian.log', + error_file: '/opt/zhuyuan-brain/proxy/logs/guardian-error.log', + time: true + }, + { + name: 'zy-reverse-boost', + version: '3.0.0', + script: '/opt/zhuyuan-brain/proxy/service/reverse-boost-agent.js', + instances: 1, + exec_mode: 'fork', + env: { + NODE_ENV: 'production', + ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy' + }, + max_memory_restart: '64M', + log_file: '/opt/zhuyuan-brain/proxy/logs/reverse-boost.log', + error_file: '/opt/zhuyuan-brain/proxy/logs/reverse-boost-error.log', + time: true + } + ] +}; diff --git a/server/proxy/service/proxy-guardian.js b/server/proxy/service/proxy-guardian.js index 6812db8f..e4ddbb8d 100644 --- a/server/proxy/service/proxy-guardian.js +++ b/server/proxy/service/proxy-guardian.js @@ -30,10 +30,8 @@ const path = require('path'); const https = require('https'); const http = require('http'); -const DATA_DIR = process.env.ZY_PROXY_DATA_DIR || '/opt/zhuyuan/proxy/data'; -const LOG_DIR = process.env.ZY_PROXY_LOG_DIR || '/opt/zhuyuan/proxy/logs'; -const LLM_API_KEY = process.env.ZY_LLM_API_KEY || ''; -const LLM_BASE_URL = process.env.ZY_LLM_BASE_URL || ''; +const DATA_DIR = process.env.ZY_PROXY_DATA_DIR || '/opt/zhuyuan-brain/proxy/data'; +const LOG_DIR = process.env.ZY_PROXY_LOG_DIR || '/opt/zhuyuan-brain/proxy/logs'; const GUARDIAN_FILE = path.join(DATA_DIR, 'guardian-status.json'); const CHECK_INTERVAL = 10 * 60 * 1000; // 10分钟 @@ -101,7 +99,7 @@ function checkPort443() { // ── 检查订阅服务 ───────────────────────────── function checkSubscriptionService() { return new Promise((resolve) => { - const req = http.get('http://127.0.0.1:3802/health', { timeout: 5000 }, (res) => { + const req = http.get('http://127.0.0.1:3803/health', { timeout: 5000 }, (res) => { let data = ''; res.on('data', (d) => { data += d; }); res.on('end', () => { @@ -201,7 +199,7 @@ function autoFix(checkName) { case 'subscription_service': // 通过PM2重启订阅服务 - const pmResult = runCmd('pm2 restart zy-proxy-sub', 15000); + const pmResult = runCmd('pm2 restart zy-proxy-v2-sub', 15000); if (pmResult.ok) { console.log('[守护Agent] 订阅服务已重启'); return true; @@ -213,14 +211,17 @@ function autoFix(checkName) { } } -// ── 调用LLM推理 ───────────────────────────── +// ── 调用LLM推理 (V3: 使用llm-router动态路由) ── async function consultLLM(issue) { - if (!LLM_API_KEY || !LLM_BASE_URL) { - console.log('[守护Agent] LLM API未配置,跳过推理'); + let llmRouter; + try { + llmRouter = require('./llm-router'); + } catch { + console.log('[守护Agent] LLM路由器未加载,跳过推理'); return null; } - const prompt = `你是铸渊专线的代理守护Agent。以下是当前检测到的问题: + const prompt = `你是光湖语言世界VPN的守护Agent。以下是当前检测到的问题: 问题描述: ${issue.detail} 检查项: ${issue.checkName} @@ -229,46 +230,13 @@ async function consultLLM(issue) { 请分析可能的原因并给出解决建议。回答要简洁明确。`; - return new Promise((resolve) => { - const urlObj = new URL(LLM_BASE_URL); - const postData = JSON.stringify({ - model: 'deepseek-chat', - messages: [{ role: 'user', content: prompt }], - max_tokens: 500 - }); - - const options = { - hostname: urlObj.hostname, - port: urlObj.port || 443, - path: '/v1/chat/completions', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${LLM_API_KEY}`, - 'Content-Length': Buffer.byteLength(postData) - }, - timeout: 30000 - }; - - const req = https.request(options, (res) => { - let data = ''; - res.on('data', (d) => { data += d; }); - res.on('end', () => { - try { - const json = JSON.parse(data); - const answer = json.choices?.[0]?.message?.content || '无响应'; - resolve(answer); - } catch { - resolve(null); - } - }); - }); - - req.on('error', () => resolve(null)); - req.on('timeout', () => { req.destroy(); resolve(null); }); - req.write(postData); - req.end(); + const result = await llmRouter.callLLM(prompt, { + systemPrompt: '你是光湖语言世界VPN系统的守护Agent AI。负责诊断代理服务异常并给出修复建议。', + maxTokens: 500, + timeout: 30000 }); + + return result ? result.content : null; } // ── 发送告警邮件 ───────────────────────────── @@ -383,9 +351,9 @@ async function patrol() { } // ── 启动守护循环 ───────────────────────────── -console.log('🛡️ 铸渊专线守护Agent启动'); +console.log('🛡️ 光湖语言世界 · 守护Agent启动'); console.log(` 巡检间隔: ${CHECK_INTERVAL / 1000}秒`); -console.log(` LLM API: ${LLM_API_KEY ? '已配置' : '未配置'}`); +console.log(` LLM路由器: 动态多模型 (通过llm-router.js)`); // 立即执行一次 patrol().catch(console.error); diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js new file mode 100644 index 00000000..7c616559 --- /dev/null +++ b/server/proxy/service/subscription-server-v3.js @@ -0,0 +1,853 @@ +#!/usr/bin/env node +// ═══════════════════════════════════════════════ +// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +// 📜 Copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════════════ +// server/proxy/service/subscription-server-v3.js +// 🌐 光湖语言世界 · 多用户订阅服务 — 冰朔开发维护 +// +// V3升级 (从V2演进 · V2继续运行不受影响): +// 品牌: 铸渊专线V2 → 光湖语言世界 +// 流量池: 2000GB硬切 (到量即停 · 非仅告警) +// 配置优化: keep-alive 15s · 增强DIRECT分流 +// 反向加速: 零缓存直通 · splice模式 +// 仪表盘: /dashboard/{token} HTML页面 +// +// 部署在大脑服务器 (ZY-SVR-005 · 43.156.237.110) +// 每个邮箱一条独立专线,Token认证隔离 +// +// 测试端口: 3805 (V2=3803继续运行) +// 测试路径: /api/proxy-v3/sub/{token} +// 测试通过后: Nginx切换 /api/proxy-v2/ → 3805 +// ═══════════════════════════════════════════════ + +'use strict'; + +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const url = require('url'); + +const PORT = process.env.ZY_PROXY_V3_PORT || 3805; +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'); + +// ── 加载密钥 ──────────────────────────────── +function loadKeys() { + const keys = {}; + try { + const content = fs.readFileSync(KEYS_FILE, 'utf8'); + for (const line of content.split('\n')) { + if (line.startsWith('#') || !line.includes('=')) continue; + const [key, ...vals] = line.split('='); + keys[key.trim()] = vals.join('=').trim(); + } + } catch (err) { + keys.ZY_PROXY_REALITY_PUBLIC_KEY = process.env.ZY_PROXY_REALITY_PUBLIC_KEY || ''; + keys.ZY_PROXY_REALITY_SHORT_ID = process.env.ZY_PROXY_REALITY_SHORT_ID || ''; + } + return keys; +} + +// ── 从环境变量或密钥文件读取值 ────────────── +function getEnvOrKey(envName) { + if (process.env[envName]) return process.env[envName]; + + try { + const content = fs.readFileSync(KEYS_FILE, 'utf8'); + for (const line of content.split('\n')) { + if (line.startsWith('#') || !line.includes('=')) continue; + const [key, ...vals] = line.split('='); + if (key.trim() === envName) { + const val = vals.join('=').trim(); + if (val) return val; + } + } + } catch { /* ignore */ } + + return ''; +} + +// ── 获取服务器IP ──────────────────────────── +function getServerHost() { + return getEnvOrKey('ZY_BRAIN_HOST') || getEnvOrKey('ZY_SERVER_HOST') || '0.0.0.0'; +} + +// ── 构建所有可用VPN节点 ────────────────────── +// 优先从ZY-CLOUD活模块获取(动态·实时健康检查后的活节点) +// 回退到静态配置(ZY-CLOUD未运行时) +// V3: 节点名统一为"光湖"品牌 +function buildVpnNodes() { + // 优先: 从ZY-CLOUD活模块的动态节点列表读取 + const liveNodesFile = path.join(DATA_DIR, 'nodes-live.json'); + try { + const liveData = JSON.parse(fs.readFileSync(liveNodesFile, 'utf8')); + // 检查数据是否新鲜(使用与ZY-CLOUD相同的心跳过期阈值) + const age = Date.now() - new Date(liveData.updated_at).getTime(); + if (age < LIVE_NODES_FRESHNESS_MS * 2 && liveData.nodes && liveData.nodes.length > 0) { + // V3: 重写节点名为光湖品牌 + return liveData.nodes.map(n => ({ + ...n, + name: n.name.replace(/铸渊专线V2/g, '光湖') + })); + } + } catch { /* ZY-CLOUD未运行,回退到静态配置 */ } + + // 回退: 从环境变量/密钥文件静态构建 + return buildStaticNodes(); +} + +// ── 静态节点构建(回退用)──────────────────── +function buildStaticNodes() { + const nodes = []; + + // 节点1: 大脑服务器 (ZY-SVR-005 · 新加坡一区 · 4核8G · 主力) + const brainHost = getEnvOrKey('ZY_BRAIN_HOST'); + const brainPbk = getEnvOrKey('ZY_PROXY_REALITY_PUBLIC_KEY'); + const brainSid = getEnvOrKey('ZY_PROXY_REALITY_SHORT_ID'); + if (brainHost && brainPbk && brainSid) { + nodes.push({ + id: 'zy-brain-sg1', + name: '🧠 光湖-SG1(大脑)', + host: brainHost, + port: 443, + pbk: brainPbk, + sid: brainSid, + region: 'sg-zone1', + priority: 1 + }); + } + + // 节点2: 面孔服务器 (ZY-SVR-002 · 新加坡二区 · 2核8G · 备用) + const faceHost = getEnvOrKey('ZY_FACE_HOST'); + const facePbk = getEnvOrKey('ZY_FACE_REALITY_PUBLIC_KEY'); + const faceSid = getEnvOrKey('ZY_FACE_REALITY_SHORT_ID'); + if (faceHost && facePbk && faceSid) { + nodes.push({ + id: 'zy-face-sg2', + name: '🏛️ 光湖-SG2(面孔)', + host: faceHost, + port: 443, + pbk: facePbk, + sid: faceSid, + region: 'sg-zone2', + priority: 2 + }); + } + + // 节点3: CN中转 (国内→SG · 对国内用户低延迟) + const cnHost = getEnvOrKey('ZY_CN_RELAY_HOST'); + const cnPort = parseInt(getEnvOrKey('ZY_CN_RELAY_PORT') || '2053', 10); + // CN中转是TCP透传,Reality密钥用主力节点的(大脑) + if (cnHost && brainPbk && brainSid) { + nodes.push({ + id: 'zy-cn-relay', + name: '🇨🇳 光湖-CN中转', + host: cnHost, + port: cnPort, + pbk: brainPbk, + sid: brainSid, + region: 'cn-relay', + priority: 3 + }); + } + + return nodes; +} + +// ── 生成subscription-userinfo头 ────────────── +// 共享流量池模型: total=池配额(2000GB),upload+download=池总用量 +// 使用缓存的池状态文件(traffic-monitor-v2每5分钟更新)以减少计算开销 +function generateUserInfoHeader(user) { + const nextMonth = new Date(); + nextMonth.setMonth(nextMonth.getMonth() + 1); + nextMonth.setDate(1); + nextMonth.setHours(0, 0, 0, 0); + + // 优先从缓存文件读取池状态 (traffic-monitor-v2每5分钟写入) + let poolUpload = 0; + let poolDownload = 0; + try { + const cached = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'pool-quota-status.json'), 'utf8')); + poolUpload = cached.pool_upload_bytes || 0; + poolDownload = cached.pool_download_bytes || 0; + } catch { + // 缓存不可用时实时计算 + const poolStatus = userManager.getPoolStatus(); + poolUpload = poolStatus.pool_upload_bytes; + poolDownload = poolStatus.pool_download_bytes; + } + + return `upload=${poolUpload}; download=${poolDownload}; total=${userManager.POOL_QUOTA_BYTES}; expire=${Math.floor(nextMonth.getTime() / 1000)}`; +} + +// ── 生成VLESS URI (Shadowrocket · 多节点) ───── +function generateVlessUris(user, nodes) { + return nodes.map(node => { + const params = new URLSearchParams({ + encryption: 'none', + flow: 'xtls-rprx-vision', + security: 'reality', + sni: 'www.microsoft.com', + fp: 'chrome', + pbk: node.pbk, + sid: node.sid, + type: 'tcp', + headerType: 'none' + }); + const label = encodeURIComponent(node.name); + return `vless://${user.uuid}@${node.host}:${node.port}?${params.toString()}#${label}`; + }).join('\n'); +} + +// ── 生成Clash YAML配置 (多节点智能选路) ────── +function generateClashYaml(user, nodes) { + // 构建proxies块 — 每个节点一条 + const proxyBlocks = nodes.map(node => ` - name: "${node.name}" + type: vless + server: ${node.host} + port: ${node.port} + uuid: ${user.uuid} + network: tcp + tls: true + udp: true + flow: xtls-rprx-vision + skip-cert-verify: false + servername: www.microsoft.com + reality-opts: + public-key: ${node.pbk} + short-id: ${node.sid} + client-fingerprint: chrome`).join('\n'); + + // 节点名列表 + const nodeNames = nodes.map(n => ` - "${n.name}"`).join('\n'); + + // url-test自动选择组 (延迟最低的自动生效) + const autoSelectBlock = nodes.length > 1 ? ` + - name: "♻️ 自动选择" + type: url-test + proxies: +${nodeNames} + url: "http://www.gstatic.com/generate_204" + interval: 300 + tolerance: 50 +` : ''; + + // 主代理组 + const mainProxies = nodes.length > 1 + ? ` - "♻️ 自动选择"\n${nodeNames}\n - DIRECT` + : `${nodeNames}\n - DIRECT`; + + // AI/开发工具代理组的节点列表 + const toolProxies = nodes.length > 1 + ? ` - "♻️ 自动选择"\n${nodeNames}` + : nodeNames; + + return `# 光湖语言世界 · ${user.label} 的独立专线 — 冰朔开发维护 +# 自动生成 · ${new Date().toISOString()} +# ⚠️ 此配置为 ${user.email} 专属,请勿分享 +# 每人一条独立线路 · ${nodes.length}个节点智能选路 +# 服务器只是桥梁,你的光纤才是主引擎 🚀 + +# ── 全局设置 ────────────────────────────── +mixed-port: 7890 +allow-lan: false +mode: rule +log-level: info +ipv6: false +unified-delay: true +tcp-concurrent: true +find-process-mode: strict +geodata-mode: true +geodata-loader: standard +global-client-fingerprint: chrome +keep-alive-interval: 15 +external-controller: 127.0.0.1:9090 + +# ── GeoData 数据源 ──────────────────────── +geox-url: + geoip: "https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat" + geosite: "https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat" + mmdb: "https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb" + +# ── DNS 设置 (fake-ip模式) ──────────────── +dns: + enable: true + listen: 0.0.0.0:1053 + ipv6: false + enhanced-mode: fake-ip + fake-ip-range: 198.18.0.1/16 + fake-ip-filter: + - "*.lan" + - "*.local" + - "*.direct" + - "localhost.ptlogin2.qq.com" + - "dns.msftncsi.com" + - "*.msftconnecttest.com" + - "*.msftncsi.com" + - "+.stun.*.*" + - "+.stun.*.*.*" + - "lens.l.google.com" + - "stun.l.google.com" + - "time.*.com" + - "time.*.gov" + - "time.*.edu.cn" + - "time.*.apple.com" + - "time-ios.apple.com" + - "time-macos.apple.com" + - "ntp.*.com" + - "+.pool.ntp.org" + - "music.163.com" + - "*.music.163.com" + - "*.126.net" + default-nameserver: + - 223.5.5.5 + - 119.29.29.29 + - 1.0.0.1 + nameserver: + - https://dns.alidns.com/dns-query + - https://doh.pub/dns-query + fallback: + - https://1.0.0.1/dns-query + - https://dns.google/dns-query + - tls://8.8.4.4:853 + fallback-filter: + geoip: true + geoip-code: CN + geosite: + - gfw + ipcidr: + - 240.0.0.0/4 + domain: + - "+.google.com" + - "+.facebook.com" + - "+.youtube.com" + - "+.github.com" + - "+.googleapis.com" + - "+.openai.com" + - "+.anthropic.com" + +# ── 域名嗅探 ────────────────────────────── +sniffer: + enable: true + force-dns-mapping: true + parse-pure-ip: true + override-destination: true + sniff: + HTTP: + ports: [80, 8080-8880] + override-destination: true + TLS: + ports: [443, 8443] + QUIC: + ports: [443, 8443] + skip-domain: + - "Mijia Cloud" + - "+.push.apple.com" + +# ── 代理节点 (${nodes.length}个 · 智能选路) ── +proxies: +${proxyBlocks} + +# ── 代理组 ──────────────────────────────── +proxy-groups: + - name: "🌐 光湖语言世界" + type: select + proxies: +${mainProxies} +${autoSelectBlock} + - name: "🤖 AI服务" + type: select + proxies: +${toolProxies} + + - name: "💻 开发工具" + type: select + proxies: +${toolProxies} + + - name: "📺 流媒体" + type: select + proxies: +${toolProxies} + +# ── 路由规则 ────────────────────────────── +rules: + # AI服务 + - DOMAIN-SUFFIX,openai.com,🤖 AI服务 + - DOMAIN-SUFFIX,anthropic.com,🤖 AI服务 + - DOMAIN-SUFFIX,claude.ai,🤖 AI服务 + - DOMAIN-SUFFIX,chatgpt.com,🤖 AI服务 + - DOMAIN-SUFFIX,gemini.google.com,🤖 AI服务 + - DOMAIN-SUFFIX,perplexity.ai,🤖 AI服务 + - DOMAIN-SUFFIX,poe.com,🤖 AI服务 + - DOMAIN-SUFFIX,deepseek.com,🤖 AI服务 + - DOMAIN-SUFFIX,siliconflow.cn,🤖 AI服务 + + # 开发工具 + - DOMAIN-SUFFIX,github.com,💻 开发工具 + - DOMAIN-SUFFIX,githubusercontent.com,💻 开发工具 + - DOMAIN-SUFFIX,github.io,💻 开发工具 + - DOMAIN-SUFFIX,githubassets.com,💻 开发工具 + - DOMAIN-SUFFIX,copilot.microsoft.com,💻 开发工具 + - DOMAIN-SUFFIX,npmjs.com,💻 开发工具 + - DOMAIN-SUFFIX,docker.com,💻 开发工具 + - DOMAIN-SUFFIX,docker.io,💻 开发工具 + - DOMAIN-SUFFIX,stackoverflow.com,💻 开发工具 + - DOMAIN-SUFFIX,pypi.org,💻 开发工具 + + # 流媒体 + - DOMAIN-SUFFIX,youtube.com,📺 流媒体 + - DOMAIN-SUFFIX,googlevideo.com,📺 流媒体 + - DOMAIN-SUFFIX,netflix.com,📺 流媒体 + - DOMAIN-SUFFIX,spotify.com,📺 流媒体 + - DOMAIN-SUFFIX,tiktok.com,📺 流媒体 + + # 社交媒体 + - DOMAIN-SUFFIX,twitter.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,x.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,google.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,googleapis.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,telegram.org,🌐 光湖语言世界 + - DOMAIN-SUFFIX,t.me,🌐 光湖语言世界 + - DOMAIN-SUFFIX,instagram.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,facebook.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,whatsapp.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,wikipedia.org,🌐 光湖语言世界 + - DOMAIN-SUFFIX,reddit.com,🌐 光湖语言世界 + - DOMAIN-SUFFIX,discord.com,🌐 光湖语言世界 + + # Apple (直连) + - DOMAIN-SUFFIX,apple.com,DIRECT + - DOMAIN-SUFFIX,icloud.com,DIRECT + + # CDN静态资源 (直连 · 节省池带宽) + - DOMAIN-SUFFIX,cdn.jsdelivr.net,DIRECT + - DOMAIN-SUFFIX,cdnjs.cloudflare.com,DIRECT + - DOMAIN-SUFFIX,unpkg.com,DIRECT + - DOMAIN-SUFFIX,bootcdn.net,DIRECT + + # 国内直连 + - DOMAIN-SUFFIX,cn,DIRECT + - DOMAIN-SUFFIX,taobao.com,DIRECT + - DOMAIN-SUFFIX,tmall.com,DIRECT + - DOMAIN-SUFFIX,alipay.com,DIRECT + - DOMAIN-SUFFIX,aliyun.com,DIRECT + - DOMAIN-SUFFIX,jd.com,DIRECT + - DOMAIN-SUFFIX,qq.com,DIRECT + - DOMAIN-SUFFIX,tencent.com,DIRECT + - DOMAIN-SUFFIX,bilibili.com,DIRECT + - DOMAIN-SUFFIX,baidu.com,DIRECT + - DOMAIN-SUFFIX,zhihu.com,DIRECT + - DOMAIN-SUFFIX,douyin.com,DIRECT + - DOMAIN-SUFFIX,weibo.com,DIRECT + - DOMAIN-SUFFIX,163.com,DIRECT + - DOMAIN-SUFFIX,126.com,DIRECT + - DOMAIN-SUFFIX,xiaomi.com,DIRECT + - DOMAIN-SUFFIX,huawei.com,DIRECT + - DOMAIN-SUFFIX,vivo.com,DIRECT + - DOMAIN-SUFFIX,oppo.com,DIRECT + - DOMAIN-SUFFIX,meituan.com,DIRECT + - DOMAIN-SUFFIX,dianping.com,DIRECT + - DOMAIN-SUFFIX,pinduoduo.com,DIRECT + + # 局域网直连 + - IP-CIDR,192.168.0.0/16,DIRECT + - IP-CIDR,10.0.0.0/8,DIRECT + - IP-CIDR,172.16.0.0/12,DIRECT + - IP-CIDR,127.0.0.0/8,DIRECT + + # GeoIP中国直连 + - GEOIP,CN,DIRECT + + # 默认走代理 + - MATCH,🌐 光湖语言世界 +`; +} + +// ── 检测客户端类型 ─────────────────────────── +function detectClientType(userAgent) { + const ua = (userAgent || '').toLowerCase(); + if (ua.includes('clash') || ua.includes('mihomo') || ua.includes('stash')) return 'clash'; + if (ua.includes('shadowrocket') || ua.includes('quantumult') || ua.includes('surge')) return 'base64'; + return 'clash'; +} + +// ── HTTP服务器 ─────────────────────────────── +const server = http.createServer((req, res) => { + try { + const parsedUrl = url.parse(req.url, true); + const pathname = parsedUrl.pathname; + + // 健康检查 + if (pathname === '/health') { + const users = userManager.getEnabledUsers(); + const nodes = buildVpnNodes(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + status: 'ok', + service: 'zy-proxy-v3-subscription', + brand: '光湖语言世界 — 冰朔开发维护', + version: '3.0.0', + users_count: users.length, + nodes_count: nodes.length, + smart_routing: nodes.length > 1 ? 'url-test' : 'single', + server: 'ZY-SVR-005 · Brain', + pool_hard_cutoff: true + })); + return; + } + + // ── 流量池硬切检查 (2000GB楚河汉界) ────── + // V3新增: 到量即停,不仅仅是告警 + function isPoolExhausted() { + try { + const cached = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'pool-quota-status.json'), 'utf8')); + const totalUsed = (cached.pool_upload_bytes || 0) + (cached.pool_download_bytes || 0); + return totalUsed >= userManager.POOL_QUOTA_BYTES; + } catch { + const poolStatus = userManager.getPoolStatus(); + return poolStatus.pool_used_bytes >= userManager.POOL_QUOTA_BYTES; + } + } + + // V2订阅端点: /sub/{token} + // token是每个用户独立的,不会串线 + const subMatch = pathname.match(/^\/sub\/([a-f0-9]+)$/); + if (subMatch) { + const token = subMatch[1]; + const user = userManager.findUserByToken(token); + + if (!user) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('Forbidden'); + return; + } + + // V3硬切: 流量池耗尽 → 返回空配置+提示 + if (isPoolExhausted()) { + const poolStatus = userManager.getPoolStatus(); + const exhaustedYaml = `# ⚠️ 光湖语言世界 · 本月流量池已用完 +# 流量池: ${poolStatus.pool_used_gb.toFixed(1)}GB / ${poolStatus.pool_total_gb}GB +# 每月1号自动重置 · 届时刷新订阅即可恢复 +# 如有紧急需求请联系冰朔 + +mixed-port: 7890 +allow-lan: false +mode: direct +`; + res.writeHead(200, { + 'Content-Type': 'text/yaml; charset=utf-8', + 'subscription-userinfo': generateUserInfoHeader(user), + 'profile-update-interval': '1', + 'profile-title': 'base64:' + Buffer.from(`⚠️ 光湖·${user.label}·流量已用完`).toString('base64'), + }); + res.end(exhaustedYaml); + return; + } + + const nodes = buildVpnNodes(); + if (nodes.length === 0) { + res.writeHead(503, { 'Content-Type': 'text/plain' }); + res.end('No VPN nodes configured'); + return; + } + + const clientType = detectClientType(req.headers['user-agent']); + const userInfoHeader = generateUserInfoHeader(user); + + if (clientType === 'clash') { + const yaml = generateClashYaml(user, nodes); + res.writeHead(200, { + 'Content-Type': 'text/yaml; charset=utf-8', + 'Content-Disposition': `attachment; filename="guanghu-vpn-${user.label}.yaml"`, + 'subscription-userinfo': userInfoHeader, + 'profile-update-interval': '6', + 'profile-title': 'base64:' + Buffer.from(`光湖语言世界·${user.label}`).toString('base64'), + }); + res.end(yaml); + } else { + const vlessUris = generateVlessUris(user, nodes); + const encoded = Buffer.from(vlessUris).toString('base64'); + res.writeHead(200, { + 'Content-Type': 'text/plain; charset=utf-8', + 'subscription-userinfo': userInfoHeader, + 'profile-update-interval': '6', + }); + res.end(encoded); + } + return; + } + + // 用户配额查询: /quota/{token} + const quotaMatch = pathname.match(/^\/quota\/([a-f0-9]+)$/); + if (quotaMatch) { + const token = quotaMatch[1]; + const user = userManager.findUserByToken(token); + + if (!user) { + res.writeHead(403, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: true, code: 'FORBIDDEN', message: '认证失败' })); + return; + } + + const poolStatus = userManager.getPoolStatus(); + const userUsedGB = (user.traffic.upload_bytes + user.traffic.download_bytes) / (1024 ** 3); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + email: user.email, + label: user.label, + pool: { + total_gb: poolStatus.pool_total_gb, + used_gb: parseFloat(poolStatus.pool_used_gb.toFixed(2)), + remaining_gb: poolStatus.pool_remaining_gb, + percentage_used: poolStatus.pool_percentage, + users_count: poolStatus.users_count, + period: poolStatus.period, + reset_day: 1 + }, + personal: { + used_gb: parseFloat(userUsedGB.toFixed(2)), + period: user.traffic.period + }, + updated_at: new Date().toISOString() + })); + return; + } + + // 用户状态: /status/{token} + const statusMatch = pathname.match(/^\/status\/([a-f0-9]+)$/); + if (statusMatch) { + const token = statusMatch[1]; + const user = userManager.findUserByToken(token); + + if (!user) { + res.writeHead(403, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: true, code: 'FORBIDDEN', message: '认证失败' })); + return; + } + + const poolStatus = userManager.getPoolStatus(); + const userUsedGB = (user.traffic.upload_bytes + user.traffic.download_bytes) / (1024 ** 3); + const nodes = buildVpnNodes(); + + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ + user: { + email: user.email, + label: user.label, + enabled: user.enabled + }, + server: { + status: 'online', + brand: '光湖语言世界 — 冰朔开发维护', + version: '3.0.0', + uptime_seconds: Math.floor(process.uptime()), + region: 'sg', + region_name: 'Singapore', + server_code: 'ZY-SVR-005' + }, + nodes: nodes.map(n => ({ + id: n.id, + name: n.name, + region: n.region, + port: n.port + })), + smart_routing: nodes.length > 1 ? 'url-test (自动选最快)' : 'single-node', + pool: { + total_gb: poolStatus.pool_total_gb, + used_gb: parseFloat(poolStatus.pool_used_gb.toFixed(2)), + remaining_gb: poolStatus.pool_remaining_gb, + percentage_used: poolStatus.pool_percentage, + users_count: poolStatus.users_count, + period: poolStatus.period, + reset_day: 1 + }, + personal: { + used_gb: parseFloat(userUsedGB.toFixed(2)), + period: user.traffic.period + }, + updated_at: new Date().toISOString() + })); + return; + } + + // ── V3 流量仪表盘: /dashboard/{token} ────── + // 手机浏览器可查看 · 流量/节点/系统状态 + const dashMatch = pathname.match(/^\/dashboard\/([a-f0-9]+)$/); + if (dashMatch) { + const token = dashMatch[1]; + const user = userManager.findUserByToken(token); + + if (!user) { + res.writeHead(403, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end('

403 认证失败

'); + return; + } + + const poolStatus = userManager.getPoolStatus(); + const userUsedGB = ((user.traffic.upload_bytes + user.traffic.download_bytes) / (1024 ** 3)).toFixed(2); + const nodes = buildVpnNodes(); + const poolPct = Math.min(poolStatus.pool_percentage, 100).toFixed(1); + + // 读取反向加速状态 + let boostStatus = '未检测'; + try { + const bs = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'reverse-boost-status.json'), 'utf8')); + boostStatus = bs.current?.bbr?.is_bbr ? '✅ BBR加速中' : '⚠️ 未加速'; + } catch { /* ignore */ } + + // 读取今日流量快照 + let todayGB = '—'; + try { + const snap = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'daily-traffic-snapshot.json'), 'utf8')); + const today = new Date().toISOString().slice(0, 10); + if (snap.date === today && snap.per_user?.[user.email]) { + todayGB = snap.per_user[user.email].total_gb.toFixed(2); + } + } catch { /* ignore */ } + + const poolBarColor = poolPct > 90 ? '#e74c3c' : poolPct > 70 ? '#f39c12' : '#2ecc71'; + + const html = ` + + + + +光湖语言世界 · ${user.label} + + + +
+

🌐 光湖语言世界

+

${user.label} 的专属仪表盘 — 冰朔开发维护

+
+ +
+

📊 流量概览

+
今日用量${todayGB} GB
+
个人本月${userUsedGB} GB
+
流量池${poolStatus.pool_used_gb.toFixed(1)} / ${poolStatus.pool_total_gb} GB
+
+
${poolPct}%
+
+
重置日期每月1号
+
+ +
+

🔌 节点状态 (${nodes.length}个)

+ ${nodes.map(n => `
${n.name}${n.latency_ms ? n.latency_ms + 'ms' : '在线'}
`).join('\n ')} +
+ +
+

⚡ 系统状态

+
服务版本V3.0
+
反向加速${boostStatus}
+
在线用户${poolStatus.users_count}
+
智能选路${nodes.length > 1 ? '✅ url-test' : '单节点'}
+
+ + + +`; + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(html); + return; + } + + // 404 + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found'); + } catch (err) { + console.error('❌ 请求处理错误 [%s %s]:', req.method, req.url, err.message || err); + try { + if (!res.headersSent) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + } + res.end('Internal Server Error'); + } catch (writeErr) { + console.error(' ⚠️ 响应写入失败:', writeErr.message); + } + } +}); + +server.on('error', (err) => { + console.error('❌ 服务器错误:', err.message); + if (err.code === 'EADDRINUSE') { + console.error(' 端口 %d 已被占用', PORT); + process.exit(1); + } +}); + +server.on('clientError', (err, socket) => { + console.error('⚠️ 客户端连接错误:', err.code || err.message); + if (socket.writable) { + socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); + } +}); + +server.listen(PORT, '127.0.0.1', () => { + const users = userManager.getEnabledUsers(); + const poolGB = userManager.POOL_QUOTA_BYTES / (1024 ** 3); + console.log(`🌐 光湖语言世界 · V3订阅服务已启动: http://127.0.0.1:${PORT}`); + console.log(` 品牌: 光湖语言世界 — 冰朔开发维护`); + console.log(` 版本: V3.0 (多用户独立专线·共享流量池·硬切·AI推理)`); + console.log(` 用户数: ${users.length}`); + console.log(` 流量池: ${poolGB}GB/月 (全用户共享·每月1号重置·到量即停)`); + console.log(` 服务器: ZY-SVR-005 · Brain`); + console.log(` ──────────────────────────`); + console.log(` 订阅端点: /sub/{user_token}`); + console.log(` 配额查询: /quota/{user_token}`); + console.log(` 服务状态: /status/{user_token}`); + console.log(` 流量仪表盘: /dashboard/{user_token}`); + console.log(` 健康检查: /health`); +}); + +// Graceful shutdown +function gracefulShutdown(signal) { + console.log(`\n${signal} received. Shutting down gracefully...`); + const forceExit = setTimeout(() => { process.exit(1); }, 5000); + server.close(() => { + clearTimeout(forceExit); + console.log('Server closed.'); + process.exit(0); + }); +} +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); +process.on('SIGINT', () => gracefulShutdown('SIGINT')); + +process.on('uncaughtException', (err) => { + console.error('❌ 未捕获的异常:', err.message); + console.error(err.stack); + gracefulShutdown('uncaughtException'); +}); +process.on('unhandledRejection', (reason) => { + console.error('❌ 未处理的Promise拒绝:', reason); +}); diff --git a/server/proxy/service/traffic-monitor-v2.js b/server/proxy/service/traffic-monitor-v2.js index cbb6f5d9..735e3572 100644 --- a/server/proxy/service/traffic-monitor-v2.js +++ b/server/proxy/service/traffic-monitor-v2.js @@ -177,9 +177,42 @@ function monitor() { fs.mkdirSync(DATA_DIR, { recursive: true }); fs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2)); + + // V3: 每日流量快照 (供仪表盘使用) + saveDailySnapshot(perUserTraffic); + console.log('[V2流量监控] 检查完成'); } +// ── V3: 每日流量快照 ───────────────────────── +// 记录每天各用户的流量,供/dashboard/{token}使用 +function saveDailySnapshot(perUserTraffic) { + const snapshotFile = path.join(DATA_DIR, 'daily-traffic-snapshot.json'); + const today = new Date().toISOString().slice(0, 10); + + let snapshot; + try { + snapshot = JSON.parse(fs.readFileSync(snapshotFile, 'utf8')); + // 如果日期变了,重置快照 + if (snapshot.date !== today) { + snapshot = { date: today, per_user: {}, updated_at: new Date().toISOString() }; + } + } catch { + snapshot = { date: today, per_user: {}, updated_at: new Date().toISOString() }; + } + + for (const [email, traffic] of Object.entries(perUserTraffic)) { + snapshot.per_user[email] = { + upload_gb: parseFloat((traffic.upload / (1024 ** 3)).toFixed(2)), + download_gb: parseFloat((traffic.download / (1024 ** 3)).toFixed(2)), + total_gb: parseFloat(((traffic.upload + traffic.download) / (1024 ** 3)).toFixed(2)) + }; + } + + snapshot.updated_at = new Date().toISOString(); + fs.writeFileSync(snapshotFile, JSON.stringify(snapshot, null, 2)); +} + // ── 启动监控循环 ───────────────────────────── console.log('📊 铸渊专线V2流量监控启动 · 共享流量池模型'); console.log(` 检查间隔: ${CHECK_INTERVAL / 1000}秒`); From 7d33e491017280448e7bb788d6276179948665ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:48:04 +0000 Subject: [PATCH 3/4] =?UTF-8?q?V3=E5=8D=87=E7=BA=A7Phase3:=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0deploy-brain-proxy.sh=E6=94=AF=E6=8C=81deploy-v3/switc?= =?UTF-8?q?h-v3,=20=E6=9B=B4=E6=96=B0=E5=B7=A5=E4=BD=9C=E6=B5=81=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0V3=E6=93=8D=E4=BD=9C=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/226aae4f-48b7-4668-850c-7858729e6aa0 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .github/workflows/deploy-brain-proxy.yml | 6 +- server/proxy/deploy-brain-proxy.sh | 168 ++++++++++++++++++++++- 2 files changed, 171 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-brain-proxy.yml b/.github/workflows/deploy-brain-proxy.yml index 809b70bc..5498f5f5 100644 --- a/.github/workflows/deploy-brain-proxy.yml +++ b/.github/workflows/deploy-brain-proxy.yml @@ -28,6 +28,8 @@ on: - remove-user - list-users - send-subscription + - deploy-v3 + - switch-v3 default: 'status' email: description: '用户邮箱 (add-user/remove-user/send-subscription时需要)' @@ -55,7 +57,9 @@ jobs: github.event.inputs.action == 'install' || github.event.inputs.action == 'update' || github.event.inputs.action == 'status' || - github.event.inputs.action == 'restart' + github.event.inputs.action == 'restart' || + github.event.inputs.action == 'deploy-v3' || + github.event.inputs.action == 'switch-v3' steps: - name: '📥 检出代码' diff --git a/server/proxy/deploy-brain-proxy.sh b/server/proxy/deploy-brain-proxy.sh index 0d6c0742..87dffd56 100644 --- a/server/proxy/deploy-brain-proxy.sh +++ b/server/proxy/deploy-brain-proxy.sh @@ -4,7 +4,7 @@ # 📜 Copyright: 国作登字-2026-A-00037559 # ═══════════════════════════════════════════════ # server/proxy/deploy-brain-proxy.sh -# 🚀 铸渊专线V2 · 大脑服务器部署脚本 +# 🚀 铸渊专线V2 + 光湖语言世界V3 · 大脑服务器部署脚本 # # 在大脑服务器(ZY-SVR-005)上执行 # 部署多用户VPN系统 (独立于面孔服务器的V1) @@ -17,6 +17,8 @@ # bash deploy-brain-proxy.sh add-user [quota_gb] — 添加用户 # bash deploy-brain-proxy.sh remove-user — 移除用户 # bash deploy-brain-proxy.sh list-users — 列出用户 +# bash deploy-brain-proxy.sh deploy-v3 — 部署V3测试环境 +# bash deploy-brain-proxy.sh switch-v3 — 切换V2→V3 (用户刷新即升级) # ═══════════════════════════════════════════════ set -uo pipefail @@ -196,8 +198,16 @@ deploy_services() { cp "$REPO_PROXY_DIR"/service/vpn-worker.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/ecosystem.brain-proxy.config.js "$PROXY_DIR/" + # 复制V3服务文件 (独立运行·不影响V2) + cp "$REPO_PROXY_DIR"/service/subscription-server-v3.js "$PROXY_DIR/service/" + cp "$REPO_PROXY_DIR"/service/llm-router.js "$PROXY_DIR/service/" + cp "$REPO_PROXY_DIR"/service/reverse-boost-agent.js "$PROXY_DIR/service/" + cp "$REPO_PROXY_DIR"/service/proxy-guardian.js "$PROXY_DIR/service/" + cp "$REPO_PROXY_DIR"/ecosystem.brain-proxy-v3.config.js "$PROXY_DIR/" + # 复制V2 Nginx配置参考 cp "$REPO_PROXY_DIR"/config/nginx-brain-proxy-snippet.conf "$PROXY_DIR/config/" 2>/dev/null || true + cp "$REPO_PROXY_DIR"/config/nginx-brain-proxy-v3-snippet.conf "$PROXY_DIR/config/" 2>/dev/null || true # 复制共用文件(发邮件等) cp "$REPO_PROXY_DIR"/service/send-subscription.js "$PROXY_DIR/service/" 2>/dev/null || true @@ -377,6 +387,15 @@ health_check() { echo " ❌ V2订阅服务: 端口3803无响应" fi + # V3订阅服务 (如果已部署) + if curl -sf http://127.0.0.1:3805/health >/dev/null 2>&1; then + HEALTH3=$(curl -sf http://127.0.0.1:3805/health) + USERS3=$(echo "$HEALTH3" | grep -o '"users_count":[0-9]*' | cut -d: -f2) + echo " ✅ V3订阅服务: 正常 (${USERS3:-0}个用户) · 光湖语言世界" + else + echo " ⚠️ V3订阅服务: 端口3805无响应 (未部署或未启动)" + fi + # Nginx反向代理 if systemctl is-active --quiet nginx 2>/dev/null; then if curl -sf http://127.0.0.1:80/api/proxy-v2/health >/dev/null 2>&1; then @@ -468,6 +487,149 @@ list_users() { node "$PROXY_DIR/service/user-manager.js" list } +# ── deploy-v3: 部署V3测试环境 ────────────────── +# V2继续运行·V3独立启动·通过/api/proxy-v3/测试 +deploy_v3() { + echo "部署V3测试环境 (V2继续运行)..." + deploy_services + + # 加载密钥作为环境变量 + if [ -f "$PROXY_DIR/.env.keys" ]; then + set -a + # shellcheck source=/dev/null + source "$PROXY_DIR/.env.keys" + set +a + fi + + # 启动V3 PM2进程 + cd "$PROXY_DIR" || { echo "❌ 无法进入 $PROXY_DIR"; return 1; } + pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env + pm2 save + + # 添加V3 Nginx测试路径 + NGINX_CONF="" + for candidate in /etc/nginx/sites-enabled/zhuyuan-brain.conf /etc/nginx/sites-enabled/default; do + if [ -f "$candidate" ]; then + NGINX_CONF="$candidate" + break + fi + done + + if [ -n "$NGINX_CONF" ] && ! grep -q "proxy-v3" "$NGINX_CONF" 2>/dev/null; then + echo " 添加V3测试路径..." + sed -i '/^}/i\ + # ─── 光湖语言世界V3测试 (端口 3805) ───\ + location /api/proxy-v3/ {\ + proxy_pass http://127.0.0.1:3805/;\ + proxy_http_version 1.1;\ + proxy_set_header Host $host;\ + proxy_set_header X-Real-IP $remote_addr;\ + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\ + proxy_set_header X-Forwarded-Proto $scheme;\ + proxy_connect_timeout 10s;\ + proxy_read_timeout 30s;\ + proxy_send_timeout 30s;\ + add_header X-Content-Type-Options nosniff always;\ + add_header X-Frame-Options DENY always;\ + add_header Cache-Control "no-store, no-cache, must-revalidate" always;\ + }' "$NGINX_CONF" || true + echo " ✅ V3测试路径已添加" + else + echo " proxy-v3配置已存在或Nginx配置未找到" + fi + + if nginx -t 2>/dev/null; then + nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true + echo " ✅ Nginx已重载" + fi + + echo "" + echo "════════════════════════════════════════" + echo "✅ V3测试环境已就绪" + echo "" + echo "测试方法:" + echo " V3订阅: https://域名/api/proxy-v3/sub/{token}" + echo " V3仪表盘: https://域名/api/proxy-v3/dashboard/{token}" + echo " V3健康: https://域名/api/proxy-v3/health" + echo "" + echo "V2继续运行在 /api/proxy-v2/ (端口3803)" + echo "V3测试运行在 /api/proxy-v3/ (端口3805)" + echo "" + echo "测试通过后执行: bash deploy-brain-proxy.sh switch-v3" + echo "════════════════════════════════════════" + + # V3健康检查 + sleep 2 + if curl -sf http://127.0.0.1:3805/health >/dev/null 2>&1; then + HEALTH=$(curl -sf http://127.0.0.1:3805/health) + echo " ✅ V3订阅服务: 正常" + echo " $HEALTH" + else + echo " ⚠️ V3订阅服务(3805)尚未就绪,请稍等几秒后检查" + fi +} + +# ── switch-v3: 切换V2→V3 ────────────────────── +# 用户刷新订阅即获取V3配置 +switch_v3() { + echo "切换 /api/proxy-v2/ → V3 (端口3805)..." + + NGINX_CONF="" + for candidate in /etc/nginx/sites-enabled/zhuyuan-brain.conf /etc/nginx/sites-enabled/default; do + if [ -f "$candidate" ]; then + NGINX_CONF="$candidate" + break + fi + done + + if [ -z "$NGINX_CONF" ]; then + echo "❌ 未找到Nginx配置文件" + exit 1 + fi + + # 验证V3服务是否运行 + if ! curl -sf http://127.0.0.1:3805/health >/dev/null 2>&1; then + echo "❌ V3服务(3805)未运行!请先执行 deploy-v3" + exit 1 + fi + + # 备份当前Nginx配置 + cp "$NGINX_CONF" "${NGINX_CONF}.v2-backup" + echo " ✅ 已备份Nginx配置到 ${NGINX_CONF}.v2-backup" + + # 将 /api/proxy-v2/ 的 proxy_pass 从 3803 改为 3805 + if grep -q "proxy_pass.*127\.0\.0\.1:3803" "$NGINX_CONF" 2>/dev/null; then + sed -i 's|proxy_pass[[:space:]]*http://127\.0\.0\.1:3803|proxy_pass http://127.0.0.1:3805|g' "$NGINX_CONF" + echo " ✅ /api/proxy-v2/ 已切换到V3 (3803→3805)" + else + echo " ⚠️ 未找到3803端口配置,可能已切换" + fi + + # 重载Nginx + if nginx -t 2>/dev/null; then + nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true + echo " ✅ Nginx已重载" + else + echo " ❌ Nginx配置验证失败,回滚..." + cp "${NGINX_CONF}.v2-backup" "$NGINX_CONF" + nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true + echo " ✅ 已回滚到V2配置" + exit 1 + fi + + echo "" + echo "════════════════════════════════════════" + echo "✅ V2→V3 切换完成!" + echo "" + echo "现在 /api/proxy-v2/sub/{token} 指向V3 (端口3805)" + echo "用户刷新订阅即可获取「光湖语言世界」配置" + echo "" + echo "回滚方法 (如有问题):" + echo " cp ${NGINX_CONF}.v2-backup $NGINX_CONF" + echo " nginx -s reload" + echo "════════════════════════════════════════" +} + # ── 执行 ────────────────────────────────────── case "$ACTION" in install) install ;; @@ -477,8 +639,10 @@ case "$ACTION" in add-user) add_user "$@" ;; remove-user) remove_user "$@" ;; list-users) list_users ;; + deploy-v3) deploy_v3 ;; + switch-v3) switch_v3 ;; *) - echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users}" + echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users|deploy-v3|switch-v3}" exit 1 ;; esac From 0bac15c1e3ff45dab2fb21255236602278377e71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:52:35 +0000 Subject: [PATCH 4/4] =?UTF-8?q?V3=E4=BF=AE=E5=A4=8D:=20XSS=E9=98=B2?= =?UTF-8?q?=E6=8A=A4(HTML=E8=BD=AC=E4=B9=89),=20LLM=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=99=A8=E8=B7=AF=E5=BE=84=E4=BF=AE=E5=A4=8D,=20context?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E9=99=90=E5=88=B6,=20deploy=20grep/sed?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/226aae4f-48b7-4668-850c-7858729e6aa0 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/proxy/deploy-brain-proxy.sh | 4 ++-- server/proxy/service/llm-router.js | 11 ++++++----- server/proxy/service/subscription-server-v3.js | 14 +++++++++----- server/proxy/service/zy-cloud-vpn.js | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/server/proxy/deploy-brain-proxy.sh b/server/proxy/deploy-brain-proxy.sh index 87dffd56..cc538543 100644 --- a/server/proxy/deploy-brain-proxy.sh +++ b/server/proxy/deploy-brain-proxy.sh @@ -598,8 +598,8 @@ switch_v3() { echo " ✅ 已备份Nginx配置到 ${NGINX_CONF}.v2-backup" # 将 /api/proxy-v2/ 的 proxy_pass 从 3803 改为 3805 - if grep -q "proxy_pass.*127\.0\.0\.1:3803" "$NGINX_CONF" 2>/dev/null; then - sed -i 's|proxy_pass[[:space:]]*http://127\.0\.0\.1:3803|proxy_pass http://127.0.0.1:3805|g' "$NGINX_CONF" + if grep -q "127\.0\.0\.1:3803" "$NGINX_CONF" 2>/dev/null; then + sed -i 's|127\.0\.0\.1:3803|127.0.0.1:3805|g' "$NGINX_CONF" echo " ✅ /api/proxy-v2/ 已切换到V3 (3803→3805)" else echo " ⚠️ 未找到3803端口配置,可能已切换" diff --git a/server/proxy/service/llm-router.js b/server/proxy/service/llm-router.js index c38fa446..a0f2cac6 100644 --- a/server/proxy/service/llm-router.js +++ b/server/proxy/service/llm-router.js @@ -132,12 +132,13 @@ function _callSingleModel(baseUrl, apiKey, model, prompt, systemPrompt, maxToken temperature: 0.3 }); - // 构建路径:如果baseUrl包含路径则使用,否则追加 /v1/chat/completions + // 构建路径:如果baseUrl已包含chat/completions路径则使用 + // 否则追加 /v1/chat/completions let apiPath = urlObj.pathname; - if (apiPath === '/' || apiPath === '') { + if (!apiPath || apiPath === '/') { apiPath = '/v1/chat/completions'; - } else if (!apiPath.endsWith('/chat/completions')) { - apiPath = apiPath.replace(/\/$/, '') + '/v1/chat/completions'; + } else if (!apiPath.includes('/chat/completions')) { + apiPath = apiPath.replace(/\/+$/, '') + '/chat/completions'; } const options = { @@ -161,7 +162,7 @@ function _callSingleModel(baseUrl, apiKey, model, prompt, systemPrompt, maxToken res.on('end', () => { try { if (res.statusCode >= 400) { - reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)); + reject(new Error(`HTTP ${res.statusCode}: 请求失败`)); return; } const json = JSON.parse(data); diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 7c616559..abec1379 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -694,7 +694,8 @@ mode: direct const poolStatus = userManager.getPoolStatus(); const userUsedGB = ((user.traffic.upload_bytes + user.traffic.download_bytes) / (1024 ** 3)).toFixed(2); const nodes = buildVpnNodes(); - const poolPct = Math.min(poolStatus.pool_percentage, 100).toFixed(1); + const poolUsedGB = (typeof poolStatus.pool_used_gb === 'number' ? poolStatus.pool_used_gb : 0).toFixed(1); + const poolPct = Math.min(typeof poolStatus.pool_percentage === 'number' ? poolStatus.pool_percentage : 0, 100).toFixed(1); // 读取反向加速状态 let boostStatus = '未检测'; @@ -715,12 +716,15 @@ mode: direct const poolBarColor = poolPct > 90 ? '#e74c3c' : poolPct > 70 ? '#f39c12' : '#2ecc71'; + // HTML转义防止XSS + const esc = (s) => String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + const html = ` -光湖语言世界 · ${user.label} +光湖语言世界 · ${esc(user.label)}