Initial YakPanel commit
This commit is contained in:
85
YakPanel-server/backend/app/api/ssl.py
Normal file
85
YakPanel-server/backend/app/api/ssl.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""YakPanel - SSL/Domains API - Let's Encrypt via certbot"""
|
||||
import os
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.config import get_runtime_config
|
||||
from app.core.utils import exec_shell_sync
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.models.site import Site, Domain
|
||||
|
||||
router = APIRouter(prefix="/ssl", tags=["ssl"])
|
||||
|
||||
|
||||
@router.get("/domains")
|
||||
async def ssl_domains(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List all domains from sites with site path for certbot webroot"""
|
||||
result = await db.execute(
|
||||
select(Domain, Site).join(Site, Domain.pid == Site.id).order_by(Domain.name)
|
||||
)
|
||||
rows = result.all()
|
||||
return [
|
||||
{
|
||||
"id": d.id,
|
||||
"name": d.name,
|
||||
"port": d.port,
|
||||
"site_id": s.id,
|
||||
"site_name": s.name,
|
||||
"site_path": s.path,
|
||||
}
|
||||
for d, s in rows
|
||||
]
|
||||
|
||||
|
||||
class RequestCertRequest(BaseModel):
|
||||
domain: str
|
||||
webroot: str
|
||||
email: str
|
||||
|
||||
|
||||
@router.post("/request")
|
||||
async def ssl_request_cert(
|
||||
body: RequestCertRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Request Let's Encrypt certificate via certbot (webroot challenge)"""
|
||||
if not body.domain or not body.webroot or not body.email:
|
||||
raise HTTPException(status_code=400, detail="domain, webroot and email required")
|
||||
if ".." in body.domain or ".." in body.webroot:
|
||||
raise HTTPException(status_code=400, detail="Invalid path")
|
||||
cfg = get_runtime_config()
|
||||
allowed = [os.path.abspath(cfg["www_root"]), os.path.abspath(cfg["setup_path"])]
|
||||
webroot_abs = os.path.abspath(body.webroot)
|
||||
if not any(webroot_abs.startswith(a + os.sep) or webroot_abs == a for a in allowed):
|
||||
raise HTTPException(status_code=400, detail="Webroot must be under www_root or setup_path")
|
||||
cmd = (
|
||||
f'certbot certonly --webroot -w "{body.webroot}" -d "{body.domain}" '
|
||||
f'--non-interactive --agree-tos --email "{body.email}"'
|
||||
)
|
||||
out, err = exec_shell_sync(cmd, timeout=120)
|
||||
if err and "error" in err.lower() and "successfully" not in err.lower():
|
||||
raise HTTPException(status_code=500, detail=err.strip() or out.strip())
|
||||
return {"status": True, "msg": "Certificate requested", "output": out}
|
||||
|
||||
|
||||
@router.get("/certificates")
|
||||
async def ssl_list_certificates(current_user: User = Depends(get_current_user)):
|
||||
"""List existing Let's Encrypt certificates"""
|
||||
live_dir = "/etc/letsencrypt/live"
|
||||
if not os.path.isdir(live_dir):
|
||||
return {"certificates": []}
|
||||
certs = []
|
||||
for name in os.listdir(live_dir):
|
||||
if name.startswith("."):
|
||||
continue
|
||||
path = os.path.join(live_dir, name)
|
||||
if os.path.isdir(path) and os.path.isfile(os.path.join(path, "fullchain.pem")):
|
||||
certs.append({"name": name, "path": path})
|
||||
return {"certificates": sorted(certs, key=lambda x: x["name"])}
|
||||
Reference in New Issue
Block a user