"""Configuration loader for AUR-Shield.""" from __future__ import annotations import os from dataclasses import dataclass, field from pathlib import Path from typing import Any import yaml @dataclass class OllamaConfig: url: str = "http://localhost:11434" model: str = "qwen2.5:latest" timeout: int = 120 @dataclass class ServerConfig: host: str = "0.0.0.0" port: int = 8443 repo_dir: str = "/var/cache/aur-shield/repo" work_dir: str = "/var/cache/aur-shield/build" build_user: str = "nobody" scan_only: bool = False @dataclass class SecurityConfig: block_patterns: list[str] = field(default_factory=lambda: [ r"curl.*\|.*bash", r"wget.*/tmp/.*\|.*sh", r"eval.*base64", r"nc\s+-.*\d+", r"/dev/tcp/", r"systemctl.*enable.*--now", ]) max_pkg_size_mb: int = 500 allowed_source_schemes: list[str] = field(default_factory=lambda: [ "https", "http", "git", "ftp", "file", ]) @dataclass class CacheConfig: ttl_hours: int = 168 max_cache_gb: int = 10 @dataclass class BuildConfig: use_devtools: bool = True timeout: int = 600 @dataclass class Config: ollama: OllamaConfig = field(default_factory=OllamaConfig) server: ServerConfig = field(default_factory=ServerConfig) security: SecurityConfig = field(default_factory=SecurityConfig) cache: CacheConfig = field(default_factory=CacheConfig) build: BuildConfig = field(default_factory=BuildConfig) @classmethod def load(cls, path: str | Path | None = None) -> "Config": """Load config from YAML file, falling back to defaults.""" if path is None: path = Path(os.environ.get( "AUR_SHIELD_CONFIG", "config.yaml", )) path = Path(path) if not path.exists(): return cls() with open(path) as f: raw: dict[str, Any] = yaml.safe_load(f) or {} cfg = cls() if "ollama" in raw: cfg.ollama = OllamaConfig(**raw["ollama"]) if "server" in raw: cfg.server = ServerConfig(**raw["server"]) if "security" in raw: cfg.security = SecurityConfig(**raw["security"]) if "cache" in raw: cfg.cache = CacheConfig(**raw["cache"]) if "build" in raw: cfg.build = BuildConfig(**raw["build"]) return cfg