From bbd3f49ce53a4403530c63692e94e080ba92d96d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 04:03:22 +0000 Subject: [PATCH 1/3] feat: add protocol mirror engine for Xray-core auto-update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds server/proxy/service/protocol-mirror.js — the Protocol Mirror Engine that monitors Xray-core releases and manages automatic updates with: - GitHub API polling (every 6h) with result caching - Installed vs latest version comparison - TLS/Reality fingerprint consistency checks - LLM-powered update risk analysis via llm-router.js - Safe auto-update: download → backup → install → verify → restart - Failure protection: 3 consecutive failures triggers admin alert - Update summary generation for email-hub distribution - State persistence to data/protocol-mirror-status.json Runs as a PM2-managed service checking every 30 minutes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/proxy/service/protocol-mirror.js | 657 ++++++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 server/proxy/service/protocol-mirror.js diff --git a/server/proxy/service/protocol-mirror.js b/server/proxy/service/protocol-mirror.js new file mode 100644 index 00000000..f96d1f48 --- /dev/null +++ b/server/proxy/service/protocol-mirror.js @@ -0,0 +1,657 @@ +#!/usr/bin/env node +// ═══════════════════════════════════════════════ +// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +// 📜 Copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════════════ +// server/proxy/service/protocol-mirror.js +// 🪞 铸渊专线 · 协议镜像引擎 +// +// 核心概念: 镜像同步 +// - 上游协议 (Xray-core) 更新时自动检测 +// - 自动下载/备份/升级/验证/重启 +// - TLS/Reality 指纹一致性校验 +// - 对外呈现标准 Xray 行为 (伪装) +// +// 安全机制: +// - LLM分析更新风险 (安全补丁/破坏性变更) +// - 失败3次自动告警管理员 +// - 升级前备份,失败可回滚 +// +// 运行方式: PM2 managed (zy-protocol-mirror) +// 检查间隔: 每30分钟 (GitHub API每6小时) +// ═══════════════════════════════════════════════ + +'use strict'; + +const { execSync, execFileSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const https = require('https'); + +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 MIRROR_FILE = path.join(DATA_DIR, 'protocol-mirror-status.json'); +const XRAY_BIN = process.env.ZY_XRAY_BIN || '/usr/local/bin/xray'; +const XRAY_BACKUP_DIR = path.join(DATA_DIR, 'xray-backups'); + +const CHECK_INTERVAL = 30 * 60 * 1000; // 30分钟 +const GITHUB_CHECK_INTERVAL = 6 * 60 * 60 * 1000; // 6小时 +const MAX_FAILED_UPDATES = 3; +const MAX_UPDATE_HISTORY = 20; + +// ── 读取镜像状态 ───────────────────────────── +function readMirrorStatus() { + try { + return JSON.parse(fs.readFileSync(MIRROR_FILE, 'utf8')); + } catch { + return { + installed_version: null, + latest_version: null, + update_available: false, + last_github_check: null, + last_fingerprint_check: null, + update_history: [], + failed_updates: 0, + mirror_status: 'initializing', + fingerprint: { + tls_match: true, + reality_match: true, + protocol_version: 'unknown' + } + }; + } +} + +// ── 保存镜像状态 ───────────────────────────── +function saveMirrorStatus(status) { + if (!fs.existsSync(DATA_DIR)) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + } + fs.writeFileSync(MIRROR_FILE, JSON.stringify(status, null, 2)); +} + +// ── 执行命令 ───────────────────────────────── +function runCmd(cmd, timeout = 10000) { + try { + return { ok: true, output: execSync(cmd, { encoding: 'utf8', timeout }).trim() }; + } catch (err) { + return { ok: false, output: err.message }; + } +} + +// ── HTTPS GET 请求 ─────────────────────────── +function httpsGet(url, headers = {}) { + return new Promise((resolve, reject) => { + const urlObj = new URL(url); + const options = { + hostname: urlObj.hostname, + path: urlObj.pathname + urlObj.search, + method: 'GET', + headers: { + 'User-Agent': 'ZY-Protocol-Mirror/1.0', + ...headers + }, + timeout: 15000 + }; + + const req = https.request(options, (res) => { + // 处理重定向 + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + httpsGet(res.headers.location, headers).then(resolve).catch(reject); + return; + } + + let data = ''; + res.on('data', (d) => { data += d; }); + res.on('end', () => { + resolve({ statusCode: res.statusCode, body: data, headers: res.headers }); + }); + }); + + req.on('error', (err) => { + reject(new Error(`HTTPS请求失败: ${err.message}`)); + }); + req.on('timeout', () => { + req.destroy(); + reject(new Error('HTTPS请求超时')); + }); + req.end(); + }); +} + +// ── 获取已安装的Xray版本 ──────────────────── +function getInstalledVersion() { + try { + const output = execFileSync(XRAY_BIN, ['version'], { + encoding: 'utf8', + timeout: 5000 + }).trim(); + + // Xray version output: "Xray 1.8.x (Xray, Penetrates Everything.) ..." + const match = output.match(/Xray\s+(\d+\.\d+\.\d+)/i); + if (match) { + return match[1]; + } + console.error('[协议镜像] 无法解析Xray版本号:', output.slice(0, 100)); + return null; + } catch (err) { + console.error('[协议镜像] 获取Xray版本失败:', err.message); + return null; + } +} + +// ── 查询GitHub最新版本 ────────────────────── +async function fetchLatestRelease() { + try { + const resp = await httpsGet('https://api.github.com/repos/XTLS/Xray-core/releases/latest', { + 'Accept': 'application/vnd.github.v3+json' + }); + + if (resp.statusCode !== 200) { + console.error(`[协议镜像] GitHub API返回 ${resp.statusCode}`); + return null; + } + + const release = JSON.parse(resp.body); + const tagName = release.tag_name || ''; + // tag格式: "v1.8.x" + const version = tagName.replace(/^v/, ''); + const body = release.body || ''; + const publishedAt = release.published_at || null; + + // 查找Linux AMD64二进制下载链接 + let downloadUrl = null; + if (release.assets && Array.isArray(release.assets)) { + const linuxAsset = release.assets.find(a => + a.name && a.name.includes('linux') && a.name.includes('64') && a.name.endsWith('.zip') + ); + if (linuxAsset) { + downloadUrl = linuxAsset.browser_download_url; + } + } + + return { version, body, publishedAt, downloadUrl, tagName }; + } catch (err) { + console.error('[协议镜像] 获取GitHub release失败:', err.message); + return null; + } +} + +// ── 版本比较 ───────────────────────────────── +function isNewer(latest, installed) { + if (!latest || !installed) return false; + const latestParts = latest.split('.').map(Number); + const installedParts = installed.split('.').map(Number); + + for (let i = 0; i < 3; i++) { + const l = latestParts[i] || 0; + const c = installedParts[i] || 0; + if (l > c) return true; + if (l < c) return false; + } + return false; +} + +// ── 协议指纹检查 ───────────────────────────── +function checkFingerprint() { + console.log('[协议镜像] 检查协议指纹一致性...'); + const fingerprint = { + tls_match: true, + reality_match: true, + protocol_version: 'unknown' + }; + + // 检查TLS配置是否符合标准Xray行为 + try { + const configPath = process.env.ZY_XRAY_CONFIG || '/usr/local/etc/xray/config.json'; + if (fs.existsSync(configPath)) { + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + + // 验证Reality配置存在且结构完整 + const inbounds = config.inbounds || []; + const realityInbound = inbounds.find(ib => + ib.streamSettings && + ib.streamSettings.realitySettings + ); + + if (realityInbound) { + const reality = realityInbound.streamSettings.realitySettings; + fingerprint.reality_match = !!(reality.privateKey && reality.shortIds); + if (!fingerprint.reality_match) { + console.log('[协议镜像] ⚠️ Reality配置不完整'); + } + } + + // 验证TLS相关配置 + const tlsInbound = inbounds.find(ib => + ib.streamSettings && + ib.streamSettings.security === 'tls' + ); + + if (tlsInbound) { + const tls = tlsInbound.streamSettings.tlsSettings || {}; + fingerprint.tls_match = !!(tls.certificates && tls.certificates.length > 0); + } + } + } catch (err) { + console.error('[协议镜像] 指纹检查异常:', err.message); + fingerprint.tls_match = false; + } + + // 确认Xray进程运行中的协议版本 + const installed = getInstalledVersion(); + if (installed) { + fingerprint.protocol_version = 'matching'; + } else { + fingerprint.protocol_version = 'unavailable'; + } + + return fingerprint; +} + +// ── LLM更新风险分析 ───────────────────────── +async function consultLLMForUpdate(releaseInfo, installedVersion) { + let llmRouter; + try { + llmRouter = require('./llm-router'); + } catch { + console.log('[协议镜像] LLM路由器未加载,跳过风险分析'); + return null; + } + + const prompt = `你是光湖语言世界VPN系统的协议镜像引擎。Xray-core 有新版本发布,请分析更新风险。 + +当前版本: v${installedVersion} +最新版本: v${releaseInfo.version} +发布时间: ${releaseInfo.publishedAt || '未知'} + +更新日志 (前1500字): +${(releaseInfo.body || '无更新日志').slice(0, 1500)} + +请回答以下问题 (JSON格式): +1. is_critical: 是否是安全修复 (true/false) +2. changes_summary: 主要变更摘要 (一句话) +3. risk_level: 更新风险等级 (low/medium/high) +4. recommend_auto_update: 是否推荐自动更新 (true/false) +5. reason: 推荐理由 (一句话)`; + + let result; + try { + result = await llmRouter.callLLM(prompt, { + systemPrompt: '你是光湖语言世界VPN系统的协议分析AI。负责评估Xray-core更新的风险和必要性。请用JSON格式回答。', + maxTokens: 500, + timeout: 30000 + }); + } catch (err) { + console.error('[协议镜像] LLM调用异常:', err.message); + return null; + } + + if (!result || !result.content) return null; + + // 尝试解析LLM返回的JSON + try { + const jsonMatch = result.content.match(/\{[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]); + } + } catch { + console.log('[协议镜像] LLM返回内容无法解析为JSON'); + } + + return { raw_analysis: result.content, model: result.model }; +} + +// ── 下载文件 ───────────────────────────────── +function downloadFile(url, destPath) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(destPath); + + const cleanup = () => { + file.close(); + if (fs.existsSync(destPath)) fs.unlinkSync(destPath); + }; + + const doRequest = (reqUrl) => { + const urlObj = new URL(reqUrl); + const options = { + hostname: urlObj.hostname, + path: urlObj.pathname + urlObj.search, + method: 'GET', + headers: { 'User-Agent': 'ZY-Protocol-Mirror/1.0' }, + timeout: 120000 + }; + + const req = https.request(options, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + doRequest(res.headers.location); + return; + } + if (res.statusCode !== 200) { + cleanup(); + reject(new Error(`下载失败: HTTP ${res.statusCode}`)); + return; + } + res.pipe(file); + file.on('finish', () => { + file.close(); + resolve(destPath); + }); + }); + + req.on('error', (err) => { + cleanup(); + reject(new Error(`下载错误: ${err.message}`)); + }); + req.on('timeout', () => { + req.destroy(); + cleanup(); + reject(new Error('下载超时')); + }); + req.end(); + }; + + doRequest(url); + }); +} + +// ── 执行更新 ───────────────────────────────── +async function performUpdate() { + const status = readMirrorStatus(); + + if (!status.update_available || !status.latest_version) { + console.log('[协议镜像] 无可用更新'); + return { ok: false, detail: '无可用更新' }; + } + + if (status.failed_updates >= MAX_FAILED_UPDATES) { + console.error('[协议镜像] 已连续失败3次,需人工介入'); + sendAlertEmail( + '🪞 协议镜像更新失败 - 需人工介入', + `Xray-core 更新已连续失败 ${status.failed_updates} 次。\n` + + `目标版本: v${status.latest_version}\n` + + `当前版本: v${status.installed_version}\n` + + `请手动检查并更新。` + ); + return { ok: false, detail: `连续失败${status.failed_updates}次,已通知管理员` }; + } + + console.log(`[协议镜像] 开始更新: v${status.installed_version} → v${status.latest_version}`); + status.mirror_status = 'updating'; + saveMirrorStatus(status); + + const updateRecord = { + from_version: status.installed_version, + to_version: status.latest_version, + started_at: new Date().toISOString(), + completed_at: null, + success: false, + detail: '' + }; + + try { + // 1. 获取下载链接 + const release = await fetchLatestRelease(); + if (!release || !release.downloadUrl) { + throw new Error('无法获取下载链接'); + } + + // 2. 下载新版本 + console.log('[协议镜像] 下载新版本...'); + const downloadPath = path.join(DATA_DIR, `xray-${release.version}.zip`); + await downloadFile(release.downloadUrl, downloadPath); + + // 3. 备份当前版本 + console.log('[协议镜像] 备份当前版本...'); + if (!fs.existsSync(XRAY_BACKUP_DIR)) { + fs.mkdirSync(XRAY_BACKUP_DIR, { recursive: true }); + } + const backupPath = path.join(XRAY_BACKUP_DIR, `xray-${status.installed_version}-${Date.now()}`); + if (fs.existsSync(XRAY_BIN)) { + fs.copyFileSync(XRAY_BIN, backupPath); + console.log(`[协议镜像] 备份完成: ${backupPath}`); + } + + // 4. 解压并安装 + console.log('[协议镜像] 解压并安装...'); + const extractDir = path.join(DATA_DIR, 'xray-extract'); + if (fs.existsSync(extractDir)) { + execFileSync('rm', ['-rf', extractDir], { timeout: 10000 }); + } + fs.mkdirSync(extractDir, { recursive: true }); + execFileSync('unzip', ['-o', downloadPath, '-d', extractDir], { + encoding: 'utf8', + timeout: 30000 + }); + + const newBin = path.join(extractDir, 'xray'); + if (!fs.existsSync(newBin)) { + throw new Error('解压后未找到xray二进制文件'); + } + + // 设置执行权限并安装 + fs.chmodSync(newBin, 0o755); + execFileSync('cp', [newBin, XRAY_BIN], { timeout: 10000 }); + + // 5. 验证安装 + console.log('[协议镜像] 验证安装...'); + const newVersion = getInstalledVersion(); + if (!newVersion) { + throw new Error('安装后无法获取版本号'); + } + console.log(`[协议镜像] 新版本已安装: v${newVersion}`); + + // 6. 重启Xray服务 + console.log('[协议镜像] 重启Xray服务...'); + try { + execFileSync('systemctl', ['restart', 'xray'], { encoding: 'utf8', timeout: 30000 }); + } catch (err) { + throw new Error(`重启失败: ${err.message}`); + } + + // 等待2秒后验证进程 + await new Promise(r => setTimeout(r, 2000)); + try { + execFileSync('pgrep', ['-x', 'xray'], { encoding: 'utf8', timeout: 5000 }); + } catch { + throw new Error('Xray重启后进程未运行'); + } + + // 7. 清理下载文件 + if (fs.existsSync(downloadPath)) fs.unlinkSync(downloadPath); + if (fs.existsSync(extractDir)) { + execFileSync('rm', ['-rf', extractDir], { timeout: 10000 }); + } + + // 更新成功 + updateRecord.success = true; + updateRecord.completed_at = new Date().toISOString(); + updateRecord.detail = `成功从 v${status.installed_version} 更新到 v${newVersion}`; + + status.installed_version = newVersion; + status.update_available = false; + status.failed_updates = 0; + status.mirror_status = 'synced'; + status.update_history.push(updateRecord); + + if (status.update_history.length > MAX_UPDATE_HISTORY) { + status.update_history = status.update_history.slice(-MAX_UPDATE_HISTORY); + } + + saveMirrorStatus(status); + console.log(`[协议镜像] ✅ 更新完成: v${newVersion}`); + + // 发送更新成功通知 + sendAlertEmail( + `🪞 Xray-core 已更新至 v${newVersion}`, + getUpdateSummary() + ); + + return { ok: true, detail: updateRecord.detail }; + } catch (err) { + console.error('[协议镜像] ❌ 更新失败:', err.message); + + updateRecord.completed_at = new Date().toISOString(); + updateRecord.detail = `更新失败: ${err.message}`; + + status.failed_updates++; + status.mirror_status = 'error'; + status.update_history.push(updateRecord); + + if (status.update_history.length > MAX_UPDATE_HISTORY) { + status.update_history = status.update_history.slice(-MAX_UPDATE_HISTORY); + } + + saveMirrorStatus(status); + + // 连续失败达到上限时告警 + if (status.failed_updates >= MAX_FAILED_UPDATES) { + sendAlertEmail( + '🪞 协议镜像更新失败 - 需人工介入', + `Xray-core 更新已连续失败 ${status.failed_updates} 次。\n` + + `目标版本: v${status.latest_version}\n` + + `当前版本: v${status.installed_version}\n` + + `最后错误: ${err.message}\n` + + `请手动检查并更新。` + ); + } + + return { ok: false, detail: `更新失败: ${err.message}` }; + } +} + +// ── 发送告警邮件 ───────────────────────────── +function sendAlertEmail(subject, body) { + try { + const sendScript = path.join(__dirname, 'send-subscription.js'); + execFileSync('node', [sendScript, 'alert', `${subject}\n\n${body}`], { + encoding: 'utf8', + timeout: 30000 + }); + console.log('[协议镜像] 通知邮件已发送'); + } catch (err) { + console.error('[协议镜像] 邮件发送失败:', err.message); + } +} + +// ── 获取更新摘要 ───────────────────────────── +function getUpdateSummary() { + const status = readMirrorStatus(); + const lastUpdate = status.update_history.length > 0 + ? status.update_history[status.update_history.length - 1] + : null; + + const lines = [ + '🪞 协议镜像引擎 · 状态报告', + '─'.repeat(30), + `当前版本: v${status.installed_version || '未知'}`, + `最新版本: v${status.latest_version || '未知'}`, + `镜像状态: ${status.mirror_status}`, + `更新可用: ${status.update_available ? '是' : '否'}`, + `失败次数: ${status.failed_updates}`, + '' + ]; + + if (lastUpdate) { + lines.push('最近更新:'); + lines.push(` ${lastUpdate.from_version} → ${lastUpdate.to_version}`); + lines.push(` 时间: ${lastUpdate.completed_at}`); + lines.push(` 结果: ${lastUpdate.success ? '成功' : '失败'}`); + lines.push(` 详情: ${lastUpdate.detail}`); + } + + lines.push(''); + lines.push('指纹检查:'); + lines.push(` TLS匹配: ${status.fingerprint.tls_match ? '✅' : '❌'}`); + lines.push(` Reality匹配: ${status.fingerprint.reality_match ? '✅' : '❌'}`); + lines.push(` 协议版本: ${status.fingerprint.protocol_version}`); + + return lines.join('\n'); +} + +// ── 获取镜像状态 ───────────────────────────── +function getMirrorStatus() { + return readMirrorStatus(); +} + +// ── 主检查流程 ─────────────────────────────── +async function checkForUpdates() { + console.log('[协议镜像] 开始检查...'); + const status = readMirrorStatus(); + + // 1. 获取已安装版本 + const installedVersion = getInstalledVersion(); + if (installedVersion) { + status.installed_version = installedVersion; + } + + // 2. 指纹检查 (每次都执行) + status.fingerprint = checkFingerprint(); + status.last_fingerprint_check = new Date().toISOString(); + + if (!status.fingerprint.tls_match || !status.fingerprint.reality_match) { + console.log('[协议镜像] ⚠️ 指纹不一致,可能被探测'); + } + + // 3. 检查GitHub最新版本 (受频率限制) + const now = Date.now(); + const lastCheck = status.last_github_check ? new Date(status.last_github_check).getTime() : 0; + const shouldCheckGitHub = (now - lastCheck) >= GITHUB_CHECK_INTERVAL; + + if (shouldCheckGitHub) { + console.log('[协议镜像] 查询GitHub最新版本...'); + const release = await fetchLatestRelease(); + + if (release && release.version) { + status.latest_version = release.version; + status.last_github_check = new Date().toISOString(); + + if (installedVersion && isNewer(release.version, installedVersion)) { + console.log(`[协议镜像] 🆕 发现新版本: v${release.version} (当前: v${installedVersion})`); + status.update_available = true; + status.mirror_status = 'outdated'; + + // LLM风险分析 + const analysis = await consultLLMForUpdate(release, installedVersion); + if (analysis) { + console.log('[协议镜像] LLM分析结果:', JSON.stringify(analysis).slice(0, 200)); + } + } else { + status.update_available = false; + if (status.mirror_status !== 'error') { + status.mirror_status = 'synced'; + } + console.log(`[协议镜像] ✅ 版本已同步: v${installedVersion || '未知'}`); + } + } else { + console.log('[协议镜像] GitHub查询跳过 (API不可达或已限流)'); + } + } else { + const nextCheckMin = Math.round((GITHUB_CHECK_INTERVAL - (now - lastCheck)) / 60000); + console.log(`[协议镜像] 使用缓存的GitHub结果 (下次查询: ${nextCheckMin}分钟后)`); + } + + saveMirrorStatus(status); + + // 打印摘要 + console.log(`[协议镜像] 检查完成 | 版本: v${status.installed_version || '?'} | 最新: v${status.latest_version || '?'} | 状态: ${status.mirror_status}`); + + return status; +} + +// ── 模块导出 ───────────────────────────────── +module.exports = { checkForUpdates, performUpdate, getMirrorStatus, getUpdateSummary }; + +// ── 启动协议镜像引擎 ──────────────────────── +console.log('🪞 光湖语言世界 · 协议镜像引擎启动'); +console.log(` 检查间隔: ${CHECK_INTERVAL / 1000}秒`); +console.log(` GitHub API间隔: ${GITHUB_CHECK_INTERVAL / 3600000}小时`); +console.log(` LLM路由器: 动态多模型 (通过llm-router.js)`); + +// 立即执行一次 +checkForUpdates().catch(console.error); + +// 定期执行 +setInterval(() => { + checkForUpdates().catch(console.error); +}, CHECK_INTERVAL); From c10556b6d3477eb8e1cac75a65320e9fa418496c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 04:26:30 +0000 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E2=88=9E=E7=89=88=E6=9C=AC=20Phase?= =?UTF-8?q?=201=20=E2=80=94=20=E9=82=AE=E4=BB=B6=E4=B8=AD=E6=9E=A2=20+=20?= =?UTF-8?q?=E8=87=AA=E4=B8=BB=E8=BF=9B=E5=8C=96=E5=BC=95=E6=93=8E=20+=20?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=88=86=E6=B5=81=E5=A2=9E=E5=BC=BA=20+=20?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E9=85=8D=E7=BD=AE=20+=20=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/c5b8b0a2-c3fb-437f-bd6f-07549f2c5709 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .github/workflows/deploy-brain-proxy.yml | 4 +- .github/workflows/infinity-evolution.yml | 166 +++++ server/proxy/deploy-brain-proxy.sh | 76 ++- .../proxy/ecosystem.brain-proxy-v3.config.js | 51 +- server/proxy/service/auto-evolution.js | 510 +++++++++++++++ server/proxy/service/email-hub.js | 590 ++++++++++++++++++ .../proxy/service/subscription-server-v3.js | 26 +- 7 files changed, 1411 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/infinity-evolution.yml create mode 100644 server/proxy/service/auto-evolution.js create mode 100644 server/proxy/service/email-hub.js diff --git a/.github/workflows/deploy-brain-proxy.yml b/.github/workflows/deploy-brain-proxy.yml index 56ad4ae7..c67d3533 100644 --- a/.github/workflows/deploy-brain-proxy.yml +++ b/.github/workflows/deploy-brain-proxy.yml @@ -31,6 +31,7 @@ on: - send-subscription-v3 - deploy-v3 - switch-v3 + - deploy-infinity default: 'status' email: description: '用户邮箱 (add-user/remove-user/send-subscription/send-subscription-v3时需要)' @@ -60,7 +61,8 @@ jobs: github.event.inputs.action == 'status' || github.event.inputs.action == 'restart' || github.event.inputs.action == 'deploy-v3' || - github.event.inputs.action == 'switch-v3' + github.event.inputs.action == 'switch-v3' || + github.event.inputs.action == 'deploy-infinity' steps: - name: '📥 检出代码' diff --git a/.github/workflows/infinity-evolution.yml b/.github/workflows/infinity-evolution.yml new file mode 100644 index 00000000..db2fed5b --- /dev/null +++ b/.github/workflows/infinity-evolution.yml @@ -0,0 +1,166 @@ +# ═══════════════════════════════════════════════ +# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +# 📜 Copyright: 国作登字-2026-A-00037559 +# ═══════════════════════════════════════════════ +# ♾️ 光湖语言世界 · 自主进化定时工作流 +# +# 定时触发∞版本的关键任务: +# - 每月1号: 月度进化 (LLM分析 + 流量重置邮件) +# - 每周五: 用户反馈处理 (Phase 2) +# - 手动触发: 任意∞版本操作 +# +# 注: 大部分定时任务由服务器上的PM2进程 (auto-evolution.js) +# 自动执行。此工作流作为补充/备份触发机制。 +# ═══════════════════════════════════════════════ + +name: '♾️ 光湖∞ · 自主进化' + +on: + schedule: + # 每月1号 UTC 16:00 = CST 00:00 (月度进化) + - cron: '0 16 1 * *' + workflow_dispatch: + inputs: + action: + description: '操作类型' + required: true + type: choice + options: + - monthly-evolution + - deploy-infinity + - status + - send-monthly-reset + - send-update-notify + - send-traffic-warn + default: 'status' + message: + description: '附加消息 (update-notify描述 / traffic-warn百分比)' + required: false + type: string + +permissions: + contents: read + +concurrency: + group: infinity-evolution + cancel-in-progress: false + +jobs: + # ═══ §1 ∞版本部署/管理 ═══ + infinity-action: + name: '♾️ ${{ github.event.inputs.action || ''monthly-evolution'' }}' + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: '📥 检出代码' + uses: actions/checkout@v4 + + - name: '🔑 配置SSH' + run: | + mkdir -p ~/.ssh + echo "${{ secrets.ZY_BRAIN_KEY }}" > ~/.ssh/brain_key + chmod 600 ~/.ssh/brain_key + ssh-keyscan -H ${{ secrets.ZY_BRAIN_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true + + - name: '♾️ 执行∞操作' + env: + ACTION: ${{ github.event.inputs.action || 'monthly-evolution' }} + MESSAGE: ${{ github.event.inputs.message || '' }} + ZY_SMTP_USER: ${{ secrets.ZY_SMTP_USER }} + ZY_SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }} + run: | + SSH_CMD="ssh -i ~/.ssh/brain_key -o StrictHostKeyChecking=no ${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }}" + PROXY_DIR="/opt/zhuyuan-brain/proxy" + + echo "════════════════════════════════════════" + echo "♾️ 光湖∞ · 自主进化 · action=$ACTION" + echo "════════════════════════════════════════" + + case "$ACTION" in + deploy-infinity) + echo "部署∞版本..." + # 先同步最新代码到服务器 + scp -i ~/.ssh/brain_key -o StrictHostKeyChecking=no \ + server/proxy/service/auto-evolution.js \ + server/proxy/service/email-hub.js \ + server/proxy/service/protocol-mirror.js \ + server/proxy/ecosystem.brain-proxy-v3.config.js \ + ${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }}:/tmp/ + + $SSH_CMD " + cp /tmp/auto-evolution.js $PROXY_DIR/service/ + cp /tmp/email-hub.js $PROXY_DIR/service/ + cp /tmp/protocol-mirror.js $PROXY_DIR/service/ + cp /tmp/ecosystem.brain-proxy-v3.config.js $PROXY_DIR/ + mkdir -p $PROXY_DIR/data/evolution-reports + cd $PROXY_DIR && pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env && pm2 save + echo '✅ ∞版本已部署' + pm2 list + " + ;; + + monthly-evolution) + echo "触发月度进化..." + $SSH_CMD " + cd $PROXY_DIR + export ZY_SMTP_USER='$ZY_SMTP_USER' + export ZY_SMTP_PASS='$ZY_SMTP_PASS' + node service/email-hub.js monthly-reset 2>&1 || echo '⚠️ 月度邮件发送遇到问题' + echo '✅ 月度进化已触发' + " + ;; + + status) + echo "查询∞版本状态..." + $SSH_CMD " + cd $PROXY_DIR + echo '── PM2进程 ──' + pm2 list 2>/dev/null || echo '⚠️ PM2不可用' + echo '' + echo '── 进化状态 ──' + cat data/auto-evolution-status.json 2>/dev/null || echo '(未初始化)' + echo '' + echo '── 镜像状态 ──' + cat data/protocol-mirror-status.json 2>/dev/null || echo '(未初始化)' + echo '' + echo '── 流量池 ──' + cat data/pool-quota-status.json 2>/dev/null || echo '(未初始化)' + " + ;; + + send-monthly-reset) + echo "发送月初重置邮件..." + $SSH_CMD " + cd $PROXY_DIR + export ZY_SMTP_USER='$ZY_SMTP_USER' + export ZY_SMTP_PASS='$ZY_SMTP_PASS' + node service/email-hub.js monthly-reset + " + ;; + + send-update-notify) + echo "发送更新通知..." + $SSH_CMD " + cd $PROXY_DIR + export ZY_SMTP_USER='$ZY_SMTP_USER' + export ZY_SMTP_PASS='$ZY_SMTP_PASS' + node service/email-hub.js update-notify '${MESSAGE:-系统已更新}' + " + ;; + + send-traffic-warn) + echo "发送流量预警..." + $SSH_CMD " + cd $PROXY_DIR + export ZY_SMTP_USER='$ZY_SMTP_USER' + export ZY_SMTP_PASS='$ZY_SMTP_PASS' + node service/email-hub.js traffic-warn '${MESSAGE:-70}' + " + ;; + + *) + echo "❌ 未知操作: $ACTION" + exit 1 + ;; + esac diff --git a/server/proxy/deploy-brain-proxy.sh b/server/proxy/deploy-brain-proxy.sh index 25458195..fabd1b52 100644 --- a/server/proxy/deploy-brain-proxy.sh +++ b/server/proxy/deploy-brain-proxy.sh @@ -645,6 +645,7 @@ deploy_v3() { echo "V3运行在 /api/proxy-v3/ (端口3805)" echo "" echo "切换V2用户到V3: bash deploy-brain-proxy.sh switch-v3" + echo "升级到∞版本: bash deploy-brain-proxy.sh deploy-infinity" echo "════════════════════════════════════════" # V3健康检查 @@ -753,6 +754,78 @@ switch_v3() { echo "════════════════════════════════════════" } +# ── deploy-infinity: 部署∞版本 ───────────────── +# 在V3基础上启动自主进化引擎和协议镜像引擎 +deploy_infinity() { + echo "部署 ∞ 版本 (在V3基础上添加自主进化能力)..." + deploy_services + + # 确保V3进程已在运行 + if ! pm2 list 2>/dev/null | grep -q "zy-proxy-v3-sub"; then + echo " ⚠️ V3进程未运行,先部署V3..." + deploy_v3 + fi + + # 加载密钥作为环境变量 + if [ -f "$PROXY_DIR/.env.keys" ]; then + set -a + # shellcheck source=/dev/null + source "$PROXY_DIR/.env.keys" + set +a + fi + + # 复制 ∞ 版本新增文件 + echo " 复制∞版本模块..." + for f in auto-evolution.js email-hub.js protocol-mirror.js; do + if [ -f "$REPO_PROXY_DIR/service/$f" ]; then + cp "$REPO_PROXY_DIR/service/$f" "$PROXY_DIR/service/$f" + echo " ✅ $f" + else + echo " ⚠️ $f 不存在于仓库" + fi + done + + # 复制更新后的PM2配置 + cp "$REPO_PROXY_DIR/ecosystem.brain-proxy-v3.config.js" "$PROXY_DIR/ecosystem.brain-proxy-v3.config.js" + + # 创建进化报告目录 + mkdir -p "$PROXY_DIR/data/evolution-reports" + + # 重启所有V3+∞进程 (PM2会根据新配置启动新增进程) + cd "$PROXY_DIR" || { echo "❌ 无法进入 $PROXY_DIR"; return 1; } + pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env + pm2 save + + echo "" + echo "════════════════════════════════════════" + echo "♾️ ∞ 版本部署完成!" + echo "" + echo "新增进程:" + echo " zy-auto-evolution — 自主进化引擎 (调度中枢)" + echo " zy-protocol-mirror — 协议镜像引擎 (Xray自动更新)" + echo "" + echo "定时任务:" + echo " 每30分钟 — 协议版本检查" + echo " 每月1号 — 月度进化 + 流量重置邮件" + echo " 流量70% — 全用户预警邮件" + echo "" + echo "PM2进程列表:" + pm2 list 2>/dev/null || true + echo "════════════════════════════════════════" + + # 健康检查 + sleep 2 + echo "" + echo "∞ 版本健康检查:" + for proc in zy-proxy-v3-sub zy-proxy-guardian zy-reverse-boost zy-auto-evolution zy-protocol-mirror; do + if pm2 show "$proc" 2>/dev/null | grep -q "online"; then + echo " ✅ $proc: 在线" + else + echo " ⚠️ $proc: 未就绪 (等待启动)" + fi + done +} + # ── 执行 ────────────────────────────────────── case "$ACTION" in install) install ;; @@ -764,8 +837,9 @@ case "$ACTION" in list-users) list_users ;; deploy-v3) deploy_v3 ;; switch-v3) switch_v3 ;; + deploy-infinity) deploy_infinity ;; *) - echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users|deploy-v3|switch-v3}" + echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users|deploy-v3|switch-v3|deploy-infinity}" exit 1 ;; esac diff --git a/server/proxy/ecosystem.brain-proxy-v3.config.js b/server/proxy/ecosystem.brain-proxy-v3.config.js index 960becac..0524058d 100644 --- a/server/proxy/ecosystem.brain-proxy-v3.config.js +++ b/server/proxy/ecosystem.brain-proxy-v3.config.js @@ -1,20 +1,19 @@ // ═══════════════════════════════════════════════ -// 光湖语言世界 V3 · PM2 大脑服务器代理配置 +// 光湖语言世界 ∞ · PM2 大脑服务器代理配置 // 部署在 ZY-SVR-005 (43.156.237.110) · 大脑服务器 // -// V3独立于V2运行,测试通过后切换Nginx即可 -// V2进程 (ecosystem.brain-proxy.config.js) 继续运行 +// ∞ 版本在V3基础上新增: +// - zy-auto-evolution: 自主进化引擎 (调度所有定时任务) +// - zy-protocol-mirror: 协议镜像引擎 (Xray自动更新) // -// 切换方式: -// 测试中: /api/proxy-v3/ → 3805 -// 切换后: /api/proxy-v2/ → 3805 (Nginx改一行) +// V2进程 (ecosystem.brain-proxy.config.js) 继续运行 // ═══════════════════════════════════════════════ module.exports = { apps: [ { name: 'zy-proxy-v3-sub', - version: '3.0.0', + version: '∞', script: '/opt/zhuyuan-brain/proxy/service/subscription-server-v3.js', instances: 1, exec_mode: 'fork', @@ -30,7 +29,7 @@ module.exports = { }, { name: 'zy-proxy-guardian', - version: '3.0.0', + version: '∞', script: '/opt/zhuyuan-brain/proxy/service/proxy-guardian.js', instances: 1, exec_mode: 'fork', @@ -47,7 +46,7 @@ module.exports = { }, { name: 'zy-reverse-boost', - version: '3.0.0', + version: '∞', script: '/opt/zhuyuan-brain/proxy/service/reverse-boost-agent.js', instances: 1, exec_mode: 'fork', @@ -59,6 +58,40 @@ module.exports = { log_file: '/opt/zhuyuan-brain/proxy/logs/reverse-boost.log', error_file: '/opt/zhuyuan-brain/proxy/logs/reverse-boost-error.log', time: true + }, + { + name: 'zy-auto-evolution', + version: '∞', + script: '/opt/zhuyuan-brain/proxy/service/auto-evolution.js', + instances: 1, + exec_mode: 'fork', + env: { + NODE_ENV: 'production', + ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy', + ZY_PROXY_DATA_DIR: '/opt/zhuyuan-brain/proxy/data', + ZY_PROXY_LOG_DIR: '/opt/zhuyuan-brain/proxy/logs' + }, + max_memory_restart: '64M', + log_file: '/opt/zhuyuan-brain/proxy/logs/auto-evolution.log', + error_file: '/opt/zhuyuan-brain/proxy/logs/auto-evolution-error.log', + time: true + }, + { + name: 'zy-protocol-mirror', + version: '∞', + script: '/opt/zhuyuan-brain/proxy/service/protocol-mirror.js', + instances: 1, + exec_mode: 'fork', + env: { + NODE_ENV: 'production', + ZY_BRAIN_PROXY_DIR: '/opt/zhuyuan-brain/proxy', + ZY_PROXY_DATA_DIR: '/opt/zhuyuan-brain/proxy/data', + ZY_PROXY_LOG_DIR: '/opt/zhuyuan-brain/proxy/logs' + }, + max_memory_restart: '64M', + log_file: '/opt/zhuyuan-brain/proxy/logs/protocol-mirror.log', + error_file: '/opt/zhuyuan-brain/proxy/logs/protocol-mirror-error.log', + time: true } ] }; diff --git a/server/proxy/service/auto-evolution.js b/server/proxy/service/auto-evolution.js new file mode 100644 index 00000000..4c459ab6 --- /dev/null +++ b/server/proxy/service/auto-evolution.js @@ -0,0 +1,510 @@ +#!/usr/bin/env node +// ═══════════════════════════════════════════════ +// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +// 📜 Copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════════════ +// server/proxy/service/auto-evolution.js +// ♾️ 光湖语言世界 · 自主进化引擎 +// +// ∞版本核心调度中枢 — 管理所有定时任务和自我进化生命周期 +// +// 调度表 (Asia/Shanghai时区): +// - 每30分钟: 协议版本检查 (protocol-mirror.js) +// - 每月1号 00:00: 月度进化周期 (LLM分析 + 流量重置邮件) +// - 每周五 20:00: 处理用户反馈 (Phase 2) +// - 每周一 09:00: 推送反馈结果 (Phase 2) +// - 流量池70%: 触发全用户预警邮件 +// +// 更新编排流水线: +// 备份 → 应用 → 验证 → 通知用户 → 失败3次升级告警 +// +// 运行方式: PM2 managed (zy-auto-evolution) +// ═══════════════════════════════════════════════ + +'use strict'; + +const { execFileSync } = 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 EVOLUTION_FILE = path.join(DATA_DIR, 'auto-evolution-status.json'); +const REPORTS_DIR = path.join(DATA_DIR, 'evolution-reports'); +const POOL_STATUS_FILE = path.join(DATA_DIR, 'pool-quota-status.json'); +const GUARDIAN_FILE = path.join(DATA_DIR, 'guardian-status.json'); +const MIRROR_FILE = path.join(DATA_DIR, 'protocol-mirror-status.json'); +const USERS_FILE = path.join(DATA_DIR, 'users.json'); + +const SCHEDULE_CHECK_INTERVAL = 5 * 60 * 1000; // 每5分钟检查调度 +const PROTOCOL_CHECK_INTERVAL = 30 * 60 * 1000; // 每30分钟协议检查 +const MAX_UPDATE_RETRIES = 3; + +// ── 获取中国时间 ───────────────────────────── +function getChinaTime() { + // 返回中国标准时间 + const now = new Date(); + const utc = now.getTime() + now.getTimezoneOffset() * 60000; + return new Date(utc + 8 * 3600000); // UTC+8 +} + +function getChinaTimeStr() { + return getChinaTime().toISOString().replace('T', ' ').slice(0, 19) + ' CST'; +} + +// ── 读取进化状态 ───────────────────────────── +function readEvolutionStatus() { + try { + return JSON.parse(fs.readFileSync(EVOLUTION_FILE, 'utf8')); + } catch { + return { + version: '∞', + started_at: new Date().toISOString(), + last_schedule_check: null, + schedules: { + protocol_check: { interval_min: 30, last_run: null }, + monthly_evolution: { schedule: '每月1号 00:00', last_run: null }, + weekly_feedback: { schedule: '每周五 20:00', last_run: null }, + weekly_response: { schedule: '每周一 09:00', last_run: null }, + traffic_alert_70: { last_run: null } + }, + evolution_count: 0, + updates_applied: 0, + updates_failed: 0, + last_monthly_report: null, + status: 'running' + }; + } +} + +// ── 保存进化状态 ───────────────────────────── +function saveEvolutionStatus(status) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + fs.writeFileSync(EVOLUTION_FILE, JSON.stringify(status, null, 2)); +} + +// ── 安全读取JSON ───────────────────────────── +function safeReadJSON(filePath) { + try { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch { + return null; + } +} + +// ── 安全加载模块 ───────────────────────────── +function safeRequire(modulePath) { + try { + return require(modulePath); + } catch (err) { + console.error(`[自主进化] 模块加载失败: ${modulePath} — ${err.message}`); + return null; + } +} + +// ═══════════════════════════════════════════════ +// §1 协议版本检查 (每30分钟) +// ═══════════════════════════════════════════════ +async function runProtocolCheck() { + console.log('[自主进化] ── 协议版本检查 ──'); + + const protocolMirror = safeRequire('./protocol-mirror'); + if (!protocolMirror) { + console.log('[自主进化] 协议镜像模块不可用,跳过'); + return; + } + + try { + const result = await protocolMirror.checkForUpdates(); + if (result && result.update_available) { + console.log(`[自主进化] 🆕 检测到Xray更新: ${result.installed_version} → ${result.latest_version}`); + await orchestrateUpdate({ + type: 'xray-core', + from_version: result.installed_version, + to_version: result.latest_version, + source: 'protocol-mirror' + }); + } else { + console.log('[自主进化] ✅ 协议版本已是最新'); + } + } catch (err) { + console.error('[自主进化] 协议检查异常:', err.message); + } +} + +// ═══════════════════════════════════════════════ +// §2 月度进化周期 (每月1号 00:00) +// ═══════════════════════════════════════════════ +async function monthlyEvolution() { + console.log('[自主进化] ════ 月度进化周期开始 ════'); + const status = readEvolutionStatus(); + + // 1. 收集系统状态 + const guardian = safeReadJSON(GUARDIAN_FILE) || {}; + const mirror = safeReadJSON(MIRROR_FILE) || {}; + const pool = safeReadJSON(POOL_STATUS_FILE) || {}; + const users = safeReadJSON(USERS_FILE) || { users: [] }; + const userCount = (users.users || []).filter(u => u.enabled !== false).length; + + console.log(`[自主进化] 系统状态: ${userCount}活跃用户, 守护=${guardian.status || '未知'}, 镜像=${mirror.mirror_status || '未知'}`); + + // 2. LLM 深度推理 — 系统健康分析 + const llmRouter = safeRequire('./llm-router'); + let llmAnalysis = null; + + if (llmRouter) { + try { + const prompt = `你是光湖语言世界VPN系统的月度分析引擎。现在是每月1号,请分析系统状态并给出建议。 + +当前状态: +- 活跃用户: ${userCount}人 +- 守护Agent状态: ${guardian.status || '未知'},自动修复${guardian.auto_fixes || 0}次,LLM咨询${guardian.llm_consultations || 0}次 +- 协议镜像: ${mirror.mirror_status || '未知'},已安装版本=${mirror.installed_version || '未知'},最新=${mirror.latest_version || '未知'} +- 上月流量池: 已用${pool.pool_used_gb ? pool.pool_used_gb.toFixed(1) : '?'}GB / ${pool.pool_total_gb || 2000}GB (${pool.pool_percentage || 0}%) +- 累计进化次数: ${status.evolution_count} +- 累计更新: 成功${status.updates_applied}次,失败${status.updates_failed}次 + +请回答: +1. 当前系统是否健康?(一句话) +2. 是否需要升级Xray或调整配置?(是/否+理由) +3. 流量使用是否正常?(一句话) +4. 本月优化建议 (最多3条) + +请用JSON格式回复: {"healthy":bool,"need_upgrade":bool,"analysis":"...","suggestions":["..."]}`; + + const result = await llmRouter.callLLM(prompt, { + systemPrompt: '你是光湖语言世界VPN系统的AI分析引擎。请以JSON格式简洁回复。', + maxTokens: 800, + timeout: 60000 + }); + + if (result) { + llmAnalysis = result.content; + console.log(`[自主进化] LLM分析完成 (模型: ${result.model})`); + } + } catch (err) { + console.error('[自主进化] LLM月度分析失败:', err.message); + } + } + + // 3. 发送月初重置邮件给所有用户 + console.log('[自主进化] 发送月初重置邮件...'); + try { + const emailHub = safeRequire('./email-hub'); + if (emailHub) { + const result = await emailHub.sendMonthlyResetEmail(); + console.log(`[自主进化] 月初邮件: ${result.sent}成功 / ${result.failed}失败`); + } else { + // 回退: 用send-subscription.js的alert命令 + const sendScript = path.join(__dirname, 'send-subscription.js'); + execFileSync('node', [sendScript, 'alert', '每月1号流量池已重置 · 本月可用2000GB'], { + encoding: 'utf8', timeout: 30000 + }); + console.log('[自主进化] 月初邮件: 已通过alert方式发送给管理员'); + } + } catch (err) { + console.error('[自主进化] 月初邮件发送失败:', err.message); + } + + // 4. 生成月度进化报告 + const report = { + period: getChinaTime().toISOString().slice(0, 7), + generated_at: new Date().toISOString(), + user_count: userCount, + guardian_status: guardian.status || 'unknown', + mirror_status: mirror.mirror_status || 'unknown', + pool_last_month: { + used_gb: pool.pool_used_gb || 0, + total_gb: pool.pool_total_gb || 2000, + percentage: pool.pool_percentage || 0 + }, + llm_analysis: llmAnalysis, + evolution_count: status.evolution_count, + updates: { applied: status.updates_applied, failed: status.updates_failed } + }; + + fs.mkdirSync(REPORTS_DIR, { recursive: true }); + const reportFile = path.join(REPORTS_DIR, `${report.period}.json`); + fs.writeFileSync(reportFile, JSON.stringify(report, null, 2)); + console.log(`[自主进化] 月度报告已保存: ${reportFile}`); + + // 5. 更新状态 + status.evolution_count++; + status.last_monthly_report = report.period; + status.schedules.monthly_evolution.last_run = new Date().toISOString(); + saveEvolutionStatus(status); + + console.log('[自主进化] ════ 月度进化周期完成 ════'); + return report; +} + +// ═══════════════════════════════════════════════ +// §3 更新编排 (检测到更新时触发) +// ═══════════════════════════════════════════════ +async function orchestrateUpdate(updateInfo) { + console.log(`[自主进化] ═══ 开始更新编排: ${updateInfo.type} ═══`); + const status = readEvolutionStatus(); + + let retries = 0; + let success = false; + + while (retries < MAX_UPDATE_RETRIES && !success) { + retries++; + console.log(`[自主进化] 尝试 ${retries}/${MAX_UPDATE_RETRIES}...`); + + try { + // Phase 1: 备份 + console.log('[自主进化] Phase 1: 备份当前状态...'); + // protocol-mirror.js 内部会处理备份 + + // Phase 2: 应用更新 + console.log('[自主进化] Phase 2: 应用更新...'); + const protocolMirror = safeRequire('./protocol-mirror'); + if (!protocolMirror) { + throw new Error('协议镜像模块不可用'); + } + await protocolMirror.performUpdate(); + + // Phase 3: 验证 + console.log('[自主进化] Phase 3: 验证更新...'); + const mirrorStatus = safeReadJSON(MIRROR_FILE); + if (mirrorStatus && mirrorStatus.mirror_status === 'error') { + throw new Error('更新后镜像状态异常'); + } + + success = true; + status.updates_applied++; + console.log('[自主进化] ✅ 更新成功'); + + } catch (err) { + console.error(`[自主进化] ❌ 更新失败 (尝试${retries}): ${err.message}`); + if (retries < MAX_UPDATE_RETRIES) { + console.log('[自主进化] 等待30秒后重试...'); + await new Promise(r => setTimeout(r, 30000)); + } + } + } + + if (success) { + // Phase 4: 通知用户 + console.log('[自主进化] Phase 4: 通知所有用户...'); + try { + const emailHub = safeRequire('./email-hub'); + if (emailHub) { + const description = `Xray核心已从 ${updateInfo.from_version} 升级到 ${updateInfo.to_version}。\n安全性和性能已优化,无需手动操作。\n刷新订阅即可获取最新配置。`; + await emailHub.sendUpdateNotifyEmail(description); + } + } catch (err) { + console.error('[自主进化] 更新通知邮件发送失败:', err.message); + } + } else { + // Phase 5: 3次失败 → 告警管理员 + status.updates_failed++; + console.error('[自主进化] ⚠️ 更新3次均失败,告警管理员...'); + try { + const sendScript = path.join(__dirname, 'send-subscription.js'); + const msg = `Xray更新失败(${MAX_UPDATE_RETRIES}次)\n类型: ${updateInfo.type}\n目标版本: ${updateInfo.to_version}\n请手动检查`; + execFileSync('node', [sendScript, 'alert', msg], { + encoding: 'utf8', timeout: 30000 + }); + } catch (err) { + console.error('[自主进化] 告警邮件也发送失败:', err.message); + } + } + + saveEvolutionStatus(status); + console.log(`[自主进化] ═══ 更新编排完成 (${success ? '成功' : '失败'}) ═══`); + return success; +} + +// ═══════════════════════════════════════════════ +// §4 流量预警处理 +// ═══════════════════════════════════════════════ +async function handleTrafficAlert(percentage) { + console.log(`[自主进化] 流量预警触发: ${percentage}%`); + + try { + const emailHub = safeRequire('./email-hub'); + if (emailHub) { + await emailHub.sendTrafficWarnEmail(percentage); + } else { + const sendScript = path.join(__dirname, 'send-subscription.js'); + execFileSync('node', [sendScript, 'alert', `流量池已使用${percentage}%,请注意用量`], { + encoding: 'utf8', timeout: 30000 + }); + } + } catch (err) { + console.error('[自主进化] 流量预警邮件发送失败:', err.message); + } +} + +// ═══════════════════════════════════════════════ +// §5 用户反馈处理 (Phase 2 预留) +// ═══════════════════════════════════════════════ +async function processWeeklyFeedback() { + console.log('[自主进化] 📥 用户反馈处理 (Phase 2 — 待实现)'); + // Phase 2: 从COS桶读取反馈 → LLM评估 → 生成处理结果 + const status = readEvolutionStatus(); + status.schedules.weekly_feedback.last_run = new Date().toISOString(); + saveEvolutionStatus(status); +} + +async function sendWeeklyResponse() { + console.log('[自主进化] 📤 反馈结果推送 (Phase 2 — 待实现)'); + // Phase 2: 将处理结果推送给对应用户 + const status = readEvolutionStatus(); + status.schedules.weekly_response.last_run = new Date().toISOString(); + saveEvolutionStatus(status); +} + +// ═══════════════════════════════════════════════ +// §6 流量池监测 (检查是否需要触发70%预警) +// ═══════════════════════════════════════════════ +async function checkTrafficPoolAlert() { + const pool = safeReadJSON(POOL_STATUS_FILE); + if (!pool) return; + + const status = readEvolutionStatus(); + const pct = pool.pool_percentage || 0; + + // 70%预警 (每月只触发一次) + if (pct >= 70) { + const lastRun = status.schedules.traffic_alert_70.last_run; + const currentPeriod = pool.period || new Date().toISOString().slice(0, 7); + + // 检查本月是否已发过 + if (!lastRun || !lastRun.startsWith(currentPeriod.replace('-', '').slice(0, 6))) { + await handleTrafficAlert(Math.round(pct)); + status.schedules.traffic_alert_70.last_run = new Date().toISOString(); + saveEvolutionStatus(status); + } + } +} + +// ═══════════════════════════════════════════════ +// §7 调度引擎 (每5分钟检查) +// ═══════════════════════════════════════════════ +async function checkSchedule() { + const now = getChinaTime(); + const status = readEvolutionStatus(); + status.last_schedule_check = new Date().toISOString(); + + const hour = now.getHours(); + const minute = now.getMinutes(); + const day = now.getDate(); + const dayOfWeek = now.getDay(); // 0=周日, 5=周五, 1=周一 + + // ── 协议版本检查 (每30分钟) ── + const lastProtocol = status.schedules.protocol_check.last_run; + if (!lastProtocol || (Date.now() - new Date(lastProtocol).getTime()) >= PROTOCOL_CHECK_INTERVAL) { + try { + await runProtocolCheck(); + status.schedules.protocol_check.last_run = new Date().toISOString(); + } catch (err) { + console.error('[自主进化] 协议检查调度异常:', err.message); + } + } + + // ── 月度进化 (每月1号 00:00-00:04) ── + if (day === 1 && hour === 0 && minute < 5) { + const lastMonthly = status.schedules.monthly_evolution.last_run; + const today = now.toISOString().slice(0, 10); + if (!lastMonthly || !lastMonthly.startsWith(today.slice(0, 4))) { + // 进一步检查: 确保本月还没执行过 + const lastRunDate = lastMonthly ? new Date(lastMonthly) : null; + const lastRunMonth = lastRunDate ? `${lastRunDate.getFullYear()}-${String(lastRunDate.getMonth() + 1).padStart(2, '0')}` : ''; + const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; + + if (lastRunMonth !== currentMonth) { + try { + await monthlyEvolution(); + } catch (err) { + console.error('[自主进化] 月度进化调度异常:', err.message); + } + } + } + } + + // ── 周五20:00 用户反馈处理 ── + if (dayOfWeek === 5 && hour === 20 && minute < 5) { + const lastFeedback = status.schedules.weekly_feedback.last_run; + const today = now.toISOString().slice(0, 10); + if (!lastFeedback || !lastFeedback.startsWith(today)) { + try { + await processWeeklyFeedback(); + } catch (err) { + console.error('[自主进化] 反馈处理调度异常:', err.message); + } + } + } + + // ── 周一09:00 推送反馈结果 ── + if (dayOfWeek === 1 && hour === 9 && minute < 5) { + const lastResponse = status.schedules.weekly_response.last_run; + const today = now.toISOString().slice(0, 10); + if (!lastResponse || !lastResponse.startsWith(today)) { + try { + await sendWeeklyResponse(); + } catch (err) { + console.error('[自主进化] 反馈推送调度异常:', err.message); + } + } + } + + // ── 流量池预警检测 ── + try { + await checkTrafficPoolAlert(); + } catch (err) { + console.error('[自主进化] 流量预警检测异常:', err.message); + } + + saveEvolutionStatus(status); +} + +// ── 获取进化状态 (供外部查询) ───────────────── +function getEvolutionStatus() { + return readEvolutionStatus(); +} + +// ═══════════════════════════════════════════════ +// 导出 +// ═══════════════════════════════════════════════ +module.exports = { + monthlyEvolution, + orchestrateUpdate, + handleTrafficAlert, + getEvolutionStatus, + runProtocolCheck +}; + +// ═══════════════════════════════════════════════ +// 启动自主进化引擎 +// ═══════════════════════════════════════════════ +if (require.main === module) { + console.log('♾️ 光湖语言世界 · 自主进化引擎启动 (∞ 版本)'); + console.log(` 调度间隔: ${SCHEDULE_CHECK_INTERVAL / 1000}秒`); + console.log(` 协议检查: 每${PROTOCOL_CHECK_INTERVAL / 60000}分钟`); + console.log(` 月度进化: 每月1号 00:00 CST`); + console.log(` 反馈处理: 每周五 20:00 CST (Phase 2)`); + console.log(` 反馈推送: 每周一 09:00 CST (Phase 2)`); + console.log(` 当前时间: ${getChinaTimeStr()}`); + console.log(` 数据目录: ${DATA_DIR}`); + + // 初始化状态文件 + const status = readEvolutionStatus(); + status.started_at = new Date().toISOString(); + status.status = 'running'; + saveEvolutionStatus(status); + + // 立即执行一次协议检查 + runProtocolCheck().catch(err => { + console.error('[自主进化] 初始协议检查失败:', err.message); + }); + + // 定期调度检查 + setInterval(() => { + checkSchedule().catch(err => { + console.error('[自主进化] 调度检查异常:', err.message); + }); + }, SCHEDULE_CHECK_INTERVAL); +} diff --git a/server/proxy/service/email-hub.js b/server/proxy/service/email-hub.js new file mode 100644 index 00000000..efadd877 --- /dev/null +++ b/server/proxy/service/email-hub.js @@ -0,0 +1,590 @@ +#!/usr/bin/env node +// ═══════════════════════════════════════════════ +// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 +// 📜 Copyright: 国作登字-2026-A-00037559 +// ═══════════════════════════════════════════════ +// server/proxy/service/email-hub.js +// 📧 光湖语言世界 · 邮件通信中枢 +// +// ∞版本邮件中枢 — 统一管理所有用户邮件推送 +// +// 邮件类型: +// 1. 月初重置通知 — 每月1号通知流量池已重置 +// 2. 更新升级通知 — 系统更新后告知用户 +// 3. 流量预警通知 — 70%/80%/90%/100% 阶梯告警 +// 4. 安全风险提醒 — 单用户异常行为告知 +// 5. 反馈确认回复 — 收到用户反馈后的自动确认 +// +// 所有邮件底部附「意见反馈」链接 +// +// 用法: +// node email-hub.js monthly-reset — 发送月初重置邮件给所有用户 +// node email-hub.js update-notify — 发送更新通知给所有用户 +// node email-hub.js traffic-warn — 发送流量预警给所有用户 +// node email-hub.js security-warn — 发送安全提醒给单用户 +// node email-hub.js feedback-ack — 发送反馈确认给单用户 +// +// 运行方式: CLI调用 (由auto-evolution.js调度) +// ═══════════════════════════════════════════════ + +'use strict'; + +const tls = require('tls'); +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 USERS_FILE = path.join(DATA_DIR, 'users.json'); +const KEYS_FILE = process.env.ZY_PROXY_KEYS_FILE || path.join(PROXY_DIR, '.env.keys'); +const POOL_STATUS_FILE = path.join(DATA_DIR, 'pool-quota-status.json'); +const EMAIL_LOG_FILE = path.join(DATA_DIR, 'email-hub-log.json'); + +// ── 加载配置 ───────────────────────────────── +function loadConfig() { + const config = { + smtp_user: process.env.ZY_SMTP_USER || '', + smtp_pass: process.env.ZY_SMTP_PASS || '', + server_host: process.env.ZY_SERVER_HOST || '' + }; + + 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('='); + const k = key.trim(); + const v = vals.join('=').trim(); + if (!v) continue; + if (k === 'ZY_SERVER_HOST' && !config.server_host) config.server_host = v; + if (k === 'ZY_SMTP_USER' && !config.smtp_user) config.smtp_user = v; + if (k === 'ZY_SMTP_PASS' && !config.smtp_pass) config.smtp_pass = v; + } + } catch { /* ignore */ } + + return config; +} + +// ── SMTP发送邮件 ───────────────────────────── +function detectSmtpHost(email) { + if (email.includes('@qq.com')) return 'smtp.qq.com'; + if (email.includes('@163.com')) return 'smtp.163.com'; + if (email.includes('@126.com')) return 'smtp.126.com'; + if (email.includes('@gmail.com')) return 'smtp.gmail.com'; + if (email.includes('@outlook.com') || email.includes('@hotmail.com')) return 'smtp.office365.com'; + if (email.includes('@yeah.net')) return 'smtp.yeah.net'; + return 'smtp.qq.com'; +} + +async function sendEmail(to, subject, htmlBody) { + const config = loadConfig(); + if (!config.smtp_user || !config.smtp_pass) { + throw new Error('SMTP未配置 (需要ZY_SMTP_USER和ZY_SMTP_PASS)'); + } + + const smtpHost = detectSmtpHost(config.smtp_user); + const smtpPort = 465; + + return new Promise((resolve, reject) => { + const socket = tls.connect(smtpPort, smtpHost, {}, () => { + let step = 0; + const from = config.smtp_user; + + const commands = [ + `EHLO zy-proxy\r\n`, + `AUTH LOGIN\r\n`, + `${Buffer.from(from).toString('base64')}\r\n`, + `${Buffer.from(config.smtp_pass).toString('base64')}\r\n`, + `MAIL FROM:<${from}>\r\n`, + `RCPT TO:<${to}>\r\n`, + `DATA\r\n`, + `From: =?UTF-8?B?${Buffer.from('光湖语言世界').toString('base64')}?= <${from}>\r\nTo: <${to}>\r\nSubject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=\r\nContent-Type: text/html; charset=utf-8\r\nMIME-Version: 1.0\r\n\r\n${htmlBody}\r\n.\r\n`, + `QUIT\r\n` + ]; + + socket.on('data', () => { + if (step < commands.length) { + socket.write(commands[step]); + step++; + } + if (step >= commands.length) { + resolve(true); + } + }); + + socket.on('error', (err) => { reject(err); }); + }); + + socket.on('error', (err) => { reject(err); }); + + setTimeout(() => { + try { socket.destroy(); } catch { /* ignore */ } + reject(new Error('SMTP超时(30s)')); + }, 30000); + }); +} + +// ── 加载所有启用用户 ───────────────────────── +function getEnabledUsers() { + try { + const data = JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); + return (data.users || []).filter(u => u.enabled !== false); + } catch { + return []; + } +} + +// ── 加载流量池状态 ──────────────────────────── +function getPoolStatus() { + try { + return JSON.parse(fs.readFileSync(POOL_STATUS_FILE, 'utf8')); + } catch { + return { pool_total_gb: 2000, pool_used_gb: 0, pool_percentage: 0, period: '' }; + } +} + +// ── 记录邮件发送日志 ───────────────────────── +function logEmail(type, recipients, success, error) { + let log; + try { + log = JSON.parse(fs.readFileSync(EMAIL_LOG_FILE, 'utf8')); + } catch { + log = { entries: [] }; + } + + log.entries.push({ + type, + recipients: Array.isArray(recipients) ? recipients.length : 1, + success, + error: error || null, + time: new Date().toISOString() + }); + + // 只保留最近100条 + if (log.entries.length > 100) { + log.entries = log.entries.slice(-100); + } + + fs.mkdirSync(DATA_DIR, { recursive: true }); + fs.writeFileSync(EMAIL_LOG_FILE, JSON.stringify(log, null, 2)); +} + +// ── 反馈链接 (所有邮件底部附带) ─────────────── +function getFeedbackFooter(config) { + const host = config.server_host || 'guanghulab.com'; + return ` +
+
+

