多配置问题修复
This commit is contained in:
@@ -299,6 +299,17 @@ async def create_domain(data: DomainCreate, db: AsyncSession = Depends(get_db),
|
||||
dump = data.model_dump()
|
||||
if not dump.get("cert_dir"):
|
||||
dump["cert_dir"] = dump["domain"].lstrip("*.")
|
||||
|
||||
# 校验:同域名 + 同服务器 + 同 ACME 配置不允许重复
|
||||
conditions = [Domain.domain == dump["domain"], Domain.server_id == dump["server_id"]]
|
||||
if dump.get("acme_config_id"):
|
||||
conditions.append(Domain.acme_config_id == dump["acme_config_id"])
|
||||
else:
|
||||
conditions.append(Domain.acme_config_id.is_(None))
|
||||
existing = (await db.execute(select(Domain).where(*conditions))).scalars().first()
|
||||
if existing:
|
||||
raise HTTPException(409, "该域名在此服务器下已存在相同 ACME 配置的记录")
|
||||
|
||||
domain = Domain(**dump)
|
||||
db.add(domain)
|
||||
await db.flush()
|
||||
@@ -312,7 +323,27 @@ async def update_domain(domain_id: int, data: DomainUpdate, db: AsyncSession = D
|
||||
domain = result.scalar_one_or_none()
|
||||
if not domain:
|
||||
raise HTTPException(404, "Domain not found")
|
||||
for key, value in data.model_dump(exclude_unset=True).items():
|
||||
|
||||
update_fields = data.model_dump(exclude_unset=True)
|
||||
|
||||
# 如果改了 server_id 或 acme_config_id,校验是否与已有记录冲突
|
||||
new_server_id = update_fields.get("server_id", domain.server_id)
|
||||
new_acme_config_id = update_fields.get("acme_config_id", domain.acme_config_id)
|
||||
if "server_id" in update_fields or "acme_config_id" in update_fields:
|
||||
conditions = [
|
||||
Domain.domain == domain.domain,
|
||||
Domain.server_id == new_server_id,
|
||||
Domain.id != domain_id,
|
||||
]
|
||||
if new_acme_config_id:
|
||||
conditions.append(Domain.acme_config_id == new_acme_config_id)
|
||||
else:
|
||||
conditions.append(Domain.acme_config_id.is_(None))
|
||||
dup = (await db.execute(select(Domain).where(*conditions))).scalars().first()
|
||||
if dup:
|
||||
raise HTTPException(409, "该域名在此服务器下已存在相同 ACME 配置的记录")
|
||||
|
||||
for key, value in update_fields.items():
|
||||
setattr(domain, key, value)
|
||||
return {"id": domain.id}
|
||||
|
||||
@@ -606,7 +637,7 @@ async def list_acme_logs(status: str | None = None, db: AsyncSession = Depends(g
|
||||
|
||||
# ──────────────── 部署脚本 ────────────────
|
||||
|
||||
async def _render_script(domain: str, server_name: str, os: str, db: AsyncSession) -> tuple[str, Server, Domain]:
|
||||
async def _render_script(domain: str, server_name: str, os: str, db: AsyncSession, acme_config_id: int | None = None) -> tuple[str, Server, Domain]:
|
||||
"""渲染部署脚本,返回 (script, server, domain_obj)"""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
@@ -618,9 +649,11 @@ async def _render_script(domain: str, server_name: str, os: str, db: AsyncSessio
|
||||
if not server:
|
||||
raise HTTPException(404, "Server not found")
|
||||
|
||||
result = await db.execute(
|
||||
select(Domain).where(Domain.domain == domain, Domain.server_id == server.id)
|
||||
)
|
||||
# 同一域名+服务器可能有多条记录(测试/正式不同 ACME 配置),用 acme_config_id 精确匹配
|
||||
conditions = [Domain.domain == domain, Domain.server_id == server.id]
|
||||
if acme_config_id is not None:
|
||||
conditions.append(Domain.acme_config_id == acme_config_id)
|
||||
result = await db.execute(select(Domain).where(*conditions))
|
||||
d = result.scalars().first()
|
||||
if not d:
|
||||
raise HTTPException(404, "Domain not found for this server")
|
||||
@@ -644,11 +677,12 @@ async def admin_generate_script(
|
||||
domain: str,
|
||||
server_name: str,
|
||||
os: str = "linux",
|
||||
acme_config_id: int | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: bool = Depends(require_auth),
|
||||
):
|
||||
"""管理端生成部署脚本(JSON)"""
|
||||
script, server, d = await _render_script(domain, server_name, os, db)
|
||||
script, server, d = await _render_script(domain, server_name, os, db, acme_config_id)
|
||||
return {
|
||||
"script": script,
|
||||
"token": server.token,
|
||||
@@ -663,11 +697,12 @@ async def admin_download_script(
|
||||
domain: str,
|
||||
server_name: str,
|
||||
os: str = "linux",
|
||||
acme_config_id: int | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: bool = Depends(require_auth),
|
||||
):
|
||||
"""下载脚本文件(纯文本,供 curl 使用)"""
|
||||
script, _, _ = await _render_script(domain, server_name, os, db)
|
||||
script, _, _ = await _render_script(domain, server_name, os, db, acme_config_id)
|
||||
ext = "ps1" if os == "windows" else "sh"
|
||||
filename = f"deploy-{domain.replace('*', '_')}.{ext}"
|
||||
return PlainTextResponse(
|
||||
@@ -683,6 +718,7 @@ async def admin_setup_script(
|
||||
server_name: str,
|
||||
os: str = "linux",
|
||||
token: str = "",
|
||||
acme_config_id: int | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: bool = Depends(require_auth),
|
||||
):
|
||||
@@ -697,6 +733,8 @@ async def admin_setup_script(
|
||||
base_url = settings.base_url.rstrip("/")
|
||||
ext = "ps1" if os == "windows" else "sh"
|
||||
download_url = f"{base_url}/admin/api/script/download?domain={quote(domain)}&server_name={quote(server_name)}&os={os}"
|
||||
if acme_config_id is not None:
|
||||
download_url += f"&acme_config_id={acme_config_id}"
|
||||
|
||||
template_name = "setup-cert.ps1.j2" if os == "windows" else "setup-cert.sh.j2"
|
||||
tpl = jinja_env.get_template(template_name)
|
||||
|
||||
Reference in New Issue
Block a user