首次提交

This commit is contained in:
2026-07-18 20:09:26 +08:00
commit 521ad3b635
48 changed files with 7199 additions and 0 deletions
+242
View File
@@ -0,0 +1,242 @@
"""
CertCenter 部署验证脚本
用法: python -m tests.test_setup
"""
import sys
import os
import json
import requests
from pathlib import Path
# 添加项目根目录到 path
sys.path.insert(0, str(Path(__file__).parent.parent))
PASS = ""
FAIL = ""
SKIP = "⏭️"
results = []
def check(name, func):
"""执行检查并记录结果"""
try:
ok, msg = func()
status = PASS if ok else FAIL
results.append((name, status, msg))
print(f" {status} {name}: {msg}")
except Exception as e:
results.append((name, FAIL, str(e)))
print(f" {FAIL} {name}: {e}")
def test_python_deps():
"""检查 Python 依赖"""
deps = ["fastapi", "uvicorn", "sqlalchemy", "acme", "cryptography", "jinja2"]
missing = []
for dep in deps:
try:
__import__(dep)
except ImportError:
missing.append(dep)
if missing:
return False, f"缺少依赖: {', '.join(missing)},执行 pip install -r requirements.txt"
return True, "所有依赖已安装"
def test_certbot():
"""检查 certbot 是否可用"""
import subprocess
try:
result = subprocess.run(["certbot", "--version"], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
return True, result.stdout.strip()
return False, "certbot 命令不可用"
except FileNotFoundError:
return False, "certbot 未安装,执行 pip install certbot"
def test_certbot_dns_plugin():
"""检查 certbot DNS 插件"""
try:
import certbot_dns_alicloud
return True, "certbot-dns-alicloud 已安装"
except ImportError:
try:
import certbot_dns_cloudflare
return True, "certbot-dns-cloudflare 已安装"
except ImportError:
return False, "未检测到 DNS 插件,执行 pip install certbot-dns-alicloud"
def test_env_file():
"""检查 .env 文件"""
env_path = Path(__file__).parent.parent / ".env"
if not env_path.exists():
return False, ".env 文件不存在,执行 cp .env .env"
return True, ".env 文件存在"
def test_database():
"""检查数据库连接"""
from backend.config import get_settings
settings = get_settings()
db_path = settings.database_url.replace("sqlite+aiosqlite:///", "")
if "sqlite" in settings.database_url:
return True, f"SQLite 数据库: {db_path}"
return True, f"数据库: {settings.database_url}"
def test_frontend_dist():
"""检查前端构建产物"""
dist = Path(__file__).parent.parent / "frontend" / "dist"
if dist.exists() and (dist / "index.html").exists():
return True, f"前端已构建: {dist}"
return False, f"前端未构建,执行 cd frontend && npm install && npm run build"
def test_cert_dir():
"""检查证书存储目录"""
from backend.config import get_settings
settings = get_settings()
cert_dir = Path(settings.cert_dir)
if cert_dir.exists():
return True, f"证书目录存在: {cert_dir}"
try:
cert_dir.mkdir(parents=True, exist_ok=True)
return True, f"证书目录已创建: {cert_dir}"
except Exception as e:
return False, f"无法创建证书目录: {e}"
def test_base_url():
"""检查 BASE_URL 配置"""
from backend.config import get_settings
settings = get_settings()
if "example.com" in settings.base_url:
return False, f"BASE_URL 仍为默认值: {settings.base_url},请修改 .env"
return True, f"BASE_URL: {settings.base_url}"
def test_letsencrypt_staging():
"""测试 Let's Encrypt 测试环境连通性"""
try:
resp = requests.get(
"https://acme-staging-v02.api.letsencrypt.org/directory",
timeout=10,
)
if resp.status_code == 200:
data = resp.json()
return True, f"Staging 环境可达, endpoints: {len(data)}"
return False, f"HTTP {resp.status_code}"
except Exception as e:
return False, f"无法连接 Let's Encrypt Staging: {e}"
def test_letsencrypt_production():
"""测试 Let's Encrypt 生产环境连通性"""
try:
resp = requests.get(
"https://acme-v02.api.letsencrypt.org/directory",
timeout=10,
)
if resp.status_code == 200:
return True, "生产环境可达"
return False, f"HTTP {resp.status_code}"
except Exception as e:
return False, f"无法连接 Let's Encrypt: {e}"
def test_dns_credentials():
"""检查 DNS 凭据是否配置"""
from backend.database import engine, async_session
from backend.models import AcmeConfig
from sqlalchemy import select
import asyncio
async def _check():
async with async_session() as session:
result = await session.execute(select(AcmeConfig).where(AcmeConfig.id == 1))
config = result.scalar_one_or_none()
if not config:
return False, "ACME 未配置,请在 WebUI 中配置"
if not config.email:
return False, "邮箱未配置"
creds = json.loads(config.dns_credentials or "{}")
if not creds.get("access_key") and not creds.get("api_token"):
return False, "DNS 凭据未配置"
return True, f"DNS 提商: {config.dns_provider}, 邮箱: {config.email}"
return asyncio.run(_check())
def test_api_server():
"""测试 FastAPI 服务是否能启动"""
import subprocess
import time
try:
proc = subprocess.Popen(
[sys.executable, "-m", "backend.main"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=str(Path(__file__).parent.parent),
)
time.sleep(3)
# 尝试访问
try:
resp = requests.get("http://127.0.0.1:8000/admin/api/stats", timeout=5)
if resp.status_code == 200:
proc.terminate()
return True, "服务启动成功,API 正常响应"
proc.terminate()
return False, f"服务启动但 API 返回 {resp.status_code}"
except requests.ConnectionError:
proc.terminate()
return False, "服务启动失败,端口 8000 无响应"
except Exception as e:
return False, f"启动测试失败: {e}"
def main():
print("\n" + "=" * 50)
print(" CertCenter 部署验证")
print("=" * 50)
print("\n📦 环境检查:")
check("Python 依赖", test_python_deps)
check("Certbot", test_certbot)
check("DNS 插件", test_certbot_dns_plugin)
check(".env 配置", test_env_file)
check("数据库", test_database)
check("前端构建", test_frontend_dist)
check("证书目录", test_cert_dir)
check("BASE_URL", test_base_url)
print("\n🌐 网络检查:")
check("Let's Encrypt Staging", test_letsencrypt_staging)
check("Let's Encrypt Production", test_letsencrypt_production)
print("\n⚙️ 服务检查:")
check("FastAPI 服务", test_api_server)
# 汇总
print("\n" + "=" * 50)
passed = sum(1 for _, s, _ in results if s == PASS)
failed = sum(1 for _, s, _ in results if s == FAIL)
print(f" 结果: {passed} 通过, {failed} 失败")
if failed:
print("\n 请修复以上 ❌ 标记的问题后重试。")
print(" 建议先使用 Let's Encrypt Staging 环境测试。")
else:
print("\n 所有检查通过!可以开始使用 CertCenter。")
print(" 建议首次测试使用 Staging 环境,确认无误后再切换到生产环境。")
print("=" * 50 + "\n")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())