"""配置。 产品名只出现在这里和 apps/console/lib/brand.ts —— 改名成本 = 改两个常量。 商标检索未完成前,任何地方都不要再硬编码产品名。 """ from __future__ import annotations import os from dataclasses import dataclass, field from pathlib import Path from typing import Optional APP_NAME = "FlashOps" APP_TAGLINE = "存储实验室无人值守执行层" # 仓库根目录:.../flashops REPO_ROOT = Path(__file__).resolve().parents[3] def _env(key: str, default: str) -> str: return os.environ.get(f"FLASHOPS_{key}", default) def _env_int(key: str, default: int) -> int: return int(os.environ.get(f"FLASHOPS_{key}", str(default))) def _env_float(key: str, default: float) -> float: return float(os.environ.get(f"FLASHOPS_{key}", str(default))) @dataclass(frozen=True) class TimingPolicy: """时间策略。 全部集中在这里,且引擎不直接读它 —— 引擎从 Snapshot 拿数值。 这样"30 秒心跳超时"这类规则可以在测试里用 0.03 秒验证。 """ heartbeat_interval_s: float = 5.0 heartbeat_degraded_after_s: float = 15.0 heartbeat_lost_after_s: float = 30.0 lease_ttl_s: float = 60.0 lease_poll_timeout_s: float = 20.0 step_default_timeout_s: float = 600.0 recovery_settle_s: float = 20.0 # 恢复动作后等待主机回来的时间 ac_cycle_off_s: float = 10.0 # AC 断电后的安全间隔(方案 §7.2) max_recovery_per_loop: int = 3 max_recovery_total: int = 20 @dataclass(frozen=True) class Settings: app_name: str = APP_NAME env: str = field(default_factory=lambda: _env("ENV", "development")) # 开发态:SQLite。生产:postgresql+asyncpg://... database_url: str = field( default_factory=lambda: _env( "DATABASE_URL", "sqlite+aiosqlite:///" + str(REPO_ROOT / "var" / "flashops.db") ) ) # 开发态:本地目录。生产:s3://minio:9000/flashops object_store_url: str = field( default_factory=lambda: _env("OBJECT_STORE_URL", str(REPO_ROOT / "var" / "objects")) ) # 开发态:进程内 asyncio。生产:redis://... / nats://... bus_url: Optional[str] = field(default_factory=lambda: os.environ.get("FLASHOPS_BUS_URL")) # Agent 首次注册口令。开发态未配置时允许本机演示;生产态缺失时拒绝注册。 agent_enrollment_token: Optional[str] = field( default_factory=lambda: os.environ.get("FLASHOPS_AGENT_ENROLLMENT_TOKEN") ) api_host: str = field(default_factory=lambda: _env("API_HOST", "0.0.0.0")) api_port: int = field(default_factory=lambda: _env_int("API_PORT", 8000)) cors_origins: str = field(default_factory=lambda: _env("CORS_ORIGINS", "http://localhost:3000")) # 引擎主循环的 tick 间隔。demo/测试里调小以加速。 engine_tick_s: float = field(default_factory=lambda: _env_float("ENGINE_TICK_S", 0.5)) # 演示/仿真加速倍率:1.0 = 真实速度 time_scale: float = field(default_factory=lambda: _env_float("TIME_SCALE", 1.0)) timing: TimingPolicy = field(default_factory=TimingPolicy) @property def is_sqlite(self) -> bool: return self.database_url.startswith("sqlite") @property def object_store_path(self) -> Path: return Path(self.object_store_url) _settings: Optional[Settings] = None def get_settings() -> Settings: global _settings if _settings is None: _settings = Settings() return _settings def reset_settings() -> None: """测试用:让下一次 get_settings() 重新读环境变量。""" global _settings _settings = None