Files
Nexus/server/config.py
T
Your Name a1276f38ad feat: 一键安装脚本 + PHP完全移除 + config.php→config.json
新增文件:
- deploy/install.sh: 7步一键安装(自动检测宝塔/标准模式)
- deploy/upgrade.sh: git pull + pip install + restart + 健康检查
- deploy/uninstall.sh: 停服务+删配置, 可选--purge删部署目录
- docs/install-guide.md: 完整中文安装教程(宝塔+手动)

核心改动:
- install.py: 宝塔Supervisor路径自适应, init-db返回bt_panel标记,
  config.php→config.json, 移除PHP锁文件, 生成Fernet加密密钥
- install.html: Step 5根据btPanel显示不同Supervisor/Nginx/SSL指引
- crypto.py: 移除PHP AES-CBC兼容层, 纯Fernet加密
- 删除 web/install.php (1192行PHP, 功能已完全迁移到Python)

PHP清理:
- Nginx配置: config\.php→config\.json deny规则
- config.py/main.py/migrations.py: install.php注释→install wizard
- CLAUDE.md/AGENTS.md: config.php→config.json, 移除PHP引用
- firefox_server.py: login.php→login.html默认URL

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 08:23:37 +08:00

186 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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, SettingsConfigDict
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"
API_BASE_URL: str = "" # Public site URL — used for Agent HTTPS scheme (NEXUS_API_BASE_URL)
# Database (immutable — must be set in .env)
DATABASE_URL: str = "mysql+aiomysql://root:password@127.0.0.1:3306/nexus"
# Pool params: install wizard 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 wizard; 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
# Script exec — built-in batching (servers per wave; avoids long single HTTP)
SCRIPT_EXEC_BATCH_SIZE: int = 50
SCRIPT_EXEC_CONCURRENCY: int = 10 # max parallel SSH exec calls per batch
# 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 = ""
# Login IP allowlist master switch — "true" = enforce allowlist, "false" = allow all
LOGIN_ALLOWLIST_ENABLED: str = "false"
# Subscription URL for auto-refreshing subscription IPs (every 2h)
LOGIN_SUBSCRIPTION_URL: str = ""
# IPs from subscription (replaced entirely on each refresh)
LOGIN_SUBSCRIPTION_IPS: str = ""
# Manually added IPs (never touched by auto-refresh)
LOGIN_MANUAL_IPS: str = ""
# Legacy: combined view used by auth check = subscription + manual
LOGIN_ALLOWED_IPS: str = ""
# Notification toggles — each alert type can be independently enabled/disabled
# Stored as "true"/"false" strings in MySQL settings table
NOTIFY_ALERT_CPU: str = "true" # CPU 超阈值告警
NOTIFY_ALERT_MEM: str = "true" # 内存超阈值告警
NOTIFY_ALERT_DISK: str = "true" # 磁盘超阈值告警
NOTIFY_RECOVERY: str = "true" # 资源恢复正常通知
NOTIFY_TIME_DRIFT: str = "true" # 时钟偏差严重告警(≥60s
NOTIFY_SYSTEM_REDIS: str = "true" # Redis 连接异常/恢复
NOTIFY_SYSTEM_MYSQL: str = "true" # MySQL 连接异常/恢复
NOTIFY_RESTART: str = "true" # Nexus 后端重启通知
# extra=ignore: .env 中与 MCP 共存的 MYSQL_* / NEXUS_API_BASE_URL 等不进入 Settings
model_config = SettingsConfigDict(
env_prefix="NEXUS_",
env_file=".env",
extra="ignore",
)
# ── 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",
"login_allowlist_enabled": "LOGIN_ALLOWLIST_ENABLED",
"login_subscription_url": "LOGIN_SUBSCRIPTION_URL",
"login_subscription_ips": "LOGIN_SUBSCRIPTION_IPS",
"login_manual_ips": "LOGIN_MANUAL_IPS",
"login_allowed_ips": "LOGIN_ALLOWED_IPS",
# Notification toggles
"notify_alert_cpu": "NOTIFY_ALERT_CPU",
"notify_alert_mem": "NOTIFY_ALERT_MEM",
"notify_alert_disk": "NOTIFY_ALERT_DISK",
"notify_recovery": "NOTIFY_RECOVERY",
"notify_time_drift": "NOTIFY_TIME_DRIFT",
"notify_system_redis": "NOTIFY_SYSTEM_REDIS",
"notify_system_mysql": "NOTIFY_SYSTEM_MYSQL",
"notify_restart": "NOTIFY_RESTART",
}
# 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
# Skip null values — .env value takes precedence
if value is None:
continue
# 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()