From 27455dfc648484451846978e31dbe9e9b610d0f2 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 3 May 2024 23:21:23 +0200 Subject: [PATCH] feat: add mobileraker support Signed-off-by: Dominik Willner --- kiauh/components/mobileraker/__init__.py | 16 ++ kiauh/components/mobileraker/mobileraker.py | 224 ++++++++++++++++++++ kiauh/core/menus/install_menu.py | 5 + kiauh/core/menus/remove_menu.py | 15 +- kiauh/core/menus/update_menu.py | 18 +- 5 files changed, 271 insertions(+), 7 deletions(-) create mode 100644 kiauh/components/mobileraker/__init__.py create mode 100644 kiauh/components/mobileraker/mobileraker.py diff --git a/kiauh/components/mobileraker/__init__.py b/kiauh/components/mobileraker/__init__.py new file mode 100644 index 0000000..f5fd1b2 --- /dev/null +++ b/kiauh/components/mobileraker/__init__.py @@ -0,0 +1,16 @@ +# ======================================================================= # +# Copyright (C) 2020 - 2024 Dominik Willner # +# # +# This file is part of KIAUH - Klipper Installation And Update Helper # +# https://github.com/dw-0/kiauh # +# # +# This file may be distributed under the terms of the GNU GPLv3 license # +# ======================================================================= # +from pathlib import Path + +from core.backup_manager import BACKUP_ROOT_DIR + +MOBILERAKER_REPO = "https://github.com/Clon1998/mobileraker_companion.git" +MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion") +MOBILERAKER_ENV = Path.home().joinpath("mobileraker-env") +MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups") diff --git a/kiauh/components/mobileraker/mobileraker.py b/kiauh/components/mobileraker/mobileraker.py new file mode 100644 index 0000000..7604772 --- /dev/null +++ b/kiauh/components/mobileraker/mobileraker.py @@ -0,0 +1,224 @@ +# ======================================================================= # +# Copyright (C) 2020 - 2024 Dominik Willner # +# # +# This file is part of KIAUH - Klipper Installation And Update Helper # +# https://github.com/dw-0/kiauh # +# # +# This file may be distributed under the terms of the GNU GPLv3 license # +# ======================================================================= # +import shutil +from pathlib import Path +from subprocess import run, CalledProcessError +from typing import List, Dict, Literal, Union + +from components.klipper.klipper import Klipper +from components.mobileraker import ( + MOBILERAKER_REPO, + MOBILERAKER_DIR, + MOBILERAKER_ENV, + MOBILERAKER_BACKUP_DIR, +) +from components.moonraker.moonraker import Moonraker +from core.backup_manager.backup_manager import BackupManager +from core.instance_manager.instance_manager import InstanceManager +from core.settings.kiauh_settings import KiauhSettings +from utils.common import get_install_status, check_install_dependencies +from utils.config_utils import add_config_section, remove_config_section +from utils.constants import SYSTEMD +from utils.fs_utils import remove_with_sudo +from utils.git_utils import ( + git_clone_wrapper, + git_pull_wrapper, + get_repo_name, + get_local_commit, + get_remote_commit, +) +from utils.input_utils import get_confirm +from utils.logger import Logger, DialogType +from utils.sys_utils import ( + check_python_version, + cmd_sysctl_service, + install_python_requirements, + cmd_sysctl_manage, +) + + +def install_mobileraker() -> None: + Logger.print_status("Installing Mobileraker's companion ...") + + if not check_python_version(3, 7): + return + + mr_im = InstanceManager(Moonraker) + mr_instances = mr_im.instances + if not mr_instances: + warn_msg = [ + "Moonraker not found! Mobileraker's companion will not properly work " + "without a working Moonraker installation.", + "Mobileraker's companion's update manager configuration for Moonraker " + "will not be added to any moonraker.conf.", + ] + Logger.print_dialog(DialogType.WARNING, warn_msg) + if not get_confirm( + "Continue Mobileraker's companion installation?", + default_choice=False, + allow_go_back=True, + ): + return + + package_list = ["wget", "curl", "unzip", "dfu-util"] + check_install_dependencies(package_list) + + git_clone_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR) + + try: + script = f"{MOBILERAKER_DIR}/scripts/install.sh" + run(script, shell=True, check=True) + if mr_instances: + patch_mobileraker_update_manager(mr_instances) + mr_im.restart_all_instance() + else: + Logger.print_info( + "Moonraker is not installed! Cannot add Mobileraker's companion to update manager!" + ) + Logger.print_ok("Mobileraker's companion successfully installed!") + except CalledProcessError as e: + Logger.print_error(f"Error installing Mobileraker's companion:\n{e}") + return + + +def patch_mobileraker_update_manager(instances: List[Moonraker]) -> None: + env_py = f"{MOBILERAKER_ENV}/bin/python" + add_config_section( + section="update_manager mobileraker", + instances=instances, + options=[ + ("type", "git_repo"), + ("path", "mobileraker_companion"), + ("orgin", MOBILERAKER_REPO), + ("primary_branch", "main"), + ("managed_services", "mobileraker"), + ("env", env_py), + ("requirements", "scripts/mobileraker-requirements.txt"), + ("install_script", "scripts/install.sh"), + ], + ) + + +def update_mobileraker() -> None: + try: + cmd_sysctl_service("KlipperScreen", "stop") + + if not MOBILERAKER_DIR.exists(): + Logger.print_info( + "Mobileraker's companion does not seem to be installed! Skipping ..." + ) + return + + Logger.print_status("Updating Mobileraker's companion ...") + + cmd_sysctl_service("mobileraker", "stop") + + settings = KiauhSettings() + if settings.get("kiauh", "backup_before_update"): + backup_mobileraker_dir() + + git_pull_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR) + + requirements = MOBILERAKER_DIR.joinpath("/scripts/mobileraker-requirements.txt") + install_python_requirements(MOBILERAKER_ENV, requirements) + + cmd_sysctl_service("mobileraker", "start") + + Logger.print_ok("Mobileraker's companion updated successfully.", end="\n\n") + except CalledProcessError as e: + Logger.print_error(f"Error updating Mobileraker's companion:\n{e}") + return + + +def get_mobileraker_status() -> ( + Dict[ + Literal["status", "status_code", "repo", "local", "remote"], + Union[str, int], + ] +): + files = [ + MOBILERAKER_DIR, + MOBILERAKER_ENV, + SYSTEMD.joinpath("mobileraker.service"), + ] + status = get_install_status(MOBILERAKER_DIR, files) + return { + "status": status.get("status"), + "status_code": status.get("status_code"), + "repo": get_repo_name(MOBILERAKER_DIR), + "local": get_local_commit(MOBILERAKER_DIR), + "remote": get_remote_commit(MOBILERAKER_DIR), + } + + +def remove_mobileraker() -> None: + Logger.print_status("Removing Mobileraker's companion ...") + try: + if MOBILERAKER_DIR.exists(): + Logger.print_status("Removing Mobileraker's companion directory ...") + shutil.rmtree(MOBILERAKER_DIR) + Logger.print_ok("Mobileraker's companion directory successfully removed!") + else: + Logger.print_warn("Mobileraker's companion directory not found!") + + if MOBILERAKER_ENV.exists(): + Logger.print_status("Removing Mobileraker's companion environment ...") + shutil.rmtree(MOBILERAKER_ENV) + Logger.print_ok("Mobileraker's companion environment successfully removed!") + else: + Logger.print_warn("Mobileraker's companion environment not found!") + + service = SYSTEMD.joinpath("mobileraker.service") + if service.exists(): + Logger.print_status("Removing mobileraker service ...") + cmd_sysctl_service(service, "stop") + cmd_sysctl_service(service, "disable") + remove_with_sudo(service) + cmd_sysctl_manage("deamon-reload") + cmd_sysctl_manage("reset-failed") + Logger.print_ok("Mobileraker's companion service successfully removed!") + + kl_im = InstanceManager(Klipper) + kl_instances: List[Klipper] = kl_im.instances + for instance in kl_instances: + logfile = instance.log_dir.joinpath("mobileraker.log") + if logfile.exists(): + Logger.print_status(f"Removing {logfile} ...") + Path(logfile).unlink() + Logger.print_ok(f"{logfile} successfully removed!") + + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances + if mr_instances: + Logger.print_status( + "Removing Mobileraker's companion from update manager ..." + ) + remove_config_section("update_manager mobileraker", mr_instances) + Logger.print_ok( + "Mobileraker's companion successfully removed from update manager!" + ) + + Logger.print_ok("Mobileraker's companion successfully removed!") + + except Exception as e: + Logger.print_error(f"Error removing Mobileraker's companion:\n{e}") + + +def backup_mobileraker_dir() -> None: + bm = BackupManager() + bm.backup_directory( + "mobileraker_companion", + source=MOBILERAKER_DIR, + target=MOBILERAKER_BACKUP_DIR, + ) + bm.backup_directory( + "mobileraker-env", + source=MOBILERAKER_ENV, + target=MOBILERAKER_BACKUP_DIR, + ) diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 3906f79..d1c9d27 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -13,6 +13,7 @@ from typing import Type, Optional from components.crowsnest.crowsnest import install_crowsnest from components.klipper import klipper_setup from components.klipperscreen.klipperscreen import install_klipperscreen +from components.mobileraker.mobileraker import install_mobileraker from components.moonraker import moonraker_setup from components.webui_client import client_setup from components.webui_client.client_config import client_config_setup @@ -47,6 +48,7 @@ class InstallMenu(BaseMenu): "5": Option(method=self.install_mainsail_config, menu=False), "6": Option(method=self.install_fluidd_config, menu=False), "7": Option(method=self.install_klipperscreen, menu=False), + "8": Option(method=self.install_mobileraker, menu=False), "9": Option(method=self.install_crowsnest, menu=False), } @@ -96,5 +98,8 @@ class InstallMenu(BaseMenu): def install_klipperscreen(self, **kwargs): install_klipperscreen() + def install_mobileraker(self, **kwargs): + install_mobileraker() + def install_crowsnest(self, **kwargs): install_crowsnest() diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 7339823..55714fd 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -13,6 +13,7 @@ from typing import Type, Optional from components.crowsnest.crowsnest import remove_crowsnest from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from components.klipperscreen.klipperscreen import remove_klipperscreen +from components.mobileraker.mobileraker import remove_mobileraker from components.moonraker.menus.moonraker_remove_menu import ( MoonrakerRemoveMenu, ) @@ -45,7 +46,8 @@ class RemoveMenu(BaseMenu): "3": Option(method=self.remove_mainsail, menu=True), "4": Option(method=self.remove_fluidd, menu=True), "5": Option(method=self.remove_klipperscreen, menu=True), - "6": Option(method=self.remove_crowsnest, menu=True), + "6": Option(method=self.remove_mobileraker, menu=True), + "7": Option(method=self.remove_crowsnest, menu=True), } def print_menu(self): @@ -62,11 +64,11 @@ class RemoveMenu(BaseMenu): | Firmware & API: | Touchscreen GUI: | | 1) [Klipper] | 5) [KlipperScreen] | | 2) [Moonraker] | | - | | Webcam Streamer: | - | Klipper Webinterface: | 6) [Crowsnest] | + | | Android / iOS: | + | Klipper Webinterface: | 6) [Mobileraker] | | 3) [Mainsail] | | - | 4) [Fluidd] | | - | | | + | 4) [Fluidd] | Webcam Streamer: | + | | 7) [Crowsnest] | """ )[1:] print(menu, end="") @@ -86,5 +88,8 @@ class RemoveMenu(BaseMenu): def remove_klipperscreen(self, **kwargs): remove_klipperscreen() + def remove_mobileraker(self, **kwargs): + remove_mobileraker() + def remove_crowsnest(self, **kwargs): remove_crowsnest() diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 9d0661c..528de53 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -19,6 +19,10 @@ from components.klipperscreen.klipperscreen import ( update_klipperscreen, get_klipperscreen_status, ) +from components.mobileraker.mobileraker import ( + update_mobileraker, + get_mobileraker_status, +) from components.moonraker.moonraker_setup import update_moonraker from components.moonraker.moonraker_utils import get_moonraker_status from components.webui_client.client_config.client_config_setup import ( @@ -64,6 +68,8 @@ class UpdateMenu(BaseMenu): self.fc_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.ks_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.ks_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.mb_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.mb_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.cn_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.cn_remote = f"{COLOR_WHITE}{RESET_FORMAT}" @@ -119,7 +125,7 @@ class UpdateMenu(BaseMenu): | | | | | Other: |---------------|---------------| | 7) KlipperScreen | {self.ks_local:<22} | {self.ks_remote:<22} | - | 8) Mobileraker | | | + | 8) Mobileraker | {self.mb_local:<22} | {self.mb_remote:<22} | | 9) Crowsnest | {self.cn_local:<22} | {self.cn_remote:<22} | | |-------------------------------| | 10) System | | @@ -151,7 +157,8 @@ class UpdateMenu(BaseMenu): def update_klipperscreen(self, **kwargs): update_klipperscreen() - def update_mobileraker(self, **kwargs): ... + def update_mobileraker(self, **kwargs): + update_mobileraker() def update_crowsnest(self, **kwargs): update_crowsnest() @@ -207,6 +214,13 @@ class UpdateMenu(BaseMenu): ) self.ks_remote = f"{COLOR_GREEN}{ks_status.get('remote')}{RESET_FORMAT}" + # mobileraker + mb_status = get_mobileraker_status() + self.mb_local = self.format_local_status( + mb_status.get("local"), mb_status.get("remote") + ) + self.mb_remote = f"{COLOR_GREEN}{mb_status.get('remote')}{RESET_FORMAT}" + # crowsnest cn_status = get_crowsnest_status() self.cn_local = self.format_local_status(