"""Environment-driven configuration. Everything deployment-specific comes from the environment so the same image runs locally under compose and in Kubernetes with only a ConfigMap/Secret difference. """ from __future__ import annotations import os from functools import lru_cache def _bool(name: str, default: bool = False) -> bool: return str(os.environ.get(name, str(default))).strip().lower() in {"1", "true", "yes", "on"} def _int(name: str, default: int) -> int: try: return int(os.environ.get(name, default)) except (TypeError, ValueError): return default class Settings: # --- app --------------------------------------------------------------- app_name = os.environ.get("CX_APP_NAME", "CX Triage") base_url = os.environ.get("CX_BASE_URL", "http://localhost:8080") secret_key = os.environ.get("CX_SECRET_KEY", "dev-only-change-me") session_hours = _int("CX_SESSION_HOURS", 12) static_dir = os.environ.get("CX_STATIC_DIR", "/app/static") # --- database ---------------------------------------------------------- # sqlite for local/compose, postgres in the cluster. database_url = os.environ.get("CX_DATABASE_URL", "sqlite:////data/cx-triage.db") # --- data sources ------------------------------------------------------ prometheus_base = os.environ.get("CX_PROMETHEUS_BASE", "http://10.11.254.250:9090") prometheus_relay = os.environ.get("CX_PROMETHEUS_RELAY", "") cx_tools_path = os.environ.get("CX_TOOLS_PATH", "") # --- auth -------------------------------------------------------------- # Local accounts are for development and for a cluster without SSO yet. # When CX_OIDC_ENABLED is on, Authentik becomes the source of truth. auth_local_enabled = _bool("CX_AUTH_LOCAL_ENABLED", True) bootstrap_admin_email = os.environ.get("CX_BOOTSTRAP_ADMIN_EMAIL", "admin@localhost") bootstrap_admin_password = os.environ.get("CX_BOOTSTRAP_ADMIN_PASSWORD", "") oidc_enabled = _bool("CX_OIDC_ENABLED", False) oidc_issuer = os.environ.get("CX_OIDC_ISSUER", "") # e.g. https://sso/application/o/cx-triage/ oidc_client_id = os.environ.get("CX_OIDC_CLIENT_ID", "") oidc_client_secret = os.environ.get("CX_OIDC_CLIENT_SECRET", "") oidc_scopes = os.environ.get("CX_OIDC_SCOPES", "openid email profile") oidc_admin_group = os.environ.get("CX_OIDC_ADMIN_GROUP", "cx-triage-admins") oidc_groups_claim = os.environ.get("CX_OIDC_GROUPS_CLAIM", "groups") # --- feature flags ----------------------------------------------------- # Sending must be switched on deliberately; a demo instance cannot email. feature_send_enabled = _bool("CX_FEATURE_SEND_ENABLED", False) feature_zendesk = _bool("CX_FEATURE_ZENDESK", False) feature_jira = _bool("CX_FEATURE_JIRA", False) feature_linkage_scan = _bool("CX_FEATURE_LINKAGE_SCAN", True) send_daily_cap = _int("CX_SEND_DAILY_CAP", 25) # --- integrations ------------------------------------------------------ zendesk_subdomain = os.environ.get("CX_ZENDESK_SUBDOMAIN", "") zendesk_email = os.environ.get("CX_ZENDESK_EMAIL", "") zendesk_token = os.environ.get("CX_ZENDESK_TOKEN", "") zendesk_default_public = _bool("CX_ZENDESK_PUBLIC_REPLY", True) jira_base = os.environ.get("CX_JIRA_BASE", "") jira_email = os.environ.get("CX_JIRA_EMAIL", "") jira_token = os.environ.get("CX_JIRA_TOKEN", "") jira_project = os.environ.get("CX_JIRA_PROJECT", "INFRA") jira_issue_type = os.environ.get("CX_JIRA_ISSUE_TYPE", "Task") @property def zendesk_ready(self) -> bool: return bool(self.feature_zendesk and self.zendesk_subdomain and self.zendesk_email and self.zendesk_token) @property def jira_ready(self) -> bool: return bool(self.feature_jira and self.jira_base and self.jira_email and self.jira_token and self.jira_project) def public_flags(self) -> dict: """What the frontend is allowed to know - never secrets.""" return { "app_name": self.app_name, "oidc_enabled": self.oidc_enabled, "local_login": self.auth_local_enabled, "zendesk_ready": self.zendesk_ready, "jira_ready": self.jira_ready, "send_enabled": self.feature_send_enabled, "linkage_scan": self.feature_linkage_scan, "jira_project": self.jira_project if self.jira_ready else "", } @lru_cache def get_settings() -> Settings: return Settings()