machine: add a more robust container check
Most containers do not have access to systemd, so using systemd-detect-virt will fail. We can check the cgroup and sched files of the first process to reliably determine if Moonraker is running within a container. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
179e9428c5
commit
28c8bf61da
|
@ -29,6 +29,8 @@ ALLOWED_SERVICES = [
|
||||||
"moonraker", "klipper", "webcamd", "MoonCord",
|
"moonraker", "klipper", "webcamd", "MoonCord",
|
||||||
"KlipperScreen", "moonraker-telegram-bot"
|
"KlipperScreen", "moonraker-telegram-bot"
|
||||||
]
|
]
|
||||||
|
CGROUP_PATH = "/proc/1/cgroup"
|
||||||
|
SCHED_PATH = "/proc/1/sched"
|
||||||
SYSTEMD_PATH = "/etc/systemd/system"
|
SYSTEMD_PATH = "/etc/systemd/system"
|
||||||
SD_CID_PATH = "/sys/block/mmcblk0/device/cid"
|
SD_CID_PATH = "/sys/block/mmcblk0/device/cid"
|
||||||
SD_CSD_PATH = "/sys/block/mmcblk0/device/csd"
|
SD_CSD_PATH = "/sys/block/mmcblk0/device/csd"
|
||||||
|
@ -43,23 +45,15 @@ class Machine:
|
||||||
dist_info: Dict[str, Any]
|
dist_info: Dict[str, Any]
|
||||||
dist_info = {'name': distro.name(pretty=True)}
|
dist_info = {'name': distro.name(pretty=True)}
|
||||||
dist_info.update(distro.info())
|
dist_info.update(distro.info())
|
||||||
self.virt_type = "none"
|
self.inside_container = False
|
||||||
self.virt_id = "none"
|
self.virt_id = "none"
|
||||||
self.system_info: Dict[str, Any] = {
|
self.system_info: Dict[str, Any] = {
|
||||||
'cpu_info': self._get_cpu_info(),
|
'cpu_info': self._get_cpu_info(),
|
||||||
'sd_info': self._get_sdcard_info(),
|
'sd_info': self._get_sdcard_info(),
|
||||||
'distribution': dist_info
|
'distribution': dist_info,
|
||||||
|
'virtualization': self._check_inside_container()
|
||||||
}
|
}
|
||||||
# Add system info to log rollover
|
self._update_log_rollover(log=True)
|
||||||
sys_info_msg = "\nSystem Info:"
|
|
||||||
for header, info in self.system_info.items():
|
|
||||||
sys_info_msg += f"\n\n***{header}***"
|
|
||||||
if not isinstance(info, dict):
|
|
||||||
sys_info_msg += f"\n {repr(info)}"
|
|
||||||
else:
|
|
||||||
for key, val in info.items():
|
|
||||||
sys_info_msg += f"\n {key}: {val}"
|
|
||||||
self.server.add_log_rollover_item('system_info', sys_info_msg)
|
|
||||||
self.available_services: Dict[str, Dict[str, str]] = {}
|
self.available_services: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
self.server.register_endpoint(
|
self.server.register_endpoint(
|
||||||
|
@ -92,6 +86,17 @@ class Machine:
|
||||||
self.init_evt = asyncio.Event()
|
self.init_evt = asyncio.Event()
|
||||||
event_loop.register_callback(self._initialize)
|
event_loop.register_callback(self._initialize)
|
||||||
|
|
||||||
|
def _update_log_rollover(self, log: bool = False) -> None:
|
||||||
|
sys_info_msg = "\nSystem Info:"
|
||||||
|
for header, info in self.system_info.items():
|
||||||
|
sys_info_msg += f"\n\n***{header}***"
|
||||||
|
if not isinstance(info, dict):
|
||||||
|
sys_info_msg += f"\n {repr(info)}"
|
||||||
|
else:
|
||||||
|
for key, val in info.items():
|
||||||
|
sys_info_msg += f"\n {key}: {val}"
|
||||||
|
self.server.add_log_rollover_item('system_info', sys_info_msg, log=log)
|
||||||
|
|
||||||
async def wait_for_init(self, timeout: float = None) -> None:
|
async def wait_for_init(self, timeout: float = None) -> None:
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self.init_evt.wait(), timeout)
|
await asyncio.wait_for(self.init_evt.wait(), timeout)
|
||||||
|
@ -99,12 +104,14 @@ class Machine:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _initialize(self):
|
async def _initialize(self):
|
||||||
await self._check_virt_status()
|
if not self.inside_container:
|
||||||
|
await self._check_virt_status()
|
||||||
await self._find_active_services()
|
await self._find_active_services()
|
||||||
|
self._update_log_rollover()
|
||||||
|
|
||||||
async def _handle_machine_request(self, web_request: WebRequest) -> str:
|
async def _handle_machine_request(self, web_request: WebRequest) -> str:
|
||||||
ep = web_request.get_endpoint()
|
ep = web_request.get_endpoint()
|
||||||
if self.virt_type == "container":
|
if self.inside_container:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"Cannot {ep.split('/')[-1]} from within a "
|
f"Cannot {ep.split('/')[-1]} from within a "
|
||||||
f"{self.virt_id} container")
|
f"{self.virt_id} container")
|
||||||
|
@ -294,8 +301,50 @@ class Machine:
|
||||||
await self.update_service_status(notify=False)
|
await self.update_service_status(notify=False)
|
||||||
self.init_evt.set()
|
self.init_evt.set()
|
||||||
|
|
||||||
|
def _check_inside_container(self) -> Dict[str, Any]:
|
||||||
|
cgroup_file = pathlib.Path(CGROUP_PATH)
|
||||||
|
virt_type = virt_id = "none"
|
||||||
|
if cgroup_file.exists():
|
||||||
|
try:
|
||||||
|
data = cgroup_file.read_text()
|
||||||
|
container_types = ["docker", "lxc"]
|
||||||
|
for ct in container_types:
|
||||||
|
if ct in data:
|
||||||
|
self.inside_container = True
|
||||||
|
virt_type = "container"
|
||||||
|
virt_id = ct
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Error reading {CGROUP_PATH}")
|
||||||
|
|
||||||
|
# Fall back to process schedule check
|
||||||
|
if not self.inside_container:
|
||||||
|
sched_file = pathlib.Path(SCHED_PATH)
|
||||||
|
if sched_file.exists():
|
||||||
|
try:
|
||||||
|
data = sched_file.read_text().strip()
|
||||||
|
proc_name = data.split('\n')[0].split()[0]
|
||||||
|
if proc_name not in ["init", "systemd"]:
|
||||||
|
self.inside_container = True
|
||||||
|
virt_type = "container"
|
||||||
|
virt_id = "lxc"
|
||||||
|
if (
|
||||||
|
os.path.exists("/.dockerenv") or
|
||||||
|
os.path.exists("/.dockerinit")
|
||||||
|
):
|
||||||
|
virt_id = "docker"
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Error reading {SCHED_PATH}")
|
||||||
|
|
||||||
|
self.virt_id = virt_id
|
||||||
|
return {
|
||||||
|
'virt_type': virt_type,
|
||||||
|
'virt_identifier': virt_id
|
||||||
|
}
|
||||||
|
|
||||||
async def _check_virt_status(self) -> None:
|
async def _check_virt_status(self) -> None:
|
||||||
self.virt_id = self.virt_type = "none"
|
# Fallback virtualization check
|
||||||
|
virt_id = virt_type = "none"
|
||||||
|
|
||||||
shell_cmd: SCMDComp = self.server.lookup_component('shell_command')
|
shell_cmd: SCMDComp = self.server.lookup_component('shell_command')
|
||||||
|
|
||||||
|
@ -307,31 +356,32 @@ class Machine:
|
||||||
except shell_cmd.error:
|
except shell_cmd.error:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.virt_id = resp.strip()
|
virt_id = resp.strip()
|
||||||
|
|
||||||
if self.virt_id != "none":
|
if virt_id != "none":
|
||||||
# Check explicitly for container virtualization
|
# Check explicitly for container virtualization
|
||||||
scmd = shell_cmd.build_shell_command(
|
scmd = shell_cmd.build_shell_command(
|
||||||
"systemd-detect-virt --container")
|
"systemd-detect-virt --container")
|
||||||
try:
|
try:
|
||||||
resp = await scmd.run_with_response()
|
resp = await scmd.run_with_response()
|
||||||
except shell_cmd.error:
|
except shell_cmd.error:
|
||||||
self.virt_type = "vm"
|
virt_type = "vm"
|
||||||
else:
|
else:
|
||||||
if self.virt_id == resp.strip():
|
if virt_id == resp.strip():
|
||||||
self.virt_type = "container"
|
virt_type = "container"
|
||||||
else:
|
else:
|
||||||
# Moonraker is run from within a VM inside a container
|
# Moonraker is run from within a VM inside a container
|
||||||
self.virt_type = "vm"
|
virt_type = "vm"
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Virtualized Environment Detected, Type: {self.virt_type} "
|
f"Virtualized Environment Detected, Type: {virt_type} "
|
||||||
f"id: {self.virt_id}")
|
f"id: {virt_id}")
|
||||||
else:
|
else:
|
||||||
logging.info("No Virtualization Detected")
|
logging.info("No Virtualization Detected")
|
||||||
|
|
||||||
|
self.virt_id = virt_id
|
||||||
self.system_info['virtualization'] = {
|
self.system_info['virtualization'] = {
|
||||||
'virt_type': self.virt_type,
|
'virt_type': virt_type,
|
||||||
'virt_identifier': self.virt_id
|
'virt_identifier': virt_id
|
||||||
}
|
}
|
||||||
|
|
||||||
async def update_service_status(self, notify: bool = True) -> None:
|
async def update_service_status(self, notify: bool = True) -> None:
|
||||||
|
|
Loading…
Reference in New Issue