| """Helpers for validating startup runtime expectations from `.env`.""" |
|
|
| from __future__ import annotations |
|
|
| import os |
| from dataclasses import dataclass |
| from pathlib import Path |
|
|
| from dotenv import dotenv_values |
|
|
| _CONTAINER_RUNTIME_TARGETS = {"compose", "docker"} |
|
|
|
|
| @dataclass(frozen=True) |
| class RuntimeEnvironment: |
| """Describes whether the current process is running in a container runtime.""" |
|
|
| in_container: bool |
| in_docker: bool |
| in_kubernetes: bool |
|
|
| @property |
| def label(self) -> str: |
| if self.in_kubernetes: |
| return "Kubernetes" |
| if self.in_docker: |
| return "Docker" |
| return "host" |
|
|
|
|
| def _read_cgroup_content() -> str: |
| """Best-effort read of cgroup metadata for container detection.""" |
|
|
| for candidate in ("/proc/1/cgroup", "/proc/self/cgroup"): |
| try: |
| return Path(candidate).read_text(encoding="utf-8") |
| except OSError: |
| continue |
| return "" |
|
|
|
|
| def detect_runtime_environment( |
| environ: dict[str, str] | None = None, |
| ) -> RuntimeEnvironment: |
| """Detect whether the current process is running on host, Docker, or Kubernetes.""" |
|
|
| environ = environ or os.environ |
| cgroup_content = _read_cgroup_content().lower() |
|
|
| in_kubernetes = bool( |
| environ.get("KUBERNETES_SERVICE_HOST") |
| or Path("/var/run/secrets/kubernetes.io/serviceaccount").exists() |
| or "kubepods" in cgroup_content |
| or "kubernetes" in cgroup_content |
| ) |
| in_docker = bool( |
| Path("/.dockerenv").exists() |
| or Path("/run/.containerenv").exists() |
| or any( |
| marker in cgroup_content |
| for marker in ("docker", "containerd", "libpod", "podman") |
| ) |
| ) |
|
|
| return RuntimeEnvironment( |
| in_container=in_kubernetes or in_docker, |
| in_docker=in_docker, |
| in_kubernetes=in_kubernetes, |
| ) |
|
|
|
|
| def load_runtime_target_from_env_file(env_path: str | Path = ".env") -> str | None: |
| """Return the raw LIGHTRAG_RUNTIME_TARGET value from the `.env` file, if present.""" |
|
|
| env_values = dotenv_values(str(env_path)) |
| runtime_target = env_values.get("LIGHTRAG_RUNTIME_TARGET") |
| if runtime_target is None: |
| return None |
| return runtime_target.strip() |
|
|
|
|
| def validate_runtime_target( |
| runtime_target: str | None, |
| runtime_environment: RuntimeEnvironment | None = None, |
| ) -> tuple[bool, str | None]: |
| """Validate `.env` runtime target against the current runtime environment.""" |
|
|
| if runtime_target is None: |
| return True, None |
|
|
| normalized_target = runtime_target.strip().lower() |
| runtime_environment = runtime_environment or detect_runtime_environment() |
|
|
| if normalized_target == "host": |
| if runtime_environment.in_container: |
| return ( |
| False, |
| "Configuration error in .env: LIGHTRAG_RUNTIME_TARGET=host.\n" |
| "This value from .env requires the server process to run on the host, " |
| f"but the current process is running inside {runtime_environment.label}.", |
| ) |
| return True, None |
|
|
| if normalized_target in _CONTAINER_RUNTIME_TARGETS: |
| if runtime_environment.in_container: |
| return True, None |
| return ( |
| False, |
| f"Configuration error in .env: LIGHTRAG_RUNTIME_TARGET={runtime_target}.\n" |
| "This value from .env requires the server process to run inside Docker or " |
| "Kubernetes, but the current process is running on the host.", |
| ) |
|
|
| return ( |
| False, |
| f"Configuration error in .env: LIGHTRAG_RUNTIME_TARGET={runtime_target!r}.\n" |
| "This value from .env must be 'host' or 'compose' (alias: 'docker').", |
| ) |
|
|
|
|
| def validate_runtime_target_from_env_file( |
| env_path: str | Path = ".env", |
| runtime_environment: RuntimeEnvironment | None = None, |
| ) -> tuple[bool, str | None]: |
| """Load LIGHTRAG_RUNTIME_TARGET from `.env` and validate it if declared.""" |
|
|
| runtime_target = load_runtime_target_from_env_file(env_path) |
| return validate_runtime_target(runtime_target, runtime_environment) |
|
|