feat(nginx): add ssl_cert_source/existing paths for HTTPS

Previously the SSL block always generated a self-signed cert at a
hardcoded /etc/nginx/ssl/<server_name>.{crt,key}. That worked for
test/internal use but is unusable for production HTTPS (Let's Encrypt
/ commercial certs): users couldn't point nginx at their own files
without editing the script after generation.

Add three fields (defaults preserve prior behavior):

  ssl_cert_source  select  self_signed|existing
                              self_signed (default)  - existing behavior,
                                  openssl req generates the cert at
                                  /etc/nginx/ssl/<server_name>.{crt,key}.
                              existing                - skip openssl entirely,
                                  use user-supplied ssl_cert_path /
                                  ssl_key_path.

  ssl_cert_path    text    absolute path to fullchain/cert (existing only)
  ssl_key_path     text    absolute path to private key (existing only)

Validation (raised at render time, before script generation):
  - ssl_cert_source=existing requires both paths
  - both paths must start with '/' (absolute)

Runtime behavior when existing files aren't readable (e.g. certbot
hasn't run yet): script emits a clear WARN with a hint to deploy the
cert first, and continues. nginx -t will fail in that case but the
operator can re-run the script after the cert is in place, or run
'nginx -s reload' directly.

Verified:
  - self_signed: full pipeline OK, real openssl produces a valid cert,
    real nginx -t passes.
  - existing + Let's Encrypt style paths pointing at real certs:
    nginx -t passes; no openssl invocation in the script.
  - existing + cert files absent: WARN emitted on stderr, script exits 0,
    nginx -t skipped (would fail by design — operator runs certbot then
    reloads).
  - existing with missing path: ValueError at render time.
  - existing with relative path: ValueError at render time.
  - All 50 generators still pass 'bash -n'.
This commit is contained in:
Your Name
2026-08-07 13:39:23 +08:00
parent 1a328864f5
commit cb1566c400
+43 -3
View File
@@ -389,8 +389,18 @@ class Nginx(Generator):
Field("root_path", "网站根目录", "text", default="/var/www/html",
help="静态文件根目录,留空使用默认。"),
Field("enable_ssl", "启用 HTTPS", "checkbox", default="no",
help="启用后会在 443 监听并生成自签名证书"),
help="启用后会在 443 监听并生成 SSL 配置"),
Field("ssl_port", "HTTPS 端口", "number", default="443", min_=1, max_=65535),
Field("ssl_cert_source", "SSL 证书来源", "select", default="self_signed",
options=["self_signed", "existing"],
help="self_signed=脚本生成自签证书(浏览器告警,适合测试);"
"existing=使用已有证书(Let's Encrypt/商业证书)"),
Field("ssl_cert_path", "证书路径", "text", default="",
placeholder="/etc/letsencrypt/live/example.com/fullchain.pem",
help="ssl_cert_source=existing 时必填。绝对路径。"),
Field("ssl_key_path", "私钥路径", "text", default="",
placeholder="/etc/letsencrypt/live/example.com/privkey.pem",
help="ssl_cert_source=existing 时必填。绝对路径。"),
Field("enable_proxy", "启用反向代理", "checkbox", default="no"),
Field("proxy_pass", "Proxy Pass 地址", "text", default="http://127.0.0.1:8080",
placeholder="http://127.0.0.1:8080",
@@ -407,11 +417,27 @@ class Nginx(Generator):
root = p.get("root_path", "/var/www/html")
ssl = bool_str(p.get("enable_ssl"))
ssl_port = str(p.get("ssl_port", "443"))
ssl_src = p.get("ssl_cert_source", "self_signed")
ssl_cert_path = p.get("ssl_cert_path", "").strip()
ssl_key_path = p.get("ssl_key_path", "").strip()
proxy = bool_str(p.get("enable_proxy"))
proxy_pass = p.get("proxy_pass", "http://127.0.0.1:8080")
cmbs = p.get("client_max_body_size", "50m")
wp = p.get("worker_processes", "auto")
# SSL cert resolution
if ssl:
if ssl_src == "existing":
if not ssl_cert_path or not ssl_key_path:
raise ValueError(
"ssl_cert_source=existing 时必须同时填写 ssl_cert_path "
"和 ssl_key_path"
)
if not ssl_cert_path.startswith("/") or not ssl_key_path.startswith("/"):
raise ValueError(
"ssl_cert_path / ssl_key_path 必须是绝对路径(以 / 开头)"
)
out = [bash_header(self.title)]
out.append('log "Installing nginx..."')
out.append('PKG_INSTALL nginx openssl curl')
@@ -462,18 +488,32 @@ class Nginx(Generator):
out.append(f"cat > /etc/nginx/conf.d/{sn}.conf <<'CONF_EOF'\n{siteconf}CONF_EOF")
out.append('rm -f /etc/nginx/sites-enabled/default')
if ssl:
if ssl_src == "self_signed":
# Default path: generate a self-signed cert at /etc/nginx/ssl/.
out.append('log "Generating self-signed certificate..."')
out.append(f'openssl req -x509 -nodes -newkey rsa:2048 -days 365 \\\n'
f' -keyout /etc/nginx/ssl/{sn}.key \\\n'
f' -out /etc/nginx/ssl/{sn}.crt \\\n'
f' -subj "/CN={sn}" 2>/dev/null')
cert_path = f'/etc/nginx/ssl/{sn}.crt'
key_path = f'/etc/nginx/ssl/{sn}.key'
else: # existing
# Use user-provided paths. We do NOT overwrite their files; if
# the cert/key aren't readable, log a clear warning so they
# know to deploy the cert (e.g. run certbot) first.
out.append('log "Using user-provided SSL certificate..."')
out.append(f'for f in {ssl_cert_path} {ssl_key_path}; do\n'
f' [ -r "$f" ] || warn "SSL file not readable: $f (deploy your cert first, e.g. certbot)"\n'
f'done')
cert_path = ssl_cert_path
key_path = ssl_key_path
out.append(f"cat > /etc/nginx/conf.d/{sn}-ssl.conf <<'CONF_EOF'\n"
'server {\n'
f' listen {ssl_port} ssl;\n'
f' listen [::]:{ssl_port} ssl;\n'
f' server_name {sn};\n'
f' ssl_certificate /etc/nginx/ssl/{sn}.crt;\n'
f' ssl_certificate_key /etc/nginx/ssl/{sn}.key;\n'
f' ssl_certificate {cert_path};\n'
f' ssl_certificate_key {key_path};\n'
f' root {root};\n'
f' client_max_body_size {cmbs};\n'
' index index.html;\n'