"""Application settings, loaded from environment variables.""" from __future__ import annotations import secrets from functools import lru_cache from typing import Annotated from pydantic import field_validator from pydantic_settings import BaseSettings, NoDecode, 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: Annotated[list[str], NoDecode] = [] # Docker DOCKER_SOCKET: str = "/var/run/docker.sock" HOST_PROC_PATH: str = "/host_proc" # Throwaway image used to read/write named-volume contents during backup. BACKUP_HELPER_IMAGE: str = "alpine:latest" # Multi-host agent: shared bearer token the agent requires on every request. # Only used when running the agent app (agent_app:app). AGENT_TOKEN: str = "" # Host browser sandbox roots ALLOWED_BROWSE_ROOTS: Annotated[list[str], NoDecode] = [ "/", "/mnt", "/media", "/srv", "/opt", ] HOST_ROOT_PREFIX: str = "" # e.g. "/host_root" when host / is bind-mounted # CORS CORS_ORIGINS: Annotated[list[str], NoDecode] = [ "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("["): # tolerate a JSON list too import json try: return json.loads(v) except json.JSONDecodeError: pass return [item.strip() for item in v.split(",") if item.strip()] return v @lru_cache def get_settings() -> Settings: return Settings() settings = get_settings()