"""Application settings, loaded from environment variables.""" from __future__ import annotations import secrets from functools import lru_cache from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") # Paths STACKS_DIR: str = "/opt/stackpilot/stacks" DATA_DIR: str = "/opt/stackpilot/data" # Security SECRET_KEY: str = "" # Auto-generated if empty (dev only); set in prod. ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_DAYS: int = 30 # Update checker UPDATE_CHECK_INTERVAL_MINUTES: int = 60 # Notifications (webhook URLs) NOTIFY_WEBHOOKS: list[str] = [] # Docker DOCKER_SOCKET: str = "/var/run/docker.sock" HOST_PROC_PATH: str = "/host_proc" # Host browser sandbox roots ALLOWED_BROWSE_ROOTS: list[str] = ["/", "/mnt", "/media", "/srv", "/opt"] HOST_ROOT_PREFIX: str = "" # e.g. "/host_root" when host / is bind-mounted # CORS CORS_ORIGINS: list[str] = ["http://localhost:5009", "http://localhost:5173"] # Server PORT: int = 5008 @field_validator("SECRET_KEY", mode="after") @classmethod def _ensure_secret(cls, v: str) -> str: return v or secrets.token_urlsafe(48) @field_validator( "NOTIFY_WEBHOOKS", "ALLOWED_BROWSE_ROOTS", "CORS_ORIGINS", mode="before" ) @classmethod def _split_csv(cls, v): if isinstance(v, str): v = v.strip() if not v: return [] if v.startswith("["): # JSON list return v return [item.strip() for item in v.split(",") if item.strip()] return v @lru_cache def get_settings() -> Settings: return Settings() settings = get_settings()