+ 有建议或问题?欢迎反馈 👇 +

+ + 📝 提交意见反馈 + +

+ 每周五 20:00 铸渊集中处理 · 每周一推送处理结果 +

+
`; +} + +// ── 通用邮件模板包装 ───────────────────────── +function wrapEmailTemplate(title, content, config) { + const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); + const feedbackFooter = getFeedbackFooter(config); + + return ` + + + + +
+

🌐 光湖语言世界

+

${title}

+ +
+ + ${content} + + ${feedbackFooter} + +
+

+ 光湖语言世界 · ∞版本 · ${now}
+ 国作登字-2026-A-00037559 +

+
+ +`; +} + +// ═══════════════════════════════════════════════ +// 📧 邮件类型 1: 月初重置通知 +// ═══════════════════════════════════════════════ +function generateMonthlyResetEmail(config) { + const now = new Date(); + const month = `${now.getFullYear()}年${now.getMonth() + 1}月`; + + const content = ` +
+ ✅ ${month} 流量节点已重置 +
+ +

📊 本月配额

+ + + + + +
流量池2000 GB
已使用0 GB (已重置)
重置日期每月1日
协议VLESS + Reality
+ +

+ 💡 您无需任何操作,刷新订阅即可继续使用。
+ 流量池为所有用户共享,请合理使用。 +

