"""Pydantic models for file-based fail2ban configuration management. Covers jail config files (``jail.d/``), filter definitions (``filter.d/``), and action definitions (``action.d/``). """ from pydantic import BaseModel, ConfigDict, Field # --------------------------------------------------------------------------- # Jail config file models (Task 4a) # --------------------------------------------------------------------------- class JailConfigFile(BaseModel): """Metadata for a single jail configuration file in ``jail.d/``.""" model_config = ConfigDict(strict=True) name: str = Field(..., description="Jail name (file stem, e.g. ``sshd``).") filename: str = Field(..., description="Actual filename (e.g. ``sshd.conf``).") enabled: bool = Field( ..., description=( "Whether the jail is enabled. Derived from the ``enabled`` key " "inside the file; defaults to ``true`` when the key is absent." ), ) class JailConfigFilesResponse(BaseModel): """Response for ``GET /api/config/jail-files``.""" model_config = ConfigDict(strict=True) files: list[JailConfigFile] = Field(default_factory=list) total: int = Field(..., ge=0) class JailConfigFileContent(BaseModel): """Single jail config file with its raw content.""" model_config = ConfigDict(strict=True) name: str = Field(..., description="Jail name (file stem).") filename: str = Field(..., description="Actual filename.") enabled: bool = Field(..., description="Whether the jail is enabled.") content: str = Field(..., description="Raw file content.") class JailConfigFileEnabledUpdate(BaseModel): """Payload for ``PUT /api/config/jail-files/{filename}/enabled``.""" model_config = ConfigDict(strict=True) enabled: bool = Field(..., description="New enabled state for this jail.") # --------------------------------------------------------------------------- # Generic conf-file entry (shared by filter.d and action.d) # --------------------------------------------------------------------------- class ConfFileEntry(BaseModel): """Metadata for a single ``.conf`` or ``.local`` file.""" model_config = ConfigDict(strict=True) name: str = Field(..., description="Base name without extension (e.g. ``sshd``).") filename: str = Field(..., description="Actual filename (e.g. ``sshd.conf``).") class ConfFilesResponse(BaseModel): """Response for list endpoints (``GET /api/config/filters`` and ``GET /api/config/actions``).""" model_config = ConfigDict(strict=True) files: list[ConfFileEntry] = Field(default_factory=list) total: int = Field(..., ge=0) class ConfFileContent(BaseModel): """A conf file with its raw text content.""" model_config = ConfigDict(strict=True) name: str = Field(..., description="Base name without extension.") filename: str = Field(..., description="Actual filename.") content: str = Field(..., description="Raw file content.") class ConfFileUpdateRequest(BaseModel): """Payload for ``PUT /api/config/filters/{name}`` and ``PUT /api/config/actions/{name}``.""" model_config = ConfigDict(strict=True) content: str = Field(..., description="New raw file content (must not exceed 512 KB).") class ConfFileCreateRequest(BaseModel): """Payload for ``POST /api/config/filters`` and ``POST /api/config/actions``.""" model_config = ConfigDict(strict=True) name: str = Field( ..., description="New file base name (without extension). Must contain only " "alphanumeric characters, hyphens, underscores, and dots.", ) content: str = Field(..., description="Initial raw file content (must not exceed 512 KB).")