bug修复

This commit is contained in:
2026-07-21 11:02:26 +08:00
parent 14899055a4
commit 1d7e7d999c
5 changed files with 51 additions and 27 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ import logging
import subprocess
import tempfile
from pathlib import Path
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
import requests
from cryptography import x509
@@ -326,7 +326,7 @@ class AcmeService:
try:
cert = x509.load_pem_x509_certificate(cert_path.read_bytes())
not_after = cert.not_valid_after_utc
days_left = (not_after - datetime.utcnow()).days
days_left = (not_after - datetime.now(timezone.utc)).days
return days_left <= self.config.renew_days, days_left
except Exception:
return True, 0
+29
View File
@@ -22,6 +22,35 @@ async def get_db():
raise
# ────────────── CRUD 工具函数 ──────────────
async def db_add(db: AsyncSession, obj, *, refresh: bool = False):
"""创建记录并提交。refresh=True 会在提交后刷新对象(获取生成的 id 等)。"""
db.add(obj)
await db.flush()
if refresh:
await db.refresh(obj)
await db.commit()
async def db_update(db: AsyncSession, obj, data: dict):
"""批量更新字段并提交。data 一般来自 model_dump(exclude_unset=True)。"""
for key, value in data.items():
setattr(obj, key, value)
await db.commit()
async def db_delete(db: AsyncSession, obj):
"""删除记录并提交。"""
await db.delete(obj)
await db.commit()
async def db_flush(db: AsyncSession):
"""将待写操作刷到数据库(不提交事务),用于需要获取生成 ID 等中间状态的场景。"""
await db.flush()
async def init_db():
async with engine.begin() as conn:
# 迁移旧表:acme_config → acme_configs
+15 -23
View File
@@ -14,7 +14,7 @@ import time
logger = logging.getLogger(__name__)
from backend.database import get_db
from backend.database import get_db, db_add, db_update, db_delete, db_flush
from backend.models import Server, Domain, DeployLog, AcmeConfig, AcmeLog
from backend.config import get_settings
@@ -212,9 +212,7 @@ async def get_server(server_id: int, db: AsyncSession = Depends(get_db), _: bool
@router.post("/servers", status_code=201)
async def create_server(data: ServerCreate, db: AsyncSession = Depends(get_db), _: bool = Depends(require_auth)):
server = Server(**data.model_dump())
db.add(server)
await db.flush()
await db.refresh(server)
await db_add(db, server, refresh=True)
return {"id": server.id, "name": server.name}
@@ -224,8 +222,7 @@ async def update_server(server_id: int, data: ServerUpdate, db: AsyncSession = D
server = result.scalar_one_or_none()
if not server:
raise HTTPException(404, "Server not found")
for key, value in data.model_dump(exclude_unset=True).items():
setattr(server, key, value)
await db_update(db, server, data.model_dump(exclude_unset=True))
return {"id": server.id}
@@ -235,7 +232,7 @@ async def delete_server(server_id: int, db: AsyncSession = Depends(get_db), _: b
server = result.scalar_one_or_none()
if not server:
raise HTTPException(404, "Server not found")
await db.delete(server)
await db_delete(db, server)
return {"ok": True}
@@ -311,9 +308,7 @@ async def create_domain(data: DomainCreate, db: AsyncSession = Depends(get_db),
raise HTTPException(409, "该域名在此服务器下已存在相同 ACME 配置的记录")
domain = Domain(**dump)
db.add(domain)
await db.flush()
await db.refresh(domain)
await db_add(db, domain, refresh=True)
return {"id": domain.id, "domain": domain.domain}
@@ -343,8 +338,7 @@ async def update_domain(domain_id: int, data: DomainUpdate, db: AsyncSession = D
if dup:
raise HTTPException(409, "该域名在此服务器下已存在相同 ACME 配置的记录")
for key, value in update_fields.items():
setattr(domain, key, value)
await db_update(db, domain, update_fields)
return {"id": domain.id}
@@ -354,7 +348,7 @@ async def delete_domain(domain_id: int, db: AsyncSession = Depends(get_db), _: b
domain = result.scalar_one_or_none()
if not domain:
raise HTTPException(404, "Domain not found")
await db.delete(domain)
await db_delete(db, domain)
return {"ok": True}
@@ -448,9 +442,7 @@ async def get_acme_config(config_id: int, db: AsyncSession = Depends(get_db), _:
@router.post("/acme/configs", status_code=201)
async def create_acme_config(data: AcmeConfigCreate, db: AsyncSession = Depends(get_db), _: bool = Depends(require_auth)):
config = AcmeConfig(**data.model_dump())
db.add(config)
await db.flush()
await db.refresh(config)
await db_add(db, config, refresh=True)
return {"id": config.id, "name": config.name}
@@ -460,8 +452,7 @@ async def update_acme_config(config_id: int, data: AcmeConfigUpdate, db: AsyncSe
config = result.scalar_one_or_none()
if not config:
raise HTTPException(404, "ACME config not found")
for key, value in data.model_dump(exclude_unset=True).items():
setattr(config, key, value)
await db_update(db, config, data.model_dump(exclude_unset=True))
return {"id": config.id}
@@ -475,7 +466,7 @@ async def delete_acme_config(config_id: int, db: AsyncSession = Depends(get_db),
result = await db.execute(select(Domain).where(Domain.acme_config_id == config_id))
for d in result.scalars().all():
d.acme_config_id = None
await db.delete(config)
await db_delete(db, config)
return {"ok": True}
@@ -517,7 +508,7 @@ async def issue_cert(domain_id: int, db: AsyncSession = Depends(get_db), _: bool
# 记录开始
log = AcmeLog(domain_id=domain_id, action="issue", status="pending", message=f"Issuing certificate for {domain.domain}")
db.add(log)
await db.flush()
await db_flush(db)
try:
success, msg, cert_info, raw_output = service.issue_certificate(domain.domain)
@@ -537,12 +528,12 @@ async def issue_cert(domain_id: int, db: AsyncSession = Depends(get_db), _: bool
domain.version = str(int(domain.version or "0") + 1)
logger.warning(f"无法获取证书信息: {domain.domain}")
await db.flush()
await db.commit()
return {"success": success, "message": msg}
except Exception as e:
log.status = "failed"
log.message = str(e)
await db.flush()
await db.commit()
raise HTTPException(500, str(e))
@@ -583,7 +574,7 @@ async def auto_renew_all(db: AsyncSession = Depends(get_db), _: bool = Depends(r
if need_renew:
log = AcmeLog(domain_id=d.id, action="renew", status="pending", message=f"Auto-renewing {d.domain} (config: {config.name})")
db.add(log)
await db.flush()
await db_flush(db)
success, msg, cert_info, raw_output = service.renew_certificate(d.domain)
log.status = "success" if success else "failed"
@@ -601,6 +592,7 @@ async def auto_renew_all(db: AsyncSession = Depends(get_db), _: bool = Depends(r
else:
results.append({"domain": d.domain, "success": True, "message": f"Skipped, {days_left} days left", "days_left": days_left})
await db.commit()
return {"results": results}
+2 -2
View File
@@ -6,7 +6,7 @@ from jinja2 import Environment, FileSystemLoader
from pathlib import Path
import os
from backend.database import get_db
from backend.database import get_db, db_add
from backend.models import Server, Domain, DeployLog
from backend.config import get_settings
@@ -142,5 +142,5 @@ async def report_deploy(
d = await _find_domain(db, domain, server_id=server.id, acme_config_id=acme_config_id)
log = DeployLog(domain_id=d.id, server_id=server.id, hostname=hostname, status=status, message=message)
db.add(log)
await db_add(db, log)
return {"ok": True}
+3
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# 补全 PATHcron 等非交互式环境默认 PATH 不含 /usr/sbin 等目录)
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}"
# === 由 CertCenter 生成 ===
DOMAIN="{{ domain }}"
BASE_URL="{{ base_url }}"