new changes

This commit is contained in:
Niranjan
2026-04-07 11:42:19 +05:30
parent cc45fac342
commit 10236a4cd4
14 changed files with 360 additions and 52 deletions

View File

@@ -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"""