131 lines
4.8 KiB
Python
131 lines
4.8 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,
|
|
)
|
|
|
|
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)
|
|
|
|
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)
|