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"
echo ""
echo " ══════════ V2密钥 (请添加到GitHub Secrets) ══════════"
echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY=$PUBLIC_KEY"
echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID=$SHORT_ID"
echo " ═══════════════════════════════════════════════════════"
echo ""
echo " ⚠️ 密钥已保存到: $KEYS_FILE"
echo " ⚠️ Private Key不需要添加到GitHub Secrets (已保存在服务器)"
echo " ══════════ V2密钥已生成 ══════════"
echo " ⚠️ 密钥已保存到: $KEYS_FILE (权限600·仅root可读)"
echo " ⚠️ 如需查看密钥请SSH到服务器执行: cat $KEYS_FILE"
echo " ⚠️ 部署完成后将公钥和ShortID添加到GitHub Secrets:"
echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY"
echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID"
echo " ═══════════════════════════════════"
}
# ── 部署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 DATA_DIR = path.join(PROXY_DIR, 'data');
const KEYS_FILE = path.join(PROXY_DIR, '.env.keys');
const LIVE_NODES_FRESHNESS_MS = parseInt(process.env.ZY_CLOUD_HB_EXPIRY_MS || '600000', 10);
// 引入用户管理器
const userManager = require('./user-manager');
@ -80,9 +81,9 @@ function buildVpnNodes() {
const liveNodesFile = path.join(DATA_DIR, 'nodes-live.json');
try {
const liveData = JSON.parse(fs.readFileSync(liveNodesFile, 'utf8'));
// 检查数据是否新鲜(5分钟内
// 检查数据是否新鲜(使用与ZY-CLOUD相同的心跳过期阈值
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;
}
} catch { /* ZY-CLOUD未运行回退到静态配置 */ }

View File

@ -120,6 +120,7 @@ function addUser(email, options = {}) {
throw new Error(`用户已存在: ${email} (UUID: ${existing.uuid.substring(0, 8)}...)`);
}
const now = new Date();
const user = {
email,
uuid: generateUUID(),
@ -127,11 +128,11 @@ function addUser(email, options = {}) {
label: options.label || email.split('@')[0],
quota_bytes: (options.quota_gb || 500) * 1024 * 1024 * 1024,
enabled: true,
created_at: new Date().toISOString(),
created_at: now.toISOString(),
traffic: {
upload_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 }
}
};

View File

@ -66,19 +66,16 @@ function parseConfig() {
};
}
// ── 自动检测本机公网IP ──────────────────────
// ── 自动检测本机IP ──────────────────────────
function getPublicIp() {
try {
return execSync("hostname -I | awk '{print $1}'", { encoding: 'utf8', timeout: 3000 }).trim();
} catch {
const nets = os.networkInterfaces();
for (const ifaces of Object.values(nets)) {
for (const iface of ifaces) {
if (!iface.internal && iface.family === 'IPv4') return iface.address;
}
// 优先使用Node.js原生API安全
const nets = os.networkInterfaces();
for (const ifaces of Object.values(nets)) {
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 KEYS_FILE = path.join(PROXY_DIR, '.env.keys');
// ── 时间间隔 ────────────────────────────────
const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒心跳
const DIAGNOSE_INTERVAL = 5 * 60 * 1000; // 5分钟诊断
const LEARN_INTERVAL = 30 * 60 * 1000; // 30分钟学习周期
// ── 时间间隔(可通过环境变量调整)────────────
const HEARTBEAT_INTERVAL = parseInt(process.env.ZY_CLOUD_HEARTBEAT_INTERVAL || '30000', 10);
const DIAGNOSE_INTERVAL = parseInt(process.env.ZY_CLOUD_DIAGNOSE_INTERVAL || '300000', 10);
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 基类
@ -273,7 +275,8 @@ class ZyCloudVpn extends LivingModule {
const brainPbk = this._readEnvOrKey('ZY_PROXY_REALITY_PUBLIC_KEY');
const brainSid = this._readEnvOrKey('ZY_PROXY_REALITY_SHORT_ID');
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');
}
@ -299,14 +302,14 @@ class ZyCloudVpn extends LivingModule {
for (const regNode of Object.values(this._registry.nodes)) {
if (seenIds.has(regNode.id)) continue; // 避免重复
// 检查心跳是否过期超过10分钟无心跳 → 自动注销)
// 检查心跳是否过期
const lastHb = new Date(regNode.last_heartbeat).getTime();
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)}分钟),标记为离线`);
// 不删除注册,只是标记离线(可能临时断网)
// 超过24小时无心跳才自动清理
if (age > 24 * 60 * 60 * 1000) {
if (age > NODE_UNREGISTER_MS) {
console.log(`[ZY-CLOUD VPN] 🗑️ 节点 ${regNode.name} 超24小时无心跳自动注销`);
delete this._registry.nodes[regNode.id];
this._saveRegistry();