new changes
This commit is contained in:
Binary file not shown.
@@ -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 site’s 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"):
|
||||||
|
|||||||
Binary file not shown.
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user