import os import tomllib from pathlib import Path from typing import Literal from pydantic import BaseModel, Field, ValidationError class ServiceConfig(BaseModel): name: str type: Literal["http", "tcp"] timeout: int = 5 category: str | None = None class HTTPServiceConfig(ServiceConfig): url: str = Field(alias="address") class TCPServiceConfig(ServiceConfig): port: int hostname: str ipv: Literal[4, 6] = Field(alias="ip_version", default=4) class WebConfig(BaseModel): addr: str = Field(alias="address", default="::") port: int = 4200 name: str = "velping" pings: int = 25 class PingingConfig(BaseModel): rate: int = Field(alias="seconds_between_ping", default=60) class NtfyConfig(BaseModel): enabled: bool = False server: str = "ntfy.sh" topic: str Service = TCPServiceConfig | HTTPServiceConfig class Config(BaseModel): web: WebConfig = Field(alias="frontend", default={}) ntfy: NtfyConfig = NtfyConfig(topic="") pinging: PingingConfig # NOTE: dict is legacy and likely to be removed in a future release. services: list[Service] | dict[str, Service] = [] def load_cfg() -> Config: try: with Path(os.environ.get("VELPING_CONFIG", "config.toml")).open("rb") as f: cfg = Config(**tomllib.load(f)) if isinstance(cfg.services, dict): print("Your services config is using a dict, which is deprecated.") cfg.services = list(cfg.services.values()) cfg.services = sorted(cfg.services, key=lambda s: s.category or "") except ValidationError as e: raise SystemExit(str(e)) return cfg