from __future__ import annotations from datetime import datetime, timezone from app.core.command_runner import CommandError from app.core.command_runner import CommandRunner class ServiceManager: def __init__(self, command_runner: CommandRunner) -> None: self.command_runner = command_runner def enable_service(self, service_name: str) -> None: self.command_runner.run(["systemctl", "enable", service_name]) def disable_service(self, service_name: str) -> None: self.command_runner.run(["systemctl", "disable", service_name]) def start_service(self, service_name: str) -> None: self.command_runner.run(["systemctl", "start", service_name]) def stop_service(self, service_name: str) -> None: self.command_runner.run(["systemctl", "stop", service_name]) def restart_service(self, service_name: str) -> None: self.command_runner.run(["systemctl", "restart", service_name]) def reset_failed(self, service_name: str) -> None: self.command_runner.run(["systemctl", "reset-failed", service_name]) def assert_service_active(self, service_name: str) -> None: result = self.command_runner.run(["systemctl", "is-active", service_name]) if result.stdout.strip() != "active": raise RuntimeError(f"Service is not active: {service_name}") def get_service_status(self, service_name: str) -> dict[str, object]: command = [ "systemctl", "show", service_name, "--no-pager", "--property=LoadState", "--property=ActiveState", "--property=SubState", "--property=UnitFileState", ] error_message: str | None = None try: output = self.command_runner.run(command, timeout=5).stdout except CommandError as error: output = error.stdout error_message = str(error) except Exception as error: output = "" error_message = str(error) properties = self._parse_properties(output) load_state = properties.get("LoadState", "unknown") or "unknown" active_state = properties.get("ActiveState", "unknown") or "unknown" sub_state = properties.get("SubState", "unknown") or "unknown" unit_file_state = properties.get("UnitFileState", "unknown") or "unknown" active = load_state == "loaded" and active_state == "active" enabled = unit_file_state in {"enabled", "enabled-runtime"} if load_state == "not-found": status = "not-found" elif active_state != "unknown": status = active_state else: status = "unknown" return { "serviceName": service_name, "loadState": load_state, "activeState": active_state, "subState": sub_state, "unitFileState": unit_file_state, "active": active, "enabled": enabled, "healthy": active, "status": status, "checkedAt": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"), "errorMessage": error_message, } @staticmethod def _parse_properties(output: str) -> dict[str, str]: properties: dict[str, str] = {} for line in output.splitlines(): key, separator, value = line.partition("=") if separator and key: properties[key.strip()] = value.strip() return properties