guanghulab/server/proxy/service/protocol-mirror.js
2026-05-10 13:12:44 +08:00

658 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

#!/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);