"""Application configuration from environment variables (spec section 7).""" from __future__ import annotations from functools import lru_cache from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") app_env: str = "development" # Database database_url: str = Field( default="postgresql+psycopg://mqttadmin:changeme@localhost:5432/mqttadmin", ) # Management admin bootstrap (spec 7.1) — only consumed when the # users table has no administrator. admin_initial_username: str = "admin" admin_initial_password: str = "" # Session signing session_secret: str = "" session_ttl_seconds: int = 60 * 60 * 8 # Private-key encryption at rest (spec section 43) — AES-256-GCM # master key. Never stored in PostgreSQL. secret_encryption_key: str = "" # Mosquitto management channel mosquitto_host: str = "mosquitto" mosquitto_mgmt_port: int = 1883 mosquitto_dynsec_username: str = "admin" mosquitto_dynsec_password: str = "" # Control sidecar control_base_url: str = "http://mosquitto-control:8090" control_api_token: str = "" # TLS defaults (used by Release 2 config generation) default_tls_min_version: str = "tlsv1.2" default_cert_validity_days: int = 365 default_ca_validity_days: int = 3650 # Broker container ports published to the host by compose.yaml — # listeners on other ports are only reachable INSIDE the Docker # network (the platform generates any port; compose gates access). # Comma-separated container ports; see compose.yaml `ports:`. published_ports: str = "1883,8883" @property def published_port_set(self) -> set[int]: return { int(p.strip()) for p in self.published_ports.split(",") if p.strip().isdigit() } @property def is_production(self) -> bool: return self.app_env == "production" @lru_cache def get_settings() -> Settings: return Settings()