| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- """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)
|