`; + + return wrapEmailTemplate(`${month} · 流量重置通知`, content, config); +} + +// ═══════════════════════════════════════════════ +// 📧 邮件类型 2: 更新升级通知 +// ═══════════════════════════════════════════════ +function generateUpdateNotifyEmail(description, config) { + const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }); + + const content = ` +
+ 🔄 系统已完成升级 +
+ +

📋 本次更新内容

+
+ ${escapeHtml(description).replace(/\n/g, '
')} +
+ +

+ ⏰ 更新时间: ${now}
+ 💡 大部分更新只需刷新订阅即可生效。如需重新下载订阅链接,会另行通知。 +

`; + + return wrapEmailTemplate('系统升级通知', content, config); +} + +// ═══════════════════════════════════════════════ +// 📧 邮件类型 3: 流量预警通知 +// ═══════════════════════════════════════════════ +function generateTrafficWarnEmail(percentage, poolStatus, config) { + const usedGB = poolStatus.pool_used_gb ? poolStatus.pool_used_gb.toFixed(1) : '?'; + const totalGB = poolStatus.pool_total_gb || 2000; + const remainGB = (totalGB - parseFloat(usedGB)).toFixed(1); + + let urgencyColor, urgencyBg, urgencyBorder, urgencyText; + if (percentage >= 100) { + urgencyColor = '#721c24'; urgencyBg = '#f8d7da'; urgencyBorder = '#f5c6cb'; + urgencyText = '⛔ 流量池已耗尽!所有连接已暂停。'; + } else if (percentage >= 90) { + urgencyColor = '#856404'; urgencyBg = '#fff3cd'; urgencyBorder = '#ffeaa7'; + urgencyText = `⚠️ 流量池仅剩 ${remainGB}GB,请节约使用!`; + } else { + urgencyColor = '#0c5460'; urgencyBg = '#d1ecf1'; urgencyBorder = '#bee5eb'; + urgencyText = `📊 流量池已使用 ${percentage}%,剩余 ${remainGB}GB`; + } + + const content = ` +
+ ${urgencyText} +
+ +

📊 流量池状态

+ + + + + +
已使用${usedGB} GB
总配额${totalGB} GB
使用率${percentage}%
剩余${remainGB} GB
+ +
+
+
+ +

+ 流量池每月1日重置。所有用户共享 ${totalGB}GB 月配额。 +

`; + + return wrapEmailTemplate('流量预警通知', content, config); +} + +// ═══════════════════════════════════════════════ +// 📧 邮件类型 4: 安全风险提醒 (单用户) +// ═══════════════════════════════════════════════ +function generateSecurityWarnEmail(message, config) { + const content = ` +
+ 🛡️ 安全提醒 +
+ +
+ ${escapeHtml(message).replace(/\n/g, '
')} +
+ +

💡 安全建议

+
    +
  • 避免同时使用多个VPN客户端
  • +
  • 关闭不使用的VPN连接
  • +
  • 确保订阅链接仅个人使用
  • +
  • 如有异常,请及时反馈
  • +
`; + + return wrapEmailTemplate('安全使用提醒', content, config); +} + +// ═══════════════════════════════════════════════ +// 📧 邮件类型 5: 反馈确认回复 +// ═══════════════════════════════════════════════ +function generateFeedbackAckEmail(config) { + const content = ` +
+ ✅ 您的反馈已收到 +
+ +

+ 感谢您的宝贵意见!铸渊将在以下时间处理: +

+ + + + + +
📥 收集截止每周五 20:00
🔍 深度分析周五晚间 (AI辅助评估)
📤 结果推送每周一 09:00
+ +

+ 注: 并非所有需求都会被采纳。铸渊会基于系统安全性、架构完整性和整体规划进行评估。 +

`; + + return wrapEmailTemplate('反馈已收到', content, config); +} + +// ── HTML转义 ───────────────────────────────── +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +// ═══════════════════════════════════════════════ +// 📧 批量发送函数 +// ═══════════════════════════════════════════════ + +/** + * 给所有启用用户发送邮件 + * @param {string} subject 邮件主题 + * @param {Function} htmlGenerator 生成HTML的函数(接收config参数) + * @returns {Promise<{sent: number, failed: number, errors: string[]}>} + */ +async function sendToAllUsers(subject, htmlGenerator) { + const users = getEnabledUsers(); + const config = loadConfig(); + + if (users.length === 0) { + console.log('[邮件中枢] 无启用用户,跳过发送'); + return { sent: 0, failed: 0, errors: [] }; + } + + const html = htmlGenerator(config); + let sent = 0; + let failed = 0; + const errors = []; + + console.log(`[邮件中枢] 开始批量发送 (${users.length}位用户): ${subject}`); + + for (const user of users) { + try { + await sendEmail(user.email, subject, html); + sent++; + console.log(` ✅ ${user.email}`); + // 间隔500ms避免SMTP限流 + await new Promise(r => setTimeout(r, 500)); + } catch (err) { + failed++; + errors.push(`${user.email}: ${err.message}`); + console.error(` ❌ ${user.email}: ${err.message}`); + } + } + + console.log(`[邮件中枢] 发送完成: ${sent}成功 / ${failed}失败`); + return { sent, failed, errors }; +} + +// ═══════════════════════════════════════════════ +// 📧 公开API (供auto-evolution.js调用) +// ═══════════════════════════════════════════════ + +/** + * 发送月初重置通知给所有用户 + */ +async function sendMonthlyResetEmail() { + const result = await sendToAllUsers( + '🌐 光湖语言世界 · 本月流量已重置', + (config) => generateMonthlyResetEmail(config) + ); + logEmail('monthly-reset', result.sent + result.failed, result.sent, result.errors.join('; ')); + return result; +} + +/** + * 发送更新通知给所有用户 + * @param {string} description 更新说明 + */ +async function sendUpdateNotifyEmail(description) { + const result = await sendToAllUsers( + '🌐 光湖语言世界 · 系统已升级', + (config) => generateUpdateNotifyEmail(description, config) + ); + logEmail('update-notify', result.sent + result.failed, result.sent, result.errors.join('; ')); + return result; +} + +/** + * 发送流量预警给所有用户 + * @param {number} percentage 流量使用百分比 + */ +async function sendTrafficWarnEmail(percentage) { + const poolStatus = getPoolStatus(); + const result = await sendToAllUsers( + `🌐 光湖语言世界 · 流量预警 (${percentage}%)`, + (config) => generateTrafficWarnEmail(percentage, poolStatus, config) + ); + logEmail('traffic-warn', result.sent + result.failed, result.sent, result.errors.join('; ')); + return result; +} + +/** + * 发送安全提醒给单个用户 + * @param {string} email 目标邮箱 + * @param {string} message 安全提醒内容 + */ +async function sendSecurityWarnEmail(email, message) { + const config = loadConfig(); + const html = generateSecurityWarnEmail(message, config); + + try { + await sendEmail(email, '🛡️ 光湖语言世界 · 安全提醒', html); + console.log(`[邮件中枢] ✅ 安全提醒已发送: ${email}`); + logEmail('security-warn', email, true, null); + return { sent: 1, failed: 0 }; + } catch (err) { + console.error(`[邮件中枢] ❌ 安全提醒发送失败: ${err.message}`); + logEmail('security-warn', email, false, err.message); + return { sent: 0, failed: 1 }; + } +} + +/** + * 发送反馈确认给单个用户 + * @param {string} email 目标邮箱 + */ +async function sendFeedbackAckEmail(email) { + const config = loadConfig(); + const html = generateFeedbackAckEmail(config); + + try { + await sendEmail(email, '🌐 光湖语言世界 · 反馈已收到', html); + console.log(`[邮件中枢] ✅ 反馈确认已发送: ${email}`); + logEmail('feedback-ack', email, true, null); + return { sent: 1, failed: 0 }; + } catch (err) { + console.error(`[邮件中枢] ❌ 反馈确认发送失败: ${err.message}`); + logEmail('feedback-ack', email, false, err.message); + return { sent: 0, failed: 1 }; + } +} + +// ═══════════════════════════════════════════════ +// CLI 主入口 +// ═══════════════════════════════════════════════ +async function main() { + const [,, action, arg1, arg2] = process.argv; + + if (!action) { + console.log('📧 光湖语言世界 · 邮件通信中枢'); + console.log('用法:'); + console.log(' node email-hub.js monthly-reset — 月初重置通知'); + console.log(' node email-hub.js update-notify <描述> — 更新升级通知'); + console.log(' node email-hub.js traffic-warn <百分比> — 流量预警通知'); + console.log(' node email-hub.js security-warn <邮箱> <消息> — 安全风险提醒'); + console.log(' node email-hub.js feedback-ack <邮箱> — 反馈确认回复'); + process.exit(0); + } + + switch (action) { + case 'monthly-reset': { + const result = await sendMonthlyResetEmail(); + console.log(`📧 月初重置通知: ${result.sent}成功 / ${result.failed}失败`); + break; + } + + case 'update-notify': { + if (!arg1) { + console.error('❌ 请提供更新描述'); + process.exit(1); + } + const result = await sendUpdateNotifyEmail(arg1); + console.log(`📧 更新通知: ${result.sent}成功 / ${result.failed}失败`); + break; + } + + case 'traffic-warn': { + const pct = parseInt(arg1, 10); + if (isNaN(pct)) { + console.error('❌ 请提供流量使用百分比 (数字)'); + process.exit(1); + } + const result = await sendTrafficWarnEmail(pct); + console.log(`📧 流量预警: ${result.sent}成功 / ${result.failed}失败`); + break; + } + + case 'security-warn': { + if (!arg1 || !arg2) { + console.error('❌ 请提供邮箱和消息'); + process.exit(1); + } + await sendSecurityWarnEmail(arg1, arg2); + break; + } + + case 'feedback-ack': { + if (!arg1) { + console.error('❌ 请提供邮箱'); + process.exit(1); + } + await sendFeedbackAckEmail(arg1); + break; + } + + default: + console.error(`❌ 未知操作: ${action}`); + process.exit(1); + } +} + +// 导出API (供其他模块调用) +module.exports = { + sendMonthlyResetEmail, + sendUpdateNotifyEmail, + sendTrafficWarnEmail, + sendSecurityWarnEmail, + sendFeedbackAckEmail, + getEnabledUsers, + sendEmail +}; + +// CLI直接运行 +if (require.main === module) { + main().catch(err => { + console.error('❌ 邮件中枢异常:', err.message); + process.exit(1); + }); +} diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 5c0da324..43b20343 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -424,26 +424,36 @@ rules: # Apple (直连) - DOMAIN-SUFFIX,apple.com,DIRECT - DOMAIN-SUFFIX,icloud.com,DIRECT + - DOMAIN-SUFFIX,mzstatic.com,DIRECT + - DOMAIN-SUFFIX,apple-cloudkit.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,staticfile.org,DIRECT - # 国内直连 + # 国内直连 (∞ 智能分流 · 用户切回国内网时VPN休眠) - 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,aliyuncs.com,DIRECT - DOMAIN-SUFFIX,jd.com,DIRECT - DOMAIN-SUFFIX,qq.com,DIRECT - DOMAIN-SUFFIX,tencent.com,DIRECT + - DOMAIN-SUFFIX,weixin.qq.com,DIRECT + - DOMAIN-SUFFIX,wechat.com,DIRECT - DOMAIN-SUFFIX,bilibili.com,DIRECT + - DOMAIN-SUFFIX,bilivideo.com,DIRECT - DOMAIN-SUFFIX,baidu.com,DIRECT + - DOMAIN-SUFFIX,bdstatic.com,DIRECT - DOMAIN-SUFFIX,zhihu.com,DIRECT - DOMAIN-SUFFIX,douyin.com,DIRECT + - DOMAIN-SUFFIX,bytedance.com,DIRECT + - DOMAIN-SUFFIX,toutiao.com,DIRECT - DOMAIN-SUFFIX,weibo.com,DIRECT - DOMAIN-SUFFIX,163.com,DIRECT - DOMAIN-SUFFIX,126.com,DIRECT @@ -454,6 +464,20 @@ rules: - DOMAIN-SUFFIX,meituan.com,DIRECT - DOMAIN-SUFFIX,dianping.com,DIRECT - DOMAIN-SUFFIX,pinduoduo.com,DIRECT + - DOMAIN-SUFFIX,suning.com,DIRECT + - DOMAIN-SUFFIX,csdn.net,DIRECT + - DOMAIN-SUFFIX,cnblogs.com,DIRECT + - DOMAIN-SUFFIX,gitee.com,DIRECT + - DOMAIN-SUFFIX,jianshu.com,DIRECT + - DOMAIN-SUFFIX,douban.com,DIRECT + - DOMAIN-SUFFIX,kuaishou.com,DIRECT + - DOMAIN-SUFFIX,ctrip.com,DIRECT + - DOMAIN-SUFFIX,ele.me,DIRECT + - DOMAIN-SUFFIX,netease.com,DIRECT + - DOMAIN-SUFFIX,iqiyi.com,DIRECT + - DOMAIN-SUFFIX,youku.com,DIRECT + - DOMAIN-SUFFIX,dingtalk.com,DIRECT + - DOMAIN-SUFFIX,feishu.cn,DIRECT # 局域网直连 - IP-CIDR,192.168.0.0/16,DIRECT From 76c104e99eb088a483c7cc62bbaf145b6260ca58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 04:29:58 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=AF=94=E8=BE=83=E9=80=BB=E8=BE=91=20+=20SMTP=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E6=B8=85=E7=90=86=20+=20=E5=AE=89=E5=85=A8=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/c5b8b0a2-c3fb-437f-bd6f-07549f2c5709 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .github/workflows/infinity-evolution.yml | 13 +++++---- server/proxy/service/auto-evolution.js | 26 +++++++++--------- server/proxy/service/email-hub.js | 34 ++++++++++++++++++------ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/.github/workflows/infinity-evolution.yml b/.github/workflows/infinity-evolution.yml index db2fed5b..dad4d7f3 100644 --- a/.github/workflows/infinity-evolution.yml +++ b/.github/workflows/infinity-evolution.yml @@ -81,18 +81,21 @@ jobs: deploy-infinity) echo "部署∞版本..." # 先同步最新代码到服务器 + $SSH_CMD "mkdir -p $PROXY_DIR/.deploy-tmp" + scp -i ~/.ssh/brain_key -o StrictHostKeyChecking=no \ server/proxy/service/auto-evolution.js \ server/proxy/service/email-hub.js \ server/proxy/service/protocol-mirror.js \ server/proxy/ecosystem.brain-proxy-v3.config.js \ - ${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }}:/tmp/ + ${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }}:$PROXY_DIR/.deploy-tmp/ $SSH_CMD " - cp /tmp/auto-evolution.js $PROXY_DIR/service/ - cp /tmp/email-hub.js $PROXY_DIR/service/ - cp /tmp/protocol-mirror.js $PROXY_DIR/service/ - cp /tmp/ecosystem.brain-proxy-v3.config.js $PROXY_DIR/ + cp $PROXY_DIR/.deploy-tmp/auto-evolution.js $PROXY_DIR/service/ + cp $PROXY_DIR/.deploy-tmp/email-hub.js $PROXY_DIR/service/ + cp $PROXY_DIR/.deploy-tmp/protocol-mirror.js $PROXY_DIR/service/ + cp $PROXY_DIR/.deploy-tmp/ecosystem.brain-proxy-v3.config.js $PROXY_DIR/ + rm -rf $PROXY_DIR/.deploy-tmp mkdir -p $PROXY_DIR/data/evolution-reports cd $PROXY_DIR && pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env && pm2 save echo '✅ ∞版本已部署' diff --git a/server/proxy/service/auto-evolution.js b/server/proxy/service/auto-evolution.js index 4c459ab6..95b50445 100644 --- a/server/proxy/service/auto-evolution.js +++ b/server/proxy/service/auto-evolution.js @@ -372,8 +372,9 @@ async function checkTrafficPoolAlert() { const lastRun = status.schedules.traffic_alert_70.last_run; const currentPeriod = pool.period || new Date().toISOString().slice(0, 7); - // 检查本月是否已发过 - if (!lastRun || !lastRun.startsWith(currentPeriod.replace('-', '').slice(0, 6))) { + // 检查本月是否已发过 (比较YYYY-MM) + const lastRunMonth = lastRun ? lastRun.slice(0, 7) : ''; + if (lastRunMonth !== currentPeriod) { await handleTrafficAlert(Math.round(pct)); status.schedules.traffic_alert_70.last_run = new Date().toISOString(); saveEvolutionStatus(status); @@ -408,19 +409,16 @@ async function checkSchedule() { // ── 月度进化 (每月1号 00:00-00:04) ── if (day === 1 && hour === 0 && minute < 5) { const lastMonthly = status.schedules.monthly_evolution.last_run; - const today = now.toISOString().slice(0, 10); - if (!lastMonthly || !lastMonthly.startsWith(today.slice(0, 4))) { - // 进一步检查: 确保本月还没执行过 - const lastRunDate = lastMonthly ? new Date(lastMonthly) : null; - const lastRunMonth = lastRunDate ? `${lastRunDate.getFullYear()}-${String(lastRunDate.getMonth() + 1).padStart(2, '0')}` : ''; - const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; + // 比较YYYY-MM确保本月未执行过 + const lastRunDate = lastMonthly ? new Date(lastMonthly) : null; + const lastRunMonth = lastRunDate ? `${lastRunDate.getFullYear()}-${String(lastRunDate.getMonth() + 1).padStart(2, '0')}` : ''; + const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; - if (lastRunMonth !== currentMonth) { - try { - await monthlyEvolution(); - } catch (err) { - console.error('[自主进化] 月度进化调度异常:', err.message); - } + if (lastRunMonth !== currentMonth) { + try { + await monthlyEvolution(); + } catch (err) { + console.error('[自主进化] 月度进化调度异常:', err.message); } } } diff --git a/server/proxy/service/email-hub.js b/server/proxy/service/email-hub.js index efadd877..b53329aa 100644 --- a/server/proxy/service/email-hub.js +++ b/server/proxy/service/email-hub.js @@ -86,6 +86,15 @@ async function sendEmail(to, subject, htmlBody) { const smtpPort = 465; return new Promise((resolve, reject) => { + let settled = false; + const timeoutId = setTimeout(() => { + if (!settled) { + settled = true; + try { socket.destroy(); } catch { /* ignore */ } + reject(new Error('SMTP超时(30s)')); + } + }, 30000); + const socket = tls.connect(smtpPort, smtpHost, {}, () => { let step = 0; const from = config.smtp_user; @@ -107,20 +116,29 @@ async function sendEmail(to, subject, htmlBody) { socket.write(commands[step]); step++; } - if (step >= commands.length) { + if (step >= commands.length && !settled) { + settled = true; + clearTimeout(timeoutId); resolve(true); } }); - socket.on('error', (err) => { reject(err); }); + socket.on('error', (err) => { + if (!settled) { + settled = true; + clearTimeout(timeoutId); + reject(err); + } + }); }); - socket.on('error', (err) => { reject(err); }); - - setTimeout(() => { - try { socket.destroy(); } catch { /* ignore */ } - reject(new Error('SMTP超时(30s)')); - }, 30000); + socket.on('error', (err) => { + if (!settled) { + settled = true; + clearTimeout(timeoutId); + reject(err); + } + }); }); }