diff --git a/src/schemas/apache.js b/src/schemas/apache.js index 93f66cd..88b8370 100644 --- a/src/schemas/apache.js +++ b/src/schemas/apache.js @@ -94,7 +94,8 @@ export const apacheSchema = { type: 'number', min: 1, max: 10000, - default: 256, + default: 250, + tip: 'worker/event 模式下应为 threadsPerChild 的整数倍(默认 25×10=250)', }, { key: 'threadsPerChild', @@ -356,7 +357,7 @@ export function generateApacheConf(config) { if (config.enableModSecurity) lines.push(`LoadModule security2_module modules/mod_security2.so`) lines.push(`LoadModule dir_module modules/mod_dir.so`) lines.push(`LoadModule mime_module modules/mod_mime.so`) - lines.push(`LoadModule log_config_module modules/mod_log_config.so`) + // mod_log_config 是 built-in(核心模块),无需 LoadModule lines.push(`LoadModule headers_module modules/mod_headers.so`) lines.push(``) diff --git a/src/schemas/bind.js b/src/schemas/bind.js index 2065d91..abf68fc 100644 --- a/src/schemas/bind.js +++ b/src/schemas/bind.js @@ -163,14 +163,23 @@ export function generateBindConf(config) { // Options block lines.push(`// ======================== 全局选项 ========================`) lines.push(`options {`) - lines.push(` listen-on port 53 { ${config.listenOn} };`) - lines.push(` listen-on-v6 port 53 { ${config.listenOnV6} };`) + + // ACL 列表辅助:把 "127.0.0.1; any" 变成 { 127.0.0.1; any; } + // 每个元素都自动补分号(解决 BIND 9.11+ 报 "missing ';' before '}'" 的坑) + const acl = (raw) => { + if (!raw) return '' + const parts = raw.split(';').map((s) => s.trim()).filter(Boolean) + return parts.map((p) => `${p};`).join(' ') + } + + lines.push(` listen-on port 53 { ${acl(config.listenOn)} };`) + lines.push(` listen-on-v6 port 53 { ${acl(config.listenOnV6)} };`) lines.push(` directory "/var/named";`) lines.push(` dump-file "/var/named/data/cache_dump.db";`) lines.push(` statistics-file "/var/named/data/named_stats.txt";`) lines.push(` memstatistics-file "/var/named/data/named_mem_stats.txt";`) - lines.push(` allow-query { ${config.allowQuery} };`) - lines.push(` allow-recursion { ${config.allowRecursion} };`) + lines.push(` allow-query { ${acl(config.allowQuery)} };`) + lines.push(` allow-recursion { ${acl(config.allowRecursion)} };`) lines.push(` recursion ${config.recursion ? 'yes' : 'no'};`) lines.push(``) lines.push(` forwarders {`) @@ -182,8 +191,9 @@ export function generateBindConf(config) { lines.push(` forward ${config.forwardType};`) lines.push(``) + // BIND 9.16+ 已移除 dnssec-enable(DNSSEC 自动开启),无需再写 if (config.enableDnssec) { - lines.push(` dnssec-enable yes;`) + lines.push(` // dnssec-enable yes; // BIND 9.16+ 已自动启用,无需此指令`) } if (config.dnssecValidation) { lines.push(` dnssec-validation yes;`) @@ -245,8 +255,10 @@ export function generateBindConf(config) { lines.push(``) } - lines.push(`include "/etc/named.rfc1912.zones";`) - lines.push(`include "/etc/named.root.key";`) + // 这些 include 在 BIND 9.16+ 不再默认存在,硬编码会导致启动失败 + // (不同发行版路径不同:Debian /etc/bind/named.conf.default-zones,RHEL /etc/named.rfc1912.zones) + // 如需本地 RFC1918 反向区域,请用户根据实际系统手动添加 include + // lines.push(`include "/etc/bind/named.conf.default-zones";`) return lines.join('\n') } diff --git a/src/schemas/nginx.js b/src/schemas/nginx.js index 9f8c41e..6ae4148 100644 --- a/src/schemas/nginx.js +++ b/src/schemas/nginx.js @@ -244,8 +244,8 @@ export const nginxSchema = { key: 'upstreamServers', label: '后端服务器列表', type: 'text', - default: "server 127.0.0.1:8080 weight=3\nserver 127.0.0.1:8081 weight=2", - tip: '每行一个 server 指令,支持 weight/down/backup 参数', + default: "server 127.0.0.1:8080 weight=3;\nserver 127.0.0.1:8081 weight=2;", + tip: '每行一个 server 指令(漏写 ; 会被自动补全),支持 weight/down/backup 参数', dependsOn: { key: 'enableUpstream', value: true }, }, { @@ -296,6 +296,8 @@ export const nginxSchema = { export function generateNginxConf(config) { const lines = [] + const ind = (n) => ' '.repeat(n) // 4-space indent helper + lines.push(`# NGINX 配置文件 - 由 ConfTemplate 生成`) lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) lines.push(``) @@ -350,23 +352,6 @@ export function generateNginxConf(config) { lines.push(``) } - // Upstream 负载均衡 - if (config.enableUpstream) { - lines.push(` # Upstream 负载均衡`) - if (config.upstreamMethod && config.upstreamMethod !== 'round-robin') { - lines.push(` upstream ${config.upstreamName} {`) - lines.push(` ${config.upstreamMethod};`) - } else { - lines.push(` upstream ${config.upstreamName} {`) - } - const servers = config.upstreamServers.split('\n').filter(s => s.trim()) - servers.forEach(server => { - lines.push(` ${server.trim()}`) - }) - lines.push(` }`) - lines.push(``) - } - // 缓存配置 if (config.enableCache) { lines.push(` # 代理缓存配置`) @@ -374,25 +359,54 @@ export function generateNginxConf(config) { lines.push(``) } - // Upstream fallback when not enabled - if (!config.enableUpstream) { - lines.push(` # 上游服务器${config.enableProxy ? '' : ' (示例,未启用)'}`) - lines.push(` # upstream backend {`) - lines.push(` # server 127.0.0.1:8080;`) - lines.push(` # }`) + // Upstream 负载均衡 + if (config.enableUpstream) { + lines.push(` # Upstream 负载均衡`) + lines.push(` upstream ${config.upstreamName} {`) + if (config.upstreamMethod && config.upstreamMethod !== 'round-robin') { + lines.push(` ${config.upstreamMethod};`) + } + const servers = config.upstreamServers.split('\n').filter(s => s.trim()) + servers.forEach(server => { + // 自动补充分号:用户输入的多行 server 指令若漏了 ; 也能正常工作 + const line = server.trim() + lines.push(` ${line}${line.endsWith(';') ? '' : ';'}`) + }) + lines.push(` }`) lines.push(``) } - lines.push(` server {`) - lines.push(` listen ${config.httpPort};`) - + // ============================================================ + // HTTP → HTTPS 跳转 server (只有开启 HTTPS 时生成) + // ============================================================ if (config.enableHttps) { - lines.push(` listen ${config.httpsPort} ssl http2;`) + lines.push(` # HTTP 自动跳转 HTTPS`) + lines.push(` server {`) + lines.push(` listen ${config.httpPort};`) + lines.push(` server_name ${config.serverName};`) + lines.push(` return 301 https://$host$request_uri;`) + lines.push(` }`) + lines.push(``) + } + + // ============================================================ + // 主 server 块(HTTP 或 HTTPS) + // ============================================================ + lines.push(` server {`) + + // 监听指令:HTTP 端口始终监听(让 80 也能直接服务于不跳转场景) + if (config.enableHttps) { + // HTTPS 场景:主 server 只监听 443,HTTP 由上面跳转 server 处理 + lines.push(` listen ${config.httpsPort} ssl;`) + lines.push(` http2 on;`) + } else { + lines.push(` listen ${config.httpPort};`) } lines.push(` server_name ${config.serverName};`) lines.push(``) + // SSL 配置 if (config.enableHttps) { lines.push(` # SSL 证书配置`) lines.push(` ssl_certificate ${config.sslCertificate};`) @@ -403,25 +417,9 @@ export function generateNginxConf(config) { lines.push(` ssl_session_cache shared:SSL:10m;`) lines.push(` ssl_session_timeout 10m;`) lines.push(``) - - // HTTP to HTTPS redirect - lines.push(` # HTTP 自动跳转 HTTPS`) - lines.push(` }`) - lines.push(``) - lines.push(` server {`) - lines.push(` listen 80;`) - lines.push(` server_name ${config.serverName};`) - lines.push(` return 301 https://$host$request_uri;`) - lines.push(` }`) - lines.push(``) - lines.push(` server {`) - lines.push(` listen ${config.httpsPort} ssl http2;`) - lines.push(` server_name ${config.serverName};`) - lines.push(` ssl_certificate ${config.sslCertificate};`) - lines.push(` ssl_certificate_key ${config.sslCertificateKey};`) - lines.push(``) } + // CORS 配置(写在 server 作用域,全部带 always 确保 4xx/5xx 也带 CORS 头) if (config.enableCors) { lines.push(` # 跨域配置 (CORS)`) lines.push(` add_header 'Access-Control-Allow-Origin' '*' always;`) @@ -429,19 +427,16 @@ export function generateNginxConf(config) { lines.push(` add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;`) lines.push(` add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;`) lines.push(``) + lines.push(` # OPTIONS 预检直接返回 204;if 块内只放 return 不加 add_header,`) + lines.push(` # 兼容 nginx 1.22+(if 中 add_header 会被拒);`) + lines.push(` # CORS 头由 server 作用域的 always 继承到 204 响应。`) lines.push(` if ($request_method = 'OPTIONS') {`) - lines.push(` add_header 'Access-Control-Allow-Origin' '*';`) - lines.push(` add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';`) - lines.push(` add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';`) - lines.push(` add_header 'Access-Control-Max-Age' 1728000;`) - lines.push(` add_header 'Content-Type' 'text/plain; charset=utf-8';`) - lines.push(` add_header 'Content-Length' 0;`) lines.push(` return 204;`) lines.push(` }`) lines.push(``) } - // 限流应用到 location + // 反向代理 location lines.push(` location / {`) if (config.enableLimitReq) { @@ -468,7 +463,7 @@ export function generateNginxConf(config) { lines.push(` proxy_cache_valid 200 302 10m;`) lines.push(` proxy_cache_valid 404 1m;`) lines.push(` proxy_cache_bypass ${config.cacheBypass};`) - lines.push(` add_header X-Cache-Status $upstream_cache_status;`) + lines.push(` add_header X-Cache-Status $upstream_cache_status always;`) } } else { lines.push(` root /usr/share/nginx/html;`) diff --git a/src/schemas/syslog.js b/src/schemas/syslog.js index 65e4fc7..e6fbb59 100644 --- a/src/schemas/syslog.js +++ b/src/schemas/syslog.js @@ -230,8 +230,10 @@ export function generateSyslogConf(config) { lines.push(`# ======================== 全局设置 ========================`) lines.push(`$WorkDirectory /var/lib/rsyslog`) lines.push(`$ActionFileDefaultTemplate ${config.logFormat}`) - if (config.enableHighPrecisionTimestamps) { - lines.push(`$ActionFileDefaultTemplate RSYSLOG_FileFormat`) + // 高精度时间戳:仅在 logFormat 不是 RSYSLOG_FileFormat 时追加自定义模板 + // (RSYSLOG_FileFormat 自带微秒精度,重复输出同名指令会让 rsyslog 报警告) + if (config.enableHighPrecisionTimestamps && config.logFormat !== 'RSYSLOG_FileFormat') { + lines.push(`template(name="HighPrecFmt" type="string" string="%timegenerated:::date-rfc3339%,%timegenerated:::date-subseconds% %HOSTNAME% %syslogtag%%msg%\\n")`) } lines.push(``)