diff --git a/.github/workflows/deploy-proxy-service.yml b/.github/workflows/deploy-proxy-service.yml index a26423ad..44f08981 100644 --- a/.github/workflows/deploy-proxy-service.yml +++ b/.github/workflows/deploy-proxy-service.yml @@ -91,6 +91,8 @@ jobs: "cd /opt/zhuyuan/proxy-deploy && \ export ZY_SERVER_HOST='${{ secrets.ZY_SERVER_HOST }}' && \ export ZY_CN_RELAY_HOST='${{ secrets.ZY_CN_SERVER_HOST }}' && \ + export ZY_SMTP_USER='${{ secrets.ZY_SMTP_USER }}' && \ + export ZY_SMTP_PASS='${{ secrets.ZY_SMTP_PASS }}' && \ bash deploy-proxy.sh ${{ github.event.inputs.action }}" - name: '🧹 清理SSH密钥' diff --git a/server/proxy/deploy-proxy.sh b/server/proxy/deploy-proxy.sh index c44d1c6c..12052bf5 100644 --- a/server/proxy/deploy-proxy.sh +++ b/server/proxy/deploy-proxy.sh @@ -51,7 +51,7 @@ ensure_log_permissions() { chmod 755 "$PROXY_DIR/logs" } -# ── 共用: 保存ZY_SERVER_HOST到.env.keys ── +# ── 共用: 保存环境变量到.env.keys ── save_server_host() { KEYS_FILE="$PROXY_DIR/.env.keys" if [ -n "${ZY_SERVER_HOST:-}" ] && [ -f "$KEYS_FILE" ]; then @@ -84,6 +84,28 @@ save_server_host() { fi echo " ✅ ZY_CN_RELAY_HOST 已保存到 .env.keys" fi + + # 保存SMTP凭据 (如果有) — 使守护Agent和流量监控可发送告警邮件 + # 注: 使用删除+追加方式避免sed特殊字符问题 (密码常含|&/$等) + if [ -n "${ZY_SMTP_USER:-}" ] && [ -f "$KEYS_FILE" ]; then + if grep -q "^ZY_SMTP_USER=" "$KEYS_FILE" 2>/dev/null; then + grep -v "^ZY_SMTP_USER=" "$KEYS_FILE" > "${KEYS_FILE}.tmp" && mv "${KEYS_FILE}.tmp" "$KEYS_FILE" + else + echo "" >> "$KEYS_FILE" + echo "# SMTP凭据 (部署时自动写入·守护Agent告警用)" >> "$KEYS_FILE" + fi + printf '%s\n' "ZY_SMTP_USER=${ZY_SMTP_USER}" >> "$KEYS_FILE" + chmod 600 "$KEYS_FILE" + echo " ✅ ZY_SMTP_USER 已保存到 .env.keys" + fi + if [ -n "${ZY_SMTP_PASS:-}" ] && [ -f "$KEYS_FILE" ]; then + if grep -q "^ZY_SMTP_PASS=" "$KEYS_FILE" 2>/dev/null; then + grep -v "^ZY_SMTP_PASS=" "$KEYS_FILE" > "${KEYS_FILE}.tmp" && mv "${KEYS_FILE}.tmp" "$KEYS_FILE" + fi + printf '%s\n' "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> "$KEYS_FILE" + chmod 600 "$KEYS_FILE" + echo " ✅ ZY_SMTP_PASS 已保存到 .env.keys" + fi } # ── install: 首次完整安装 ───────────────────── diff --git a/server/proxy/ecosystem.proxy.config.js b/server/proxy/ecosystem.proxy.config.js index 39f68acd..ee805d36 100644 --- a/server/proxy/ecosystem.proxy.config.js +++ b/server/proxy/ecosystem.proxy.config.js @@ -28,7 +28,8 @@ module.exports = { instances: 1, env: { NODE_ENV: 'production', - ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data' + ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data', + ZY_PROXY_KEYS_FILE: '/opt/zhuyuan/proxy/.env.keys' }, max_memory_restart: '64M', log_file: '/opt/zhuyuan/proxy/logs/monitor.log', @@ -43,7 +44,8 @@ module.exports = { env: { NODE_ENV: 'production', ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data', - ZY_PROXY_LOG_DIR: '/opt/zhuyuan/proxy/logs' + ZY_PROXY_LOG_DIR: '/opt/zhuyuan/proxy/logs', + ZY_PROXY_KEYS_FILE: '/opt/zhuyuan/proxy/.env.keys' }, max_memory_restart: '128M', log_file: '/opt/zhuyuan/proxy/logs/guardian.log', diff --git a/server/proxy/service/proxy-guardian.js b/server/proxy/service/proxy-guardian.js index 36da79f6..6812db8f 100644 --- a/server/proxy/service/proxy-guardian.js +++ b/server/proxy/service/proxy-guardian.js @@ -24,7 +24,7 @@ 'use strict'; -const { execSync, exec } = require('child_process'); +const { execSync, execFileSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const https = require('https'); @@ -275,10 +275,11 @@ async function consultLLM(issue) { function sendAlertEmail(subject, body) { try { const sendScript = path.join(__dirname, 'send-subscription.js'); - execSync( - `node "${sendScript}" alert "${subject}\n\n${body}"`, - { encoding: 'utf8', timeout: 30000 } - ); + // 使用execFileSync避免Shell命令注入 (不经过shell解释器) + execFileSync('node', [sendScript, 'alert', `${subject}\n\n${body}`], { + encoding: 'utf8', + timeout: 30000 + }); console.log('[守护Agent] 告警邮件已发送'); } catch (err) { console.error('[守护Agent] 邮件发送失败:', err.message); diff --git a/server/proxy/service/send-subscription.js b/server/proxy/service/send-subscription.js index 7b773ff8..c8cfd249 100644 --- a/server/proxy/service/send-subscription.js +++ b/server/proxy/service/send-subscription.js @@ -29,6 +29,8 @@ const path = require('path'); const KEYS_FILE = process.env.ZY_PROXY_KEYS_FILE || '/opt/zhuyuan/proxy/.env.keys'; // ── 加载配置 ───────────────────────────────── +// 优先级: 环境变量 > .env.keys文件 +// 通用读取所有键值对,确保guardian/monitor等服务也能获取完整配置 function loadConfig() { const config = { smtp_user: process.env.ZY_SMTP_USER || '', @@ -37,13 +39,21 @@ function loadConfig() { sub_token: process.env.ZY_PROXY_SUB_TOKEN || '' }; - // 尝试从本地密钥文件读取Token + // 从.env.keys文件读取所有键值对(环境变量未设置时回退) try { const content = fs.readFileSync(KEYS_FILE, 'utf8'); for (const line of content.split('\n')) { - if (line.startsWith('ZY_PROXY_SUB_TOKEN=')) { - config.sub_token = line.split('=')[1].trim(); - } + 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_PROXY_SUB_TOKEN' && !config.sub_token) config.sub_token = v; + 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 */ } diff --git a/server/proxy/service/traffic-monitor.js b/server/proxy/service/traffic-monitor.js index 6853d062..307191d1 100644 --- a/server/proxy/service/traffic-monitor.js +++ b/server/proxy/service/traffic-monitor.js @@ -15,7 +15,7 @@ 'use strict'; -const { execSync } = require('child_process'); +const { execSync, execFileSync } = require('child_process'); const fs = require('fs'); const path = require('path'); @@ -177,10 +177,11 @@ async function sendAlert(alert, quota) { const usedGB = ((quota.upload_bytes + quota.download_bytes) / (1024 ** 3)).toFixed(2); const totalGB = (quota.total_bytes / (1024 ** 3)).toFixed(0); - execSync( - `node "${sendScript}" alert "${alert.message} 已用 ${usedGB}GB / ${totalGB}GB"`, - { encoding: 'utf8', timeout: 30000 } - ); + // 使用execFileSync避免Shell命令注入 (不经过shell解释器) + execFileSync('node', [sendScript, 'alert', `${alert.message} 已用 ${usedGB}GB / ${totalGB}GB`], { + encoding: 'utf8', + timeout: 30000 + }); console.log(`[流量监控] 告警邮件已发送: ${alert.level}`); } catch (err) { console.error('[流量监控] 告警邮件发送失败:', err.message);