diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 55f6941..bccacee 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -124,7 +124,7 @@ class BaseInstance(ABC): raise NotImplementedError("Subclasses must implement the create method") @abstractmethod - def delete(self, del_remnants: bool) -> None: + def delete(self) -> None: raise NotImplementedError("Subclasses must implement the delete method") def create_folders(self, add_dirs: List[Path] = None) -> None: diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index adc72de..411a25b 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -83,7 +83,7 @@ class InstanceManager: @property def instances(self) -> List[I]: if not self._instances: - self._instances = self._find_instances() + self._instances = self.find_instances() return sorted(self._instances, key=lambda x: self._sort_instance_list(x.suffix)) @@ -101,10 +101,10 @@ class InstanceManager: else: raise ValueError("current_instance cannot be None") - def delete_instance(self, del_remnants=False) -> None: + def delete_instance(self) -> None: if self.current_instance is not None: try: - self.current_instance.delete(del_remnants) + self.current_instance.delete() except (OSError, subprocess.CalledProcessError) as e: Logger.print_error(f"Removing instance failed: {e}") raise @@ -188,7 +188,7 @@ class InstanceManager: Logger.print_error(f"{e}") raise - def _find_instances(self) -> List[I]: + def find_instances(self) -> List[I]: name = self.instance_type.__name__.lower() pattern = re.compile(f"^{name}(-[0-9a-zA-Z]+)?.service$") excluded = self.instance_type.blacklist() diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index b30e4ee..efb1650 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -67,7 +67,7 @@ class InstallMenu(BaseMenu): klipper_setup.run_klipper_setup(install=True) def install_moonraker(self): - moonraker_setup.run_moonraker_setup(install=True) + moonraker_setup.install_moonraker() def install_mainsail(self): mainsail_setup.run_mainsail_installation() diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 4883609..5526406 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -14,9 +14,8 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper import klipper_setup -from kiauh.modules.mainsail import mainsail_setup from kiauh.modules.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu -from kiauh.modules.moonraker import moonraker_setup +from kiauh.modules.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu from kiauh.utils.constants import COLOR_RED, RESET_FORMAT @@ -27,7 +26,7 @@ class RemoveMenu(BaseMenu): header=True, options={ 1: self.remove_klipper, - 2: self.remove_moonraker, + 2: MoonrakerRemoveMenu, 3: MainsailRemoveMenu, 5: self.remove_fluidd, 6: self.remove_klipperscreen, @@ -73,9 +72,6 @@ class RemoveMenu(BaseMenu): def remove_klipper(self): klipper_setup.run_klipper_setup(install=False) - def remove_moonraker(self): - moonraker_setup.run_moonraker_setup(install=False) - def remove_fluidd(self): print("remove_fluidd") diff --git a/kiauh/modules/moonraker/menus/__init__.py b/kiauh/modules/moonraker/menus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py b/kiauh/modules/moonraker/menus/moonraker_remove_menu.py new file mode 100644 index 0000000..c6d0a05 --- /dev/null +++ b/kiauh/modules/moonraker/menus/moonraker_remove_menu.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# Copyright (C) 2020 - 2023 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 textwrap + +from kiauh.core.menus import BACK_HELP_FOOTER +from kiauh.core.menus.base_menu import BaseMenu +from kiauh.modules.moonraker import moonraker_remove +from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN + + +class MoonrakerRemoveMenu(BaseMenu): + def __init__(self): + super().__init__( + header=False, + options={ + 0: self.toggle_all, + 1: self.toggle_remove_moonraker_service, + 2: self.toggle_remove_moonraker_dir, + 3: self.toggle_remove_moonraker_env, + 4: self.toggle_remove_moonraker_polkit, + 5: self.toggle_delete_moonraker_logs, + 6: self.run_removal_process, + }, + footer_type=BACK_HELP_FOOTER, + ) + self.remove_moonraker_service = False + self.remove_moonraker_dir = False + self.remove_moonraker_env = False + self.remove_moonraker_polkit = False + self.delete_moonraker_logs = False + + def print_menu(self) -> None: + header = " [ Remove Moonraker ] " + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) + checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + unchecked = "[ ]" + o1 = checked if self.remove_moonraker_service else unchecked + o2 = checked if self.remove_moonraker_dir else unchecked + o3 = checked if self.remove_moonraker_env else unchecked + o4 = checked if self.remove_moonraker_polkit else unchecked + o5 = checked if self.delete_moonraker_logs else unchecked + menu = textwrap.dedent( + f""" + /=======================================================\\ + | {color}{header:~^{count}}{RESET_FORMAT} | + |-------------------------------------------------------| + | Enter a number and hit enter to select / deselect | + | the specific option for removal. | + |-------------------------------------------------------| + | 0) Select everything | + |-------------------------------------------------------| + | 1) {o1} Remove Service | + | 2) {o2} Remove Local Repository | + | 3) {o3} Remove Python Environment | + | 4) {o4} Remove Policy Kit Rules | + | 5) {o5} Delete all Log-Files | + |-------------------------------------------------------| + | 6) Continue | + """ + )[1:] + print(menu, end="") + + def toggle_all(self) -> None: + self.remove_moonraker_service = True + self.remove_moonraker_dir = True + self.remove_moonraker_env = True + self.remove_moonraker_polkit = True + self.delete_moonraker_logs = True + + def toggle_remove_moonraker_service(self) -> None: + self.remove_moonraker_service = not self.remove_moonraker_service + + def toggle_remove_moonraker_dir(self) -> None: + self.remove_moonraker_dir = not self.remove_moonraker_dir + + def toggle_remove_moonraker_env(self) -> None: + self.remove_moonraker_env = not self.remove_moonraker_env + + def toggle_remove_moonraker_polkit(self) -> None: + self.remove_moonraker_polkit = not self.remove_moonraker_polkit + + def toggle_delete_moonraker_logs(self) -> None: + self.delete_moonraker_logs = not self.delete_moonraker_logs + + def run_removal_process(self) -> None: + if ( + not self.remove_moonraker_service + and not self.remove_moonraker_dir + and not self.remove_moonraker_env + and not self.remove_moonraker_polkit + and not self.delete_moonraker_logs + ): + error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" + print(error) + return + + moonraker_remove.run_moonraker_removal( + self.remove_moonraker_service, + self.remove_moonraker_dir, + self.remove_moonraker_env, + self.remove_moonraker_polkit, + self.delete_moonraker_logs, + ) + + self.remove_moonraker_service = False + self.remove_moonraker_dir = False + self.remove_moonraker_env = False + self.remove_moonraker_polkit = False + self.delete_moonraker_logs = False diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py index 8c6777c..4a1ae12 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/modules/moonraker/moonraker.py @@ -62,7 +62,7 @@ class Moonraker(BaseInstance): Logger.print_error(f"Error writing file: {e}") raise - def delete(self, del_remnants: bool) -> None: + def delete(self) -> None: service_file = self.get_service_file_name(extension=True) service_file_path = self.get_service_file_path() @@ -76,9 +76,6 @@ class Moonraker(BaseInstance): Logger.print_error(f"Error deleting service file: {e}") raise - if del_remnants: - self._delete_moonraker_remnants() - def write_service_file( self, service_template_path: Path, @@ -105,20 +102,6 @@ class Moonraker(BaseInstance): env_file.write(env_file_content) Logger.print_ok(f"Env file created: {env_file_target}") - def _delete_moonraker_remnants(self) -> None: - try: - Logger.print_status(f"Delete {self.moonraker_dir} ...") - shutil.rmtree(self.moonraker_dir) - Logger.print_status(f"Delete {self.env_dir} ...") - shutil.rmtree(self.env_dir) - except FileNotFoundError: - Logger.print_status("Cannot delete Moonraker directories. Not found.") - except PermissionError as e: - Logger.print_error(f"Error deleting Moonraker directories: {e}") - raise - - Logger.print_ok("Directories successfully deleted.") - def _prep_service_file( self, service_template_path: Path, env_file_path: Path ) -> str: diff --git a/kiauh/modules/moonraker/moonraker_remove.py b/kiauh/modules/moonraker/moonraker_remove.py new file mode 100644 index 0000000..e9631db --- /dev/null +++ b/kiauh/modules/moonraker/moonraker_remove.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# Copyright (C) 2020 - 2023 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 +import subprocess +from typing import List, Union + +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper_dialogs import print_instance_overview +from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.filesystem_utils import remove_file +from kiauh.utils.input_utils import get_selection_input +from kiauh.utils.logger import Logger + + +def run_moonraker_removal( + remove_service: bool, + remove_dir: bool, + remove_env: bool, + remove_polkit: bool, + delete_logs: bool, +) -> None: + im = InstanceManager(Moonraker) + + if remove_service: + Logger.print_status("Removing Moonraker instances ...") + if im.instances: + instances_to_remove = select_instances_to_remove(im.instances) + remove_instances(im, instances_to_remove) + else: + Logger.print_info("No Moonraker Services installed! Skipped ...") + + im.find_instances() + if (remove_polkit or remove_dir or remove_env) and im.instances: + Logger.print_warn("There are still other Moonraker services installed!") + Logger.print_warn("Therefor the following parts cannot be removed:") + Logger.print_warn( + """ + ● Moonraker PolicyKit rules + ● Moonraker local repository + ● Moonraker Python environment + """, + False, + ) + else: + if remove_polkit: + Logger.print_status("Removing all Moonraker policykit rules ...") + remove_polkit_rules() + if remove_dir: + Logger.print_status("Removing Moonraker local repository ...") + remove_moonraker_dir() + if remove_env: + Logger.print_status("Removing Moonraker Python environment ...") + remove_moonraker_env() + + # delete moonraker logs of all instances + if delete_logs: + Logger.print_status("Removing all Moonraker logs ...") + delete_moonraker_logs(im.instances) + + +def select_instances_to_remove( + instances: List[Moonraker], +) -> Union[List[Moonraker], None]: + print_instance_overview(instances, True, True) + + options = [str(i) for i in range(len(instances))] + options.extend(["a", "A", "b", "B"]) + + selection = get_selection_input("Select Moonraker instance to remove", options) + + instances_to_remove = [] + if selection == "b".lower(): + return None + elif selection == "a".lower(): + instances_to_remove.extend(instances) + else: + instance = instances[int(selection)] + instances_to_remove.append(instance) + + return instances_to_remove + + +def remove_instances( + instance_manager: InstanceManager, + instance_list: List[Moonraker], +) -> None: + for instance in instance_list: + Logger.print_status(f"Removing instance {instance.get_service_file_name()} ...") + instance_manager.current_instance = instance + instance_manager.stop_instance() + instance_manager.disable_instance() + instance_manager.delete_instance() + + instance_manager.reload_daemon() + + +def remove_moonraker_dir() -> None: + if not MOONRAKER_DIR.exists(): + Logger.print_info(f"'{MOONRAKER_DIR}' does not exist. Skipped ...") + return + + try: + shutil.rmtree(MOONRAKER_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{MOONRAKER_DIR}':\n{e}") + + +def remove_moonraker_env() -> None: + if not MOONRAKER_ENV_DIR.exists(): + Logger.print_info(f"'{MOONRAKER_ENV_DIR}' does not exist. Skipped ...") + return + + try: + shutil.rmtree(MOONRAKER_ENV_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{MOONRAKER_ENV_DIR}':\n{e}") + + +def remove_polkit_rules() -> None: + if not MOONRAKER_DIR.exists(): + log = "Cannot remove policykit rules. Moonraker directory not found." + Logger.print_warn(log) + return + + try: + command = [f"{MOONRAKER_DIR}/scripts/set-policykit-rules.sh", "--clear"] + subprocess.run( + command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=True + ) + except subprocess.CalledProcessError as e: + Logger.print_error(f"Error while removing policykit rules: {e}") + + Logger.print_ok("Policykit rules successfully removed!") + + +def delete_moonraker_logs(instances: List[Moonraker]) -> None: + all_logfiles = [] + for instance in instances: + all_logfiles = list(instance.log_dir.glob("moonraker.log*")) + if not all_logfiles: + Logger.print_info("No Moonraker logs found. Skipped ...") + return + + for log in all_logfiles: + Logger.print_status(f"Remove '{log}'") + remove_file(log) diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index a883071..3d16a91 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -53,53 +53,32 @@ from kiauh.utils.system_utils import ( ) -def run_moonraker_setup(install: bool) -> None: +def check_moonraker_install_requirements() -> bool: kl_im = InstanceManager(Klipper) kl_instance_list = kl_im.instances kl_instance_count = len(kl_instance_list) - mr_im = InstanceManager(Moonraker) - mr_instance_list = mr_im.instances - mr_instance_count = len(mr_instance_list) if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7): Logger.print_error("Versioncheck failed!") Logger.print_error("Python 3.7 or newer required to run Moonraker.") - return + return False is_klipper_installed = kl_instance_count > 0 - if install and not is_klipper_installed: + if not is_klipper_installed: Logger.print_warn("Klipper not installed!") Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") + return False + + +def install_moonraker() -> None: + if not check_moonraker_install_requirements(): return - is_moonraker_installed = mr_instance_count > 0 - if not install and not is_moonraker_installed: - Logger.print_warn("Moonraker not installed!") - return + kl_im = InstanceManager(Klipper) + klipper_instances = kl_im.instances + mr_im = InstanceManager(Moonraker) + moonraker_instances = mr_im.instances - if install: - install_moonraker(mr_im, mr_instance_list, kl_instance_list) - - if not install: - remove_moonraker(mr_im, mr_instance_list) - - -def handle_existing_instances(instance_list: List[Klipper]) -> bool: - instance_count = len(instance_list) - - if instance_count > 0: - print_instance_overview(instance_list) - if not get_confirm("Add new instances?", allow_go_back=True): - return False - - return True - - -def install_moonraker( - instance_manager: InstanceManager, - moonraker_instances: List[Moonraker], - klipper_instances: List[Klipper], -) -> None: selected_klipper_instance = 0 if len(klipper_instances) > 1: print_moonraker_overview( @@ -136,16 +115,16 @@ def install_moonraker( for name in instance_names: current_instance = Moonraker(suffix=name) - instance_manager.current_instance = current_instance - instance_manager.create_instance() - instance_manager.enable_instance() + mr_im.current_instance = current_instance + mr_im.create_instance() + mr_im.enable_instance() if create_example_cfg: create_example_moonraker_conf(current_instance, used_ports_map) - instance_manager.start_instance() + mr_im.start_instance() - instance_manager.reload_daemon() + mr_im.reload_daemon() def setup_moonraker_prerequesites() -> None: @@ -202,84 +181,15 @@ def install_moonraker_polkit() -> None: Logger.print_error(log) -def remove_moonraker( - instance_manager: InstanceManager, instance_list: List[Moonraker] -) -> None: - print_instance_overview(instance_list, True, True) +def handle_existing_instances(instance_list: List[Klipper]) -> bool: + instance_count = len(instance_list) - options = [str(i) for i in range(len(instance_list))] - options.extend(["a", "A", "b", "B"]) + if instance_count > 0: + print_instance_overview(instance_list) + if not get_confirm("Add new instances?", allow_go_back=True): + return False - selection = get_selection_input("Select Moonraker instance to remove", options) - - del_remnants = False - remove_polkit = False - instances_to_remove = [] - if selection == "b".lower(): - return - elif selection == "a".lower(): - question = f"Delete {MOONRAKER_DIR} and {MOONRAKER_ENV_DIR}?" - del_remnants = get_confirm(question, False, True) - instances_to_remove.extend(instance_list) - remove_polkit = True - Logger.print_status("Removing all Moonraker instances ...") - else: - instance = instance_list[int(selection)] - instance_name = instance.get_service_file_name() - instances_to_remove.append(instance) - is_last_instance = len(instance_list) == 1 - if is_last_instance: - question = f"Delete {MOONRAKER_DIR} and {MOONRAKER_ENV_DIR}?" - del_remnants = get_confirm(question, False, True) - remove_polkit = True - Logger.print_status(f"Removing Moonraker instance {instance_name} ...") - - if del_remnants is None: - Logger.print_status("Exiting Moonraker Uninstaller ...") - return - - remove_instances( - instance_manager, - instances_to_remove, - remove_polkit, - del_remnants, - ) - - -def remove_instances( - instance_manager: InstanceManager, - instance_list: List[Moonraker], - remove_polkit: bool, - del_remnants: bool, -) -> None: - for instance in instance_list: - instance_manager.current_instance = instance - instance_manager.stop_instance() - instance_manager.disable_instance() - instance_manager.delete_instance(del_remnants=del_remnants) - - if remove_polkit: - remove_polkit_rules() - - instance_manager.reload_daemon() - - -def remove_polkit_rules() -> None: - Logger.print_status("Removing all Moonraker policykit rules ...") - if not MOONRAKER_DIR.exists(): - log = "Cannot remove policykit rules. Moonraker directory not found." - Logger.print_warn(log) - return - - try: - command = [f"{MOONRAKER_DIR}/scripts/set-policykit-rules.sh", "--clear"] - subprocess.run( - command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=True - ) - except subprocess.CalledProcessError as e: - Logger.print_error(f"Error while removing policykit rules: {e}") - - Logger.print_ok("Policykit rules successfully removed!") + return True def update_moonraker() -> None: