D57: Address code review feedback - configurable timeouts, security fixes, dynamic specs

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/24c233b1-db72-4d9c-b668-22679479825b

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-05 08:12:16 +00:00 committed by GitHub
parent 4fc20144c3
commit bd58cf1b7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 31 additions and 29 deletions

View File

@ -171,13 +171,13 @@ EOF
chmod 600 "$KEYS_FILE" chmod 600 "$KEYS_FILE"
echo "" echo ""
echo " ══════════ V2密钥 (请添加到GitHub Secrets) ══════════" echo " ══════════ V2密钥已生成 ══════════"
echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY=$PUBLIC_KEY" echo " ⚠️ 密钥已保存到: $KEYS_FILE (权限600·仅root可读)"
echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID=$SHORT_ID" echo " ⚠️ 如需查看密钥,请SSH到服务器执行: cat $KEYS_FILE"
echo " ═══════════════════════════════════════════════════════" echo " ⚠️ 部署完成后,将公钥和ShortID添加到GitHub Secrets:"
echo "" echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY"
echo " ⚠️ 密钥已保存到: $KEYS_FILE" echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID"
echo " ⚠️ Private Key不需要添加到GitHub Secrets (已保存在服务器)" echo " ═══════════════════════════════════"
} }
# ── 部署V2服务代码 ──────────────────────────── # ── 部署V2服务代码 ────────────────────────────

View File

