From cb1566c4003d2f3f2b0c1bb4b749b4c2c5e93c3b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 7 Aug 2026 13:39:23 +0800 Subject: [PATCH] feat(nginx): add ssl_cert_source/existing paths for HTTPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the SSL block always generated a self-signed cert at a hardcoded /etc/nginx/ssl/.{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/.{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'. --- generators/middleware.py | 56 ++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/generators/middleware.py b/generators/middleware.py index e99c7b1..816bf4e 100644 --- a/generators/middleware.py +++ b/generators/middleware.py @@ -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: - 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') + 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'