Files
pristontale-eu-linux/ptlinux/system_identity.py

84 lines
2.5 KiB
Python

"""Identify the host OS without losing the launcher's execution runtime."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import re
EXECUTION_OS_RELEASE = Path("/etc/os-release")
CONTAINER_HOST_OS_RELEASE = Path("/run/host/os-release")
STEAM_RUNTIME_OS_ID = "steamrt"
@dataclass(frozen=True)
class SystemIdentity:
"""OS releases visible to the launcher process and its container host."""
execution_release: dict[str, str]
host_release: dict[str, str] | None
@property
def operating_system_release(self) -> dict[str, str]:
return self.host_release or self.execution_release
@property
def running_inside_steam_runtime(self) -> bool:
return (
self.execution_release.get("ID", "").strip().casefold()
== STEAM_RUNTIME_OS_ID
)
def detect_system_identity(
*,
execution_os_release: Path = EXECUTION_OS_RELEASE,
host_os_release: Path = CONTAINER_HOST_OS_RELEASE,
) -> SystemIdentity:
"""Read the runtime OS and the standard container-host identity, if present."""
execution = read_os_release(execution_os_release)
host: dict[str, str] | None = None
if execution.get("ID", "").strip().casefold() == STEAM_RUNTIME_OS_ID:
candidate = read_os_release(host_os_release)
candidate_id = candidate.get("ID", "").strip().casefold()
if candidate_id and candidate_id != STEAM_RUNTIME_OS_ID:
host = candidate
return SystemIdentity(execution_release=execution, host_release=host)
def detect_distribution_id() -> str:
"""Return the host distribution ID when a container exposes it."""
release = detect_system_identity().operating_system_release
return release.get("ID", "").strip().casefold()
def is_running_inside_steam_runtime() -> bool:
"""Return whether the launcher process itself is inside Steam Runtime."""
return detect_system_identity().running_inside_steam_runtime
def read_os_release(path: Path) -> dict[str, str]:
"""Read bounded freedesktop os-release fields without invoking host tools."""
try:
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
except OSError:
return {}
result: dict[str, str] = {}
for line in lines:
if not line or line.startswith("#") or "=" not in line:
continue
key, raw = line.split("=", 1)
if not re.fullmatch(r"[A-Z][A-Z0-9_]*", key):
continue
value = raw.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
value = value[1:-1]
value = value.replace(r"\n", " ").replace(r"\"", '"').replace(r"\\", "\\")
result[key] = value[:255]
return result