92 lines
3.3 KiB
Python
92 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
from functools import cached_property
|
|
from ipaddress import ip_network
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
RUNTIME_SETTINGS_REDIS_KEY = "sentinel:settings"
|
|
|
|
|
|
class RuntimeSettings(BaseModel):
|
|
alert_webhook_url: str | None = None
|
|
alert_threshold_count: int = Field(default=5, ge=1)
|
|
alert_threshold_seconds: int = Field(default=300, ge=1)
|
|
archive_days: int = Field(default=90, ge=1)
|
|
failsafe_mode: Literal["open", "closed"] = "closed"
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
case_sensitive=False,
|
|
)
|
|
|
|
downstream_url: str = Field(alias="DOWNSTREAM_URL")
|
|
redis_addr: str = Field(alias="REDIS_ADDR")
|
|
redis_password: str = Field(default="", alias="REDIS_PASSWORD")
|
|
pg_dsn: str = Field(alias="PG_DSN")
|
|
sentinel_hmac_secret: str = Field(alias="SENTINEL_HMAC_SECRET", min_length=32)
|
|
admin_password: str = Field(alias="ADMIN_PASSWORD", min_length=8)
|
|
admin_jwt_secret: str = Field(alias="ADMIN_JWT_SECRET", min_length=16)
|
|
trusted_proxy_ips_raw: str = Field(default="", alias="TRUSTED_PROXY_IPS")
|
|
sentinel_failsafe_mode: Literal["open", "closed"] = Field(
|
|
default="closed",
|
|
alias="SENTINEL_FAILSAFE_MODE",
|
|
)
|
|
app_port: int = Field(default=7000, alias="APP_PORT")
|
|
alert_webhook_url: str | None = Field(default=None, alias="ALERT_WEBHOOK_URL")
|
|
alert_threshold_count: int = Field(default=5, alias="ALERT_THRESHOLD_COUNT", ge=1)
|
|
alert_threshold_seconds: int = Field(default=300, alias="ALERT_THRESHOLD_SECONDS", ge=1)
|
|
archive_days: int = Field(default=90, alias="ARCHIVE_DAYS", ge=1)
|
|
|
|
redis_binding_ttl_seconds: int = 604800
|
|
downstream_max_connections: int = 512
|
|
downstream_max_keepalive_connections: int = 128
|
|
last_used_flush_interval_seconds: int = 5
|
|
last_used_queue_size: int = 10000
|
|
login_lockout_threshold: int = 5
|
|
login_lockout_seconds: int = 900
|
|
admin_jwt_expire_hours: int = 8
|
|
archive_job_interval_minutes: int = 60
|
|
archive_batch_size: int = 500
|
|
metrics_ttl_days: int = 30
|
|
webhook_timeout_seconds: int = 5
|
|
|
|
@field_validator("downstream_url")
|
|
@classmethod
|
|
def normalize_downstream_url(cls, value: str) -> str:
|
|
return value.rstrip("/")
|
|
|
|
@property
|
|
def trusted_proxy_ips(self) -> tuple[str, ...]:
|
|
parts = [item.strip() for item in self.trusted_proxy_ips_raw.split(",")]
|
|
return tuple(item for item in parts if item)
|
|
|
|
@cached_property
|
|
def trusted_proxy_networks(self):
|
|
return tuple(ip_network(item, strict=False) for item in self.trusted_proxy_ips)
|
|
|
|
def build_runtime_settings(self) -> RuntimeSettings:
|
|
return RuntimeSettings(
|
|
alert_webhook_url=self.alert_webhook_url or None,
|
|
alert_threshold_count=self.alert_threshold_count,
|
|
alert_threshold_seconds=self.alert_threshold_seconds,
|
|
archive_days=self.archive_days,
|
|
failsafe_mode=self.sentinel_failsafe_mode,
|
|
)
|
|
|
|
|
|
_settings: Settings | None = None
|
|
|
|
|
|
def get_settings() -> Settings:
|
|
global _settings
|
|
if _settings is None:
|
|
_settings = Settings()
|
|
return _settings
|