import subprocess from typing import Callable, Iterator, Sequence from l4d2host.process import CommandResult, run_command SYSTEMCTL_HELPER = "/usr/local/libexec/left4me/left4me-systemctl" JOURNALCTL_HELPER = "/usr/local/libexec/left4me/left4me-journalctl" def systemctl_command(action: str, name: str) -> list[str]: return ["sudo", "-n", SYSTEMCTL_HELPER, action, name] def journalctl_command(name: str, lines: int = 200, follow: bool = True) -> list[str]: follow_arg = "--follow" if follow else "--no-follow" return ["sudo", "-n", JOURNALCTL_HELPER, name, "--lines", str(lines), follow_arg] def start_service( name: str, *, on_stdout: Callable[[str], None] | None = None, on_stderr: Callable[[str], None] | None = None, passthrough: bool = False, should_cancel: Callable[[], bool] | None = None, ) -> CommandResult: return run_command( systemctl_command("start", name), on_stdout=on_stdout, on_stderr=on_stderr, passthrough=passthrough, should_cancel=should_cancel, ) def stop_service( name: str, *, on_stdout: Callable[[str], None] | None = None, on_stderr: Callable[[str], None] | None = None, passthrough: bool = False, should_cancel: Callable[[], bool] | None = None, ) -> CommandResult: return run_command( systemctl_command("stop", name), on_stdout=on_stdout, on_stderr=on_stderr, passthrough=passthrough, should_cancel=should_cancel, ) def show_service(name: str) -> CommandResult: return run_command(systemctl_command("show", name)) def stream_command(cmd: Sequence[str]) -> Iterator[str]: command = list(cmd) proc = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, ) try: if proc.stdout is None: return for raw in iter(proc.stdout.readline, ""): yield raw.rstrip("\n") returncode = proc.wait() if returncode is None: returncode = proc.poll() stderr = proc.stderr.read() if proc.stderr is not None else "" if returncode: raise subprocess.CalledProcessError(returncode=returncode, cmd=command, stderr=stderr) finally: if proc.poll() is None: proc.terminate() proc.wait(timeout=2) def stream_journal(name: str, *, lines: int = 200, follow: bool = True) -> Iterator[str]: return stream_command(journalctl_command(name, lines=lines, follow=follow))