new changes

This commit is contained in:
Niranjan
2026-04-07 10:35:44 +05:30
parent 097087519b
commit 88424b8836
5 changed files with 60 additions and 13 deletions

View File

@@ -14,6 +14,7 @@ from app.core.utils import environment_with_system_path
from app.api.auth import get_current_user from app.api.auth import get_current_user
from app.models.user import User from app.models.user import User
from app.models.site import Site, Domain from app.models.site import Site, Domain
from app.core.utils import exec_shell_sync
from app.services.site_service import regenerate_site_vhost from app.services.site_service import regenerate_site_vhost
router = APIRouter(prefix="/ssl", tags=["ssl"]) router = APIRouter(prefix="/ssl", tags=["ssl"])
@@ -97,6 +98,27 @@ def _certbot_missing_message() -> str:
) )
def _reload_panel_and_common_nginx() -> None:
"""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)
@router.get("/domains") @router.get("/domains")
async def ssl_domains( async def ssl_domains(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
@@ -144,6 +166,25 @@ async def ssl_request_cert(
raise HTTPException(status_code=400, detail="Webroot must be under www_root or setup_path") raise HTTPException(status_code=400, detail="Webroot must be under www_root or setup_path")
dom = body.domain.split(":")[0].strip() dom = body.domain.split(":")[0].strip()
webroot_norm = webroot_abs.rstrip(os.sep)
result_dom = await db.execute(select(Domain).where(Domain.name == dom).limit(1))
dom_row = result_dom.scalar_one_or_none()
if dom_row:
regen_pre = await regenerate_site_vhost(db, dom_row.pid)
if not regen_pre.get("status"):
raise HTTPException(
status_code=500,
detail="Cannot refresh nginx vhost before certificate request: " + str(regen_pre.get("msg", "")),
)
_reload_panel_and_common_nginx()
challenge_dir = os.path.join(webroot_norm, ".well-known", "acme-challenge")
try:
os.makedirs(challenge_dir, mode=0o755, exist_ok=True)
except OSError as e:
raise HTTPException(status_code=500, detail=f"Cannot create ACME webroot directory: {e}") from e
prefix = _certbot_command() prefix = _certbot_command()
if not prefix: if not prefix:
raise HTTPException(status_code=500, detail=_certbot_missing_message()) raise HTTPException(status_code=500, detail=_certbot_missing_message())
@@ -152,7 +193,7 @@ async def ssl_request_cert(
"certonly", "certonly",
"--webroot", "--webroot",
"-w", "-w",
body.webroot, webroot_norm,
"-d", "-d",
dom, dom,
"--non-interactive", "--non-interactive",
@@ -180,10 +221,13 @@ async def ssl_request_cert(
if proc.returncode != 0: if proc.returncode != 0:
msg = (proc.stderr or proc.stdout or "").strip() or f"certbot exited with code {proc.returncode}" msg = (proc.stderr or proc.stdout or "").strip() or f"certbot exited with code {proc.returncode}"
raise HTTPException(status_code=500, detail=msg[:8000]) hint = (
" Check: DNS A/AAAA for this domain points to this server; port 80 is reachable; "
"the website is enabled in YakPanel; nginx on port 80 loads this sites vhost (same server as panel nginx if used)."
)
raise HTTPException(status_code=500, detail=(msg + hint)[:8000])
result = await db.execute(select(Domain).where(Domain.name == dom).limit(1)) row = dom_row
row = result.scalar_one_or_none()
if row: if row:
regen = await regenerate_site_vhost(db, row.pid) regen = await regenerate_site_vhost(db, row.pid)
if not regen.get("status"): if not regen.get("status"):

View File

@@ -126,9 +126,10 @@ def _build_ssl_server_block(
f" error_page 404 /404.html;\n" f" error_page 404 /404.html;\n"
f" error_page 502 /502.html;\n" f" error_page 502 /502.html;\n"
f" location ^~ /.well-known/acme-challenge/ {{\n" f" location ^~ /.well-known/acme-challenge/ {{\n"
f" root {root_path};\n"
f' default_type "text/plain";\n' f' default_type "text/plain";\n'
f" allow all;\n" f" allow all;\n"
f" try_files $uri =404;\n" f" access_log off;\n"
f" }}\n" f" }}\n"
f"{redirect_block}\n" f"{redirect_block}\n"
r" location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {" + "\n" r" location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {" + "\n"
@@ -480,16 +481,17 @@ async def set_site_status(db: AsyncSession, site_id: int, status: int) -> dict:
async def regenerate_site_vhost(db: AsyncSession, site_id: int) -> dict: async def regenerate_site_vhost(db: AsyncSession, site_id: int) -> dict:
"""Regenerate nginx vhost for a site (e.g. after redirect changes).""" """Regenerate nginx vhost for a site (e.g. after redirect changes or before LE validation)."""
result = await db.execute(select(Site).where(Site.id == site_id)) result = await db.execute(select(Site).where(Site.id == site_id))
site = result.scalar_one_or_none() site = result.scalar_one_or_none()
if not site: if not site:
return {"status": False, "msg": "Site not found"} return {"status": False, "msg": "Site not found"}
cfg = get_runtime_config() cfg = get_runtime_config()
vhost_path = os.path.join(cfg["setup_path"], "panel", "vhost", "nginx") conf_path, disabled_path = _vhost_path(site.name)
conf_path = os.path.join(vhost_path, f"{site.name}.conf") if site.status == 1:
if site.status != 1: write_path = conf_path
return {"status": True, "msg": "Site disabled, vhost not active"} else:
write_path = disabled_path if os.path.isfile(disabled_path) else conf_path
panel_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) panel_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
template_path = os.path.join(panel_root, "webserver", "templates", "nginx_site.conf") template_path = os.path.join(panel_root, "webserver", "templates", "nginx_site.conf")
if not os.path.exists(template_path): if not os.path.exists(template_path):
@@ -507,7 +509,7 @@ async def regenerate_site_vhost(db: AsyncSession, site_id: int) -> dict:
content = _render_vhost( content = _render_vhost(
template, server_names, site.path, cfg["www_logs"], site.name, php_ver, fhttps, redirects, le_hosts template, server_names, site.path, cfg["www_logs"], site.name, php_ver, fhttps, redirects, le_hosts
) )
write_file(conf_path, content) write_file(write_path, content)
nginx_bin = os.path.join(cfg["setup_path"], "nginx", "sbin", "nginx") nginx_bin = os.path.join(cfg["setup_path"], "nginx", "sbin", "nginx")
if os.path.exists(nginx_bin): if os.path.exists(nginx_bin):
exec_shell_sync(f"{nginx_bin} -t && {nginx_bin} -s reload") exec_shell_sync(f"{nginx_bin} -t && {nginx_bin} -s reload")

View File

@@ -8,11 +8,12 @@ server {
error_page 404 /404.html; error_page 404 /404.html;
error_page 502 /502.html; error_page 502 /502.html;
# ACME HTTP-01 (Let's Encrypt). Prefix match wins over regex locations. # ACME HTTP-01 (Let's Encrypt). Prefix match beats regex; explicit root; no try_files so server error_page cannot mask failures.
location ^~ /.well-known/acme-challenge/ { location ^~ /.well-known/acme-challenge/ {
root {ROOT_PATH};
default_type "text/plain"; default_type "text/plain";
allow all; allow all;
try_files $uri =404; access_log off;
} }
# Force HTTPS (skipped for ACME — see if block) # Force HTTPS (skipped for ACME — see if block)