@ -27,6 +27,7 @@ const PORT = process.env.ZY_PROXY_V2_PORT || 3803;
const PROXY_DIR = process.env.ZY_BRAIN_PROXY_DIR || '/opt/zhuyuan-brain/proxy'; const PROXY_DIR = process.env.ZY_BRAIN_PROXY_DIR || '/opt/zhuyuan-brain/proxy';
const DATA_DIR = path.join(PROXY_DIR, 'data'); const DATA_DIR = path.join(PROXY_DIR, 'data');
const KEYS_FILE = path.join(PROXY_DIR, '.env.keys'); const KEYS_FILE = path.join(PROXY_DIR, '.env.keys');
const LIVE_NODES_FRESHNESS_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '600000', 10);
// 引入用户管理器 // 引入用户管理器
const userManager = require('./user-manager'); const userManager = require('./user-manager');
@ -80,9 +81,9 @@ function buildVpnNodes() {
const liveNodesFile = path.join(DATA_DIR, 'nodes-live.json'); const liveNodesFile = path.join(DATA_DIR, 'nodes-live.json');
try { try {
const liveData = JSON.parse(fs.readFileSync(liveNodesFile, 'utf8')); const liveData = JSON.parse(fs.readFileSync(liveNodesFile, 'utf8'));
// 检查数据是否新鲜(5分钟内) // 检查数据是否新鲜(使用与ZY-CLOUD相同的心跳过期阈值)
const age = Date.now() - new Date(liveData.updated_at).getTime(); const age = Date.now() - new Date(liveData.updated_at).getTime();
if (age < 10 * 60 * 1000 && liveData.nodes && liveData.nodes.length > 0) { if (age < LIVE_NODES_FRESHNESS_MS * 2 && liveData.nodes && liveData.nodes.length > 0) {
return liveData.nodes; return liveData.nodes;
} }
} catch { /* ZY-CLOUD未运行,回退到静态配置 */ } } catch { /* ZY-CLOUD未运行,回退到静态配置 */ }

View File

@ -120,6 +120,7 @@ function addUser(email, options = {}) {
throw new Error(`用户已存在: ${email} (UUID: ${existing.uuid.substring(0, 8)}...)`); throw new Error(`用户已存在: ${email} (UUID: ${existing.uuid.substring(0, 8)}...)`);
} }
const now = new Date();
const user = { const user = {
email, email,
uuid: generateUUID(), uuid: generateUUID(),
@ -127,11 +128,11 @@ function addUser(email, options = {}) {
label: options.label || email.split('@')[0], label: options.label || email.split('@')[0],
quota_bytes: (options.quota_gb || 500) * 1024 * 1024 * 1024, quota_bytes: (options.quota_gb || 500) * 1024 * 1024 * 1024,
enabled: true, enabled: true,
created_at: new Date().toISOString(), created_at: now.toISOString(),
traffic: { traffic: {
upload_bytes: 0, upload_bytes: 0,
download_bytes: 0, download_bytes: 0,
period: `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`, period: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`,
alerts_sent: { p80: false, p90: false, p100: false } alerts_sent: { p80: false, p90: false, p100: false }
} }
}; };

View File

@ -66,19 +66,16 @@ function parseConfig() {
}; };
} }
// ── 自动检测本机公网IP ────────────────────── // ── 自动检测本机IP ──────────────────────────
function getPublicIp() { function getPublicIp() {
try { // 优先使用Node.js原生API(安全)
return execSync("hostname -I | awk '{print $1}'", { encoding: 'utf8', timeout: 3000 }).trim(); const nets = os.networkInterfaces();
} catch { for (const ifaces of Object.values(nets)) {
const nets = os.networkInterfaces(); for (const iface of ifaces) {
for (const ifaces of Object.values(nets)) { if (!iface.internal && iface.family === 'IPv4') return iface.address;
for (const iface of ifaces) {
if (!iface.internal && iface.family === 'IPv4') return iface.address;
}
} }
return '0.0.0.0';
} }
return '0.0.0.0';
} }
// ── 自动检测本机配置 ──────────────────────── // ── 自动检测本机配置 ────────────────────────

View File

@ -37,10 +37,12 @@ const REGISTRY_FILE = path.join(DATA_DIR, 'nodes-registry.json');
const HEARTBEAT_FILE = path.join(DATA_DIR, 'zy-cloud-vpn-heartbeat.json'); const HEARTBEAT_FILE = path.join(DATA_DIR, 'zy-cloud-vpn-heartbeat.json');
const KEYS_FILE = path.join(PROXY_DIR, '.env.keys'); const KEYS_FILE = path.join(PROXY_DIR, '.env.keys');
// ── 时间间隔 ──────────────────────────────── // ── 时间间隔(可通过环境变量调整)────────────
const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒心跳 const HEARTBEAT_INTERVAL = parseInt(process.env.ZY_CLOUD_HEARTBEAT_INTERVAL || '30000', 10);
const DIAGNOSE_INTERVAL = 5 * 60 * 1000; // 5分钟诊断 const DIAGNOSE_INTERVAL = parseInt(process.env.ZY_CLOUD_DIAGNOSE_INTERVAL || '300000', 10);
const LEARN_INTERVAL = 30 * 60 * 1000; // 30分钟学习周期 const LEARN_INTERVAL = parseInt(process.env.ZY_CLOUD_LEARN_INTERVAL || '1800000', 10);
const HEARTBEAT_EXPIRY_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '600000', 10); // 10分钟
const NODE_UNREGISTER_MS = parseInt(process.env.ZY_CLOUD_UNREGISTER_MS || '86400000', 10); // 24小时
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════
// LivingModule 基类 // LivingModule 基类
@ -273,7 +275,8 @@ class ZyCloudVpn extends LivingModule {
const brainPbk = this._readEnvOrKey('ZY_PROXY_REALITY_PUBLIC_KEY'); const brainPbk = this._readEnvOrKey('ZY_PROXY_REALITY_PUBLIC_KEY');
const brainSid = this._readEnvOrKey('ZY_PROXY_REALITY_SHORT_ID'); const brainSid = this._readEnvOrKey('ZY_PROXY_REALITY_SHORT_ID');
if (brainHost && brainPbk) { if (brainHost && brainPbk) {
nodes.push(this._makeNode('zy-brain-sg1', '🧠 铸渊专线V2-SG1(大脑)', brainHost, 443, brainPbk, brainSid, 'sg-zone1', 'ZY-SVR-005', 'local', '4核8G')); const localSpecs = `${require('os').cpus().length}核${Math.round(require('os').totalmem() / (1024 ** 3))}G`;
nodes.push(this._makeNode('zy-brain-sg1', '🧠 铸渊专线V2-SG1(大脑)', brainHost, 443, brainPbk, brainSid, 'sg-zone1', 'ZY-SVR-005', 'local', localSpecs));
seenIds.add('zy-brain-sg1'); seenIds.add('zy-brain-sg1');
} }
@ -299,14 +302,14 @@ class ZyCloudVpn extends LivingModule {
for (const regNode of Object.values(this._registry.nodes)) { for (const regNode of Object.values(this._registry.nodes)) {
if (seenIds.has(regNode.id)) continue; // 避免重复 if (seenIds.has(regNode.id)) continue; // 避免重复
// 检查心跳是否过期(超过10分钟无心跳 → 自动注销) // 检查心跳是否过期
const lastHb = new Date(regNode.last_heartbeat).getTime(); const lastHb = new Date(regNode.last_heartbeat).getTime();
const age = Date.now() - lastHb; const age = Date.now() - lastHb;
if (age > 10 * 60 * 1000) { if (age > HEARTBEAT_EXPIRY_MS) {
console.log(`[ZY-CLOUD VPN] ⏰ 节点 ${regNode.name} 心跳过期(${Math.floor(age/60000)}分钟),标记为离线`); console.log(`[ZY-CLOUD VPN] ⏰ 节点 ${regNode.name} 心跳过期(${Math.floor(age/60000)}分钟),标记为离线`);
// 不删除注册,只是标记离线(可能临时断网) // 不删除注册,只是标记离线(可能临时断网)
// 超过24小时无心跳才自动清理 // 超过24小时无心跳才自动清理
if (age > 24 * 60 * 60 * 1000) { if (age > NODE_UNREGISTER_MS) {
console.log(`[ZY-CLOUD VPN] 🗑️ 节点 ${regNode.name} 超24小时无心跳,自动注销`); console.log(`[ZY-CLOUD VPN] 🗑️ 节点 ${regNode.name} 超24小时无心跳,自动注销`);
delete this._registry.nodes[regNode.id]; delete this._registry.nodes[regNode.id];
this._saveRegistry(); this._saveRegistry();