guanghulab/server/proxy/service/protocol-mirror.js

658 lines
22 KiB
JavaScript
Raw Permalink Normal View History

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