new changes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""YakPanel - SSL/Domains API - Let's Encrypt via certbot"""
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -11,11 +12,10 @@ from typing import Optional
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.config import get_runtime_config
|
||||
from app.core.utils import environment_with_system_path
|
||||
from app.core.utils import environment_with_system_path, read_file, nginx_reload_all_known, nginx_binary_candidates
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.models.site import Site, Domain
|
||||
from app.core.utils import exec_shell_sync
|
||||
from app.services.site_service import regenerate_site_vhost
|
||||
|
||||
router = APIRouter(prefix="/ssl", tags=["ssl"])
|
||||
@@ -120,25 +120,9 @@ async def _le_hostnames_for_domain_row(db: AsyncSession, dom_row: Optional[Domai
|
||||
return out if out else ([primary] if primary else [])
|
||||
|
||||
|
||||
def _reload_panel_and_common_nginx() -> None:
|
||||
def _reload_panel_and_common_nginx() -> tuple[bool, str]:
|
||||
"""Reload nginx so new vhost (ACME path) is live before certbot HTTP-01."""
|
||||
cfg = get_runtime_config()
|
||||
seen: set[str] = set()
|
||||
binaries: list[str] = []
|
||||
panel_ngx = os.path.join(cfg.get("setup_path") or "", "nginx", "sbin", "nginx")
|
||||
if os.path.isfile(panel_ngx):
|
||||
binaries.append(panel_ngx)
|
||||
seen.add(os.path.realpath(panel_ngx))
|
||||
for alt in ("/usr/sbin/nginx", "/usr/bin/nginx", "/usr/local/nginx/sbin/nginx"):
|
||||
if not os.path.isfile(alt):
|
||||
continue
|
||||
rp = os.path.realpath(alt)
|
||||
if rp in seen:
|
||||
continue
|
||||
binaries.append(alt)
|
||||
seen.add(rp)
|
||||
for ngx in binaries:
|
||||
exec_shell_sync(f'"{ngx}" -t && "{ngx}" -s reload', timeout=60)
|
||||
return nginx_reload_all_known(timeout=60)
|
||||
|
||||
|
||||
@router.get("/domains")
|
||||
@@ -199,7 +183,12 @@ async def ssl_request_cert(
|
||||
status_code=500,
|
||||
detail="Cannot refresh nginx vhost before certificate request: " + str(regen_pre.get("msg", "")),
|
||||
)
|
||||
_reload_panel_and_common_nginx()
|
||||
ok_ngx, err_ngx = _reload_panel_and_common_nginx()
|
||||
if not ok_ngx:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Nginx test/reload failed before certificate request (fix config, then retry): " + err_ngx,
|
||||
)
|
||||
|
||||
challenge_dir = os.path.join(webroot_norm, ".well-known", "acme-challenge")
|
||||
try:
|
||||
@@ -278,6 +267,118 @@ async def ssl_request_cert(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/diagnostics")
|
||||
async def ssl_diagnostics(current_user: User = Depends(get_current_user)):
|
||||
"""
|
||||
Help debug HTTP vs HTTPS: compares panel-written vhosts with what nginx -T actually loads.
|
||||
ERR_CONNECTION_REFUSED on 443 usually means no listen 443 in the active nginx, or a firewall.
|
||||
"""
|
||||
cfg = get_runtime_config()
|
||||
setup_abs = os.path.abspath((cfg.get("setup_path") or "").strip() or ".")
|
||||
vhost_dir = os.path.join(setup_abs, "panel", "vhost", "nginx")
|
||||
include_snippet = "include " + vhost_dir.replace(os.sep, "/") + "/*.conf;"
|
||||
|
||||
vhost_summaries: list[dict] = []
|
||||
if os.path.isdir(vhost_dir):
|
||||
try:
|
||||
names = sorted(os.listdir(vhost_dir))
|
||||
except OSError:
|
||||
names = []
|
||||
for fn in names:
|
||||
if not fn.endswith(".conf") or fn.startswith("."):
|
||||
continue
|
||||
fp = os.path.join(vhost_dir, fn)
|
||||
if not os.path.isfile(fp):
|
||||
continue
|
||||
body = read_file(fp) or ""
|
||||
vhost_summaries.append({
|
||||
"file": fn,
|
||||
"has_listen_80": bool(re.search(r"\blisten\s+80\b", body)),
|
||||
"has_listen_443": bool(re.search(r"\blisten\s+.*443", body)),
|
||||
"has_ssl_directives": "ssl_certificate" in body,
|
||||
})
|
||||
|
||||
any_vhost_443 = any(
|
||||
v.get("has_listen_443") and v.get("has_ssl_directives") for v in vhost_summaries
|
||||
)
|
||||
effective_listen_443 = False
|
||||
panel_include_in_effective_config = False
|
||||
nginx_t_errors: list[str] = []
|
||||
norm_vhost = vhost_dir.replace(os.sep, "/")
|
||||
env = environment_with_system_path()
|
||||
|
||||
for ngx in nginx_binary_candidates():
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[ngx, "-T"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=25,
|
||||
env=env,
|
||||
)
|
||||
except (FileNotFoundError, OSError, subprocess.TimeoutExpired) as e:
|
||||
nginx_t_errors.append(f"{ngx}: {e}")
|
||||
continue
|
||||
dump = (r.stdout or "") + (r.stderr or "")
|
||||
if r.returncode != 0:
|
||||
nginx_t_errors.append(f"{ngx}: " + (dump.strip()[:800] or f"-T exit {r.returncode}"))
|
||||
continue
|
||||
if re.search(r"\blisten\s+.*443", dump):
|
||||
effective_listen_443 = True
|
||||
if norm_vhost in dump or "panel/vhost/nginx" in dump:
|
||||
panel_include_in_effective_config = True
|
||||
|
||||
hints: list[str] = []
|
||||
if not os.path.isdir(vhost_dir):
|
||||
hints.append(f"The panel vhost directory is missing ({vhost_dir}). Create a website in YakPanel first.")
|
||||
elif not vhost_summaries:
|
||||
hints.append("There are no .conf files under the panel nginx vhost directory.")
|
||||
|
||||
le_live = "/etc/letsencrypt/live"
|
||||
le_present = False
|
||||
if os.path.isdir(le_live):
|
||||
try:
|
||||
le_present = any(
|
||||
n and not n.startswith(".")
|
||||
for n in os.listdir(le_live)
|
||||
)
|
||||
except OSError:
|
||||
le_present = False
|
||||
|
||||
if le_present and vhost_summaries and not any_vhost_443:
|
||||
hints.append(
|
||||
"Let's Encrypt certs exist on this server but panel vhosts do not include an HTTPS (listen 443 ssl) block. "
|
||||
"Regenerate the vhost: edit the site and save, or use Request SSL again."
|
||||
)
|
||||
|
||||
if any_vhost_443 and not effective_listen_443:
|
||||
hints.append(
|
||||
"Your panel .conf files define HTTPS, but nginx -T does not show any listen 443 — the daemon that handles traffic is not loading YakPanel vhosts. "
|
||||
"Add the include line below inside http { } for that nginx (e.g. /etc/nginx/nginx.conf), then nginx -t && reload."
|
||||
)
|
||||
elif vhost_summaries and not panel_include_in_effective_config:
|
||||
hints.append(
|
||||
"If http://domain shows the default 'Welcome to nginx' page, stock nginx is answering and likely does not include YakPanel vhosts. "
|
||||
"Add the include below (or symlink this directory into /etc/nginx/conf.d/)."
|
||||
)
|
||||
|
||||
if effective_listen_443:
|
||||
hints.append(
|
||||
"Loaded nginx configuration includes a 443 listener. If HTTPS still fails, open TCP port 443 on the OS firewall and cloud/VPS security group."
|
||||
)
|
||||
|
||||
return {
|
||||
"vhost_dir": vhost_dir,
|
||||
"include_snippet": include_snippet,
|
||||
"vhosts": vhost_summaries,
|
||||
"any_vhost_listen_ssl": any_vhost_443,
|
||||
"nginx_effective_listen_443": effective_listen_443,
|
||||
"panel_vhost_path_in_nginx_t": panel_include_in_effective_config,
|
||||
"nginx_t_probe_errors": nginx_t_errors,
|
||||
"hints": hints,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/certificates")
|
||||
async def ssl_list_certificates(current_user: User = Depends(get_current_user)):
|
||||
"""List existing Let's Encrypt certificates"""
|
||||
|
||||
Reference in New Issue
Block a user