Files
acme-auto/backend/main.py
T
2026-07-19 00:21:23 +08:00

132 lines
4.9 KiB
Python

from contextlib import asynccontextmanager
from pathlib import Path
import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from starlette.requests import Request
from backend.database import init_db
from backend.routers import api, admin
from backend.config import get_settings
from backend.bootstrap import ensure_self_signed_cert
from backend.routers.admin import (
login, logout, check_auth,
get_stats, list_servers, get_server, create_server, update_server, delete_server,
list_domains, get_domain, create_domain, update_domain, delete_domain,
list_logs,
list_acme_configs, get_acme_config, create_acme_config, update_acme_config, delete_acme_config,
issue_cert, renew_cert, auto_renew_all, list_acme_logs, get_cert_info,
download_cert_file, admin_generate_script, admin_download_script, admin_setup_script,
)
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("certcenter")
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
settings = get_settings()
ensure_self_signed_cert(settings.base_url, settings.cert_dir)
logger.info("=== 服务启动完成 ===")
# 打印所有已注册的路由
for route in app.routes:
methods = getattr(route, "methods", None)
path = getattr(route, "path", None)
if path:
logger.info(f" {methods} {path}")
else:
logger.info(f" [included router] {type(route).__name__}")
yield
app = FastAPI(title="CertCenter", version="1.0.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ── 请求日志中间件 ──
@app.middleware("http")
async def log_request(request: Request, call_next):
logger.info(f">>> {request.method} {request.url.path}")
response = await call_next(request)
logger.info(f"<<< {request.method} {request.url.path} -> {response.status_code}")
return response
# ── 客户端 API ──
app.include_router(api.router, prefix="/api")
# ── 管理 API(直接注册)──
app.post("/admin/api/login")(login)
app.post("/admin/api/logout")(logout)
app.get("/admin/api/me")(check_auth)
app.get("/admin/api/stats")(get_stats)
app.get("/admin/api/servers")(list_servers)
app.get("/admin/api/servers/{server_id}")(get_server)
app.post("/admin/api/servers", status_code=201)(create_server)
app.put("/admin/api/servers/{server_id}")(update_server)
app.delete("/admin/api/servers/{server_id}")(delete_server)
app.get("/admin/api/domains")(list_domains)
app.get("/admin/api/domains/{domain_id}")(get_domain)
app.post("/admin/api/domains", status_code=201)(create_domain)
app.put("/admin/api/domains/{domain_id}")(update_domain)
app.delete("/admin/api/domains/{domain_id}")(delete_domain)
app.get("/admin/api/logs")(list_logs)
app.get("/admin/api/acme/configs")(list_acme_configs)
app.get("/admin/api/acme/configs/{config_id}")(get_acme_config)
app.post("/admin/api/acme/configs")(create_acme_config)
app.put("/admin/api/acme/configs/{config_id}")(update_acme_config)
app.delete("/admin/api/acme/configs/{config_id}")(delete_acme_config)
app.post("/admin/api/acme/issue/{domain_id}")(issue_cert)
app.post("/admin/api/acme/renew/{domain_id}")(renew_cert)
app.post("/admin/api/acme/auto-renew")(auto_renew_all)
app.get("/admin/api/acme/logs")(list_acme_logs)
app.get("/admin/api/cert-info/{domain_id}")(get_cert_info)
app.get("/admin/api/cert-download/{domain_id}/{file_type}")(download_cert_file)
app.get("/admin/api/script")(admin_generate_script)
app.get("/admin/api/script/download")(admin_download_script)
app.get("/admin/api/script/setup")(admin_setup_script)
logger.info("=== 路由注册完成 ===")
for route in app.routes:
methods = getattr(route, "methods", None)
path = getattr(route, "path", None)
if path:
logger.info(f" {methods} {path}")
else:
logger.info(f" [included router] {type(route).__name__}")
# ── 前端静态文件 ──
frontend_dist = Path(__file__).parent.parent / "frontend" / "dist"
if frontend_dist.exists():
assets_dir = frontend_dist / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
index_file = frontend_dist / "index.html"
@app.middleware("http")
async def spa_fallback(request: Request, call_next):
response = await call_next(request)
if request.method == "GET" and response.status_code == 404:
path = request.url.path
if not path.startswith("/api/") and not path.startswith("/admin/api/") and not path.startswith("/assets/"):
return FileResponse(str(index_file))
return response
if __name__ == "__main__":
import uvicorn
uvicorn.run("backend.main:app", host="0.0.0.0", port=8021, reload=True)