c9a99f4fb3
代码修复: - web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc) - requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用) - 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用) 连接池三层对齐 (config.py/.env.example/session.py): - DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120 - 基于 MySQL max_connections=400, install.php 公式 文档修复 (21项): - P0: 硬编码域名/IP替换为配置变量占位符 - P1: 10个过时设计文档加归档标注 (引用旧文件结构) - P2: step-3-webssh报告 paramiko引用修正 - 6份审查报告连接池参数勘误 100/100→160/120 - ECC安全报告 EC5 datetime.utcnow 标记已修复 - docs/README.md 文档索引重写 - docs/memory/mem_nexus_overview.md 移除硬编码凭证 - docs/project/tech-stack-inventory.md paramiko标记已移除 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
5.0 KiB
Python
137 lines
5.0 KiB
Python
"""Nexus Configuration — Pydantic Settings with MySQL overrides
|
|
|
|
Settings loading priority:
|
|
1. .env file (bootstrap — required for DB connection to even work)
|
|
2. MySQL settings table (dynamic overrides — loaded at startup via load_settings_from_db)
|
|
|
|
Immutable settings (not overridable from DB):
|
|
- SECRET_KEY, API_KEY, ENCRYPTION_KEY, DATABASE_URL — must be set in .env
|
|
|
|
Mutable settings (overridable from DB):
|
|
- system_name/title, pool_size/overflow, redis_url, alert thresholds, Telegram config
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from pydantic_settings import BaseSettings
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
logger = logging.getLogger("nexus.config")
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Base settings loaded from .env file, overrideable via MySQL settings table"""
|
|
|
|
# Brand (configurable in system settings)
|
|
SYSTEM_NAME: str = "Nexus"
|
|
SYSTEM_TITLE: str = "Nexus — 服务器运维管理平台"
|
|
|
|
# Server
|
|
HOST: str = "0.0.0.0"
|
|
PORT: int = 8600
|
|
|
|
# Deployment
|
|
DEPLOY_PATH: str = "/opt/nexus" # Base directory on the server
|
|
CORS_ORIGINS: str = "" # Comma-separated: "https://example.com,http://localhost:8600"
|
|
|
|
# Database (immutable — must be set in .env)
|
|
DATABASE_URL: str = "mysql+aiomysql://root:password@127.0.0.1:3306/nexus"
|
|
# Pool params: install.php auto-calculates from MySQL max_connections
|
|
# Formula: pool_size = max(20, maxConn * 0.4), overflow = max(20, maxConn * 0.3)
|
|
# MySQL max_connections=400 → pool=160, overflow=120, max=280
|
|
# These defaults match install.php; override via .env or MySQL settings table
|
|
DB_POOL_SIZE: Optional[int] = 160
|
|
DB_MAX_OVERFLOW: Optional[int] = 120
|
|
|
|
# Security (immutable — changing breaks encryption/auth)
|
|
SECRET_KEY: str = "" # Must be set in .env; empty = startup error
|
|
API_KEY: str = ""
|
|
ENCRYPTION_KEY: str = ""
|
|
|
|
# Redis (mutable — can be changed from settings UI)
|
|
REDIS_URL: str = "redis://127.0.0.1:6379/0"
|
|
|
|
# SSH
|
|
SSH_STRICT_HOST_CHECKING: str = "true"
|
|
|
|
# Health Check / Heartbeat
|
|
HEALTH_CHECK_INTERVAL: int = 60 # seconds — Agent heartbeat interval
|
|
|
|
# Alert thresholds (mutable — configurable from settings UI)
|
|
CPU_ALERT_THRESHOLD: int = 80
|
|
MEM_ALERT_THRESHOLD: int = 80
|
|
DISK_ALERT_THRESHOLD: int = 80
|
|
|
|
# Telegram (mutable — configurable from settings UI)
|
|
TELEGRAM_BOT_TOKEN: str = ""
|
|
TELEGRAM_CHAT_ID: str = ""
|
|
|
|
model_config = {"env_prefix": "NEXUS_", "env_file": ".env"}
|
|
|
|
# ── MySQL settings table override ──
|
|
|
|
# Mapping: MySQL settings.key → Settings attribute name
|
|
# Only mutable settings are listed here; immutable ones (SECRET_KEY, API_KEY, etc.) are NOT
|
|
DB_OVERRIDE_MAP: dict = {
|
|
"system_name": "SYSTEM_NAME",
|
|
"system_title": "SYSTEM_TITLE",
|
|
"db_pool_size": "DB_POOL_SIZE",
|
|
"db_max_overflow": "DB_MAX_OVERFLOW",
|
|
"redis_url": "REDIS_URL",
|
|
"heartbeat_timeout": "HEALTH_CHECK_INTERVAL",
|
|
"cpu_alert_threshold": "CPU_ALERT_THRESHOLD",
|
|
"mem_alert_threshold": "MEM_ALERT_THRESHOLD",
|
|
"disk_alert_threshold": "DISK_ALERT_THRESHOLD",
|
|
"telegram_bot_token": "TELEGRAM_BOT_TOKEN",
|
|
"telegram_chat_id": "TELEGRAM_CHAT_ID",
|
|
}
|
|
|
|
# Settings that require int conversion from DB string values
|
|
INT_SETTINGS: set = {
|
|
"DB_POOL_SIZE", "DB_MAX_OVERFLOW", "HEALTH_CHECK_INTERVAL",
|
|
"CPU_ALERT_THRESHOLD", "MEM_ALERT_THRESHOLD", "DISK_ALERT_THRESHOLD",
|
|
}
|
|
|
|
async def load_settings_from_db(self, session: AsyncSession) -> int:
|
|
"""Override mutable settings from MySQL settings table.
|
|
|
|
Called during app startup (after init_db) to apply dynamic config.
|
|
Immutable settings (SECRET_KEY, API_KEY, ENCRYPTION_KEY, DATABASE_URL)
|
|
are NOT overridable — they stay as .env values.
|
|
|
|
Returns: number of settings overridden from DB
|
|
"""
|
|
from server.domain.models import Setting
|
|
|
|
try:
|
|
result = await session.execute(select(Setting))
|
|
rows = result.scalars().all()
|
|
|
|
overridden = 0
|
|
for row in rows:
|
|
attr_name = self.DB_OVERRIDE_MAP.get(row.key)
|
|
if attr_name and hasattr(self, attr_name):
|
|
value = row.value
|
|
# Type conversion
|
|
if attr_name in self.INT_SETTINGS:
|
|
try:
|
|
value = int(value)
|
|
except (ValueError, TypeError):
|
|
logger.warning(f"Invalid int value for {row.key}: {value}, skipping")
|
|
continue
|
|
setattr(self, attr_name, value)
|
|
overridden += 1
|
|
|
|
logger.info(
|
|
f"Settings loaded from DB: {len(rows)} keys read, "
|
|
f"{overridden} overridden"
|
|
)
|
|
return overridden
|
|
except Exception as e:
|
|
logger.error(f"Failed to load settings from DB: {e}")
|
|
return 0
|
|
|
|
|
|
settings = Settings() |