from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path ROOT_DIR = Path(__file__).resolve().parents[1] def _load_env_file(path: Path) -> dict[str, str]: values: dict[str, str] = {} if not path.exists(): return values for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) key = key.strip() value = value.strip().strip('"').strip("'") if key: values[key] = value return values def _get_env(name: str, default: str, file_values: dict[str, str]) -> str: return os.environ.get(name, file_values.get(name, default)) def _as_int(value: str, default: int) -> int: try: return int(value) except (TypeError, ValueError): return default def _resolve_path(value: str) -> Path: path = Path(value) if path.is_absolute(): return path return ROOT_DIR / path @dataclass(frozen=True) class ControllerConfig: root_dir: Path host: str port: int database_path: Path static_dir: Path controller_name: str deployment_mode: str public_base_url: str agent_stale_after_seconds: int @classmethod def from_env(cls) -> "ControllerConfig": file_values = _load_env_file(ROOT_DIR / ".env") port = _as_int(_get_env("CONTROLLER_PORT", "8001", file_values), 8001) host = _get_env("CONTROLLER_HOST", "0.0.0.0", file_values) public_base_url = _get_env( "CONTROLLER_PUBLIC_BASE_URL", f"http://127.0.0.1:{port}", file_values, ).rstrip("/") stale_after = _as_int( _get_env("AGENT_STALE_AFTER_SECONDS", "5", file_values), 5, ) return cls( root_dir=ROOT_DIR, host=host, port=port, database_path=_resolve_path( _get_env("CONTROLLER_DATABASE", "data/controller.db", file_values) ), static_dir=_resolve_path(_get_env("CONTROLLER_STATIC_DIR", "web", file_values)), controller_name=_get_env( "CONTROLLER_NAME", "System Test Controller", file_values, ), deployment_mode=_get_env("CONTROLLER_DEPLOYMENT_MODE", "script", file_values), public_base_url=public_base_url, agent_stale_after_seconds=stale_after, )