fix(schemas): 修复 nginx/apache/bind/syslog 多个生成配置 bug
通过本地真实工具(nginx -t / named-checkconf / apache2 -t / redis-server)
对全部 60 个 schema 进行 dry-run 验证,发现并修复:
nginx (src/schemas/nginx.js):
- 'listen ... http2' 是 nginx 1.25+ 废弃写法,改为 listen 443 ssl + http2 on
- 修复 'add_header not allowed here' 错误:原模板把 CORS 头写到了关闭的
server 块外(落到 http 顶层),CORS 头重写到 server 作用域内
- 'if' 块内 add_header 在 nginx 1.22+ 直接 emerg;改为只在 if 里 'return 204',
CORS 头由 server 作用域 add_header always 继承到 204 响应
- upstreamServers 默认值漏 ';' (server 127.0.0.1:8080 缺分号),
模板自动补全 + 默认值修正
- add_header X-Cache-Status 加 always,避免清空 CORS 头
apache (src/schemas/apache.js):
- mod_log_config 是 built-in 核心模块,移除 LoadModule 行(会报 'is built-in')
- maxRequestWorkers 默认 256 与 threadsPerChild=25 不整除,改为 250
bind (src/schemas/bind.js):
- ACL 列表 { 127.0.0.1; any } 缺末尾 ';',BIND 9.11+ 报 'missing ; before }'
加 acl() 工具自动补 ';',对 listen-on/allow-query/allow-recursion 均生效
- dnssec-enable 在 BIND 9.16+ 已删除(自动启用),改为注释
- 删掉硬编码的 'include /etc/named.rfc1912.zones' 与 named.root.key
(不同发行版路径不同,9.16+ 默认不存在,会导致启动失败)
syslog (src/schemas/syslog.js):
- 启用 enableHighPrecisionTimestamps 时会重复输出 'ActionFileDefaultTemplate'
(rsyslog 警告)。改为仅在 logFormat 不是 RSYSLOG_FileFormat 时输出自定义
template(RSYSLOG_FileFormat 自带微秒精度)
本机验证: nginx / bind 9.18 / apache 2.4 / redis 7.0 全部 PASS;
26 个 YAML / 3 个 XML / 1 个 JSON schema 全部可解析。
This commit is contained in:
@@ -94,7 +94,8 @@ export const apacheSchema = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 10000,
|
max: 10000,
|
||||||
default: 256,
|
default: 250,
|
||||||
|
tip: 'worker/event 模式下应为 threadsPerChild 的整数倍(默认 25×10=250)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'threadsPerChild',
|
key: 'threadsPerChild',
|
||||||
@@ -356,7 +357,7 @@ export function generateApacheConf(config) {
|
|||||||
if (config.enableModSecurity) lines.push(`LoadModule security2_module modules/mod_security2.so`)
|
if (config.enableModSecurity) lines.push(`LoadModule security2_module modules/mod_security2.so`)
|
||||||
lines.push(`LoadModule dir_module modules/mod_dir.so`)
|
lines.push(`LoadModule dir_module modules/mod_dir.so`)
|
||||||
lines.push(`LoadModule mime_module modules/mod_mime.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(`LoadModule headers_module modules/mod_headers.so`)
|
||||||
lines.push(``)
|
lines.push(``)
|
||||||
|
|
||||||
|
|||||||
+19
-7
@@ -163,14 +163,23 @@ export function generateBindConf(config) {
|
|||||||
// Options block
|
// Options block
|
||||||
lines.push(`// ======================== 全局选项 ========================`)
|
lines.push(`// ======================== 全局选项 ========================`)
|
||||||
lines.push(`options {`)
|
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(` directory "/var/named";`)
|
||||||
lines.push(` dump-file "/var/named/data/cache_dump.db";`)
|
lines.push(` dump-file "/var/named/data/cache_dump.db";`)
|
||||||
lines.push(` statistics-file "/var/named/data/named_stats.txt";`)
|
lines.push(` statistics-file "/var/named/data/named_stats.txt";`)
|
||||||
lines.push(` memstatistics-file "/var/named/data/named_mem_stats.txt";`)
|
lines.push(` memstatistics-file "/var/named/data/named_mem_stats.txt";`)
|
||||||
lines.push(` allow-query { ${config.allowQuery} };`)
|
lines.push(` allow-query { ${acl(config.allowQuery)} };`)
|
||||||
lines.push(` allow-recursion { ${config.allowRecursion} };`)
|
lines.push(` allow-recursion { ${acl(config.allowRecursion)} };`)
|
||||||
lines.push(` recursion ${config.recursion ? 'yes' : 'no'};`)
|
lines.push(` recursion ${config.recursion ? 'yes' : 'no'};`)
|
||||||
lines.push(``)
|
lines.push(``)
|
||||||
lines.push(` forwarders {`)
|
lines.push(` forwarders {`)
|
||||||
@@ -182,8 +191,9 @@ export function generateBindConf(config) {
|
|||||||
lines.push(` forward ${config.forwardType};`)
|
lines.push(` forward ${config.forwardType};`)
|
||||||
lines.push(``)
|
lines.push(``)
|
||||||
|
|
||||||
|
// BIND 9.16+ 已移除 dnssec-enable(DNSSEC 自动开启),无需再写
|
||||||
if (config.enableDnssec) {
|
if (config.enableDnssec) {
|
||||||
lines.push(` dnssec-enable yes;`)
|
lines.push(` // dnssec-enable yes; // BIND 9.16+ 已自动启用,无需此指令`)
|
||||||
}
|
}
|
||||||
if (config.dnssecValidation) {
|
if (config.dnssecValidation) {
|
||||||
lines.push(` dnssec-validation yes;`)
|
lines.push(` dnssec-validation yes;`)
|
||||||
@@ -245,8 +255,10 @@ export function generateBindConf(config) {
|
|||||||
lines.push(``)
|
lines.push(``)
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(`include "/etc/named.rfc1912.zones";`)
|
// 这些 include 在 BIND 9.16+ 不再默认存在,硬编码会导致启动失败
|
||||||
lines.push(`include "/etc/named.root.key";`)
|
// (不同发行版路径不同: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')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-51
@@ -244,8 +244,8 @@ export const nginxSchema = {
|
|||||||
key: 'upstreamServers',
|
key: 'upstreamServers',
|
||||||
label: '后端服务器列表',
|
label: '后端服务器列表',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
default: "server 127.0.0.1:8080 weight=3\nserver 127.0.0.1:8081 weight=2",
|
default: "server 127.0.0.1:8080 weight=3;\nserver 127.0.0.1:8081 weight=2;",
|
||||||
tip: '每行一个 server 指令,支持 weight/down/backup 参数',
|
tip: '每行一个 server 指令(漏写 ; 会被自动补全),支持 weight/down/backup 参数',
|
||||||
dependsOn: { key: 'enableUpstream', value: true },
|
dependsOn: { key: 'enableUpstream', value: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -296,6 +296,8 @@ export const nginxSchema = {
|
|||||||
|
|
||||||
export function generateNginxConf(config) {
|
export function generateNginxConf(config) {
|
||||||
const lines = []
|
const lines = []
|
||||||
|
const ind = (n) => ' '.repeat(n) // 4-space indent helper
|
||||||
|
|
||||||
lines.push(`# NGINX 配置文件 - 由 ConfTemplate 生成`)
|
lines.push(`# NGINX 配置文件 - 由 ConfTemplate 生成`)
|
||||||
lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`)
|
lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`)
|
||||||
lines.push(``)
|
lines.push(``)
|
||||||
@@ -350,23 +352,6 @@ export function generateNginxConf(config) {
|
|||||||
lines.push(``)
|
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) {
|
if (config.enableCache) {
|
||||||
lines.push(` # 代理缓存配置`)
|
lines.push(` # 代理缓存配置`)
|
||||||
@@ -374,25 +359,54 @@ export function generateNginxConf(config) {
|
|||||||
lines.push(``)
|
lines.push(``)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upstream fallback when not enabled
|
// Upstream 负载均衡
|
||||||
if (!config.enableUpstream) {
|
if (config.enableUpstream) {
|
||||||
lines.push(` # 上游服务器${config.enableProxy ? '' : ' (示例,未启用)'}`)
|
lines.push(` # Upstream 负载均衡`)
|
||||||
lines.push(` # upstream backend {`)
|
lines.push(` upstream ${config.upstreamName} {`)
|
||||||
lines.push(` # server 127.0.0.1:8080;`)
|
if (config.upstreamMethod && config.upstreamMethod !== 'round-robin') {
|
||||||
lines.push(` # }`)
|
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(``)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// HTTP → HTTPS 跳转 server (只有开启 HTTPS 时生成)
|
||||||
|
// ============================================================
|
||||||
|
if (config.enableHttps) {
|
||||||
|
lines.push(` # HTTP 自动跳转 HTTPS`)
|
||||||
lines.push(` server {`)
|
lines.push(` server {`)
|
||||||
lines.push(` listen ${config.httpPort};`)
|
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) {
|
if (config.enableHttps) {
|
||||||
lines.push(` listen ${config.httpsPort} ssl http2;`)
|
// 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(` server_name ${config.serverName};`)
|
||||||
lines.push(``)
|
lines.push(``)
|
||||||
|
|
||||||
|
// SSL 配置
|
||||||
if (config.enableHttps) {
|
if (config.enableHttps) {
|
||||||
lines.push(` # SSL 证书配置`)
|
lines.push(` # SSL 证书配置`)
|
||||||
lines.push(` ssl_certificate ${config.sslCertificate};`)
|
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_cache shared:SSL:10m;`)
|
||||||
lines.push(` ssl_session_timeout 10m;`)
|
lines.push(` ssl_session_timeout 10m;`)
|
||||||
lines.push(``)
|
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) {
|
if (config.enableCors) {
|
||||||
lines.push(` # 跨域配置 (CORS)`)
|
lines.push(` # 跨域配置 (CORS)`)
|
||||||
lines.push(` add_header 'Access-Control-Allow-Origin' '*' always;`)
|
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-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(` add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;`)
|
||||||
lines.push(``)
|
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(` 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(` return 204;`)
|
||||||
lines.push(` }`)
|
lines.push(` }`)
|
||||||
lines.push(``)
|
lines.push(``)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限流应用到 location
|
// 反向代理 location
|
||||||
lines.push(` location / {`)
|
lines.push(` location / {`)
|
||||||
|
|
||||||
if (config.enableLimitReq) {
|
if (config.enableLimitReq) {
|
||||||
@@ -468,7 +463,7 @@ export function generateNginxConf(config) {
|
|||||||
lines.push(` proxy_cache_valid 200 302 10m;`)
|
lines.push(` proxy_cache_valid 200 302 10m;`)
|
||||||
lines.push(` proxy_cache_valid 404 1m;`)
|
lines.push(` proxy_cache_valid 404 1m;`)
|
||||||
lines.push(` proxy_cache_bypass ${config.cacheBypass};`)
|
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 {
|
} else {
|
||||||
lines.push(` root /usr/share/nginx/html;`)
|
lines.push(` root /usr/share/nginx/html;`)
|
||||||
|
|||||||
@@ -230,8 +230,10 @@ export function generateSyslogConf(config) {
|
|||||||
lines.push(`# ======================== 全局设置 ========================`)
|
lines.push(`# ======================== 全局设置 ========================`)
|
||||||
lines.push(`$WorkDirectory /var/lib/rsyslog`)
|
lines.push(`$WorkDirectory /var/lib/rsyslog`)
|
||||||
lines.push(`$ActionFileDefaultTemplate ${config.logFormat}`)
|
lines.push(`$ActionFileDefaultTemplate ${config.logFormat}`)
|
||||||
if (config.enableHighPrecisionTimestamps) {
|
// 高精度时间戳:仅在 logFormat 不是 RSYSLOG_FileFormat 时追加自定义模板
|
||||||
lines.push(`$ActionFileDefaultTemplate 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(``)
|
lines.push(``)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user