"""Configuration loader. Reads from the path in FESTER_CONFIG env var if set, otherwise from config.yaml in the project root. Provides CONFIG as a module-level dict. F-06 / F-17 remediation: External-service endpoints (MinIO, Prometheus, Firecracker SSH key, default node host) are now resolved from environment variables with sensible fallbacks — no hardcoded ``localhost:9000`` / ``http://localhost:9090`` / ``/root/.ssh/fc_builder`` literals in the runtime modules. Operators override via env vars; the YAML config can still set them under ``CONFIG["external"]``. """ import os from pathlib import Path import yaml BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Allow override via env var (for tests / dev / multi-cluster setups) _env_config = os.environ.get("FESTER_CONFIG") if _env_config: CONFIG_PATH = _env_config else: CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml") # Graceful fallback: if the requested config doesn't exist, use a sensible # default so the backend can still boot (e.g. in a fresh clone or container). if not Path(CONFIG_PATH).exists(): CONFIG = { "master": {"name": "fester-master", "role": "control"}, "nodes": [ { "name": os.environ.get("FESTER_NODE_NAME", "localhost"), "host": os.environ.get("FESTER_NODE_HOST", "127.0.0.1"), "max_jobs": int(os.environ.get("FESTER_NODE_MAX_JOBS", "4")), }, ], "projects": [], "btc": { "enabled": True, "root": "/opt/BTC", "primary": False, }, } else: with open(CONFIG_PATH, "r") as f: CONFIG = yaml.safe_load(f) or {} # Ensure required keys exist CONFIG.setdefault("master", {"name": "fester-master", "role": "control"}) CONFIG.setdefault("nodes", []) CONFIG.setdefault("projects", []) # BTC (Build Tool Chain) configuration CONFIG.setdefault("btc", {}) CONFIG["btc"].setdefault("enabled", True) CONFIG["btc"].setdefault("root", "/opt/BTC") CONFIG["btc"].setdefault("primary", False) # --------------------------------------------------------------------- # External-service endpoints (F-06 / F-17) # --------------------------------------------------------------------- # Single source of truth for every external service Fester talks to. # Modules import EXTERNAL_CONFIG instead of hardcoding URLs / paths. def _env_bool(name: str, default: bool = False) -> bool: return os.environ.get(name, "").lower() in ("1", "true", "yes", "on") EXTERNAL_CONFIG: dict = { # MinIO build cache "minio_endpoint": os.environ.get("FESTER_MINIO_ENDPOINT", "localhost:9000"), "minio_access_key": os.environ.get("FESTER_MINIO_ACCESS_KEY", ""), "minio_secret_key": os.environ.get("FESTER_MINIO_SECRET_KEY", ""), "minio_bucket": os.environ.get("FESTER_MINIO_BUCKET", "fester-cache"), "minio_secure": _env_bool("FESTER_MINIO_SECURE", False), "minio_required": _env_bool("FESTER_MINIO_REQUIRED", False), # Prometheus "prometheus_url": os.environ.get( "FESTER_PROM_URL", os.environ.get("PROM_URL", "http://localhost:9090"), ), # Firecracker "firecracker_bin": os.environ.get("FESTER_FIRECRACKER_BIN", "firecracker"), "firecracker_ssh_key": os.environ.get( "FESTER_FC_SSH_KEY", "/root/.ssh/fc_builder", ), "firecracker_socket_dir": os.environ.get( "FESTER_FC_SOCKET_DIR", "/run/fester/firecracker", ), # Cluster agent "agent_port": int(os.environ.get("FESTER_AGENT_PORT", "8787")), } def get_external() -> dict: """Return a deep copy of the external-service config dict. Returned at call time so tests / operators can monkey-patch env vars between requests. """ return dict(EXTERNAL_CONFIG)