new changes
This commit is contained in:
Binary file not shown.
@@ -7,6 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.core.config import get_runtime_config
|
from app.core.config import get_runtime_config
|
||||||
@@ -98,6 +99,27 @@ def _certbot_missing_message() -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _le_hostnames_for_domain_row(db: AsyncSession, dom_row: Optional[Domain], primary: str) -> list[str]:
|
||||||
|
"""All distinct hostnames for the site (for -d flags). Falls back to primary."""
|
||||||
|
if not dom_row:
|
||||||
|
return [primary] if primary else []
|
||||||
|
result = await db.execute(select(Domain).where(Domain.pid == dom_row.pid).order_by(Domain.id))
|
||||||
|
rows = result.scalars().all()
|
||||||
|
seen: set[str] = set()
|
||||||
|
out: list[str] = []
|
||||||
|
for d in rows:
|
||||||
|
n = (d.name or "").strip()
|
||||||
|
if not n:
|
||||||
|
continue
|
||||||
|
key = n.lower()
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
out.append(n)
|
||||||
|
if primary and primary.lower() not in seen:
|
||||||
|
out.insert(0, primary)
|
||||||
|
return out if out else ([primary] if primary else [])
|
||||||
|
|
||||||
|
|
||||||
def _reload_panel_and_common_nginx() -> None:
|
def _reload_panel_and_common_nginx() -> None:
|
||||||
"""Reload nginx so new vhost (ACME path) is live before certbot HTTP-01."""
|
"""Reload nginx so new vhost (ACME path) is live before certbot HTTP-01."""
|
||||||
cfg = get_runtime_config()
|
cfg = get_runtime_config()
|
||||||
@@ -189,41 +211,53 @@ async def ssl_request_cert(
|
|||||||
if not prefix:
|
if not prefix:
|
||||||
raise HTTPException(status_code=500, detail=_certbot_missing_message())
|
raise HTTPException(status_code=500, detail=_certbot_missing_message())
|
||||||
|
|
||||||
cmd = prefix + [
|
hostnames = await _le_hostnames_for_domain_row(db, dom_row, dom)
|
||||||
"certonly",
|
|
||||||
"--webroot",
|
base_flags = [
|
||||||
"-w",
|
|
||||||
webroot_norm,
|
|
||||||
"-d",
|
|
||||||
dom,
|
|
||||||
"--non-interactive",
|
"--non-interactive",
|
||||||
"--agree-tos",
|
"--agree-tos",
|
||||||
"--email",
|
"--email",
|
||||||
body.email,
|
body.email,
|
||||||
"--preferred-challenges",
|
|
||||||
"http",
|
|
||||||
"--no-eff-email",
|
"--no-eff-email",
|
||||||
]
|
]
|
||||||
|
|
||||||
env = environment_with_system_path()
|
cmd_webroot = prefix + ["certonly", "--webroot", "-w", webroot_norm, *base_flags]
|
||||||
try:
|
for h in hostnames:
|
||||||
proc = subprocess.run(
|
cmd_webroot.extend(["-d", h])
|
||||||
cmd,
|
cmd_webroot.extend(["--preferred-challenges", "http"])
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=180,
|
|
||||||
env=env,
|
|
||||||
)
|
|
||||||
except FileNotFoundError:
|
|
||||||
raise HTTPException(status_code=500, detail=_certbot_missing_message()) from None
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
raise HTTPException(status_code=500, detail="certbot timed out (180s)") from None
|
|
||||||
|
|
||||||
if proc.returncode != 0:
|
cmd_nginx = prefix + ["certonly", "--nginx", *base_flags]
|
||||||
msg = (proc.stderr or proc.stdout or "").strip() or f"certbot exited with code {proc.returncode}"
|
for h in hostnames:
|
||||||
|
cmd_nginx.extend(["-d", h])
|
||||||
|
|
||||||
|
env = environment_with_system_path()
|
||||||
|
proc: subprocess.CompletedProcess[str] | None = None
|
||||||
|
last_err = ""
|
||||||
|
for cmd, label in ((cmd_webroot, "webroot"), (cmd_nginx, "nginx")):
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=300,
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise HTTPException(status_code=500, detail=_certbot_missing_message()) from None
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
raise HTTPException(status_code=500, detail="certbot timed out (300s)") from None
|
||||||
|
if proc.returncode == 0:
|
||||||
|
break
|
||||||
|
chunk = (proc.stderr or proc.stdout or "").strip() or f"exit {proc.returncode}"
|
||||||
|
last_err = f"[{label}] {chunk}"
|
||||||
|
|
||||||
|
if proc is None or proc.returncode != 0:
|
||||||
|
msg = last_err or "certbot failed"
|
||||||
hint = (
|
hint = (
|
||||||
" Check: DNS A/AAAA for this domain points to this server; port 80 is reachable; "
|
" Webroot and nginx plugins both failed. Check: "
|
||||||
"the website is enabled in YakPanel; nginx on port 80 loads this site’s vhost (same server as panel nginx if used)."
|
"DNS A/AAAA for every -d name points to this server; port 80 reaches the nginx that serves these hosts; "
|
||||||
|
"site is enabled; install python3-certbot-nginx if the nginx method reports a missing plugin. "
|
||||||
|
"If you use a CDN proxy, pause it or use DNS validation instead."
|
||||||
)
|
)
|
||||||
raise HTTPException(status_code=500, detail=(msg + hint)[:8000])
|
raise HTTPException(status_code=500, detail=(msg + hint)[:8000])
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ celery>=5.3.0
|
|||||||
|
|
||||||
# Let's Encrypt (optional if system certbot/snap not used; enables python -m certbot from panel venv)
|
# Let's Encrypt (optional if system certbot/snap not used; enables python -m certbot from panel venv)
|
||||||
certbot>=3.0.0
|
certbot>=3.0.0
|
||||||
|
certbot-nginx>=3.0.0
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
psutil>=5.9.0
|
psutil>=5.9.0
|
||||||
|
|||||||
Reference in New Issue
Block a user