多配置问题修复
This commit is contained in:
@@ -299,6 +299,17 @@ async def create_domain(data: DomainCreate, db: AsyncSession = Depends(get_db),
|
|||||||
dump = data.model_dump()
|
dump = data.model_dump()
|
||||||
if not dump.get("cert_dir"):
|
if not dump.get("cert_dir"):
|
||||||
dump["cert_dir"] = dump["domain"].lstrip("*.")
|
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)
|
domain = Domain(**dump)
|
||||||
db.add(domain)
|
db.add(domain)
|
||||||
await db.flush()
|
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()
|
domain = result.scalar_one_or_none()
|
||||||
if not domain:
|
if not domain:
|
||||||
raise HTTPException(404, "Domain not found")
|
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)
|
setattr(domain, key, value)
|
||||||
return {"id": domain.id}
|
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)"""
|
"""渲染部署脚本,返回 (script, server, domain_obj)"""
|
||||||
from jinja2 import Environment, FileSystemLoader
|
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:
|
if not server:
|
||||||
raise HTTPException(404, "Server not found")
|
raise HTTPException(404, "Server not found")
|
||||||
|
|
||||||
result = await db.execute(
|
# 同一域名+服务器可能有多条记录(测试/正式不同 ACME 配置),用 acme_config_id 精确匹配
|
||||||
select(Domain).where(Domain.domain == domain, Domain.server_id == server.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()
|
d = result.scalars().first()
|
||||||
if not d:
|
if not d:
|
||||||
raise HTTPException(404, "Domain not found for this server")
|
raise HTTPException(404, "Domain not found for this server")
|
||||||
@@ -644,11 +677,12 @@ async def admin_generate_script(
|
|||||||
domain: str,
|
domain: str,
|
||||||
server_name: str,
|
server_name: str,
|
||||||
os: str = "linux",
|
os: str = "linux",
|
||||||
|
acme_config_id: int | None = None,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
_: bool = Depends(require_auth),
|
_: bool = Depends(require_auth),
|
||||||
):
|
):
|
||||||
"""管理端生成部署脚本(JSON)"""
|
"""管理端生成部署脚本(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 {
|
return {
|
||||||
"script": script,
|
"script": script,
|
||||||
"token": server.token,
|
"token": server.token,
|
||||||
@@ -663,11 +697,12 @@ async def admin_download_script(
|
|||||||
domain: str,
|
domain: str,
|
||||||
server_name: str,
|
server_name: str,
|
||||||
os: str = "linux",
|
os: str = "linux",
|
||||||
|
acme_config_id: int | None = None,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
_: bool = Depends(require_auth),
|
_: bool = Depends(require_auth),
|
||||||
):
|
):
|
||||||
"""下载脚本文件(纯文本,供 curl 使用)"""
|
"""下载脚本文件(纯文本,供 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"
|
ext = "ps1" if os == "windows" else "sh"
|
||||||
filename = f"deploy-{domain.replace('*', '_')}.{ext}"
|
filename = f"deploy-{domain.replace('*', '_')}.{ext}"
|
||||||
return PlainTextResponse(
|
return PlainTextResponse(
|
||||||
@@ -683,6 +718,7 @@ async def admin_setup_script(
|
|||||||
server_name: str,
|
server_name: str,
|
||||||
os: str = "linux",
|
os: str = "linux",
|
||||||
token: str = "",
|
token: str = "",
|
||||||
|
acme_config_id: int | None = None,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
_: bool = Depends(require_auth),
|
_: bool = Depends(require_auth),
|
||||||
):
|
):
|
||||||
@@ -697,6 +733,8 @@ async def admin_setup_script(
|
|||||||
base_url = settings.base_url.rstrip("/")
|
base_url = settings.base_url.rstrip("/")
|
||||||
ext = "ps1" if os == "windows" else "sh"
|
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}"
|
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"
|
template_name = "setup-cert.ps1.j2" if os == "windows" else "setup-cert.sh.j2"
|
||||||
tpl = jinja_env.get_template(template_name)
|
tpl = jinja_env.get_template(template_name)
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ const loadScript = async () => {
|
|||||||
domain: scriptDomain.value.domain,
|
domain: scriptDomain.value.domain,
|
||||||
server_name: scriptDomain.value.server_name,
|
server_name: scriptDomain.value.server_name,
|
||||||
os: scriptOs.value,
|
os: scriptOs.value,
|
||||||
|
acme_config_id: scriptDomain.value.acme_config_id || undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
scriptContent.value = resp.data.script
|
scriptContent.value = resp.data.script
|
||||||
@@ -301,6 +302,7 @@ const downloadSetupScript = async () => {
|
|||||||
server_name: scriptDomain.value.server_name,
|
server_name: scriptDomain.value.server_name,
|
||||||
os: scriptOs.value,
|
os: scriptOs.value,
|
||||||
token: localStorage.getItem('admin_token') || '',
|
token: localStorage.getItem('admin_token') || '',
|
||||||
|
acme_config_id: scriptDomain.value.acme_config_id || undefined,
|
||||||
},
|
},
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user