machine: extract moonraker systemd unit info
This allows moonraker to validate and log the current unit file. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
cd24b116c6
commit
a7b50a8068
@ -54,6 +54,11 @@ SD_MFGRS = {
|
|||||||
}
|
}
|
||||||
IP_FAMILIES = {'inet': 'ipv4', 'inet6': 'ipv6'}
|
IP_FAMILIES = {'inet': 'ipv4', 'inet6': 'ipv6'}
|
||||||
NETWORK_UPDATE_SEQUENCE = 10
|
NETWORK_UPDATE_SEQUENCE = 10
|
||||||
|
SERVICE_PROPERTIES = [
|
||||||
|
"Requires", "After", "SupplementaryGroups", "EnvironmentFiles",
|
||||||
|
"ExecStart", "WorkingDirectory", "FragmentPath", "Description",
|
||||||
|
"User"
|
||||||
|
]
|
||||||
|
|
||||||
class Machine:
|
class Machine:
|
||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
@ -63,6 +68,7 @@ class Machine:
|
|||||||
dist_info.update(distro.info())
|
dist_info.update(distro.info())
|
||||||
dist_info['release_info'] = distro.distro_release_info()
|
dist_info['release_info'] = distro.distro_release_info()
|
||||||
self.inside_container = False
|
self.inside_container = False
|
||||||
|
self.moonraker_service_info: Dict[str, Any] = {}
|
||||||
self.system_info: Dict[str, Any] = {
|
self.system_info: Dict[str, Any] = {
|
||||||
'python': {
|
'python': {
|
||||||
"version": sys.version_info,
|
"version": sys.version_info,
|
||||||
@ -132,6 +138,12 @@ class Machine:
|
|||||||
sys_info_msg += f"\n {key}: {val}"
|
sys_info_msg += f"\n {key}: {val}"
|
||||||
self.server.add_log_rollover_item('system_info', sys_info_msg, log=log)
|
self.server.add_log_rollover_item('system_info', sys_info_msg, log=log)
|
||||||
|
|
||||||
|
def get_system_provider(self):
|
||||||
|
return self.sys_provider
|
||||||
|
|
||||||
|
def get_moonraker_service_info(self):
|
||||||
|
return dict(self.moonraker_service_info)
|
||||||
|
|
||||||
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)
|
||||||
@ -150,6 +162,11 @@ class Machine:
|
|||||||
avail_list = list(available_svcs.keys())
|
avail_list = list(available_svcs.keys())
|
||||||
self.system_info['available_services'] = avail_list
|
self.system_info['available_services'] = avail_list
|
||||||
self.system_info['service_state'] = available_svcs
|
self.system_info['service_state'] = available_svcs
|
||||||
|
svc_info = await self.sys_provider.extract_service_info(
|
||||||
|
"moonraker", os.getpid(), SERVICE_PROPERTIES
|
||||||
|
)
|
||||||
|
self.moonraker_service_info = svc_info
|
||||||
|
self.log_service_info(svc_info)
|
||||||
self.init_evt.set()
|
self.init_evt.set()
|
||||||
|
|
||||||
async def _handle_machine_request(self, web_request: WebRequest) -> str:
|
async def _handle_machine_request(self, web_request: WebRequest) -> str:
|
||||||
@ -472,6 +489,20 @@ class Machine:
|
|||||||
wifi_intfs[parts[0]] = parts[1].split(":")[-1].strip('"')
|
wifi_intfs[parts[0]] = parts[1].split(":")[-1].strip('"')
|
||||||
return wifi_intfs
|
return wifi_intfs
|
||||||
|
|
||||||
|
def log_service_info(self, svc_info: Dict[str, Any]) -> None:
|
||||||
|
if not svc_info:
|
||||||
|
return
|
||||||
|
name = svc_info.get("unit_name", "unknown")
|
||||||
|
msg = f"\nSystemd unit {name}:"
|
||||||
|
for key, val in svc_info.items():
|
||||||
|
if key == "properties":
|
||||||
|
msg += "\nProperties:"
|
||||||
|
for prop_key, prop in val.items():
|
||||||
|
msg += f"\n**{prop_key}={prop}"
|
||||||
|
else:
|
||||||
|
msg += f"\n{key}: {val}"
|
||||||
|
self.server.add_log_rollover_item(name, msg)
|
||||||
|
|
||||||
class BaseProvider:
|
class BaseProvider:
|
||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
self.server = config.get_server()
|
self.server = config.get_server()
|
||||||
@ -506,6 +537,11 @@ class BaseProvider:
|
|||||||
def get_available_services(self) -> Dict[str, Dict[str, str]]:
|
def get_available_services(self) -> Dict[str, Dict[str, str]]:
|
||||||
return self.available_services
|
return self.available_services
|
||||||
|
|
||||||
|
async def extract_service_info(
|
||||||
|
self, service: str, pid: int, properties: List[str], raw: bool = False
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
return {}
|
||||||
|
|
||||||
class SystemdCliProvider(BaseProvider):
|
class SystemdCliProvider(BaseProvider):
|
||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
await self._detect_active_services()
|
await self._detect_active_services()
|
||||||
@ -605,6 +641,69 @@ class SystemdCliProvider(BaseProvider):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Error processing service state update")
|
logging.exception("Error processing service state update")
|
||||||
|
|
||||||
|
async def extract_service_info(
|
||||||
|
self,
|
||||||
|
service_name: str,
|
||||||
|
pid: int,
|
||||||
|
properties: List[str],
|
||||||
|
raw: bool = False
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
service_info: Dict[str, Any] = {}
|
||||||
|
expected_name = f"{service_name}.service"
|
||||||
|
try:
|
||||||
|
resp: str = await self.shell_cmd.exec_cmd(
|
||||||
|
f"systemctl status {pid}"
|
||||||
|
)
|
||||||
|
unit_name = resp.split(maxsplit=2)[1]
|
||||||
|
service_info["unit_name"] = unit_name
|
||||||
|
service_info["is_default"] = True
|
||||||
|
if unit_name != expected_name:
|
||||||
|
service_info["is_default"] = False
|
||||||
|
logging.info(
|
||||||
|
f"Detected alternate unit name for {service_name}: "
|
||||||
|
f"{unit_name}"
|
||||||
|
)
|
||||||
|
prop_args = ",".join(properties)
|
||||||
|
props: str = await self.shell_cmd.exec_cmd(
|
||||||
|
f"systemctl show -p {prop_args} {unit_name}"
|
||||||
|
)
|
||||||
|
raw_props: Dict[str, Any] = {}
|
||||||
|
lines = [p.strip() for p in props.split("\n") if p.strip]
|
||||||
|
for line in lines:
|
||||||
|
parts = line.split("=", 1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
key = parts[0].strip()
|
||||||
|
val = parts[1].strip()
|
||||||
|
raw_props[key] = val
|
||||||
|
if raw:
|
||||||
|
service_info["properties"] = raw_props
|
||||||
|
else:
|
||||||
|
processed = self._process_raw_properties(raw_props)
|
||||||
|
service_info["properties"] = processed
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Error extracting service info")
|
||||||
|
return {}
|
||||||
|
return service_info
|
||||||
|
|
||||||
|
def _process_raw_properties(
|
||||||
|
self, raw_props: Dict[str, str]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
processed: Dict[str, Any] = {}
|
||||||
|
for key, val in raw_props.items():
|
||||||
|
processed[key] = val
|
||||||
|
if key == "ExecStart":
|
||||||
|
# this is a struct, we need to deconstruct it
|
||||||
|
match = re.search(r"argv\[\]=([^;]+);", val)
|
||||||
|
if match is not None:
|
||||||
|
processed[key] = match.group(1).strip()
|
||||||
|
elif key == "EnvironmentFiles":
|
||||||
|
if val:
|
||||||
|
processed[key] = val.split()[0]
|
||||||
|
elif key in ["Requires", "After", "SupplementaryGroups"]:
|
||||||
|
vals = [v.strip() for v in val.split() if v.strip()]
|
||||||
|
processed[key] = vals
|
||||||
|
return processed
|
||||||
|
|
||||||
class SystemdDbusProvider(BaseProvider):
|
class SystemdDbusProvider(BaseProvider):
|
||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
@ -781,6 +880,72 @@ class SystemdDbusProvider(BaseProvider):
|
|||||||
self.server.send_event("machine:service_state_changed",
|
self.server.send_event("machine:service_state_changed",
|
||||||
{service_name: dict(svc)})
|
{service_name: dict(svc)})
|
||||||
|
|
||||||
|
async def extract_service_info(
|
||||||
|
self,
|
||||||
|
service_name: str,
|
||||||
|
pid: int,
|
||||||
|
properties: List[str],
|
||||||
|
raw: bool = False
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
if not hasattr(self, "systemd_mgr"):
|
||||||
|
return {}
|
||||||
|
mgr = self.systemd_mgr
|
||||||
|
service_info: Dict[str, Any] = {}
|
||||||
|
expected_name = f"{service_name}.service"
|
||||||
|
try:
|
||||||
|
dbus_path: str
|
||||||
|
dbus_path = await mgr.call_get_unit_by_pid(pid) # type: ignore
|
||||||
|
bus = "org.freedesktop.systemd1"
|
||||||
|
unit_intf, svc_intf = await self.dbus_mgr.get_interfaces(
|
||||||
|
"org.freedesktop.systemd1", dbus_path,
|
||||||
|
[f"{bus}.Unit", f"{bus}.Service"]
|
||||||
|
)
|
||||||
|
unit_name = await unit_intf.get_id() # type: ignore
|
||||||
|
service_info["unit_name"] = unit_name
|
||||||
|
service_info["is_default"] = True
|
||||||
|
if unit_name != expected_name:
|
||||||
|
service_info["is_default"] = False
|
||||||
|
logging.info(
|
||||||
|
f"Detected alternate unit name for {service_name}: "
|
||||||
|
f"{unit_name}"
|
||||||
|
)
|
||||||
|
raw_props: Dict[str, Any] = {}
|
||||||
|
for key in properties:
|
||||||
|
snake_key = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", key).lower()
|
||||||
|
func = getattr(unit_intf, f"get_{snake_key}", None)
|
||||||
|
if func is None:
|
||||||
|
func = getattr(svc_intf, f"get_{snake_key}", None)
|
||||||
|
if func is None:
|
||||||
|
continue
|
||||||
|
val = await func()
|
||||||
|
raw_props[key] = val
|
||||||
|
if raw:
|
||||||
|
service_info["properties"] = raw_props
|
||||||
|
else:
|
||||||
|
processed = self._process_raw_properties(raw_props)
|
||||||
|
service_info["properties"] = processed
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Error Extracting Service Info")
|
||||||
|
return {}
|
||||||
|
return service_info
|
||||||
|
|
||||||
|
def _process_raw_properties(
|
||||||
|
self, raw_props: Dict[str, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
processed: Dict[str, Any] = {}
|
||||||
|
for key, val in raw_props.items():
|
||||||
|
if key == "ExecStart":
|
||||||
|
try:
|
||||||
|
val = " ".join(val[0][1])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
elif key == "EnvironmentFiles":
|
||||||
|
try:
|
||||||
|
val = val[0][0]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
processed[key] = val
|
||||||
|
return processed
|
||||||
|
|
||||||
def load_component(config: ConfigHelper) -> Machine:
|
def load_component(config: ConfigHelper) -> Machine:
|
||||||
return Machine(config)
|
return Machine(config)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user