108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
"""配置。
|
|
|
|
产品名只出现在这里和 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
|