From ce0daa52ae3fd2e66345ec76f64acb5b09010bd8 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 26 Oct 2023 13:44:43 +0200 Subject: [PATCH 001/137] feat(klipper): implement instance manager and klipper installer in python Signed-off-by: Dominik Willner --- .gitignore | 5 + kiauh.py | 15 ++ kiauh.sh | 158 ++++++++------ kiauh/__init__.py | 0 kiauh/instance_manager/__init__.py | 0 kiauh/instance_manager/base_instance.py | 85 ++++++++ kiauh/instance_manager/instance_manager.py | 151 +++++++++++++ kiauh/main.py | 20 ++ kiauh/menus/__init__.py | 0 kiauh/menus/advanced_menu.py | 40 ++++ kiauh/menus/base_menu.py | 177 ++++++++++++++++ kiauh/menus/install_menu.py | 93 ++++++++ kiauh/menus/main_menu.py | 65 ++++++ kiauh/menus/remove_menu.py | 109 ++++++++++ kiauh/menus/settings_menu.py | 36 ++++ kiauh/menus/update_menu.py | 108 ++++++++++ kiauh/modules/__init__.py | 0 kiauh/modules/klipper/__init__.py | 0 kiauh/modules/klipper/klipper.py | 164 ++++++++++++++ kiauh/modules/klipper/klipper_setup.py | 236 +++++++++++++++++++++ kiauh/modules/klipper/klipper_utils.py | 39 ++++ kiauh/modules/klipper/res/klipper.env | 1 + kiauh/modules/klipper/res/klipper.service | 18 ++ kiauh/utils/__init__.py | 0 kiauh/utils/constants.py | 29 +++ kiauh/utils/input_utils.py | 87 ++++++++ kiauh/utils/logger.py | 51 +++++ kiauh/utils/system_utils.py | 203 ++++++++++++++++++ 28 files changed, 1821 insertions(+), 69 deletions(-) create mode 100644 kiauh.py create mode 100644 kiauh/__init__.py create mode 100644 kiauh/instance_manager/__init__.py create mode 100644 kiauh/instance_manager/base_instance.py create mode 100644 kiauh/instance_manager/instance_manager.py create mode 100644 kiauh/main.py create mode 100644 kiauh/menus/__init__.py create mode 100644 kiauh/menus/advanced_menu.py create mode 100644 kiauh/menus/base_menu.py create mode 100644 kiauh/menus/install_menu.py create mode 100644 kiauh/menus/main_menu.py create mode 100644 kiauh/menus/remove_menu.py create mode 100644 kiauh/menus/settings_menu.py create mode 100644 kiauh/menus/update_menu.py create mode 100644 kiauh/modules/__init__.py create mode 100644 kiauh/modules/klipper/__init__.py create mode 100644 kiauh/modules/klipper/klipper.py create mode 100644 kiauh/modules/klipper/klipper_setup.py create mode 100644 kiauh/modules/klipper/klipper_utils.py create mode 100644 kiauh/modules/klipper/res/klipper.env create mode 100644 kiauh/modules/klipper/res/klipper.service create mode 100644 kiauh/utils/__init__.py create mode 100644 kiauh/utils/constants.py create mode 100644 kiauh/utils/input_utils.py create mode 100644 kiauh/utils/logger.py create mode 100644 kiauh/utils/system_utils.py diff --git a/.gitignore b/.gitignore index 560b8c5..83a9457 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ .vscode +.idea +.pytest_cache +.kiauh-env *.code-workspace klipper_repos.txt +klipper_repos.json + diff --git a/kiauh.py b/kiauh.py new file mode 100644 index 0000000..ec12ca5 --- /dev/null +++ b/kiauh.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from kiauh.main import main + +if __name__ == "__main__": + main() diff --git a/kiauh.sh b/kiauh.sh index aac5a8e..9b2d782 100755 --- a/kiauh.sh +++ b/kiauh.sh @@ -12,77 +12,97 @@ set -e clear -### sourcing all additional scripts -KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" -for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done -for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done +function main() { + local python_command + local entrypoint -#===================================================# -#=================== UPDATE KIAUH ==================# -#===================================================# - -function update_kiauh() { - status_msg "Updating KIAUH ..." - - cd "${KIAUH_SRCDIR}" - git reset --hard && git pull - - ok_msg "Update complete! Please restart KIAUH." - exit 0 -} - -#===================================================# -#=================== KIAUH STATUS ==================# -#===================================================# - -function kiauh_update_avail() { - [[ ! -d "${KIAUH_SRCDIR}/.git" ]] && return - local origin head - - cd "${KIAUH_SRCDIR}" - - ### abort if not on master branch - ! git branch -a | grep -q "\* master" && return - - ### compare commit hash - git fetch -q - origin=$(git rev-parse --short=8 origin/master) - head=$(git rev-parse --short=8 HEAD) - - if [[ ${origin} != "${head}" ]]; then - echo "true" + if command -v python3 &>/dev/null; then + python_command="python3" + elif command -v python &>/dev/null; then + python_command="python" + else + echo "Python is not installed. Please install Python and try again." + exit 1 fi + + entrypoint=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") + + ${python_command} "${entrypoint}/kiauh.py" } -function kiauh_update_dialog() { - [[ ! $(kiauh_update_avail) == "true" ]] && return - top_border - echo -e "|${green} New KIAUH update available! ${white}|" - hr - echo -e "|${green} View Changelog: https://git.io/JnmlX ${white}|" - blank_line - echo -e "|${yellow} It is recommended to keep KIAUH up to date. Updates ${white}|" - echo -e "|${yellow} usually contain bugfixes, important changes or new ${white}|" - echo -e "|${yellow} features. Please consider updating! ${white}|" - bottom_border +main - local yn - read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn - while true; do - case "${yn}" in - Y|y|Yes|yes|"") - do_action "update_kiauh" - break;; - N|n|No|no) - break;; - *) - deny_action "kiauh_update_dialog";; - esac - done -} - -check_euid -init_logfile -set_globals -kiauh_update_dialog -main_menu +#### sourcing all additional scripts +#KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" +#for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done +#for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done +# +##===================================================# +##=================== UPDATE KIAUH ==================# +##===================================================# +# +#function update_kiauh() { +# status_msg "Updating KIAUH ..." +# +# cd "${KIAUH_SRCDIR}" +# git reset --hard && git pull +# +# ok_msg "Update complete! Please restart KIAUH." +# exit 0 +#} +# +##===================================================# +##=================== KIAUH STATUS ==================# +##===================================================# +# +#function kiauh_update_avail() { +# [[ ! -d "${KIAUH_SRCDIR}/.git" ]] && return +# local origin head +# +# cd "${KIAUH_SRCDIR}" +# +# ### abort if not on master branch +# ! git branch -a | grep -q "\* master" && return +# +# ### compare commit hash +# git fetch -q +# origin=$(git rev-parse --short=8 origin/master) +# head=$(git rev-parse --short=8 HEAD) +# +# if [[ ${origin} != "${head}" ]]; then +# echo "true" +# fi +#} +# +#function kiauh_update_dialog() { +# [[ ! $(kiauh_update_avail) == "true" ]] && return +# top_border +# echo -e "|${green} New KIAUH update available! ${white}|" +# hr +# echo -e "|${green} View Changelog: https://git.io/JnmlX ${white}|" +# blank_line +# echo -e "|${yellow} It is recommended to keep KIAUH up to date. Updates ${white}|" +# echo -e "|${yellow} usually contain bugfixes, important changes or new ${white}|" +# echo -e "|${yellow} features. Please consider updating! ${white}|" +# bottom_border +# +# local yn +# read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn +# while true; do +# case "${yn}" in +# Y|y|Yes|yes|"") +# do_action "update_kiauh" +# break;; +# N|n|No|no) +# break;; +# *) +# deny_action "kiauh_update_dialog";; +# esac +# done +#} +# +#check_euid +#init_logfile +#set_globals +#kiauh_update_dialog +#main_menu diff --git a/kiauh/__init__.py b/kiauh/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/instance_manager/__init__.py b/kiauh/instance_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/instance_manager/base_instance.py b/kiauh/instance_manager/base_instance.py new file mode 100644 index 0000000..2f749b3 --- /dev/null +++ b/kiauh/instance_manager/base_instance.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from abc import abstractmethod, ABC +from pathlib import Path +from typing import List, Optional + + +class BaseInstance(ABC): + @classmethod + def blacklist(cls) -> List[str]: + return [] + + def __init__(self, prefix: Optional[str], name: Optional[str], + user: Optional[str], data_dir_name: Optional[str]): + self._prefix = prefix + self._name = name + self._user = user + self._data_dir_name = data_dir_name + self.data_dir = f"{Path.home()}/{self._data_dir_name}_data" + self.cfg_dir = f"{self.data_dir}/config" + self.log_dir = f"{self.data_dir}/logs" + self.comms_dir = f"{self.data_dir}/comms" + self.sysd_dir = f"{self.data_dir}/systemd" + + @property + def prefix(self) -> str: + return self._prefix + + @prefix.setter + def prefix(self, value) -> None: + self._prefix = value + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value) -> None: + self._name = value + + @property + def user(self) -> str: + return self._user + + @user.setter + def user(self, value) -> None: + self._user = value + + @property + def data_dir_name(self) -> str: + return self._data_dir_name + + @data_dir_name.setter + def data_dir_name(self, value) -> None: + self._data_dir_name = value + + @abstractmethod + def create(self) -> None: + raise NotImplementedError("Subclasses must implement the create method") + + @abstractmethod + def read(self) -> None: + raise NotImplementedError("Subclasses must implement the read method") + + @abstractmethod + def update(self) -> None: + raise NotImplementedError("Subclasses must implement the update method") + + @abstractmethod + def delete(self, del_remnants: bool) -> None: + raise NotImplementedError("Subclasses must implement the delete method") + + @abstractmethod + def get_service_file_name(self) -> str: + raise NotImplementedError( + "Subclasses must implement the get_service_file_name method") diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py new file mode 100644 index 0000000..0bbdf14 --- /dev/null +++ b/kiauh/instance_manager/instance_manager.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 os +import re +import subprocess +from pathlib import Path +from typing import Optional, List, Type, Union + +from kiauh.instance_manager.base_instance import BaseInstance +from kiauh.utils.constants import SYSTEMD +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class InstanceManager: + def __init__(self, instance_type: Type[BaseInstance], + current_instance: Optional[BaseInstance] = None) -> None: + self.instance_type = instance_type + self.current_instance = current_instance + self.instance_name = current_instance.name if current_instance is not None else None + self.instances = [] + + def get_current_instance(self) -> BaseInstance: + return self.current_instance + + def set_current_instance(self, instance: BaseInstance) -> None: + self.current_instance = instance + self.instance_name = f"{instance.prefix}-{instance.name}" if instance.name else instance.prefix + + def create_instance(self) -> None: + if self.current_instance is not None: + try: + self.current_instance.create() + except (OSError, subprocess.CalledProcessError) as e: + Logger.print_error(f"Creating instance failed: {e}") + raise + else: + raise ValueError("current_instance cannot be None") + + def delete_instance(self, del_remnants=False) -> None: + if self.current_instance is not None: + try: + self.current_instance.delete(del_remnants) + except (OSError, subprocess.CalledProcessError) as e: + Logger.print_error(f"Removing instance failed: {e}") + raise + else: + raise ValueError("current_instance cannot be None") + + def enable_instance(self) -> None: + Logger.print_info(f"Enabling {self.instance_name}.service ...") + try: + command = ["sudo", "systemctl", "enable", + f"{self.instance_name}.service"] + if subprocess.run(command, check=True): + Logger.print_ok(f"{self.instance_name}.service enabled.") + except subprocess.CalledProcessError as e: + Logger.print_error( + f"Error enabling service {self.instance_name}.service:") + Logger.print_error(f"{e}") + + def disable_instance(self) -> None: + Logger.print_info(f"Disabling {self.instance_name}.service ...") + try: + command = ["sudo", "systemctl", "disable", + f"{self.instance_name}.service"] + if subprocess.run(command, check=True): + Logger.print_ok(f"{self.instance_name}.service disabled.") + except subprocess.CalledProcessError as e: + Logger.print_error( + f"Error disabling service {self.instance_name}.service:") + Logger.print_error(f"{e}") + + def start_instance(self) -> None: + Logger.print_info(f"Starting {self.instance_name}.service ...") + try: + command = ["sudo", "systemctl", "start", + f"{self.instance_name}.service"] + if subprocess.run(command, check=True): + Logger.print_ok(f"{self.instance_name}.service started.") + except subprocess.CalledProcessError as e: + Logger.print_error( + f"Error starting service {self.instance_name}.service:") + Logger.print_error(f"{e}") + + def stop_instance(self) -> None: + Logger.print_info(f"Stopping {self.instance_name}.service ...") + try: + command = ["sudo", "systemctl", "stop", + f"{self.instance_name}.service"] + if subprocess.run(command, check=True): + Logger.print_ok(f"{self.instance_name}.service stopped.") + except subprocess.CalledProcessError as e: + Logger.print_error( + f"Error stopping service {self.instance_name}.service:") + Logger.print_error(f"{e}") + raise + + def reload_daemon(self) -> None: + Logger.print_info("Reloading systemd manager configuration ...") + try: + command = ["sudo", "systemctl", "daemon-reload"] + if subprocess.run(command, check=True): + Logger.print_ok("Systemd manager configuration reloaded") + except subprocess.CalledProcessError as e: + Logger.print_error("Error reloading systemd manager configuration:") + Logger.print_error(f"{e}") + raise + + def get_instances(self) -> List[BaseInstance]: + if not self.instances: + self._find_instances() + + return sorted(self.instances, + key=lambda x: self._sort_instance_list(x.name)) + + def _find_instances(self) -> None: + prefix = self.instance_type.__name__.lower() + pattern = re.compile(f"{prefix}(-[0-9a-zA-Z]+)?.service") + + excluded = self.instance_type.blacklist() + service_list = [ + os.path.join(SYSTEMD, service) + for service in os.listdir(SYSTEMD) + if pattern.search(service) + and not any(s in service for s in excluded)] + + instance_list = [ + self.instance_type(name=self._get_instance_name(Path(service))) + for service in service_list] + + self.instances = instance_list + + def _get_instance_name(self, file_path: Path) -> Union[str, None]: + full_name = str(file_path).split("/")[-1].split(".")[0] + if full_name.isalnum(): + return None + + return full_name.split("-")[-1] + + def _sort_instance_list(self, s): + return int(s) if s.isdigit() else s diff --git a/kiauh/main.py b/kiauh/main.py new file mode 100644 index 0000000..738b036 --- /dev/null +++ b/kiauh/main.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from kiauh.menus.main_menu import MainMenu +from kiauh.utils.logger import Logger + + +def main(): + try: + MainMenu().start() + except KeyboardInterrupt: + Logger.print_ok("\nHappy printing!\n", prefix=False) diff --git a/kiauh/menus/__init__.py b/kiauh/menus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/menus/advanced_menu.py b/kiauh/menus/advanced_menu.py new file mode 100644 index 0000000..ddc3315 --- /dev/null +++ b/kiauh/menus/advanced_menu.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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.menus.base_menu import BaseMenu +from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT + + +class AdvancedMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={}, + footer_type="back" + ) + + def print_menu(self): + menu = textwrap.dedent(f""" + /=======================================================\\ + | {COLOR_YELLOW}~~~~~~~~~~~~~ [ Advanced Menu ] ~~~~~~~~~~~~~{RESET_FORMAT} | + |-------------------------------------------------------| + | Klipper & API: | Mainsail: | + | 0) [Rollback] | 5) [Theme installer] | + | | | + | Firmware: | System: | + | 1) [Build only] | 6) [Change hostname] | + | 2) [Flash only] | | + | 3) [Build + Flash] | Extras: | + | 4) [Get MCU ID] | 7) [G-Code Shell Command] | + """)[1:] + print(menu, end="") diff --git a/kiauh/menus/base_menu.py b/kiauh/menus/base_menu.py new file mode 100644 index 0000000..6cb5be6 --- /dev/null +++ b/kiauh/menus/base_menu.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 subprocess +import sys +import textwrap +from abc import abstractmethod, ABC +from typing import Dict, Any + +from kiauh.utils.constants import COLOR_GREEN, COLOR_YELLOW, COLOR_RED, \ + COLOR_CYAN, RESET_FORMAT + + +def clear(): + subprocess.call("clear", shell=True) + + +def print_header(): + header = textwrap.dedent(f""" + /=======================================================\\ + | {COLOR_CYAN}~~~~~~~~~~~~~~~~~ [ KIAUH ] ~~~~~~~~~~~~~~~~~{RESET_FORMAT} | + | {COLOR_CYAN} Klipper Installation And Update Helper {RESET_FORMAT} | + | {COLOR_CYAN}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{RESET_FORMAT} | + \=======================================================/ + """)[1:] + print(header, end="") + + +def print_quit_footer(): + footer = textwrap.dedent(f""" + |-------------------------------------------------------| + | {COLOR_RED}Q) Quit{RESET_FORMAT} | + \=======================================================/ + """)[1:] + print(footer, end="") + + +def print_back_footer(): + footer = textwrap.dedent(f""" + |-------------------------------------------------------| + | {COLOR_GREEN}B) « Back{RESET_FORMAT} | + \=======================================================/ + """)[1:] + print(footer, end="") + + +def print_back_help_footer(): + footer = textwrap.dedent(f""" + |-------------------------------------------------------| + | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | + \=======================================================/ + """)[1:] + print(footer, end="") + + +def print_back_quit_footer(): + footer = textwrap.dedent(f""" + |-------------------------------------------------------| + | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} | + \=======================================================/ + """)[1:] + print(footer, end="") + + +def print_back_quit_help_footer(): + footer = textwrap.dedent(f""" + |-------------------------------------------------------| + | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} | + \=======================================================/ + """)[1:] + print(footer, end="") + + +class BaseMenu(ABC): + QUIT_FOOTER = "quit" + BACK_FOOTER = "back" + BACK_HELP_FOOTER = "back_help" + BACK_QUIT_FOOTER = "back_quit" + BACK_QUIT_HELP_FOOTER = "back_quit_help" + + def __init__(self, options: Dict[int, Any], options_offset=0, header=True, + footer_type="quit"): + self.options = options + self.options_offset = options_offset + self.header = header + self.footer_type = footer_type + + @abstractmethod + def print_menu(self): + raise NotImplementedError( + "Subclasses must implement the print_menu method") + + def print_footer(self): + footer_type_map = { + self.QUIT_FOOTER: print_quit_footer, + self.BACK_FOOTER: print_back_footer, + self.BACK_HELP_FOOTER: print_back_help_footer, + self.BACK_QUIT_FOOTER: print_back_quit_footer, + self.BACK_QUIT_HELP_FOOTER: print_back_quit_help_footer + } + footer_function = footer_type_map.get(self.footer_type, + print_quit_footer) + footer_function() + + def display(self): + # clear() + if self.header: + print_header() + self.print_menu() + self.print_footer() + + def handle_user_input(self): + while True: + choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}") + + error_msg = f"{COLOR_RED}Invalid input.{RESET_FORMAT}" \ + if choice.isalpha() \ + else f"{COLOR_RED}Invalid input. Select a number between {min(self.options)} and {max(self.options)}.{RESET_FORMAT}" + + if choice.isdigit() and 0 <= int(choice) < len(self.options): + return choice + elif choice.isalpha(): + allowed_input = { + "quit": ["q"], + "back": ["b"], + "back_help": ["b", "h"], + "back_quit": ["b", "q"], + "back_quit_help": ["b", "q", "h"] + } + if self.footer_type in allowed_input and choice.lower() in \ + allowed_input[self.footer_type]: + return choice + else: + print(error_msg) + else: + print(error_msg) + + def start(self): + while True: + self.display() + choice = self.handle_user_input() + + if choice == "q": + print(f"{COLOR_GREEN}###### Happy printing!{RESET_FORMAT}") + sys.exit(0) + elif choice == "b": + return + elif choice == "p": + print("help!") + else: + self.execute_option(int(choice)) + + def execute_option(self, choice): + option = self.options.get(choice, None) + + if isinstance(option, type) and issubclass(option, BaseMenu): + self.navigate_to_submenu(option) + elif callable(option): + option() + elif option is None: + raise NotImplementedError(f"No implementation for option {choice}") + else: + raise TypeError( + f"Type {type(option)} of option {choice} not of type BaseMenu or Method") + + def navigate_to_submenu(self, submenu_class): + submenu = submenu_class() + submenu.previous_menu = self + submenu.start() diff --git a/kiauh/menus/install_menu.py b/kiauh/menus/install_menu.py new file mode 100644 index 0000000..91ae994 --- /dev/null +++ b/kiauh/menus/install_menu.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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.menus.base_menu import BaseMenu +from kiauh.modules.klipper import klipper_setup +from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT + + +# noinspection PyMethodMayBeStatic +class InstallMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={ + 1: self.install_klipper, + 2: self.install_moonraker, + 3: self.install_mainsail, + 4: self.install_fluidd, + 5: self.install_klipperscreen, + 6: self.install_pretty_gcode, + 7: self.install_telegram_bot, + 8: self.install_obico, + 9: self.install_octoeverywhere, + 10: self.install_mobileraker, + 11: self.install_crowsnest + }, + footer_type="back" + ) + + def print_menu(self): + menu = textwrap.dedent(f""" + /=======================================================\\ + | {COLOR_GREEN}~~~~~~~~~~~ [ Installation Menu ] ~~~~~~~~~~~{RESET_FORMAT} | + |-------------------------------------------------------| + | You need this menu usually only for installing | + | all necessary dependencies for the various | + | functions on a completely fresh system. | + |-------------------------------------------------------| + | Firmware & API: | Other: | + | 1) [Klipper] | 6) [PrettyGCode] | + | 2) [Moonraker] | 7) [Telegram Bot] | + | | 8) $(obico_install_title) | + | Klipper Webinterface: | 9) [OctoEverywhere] | + | 3) [Mainsail] | 10) [Mobileraker] | + | 4) [Fluidd] | | + | | Webcam Streamer: | + | Touchscreen GUI: | 11) [Crowsnest] | + | 5) [KlipperScreen] | | + """)[1:] + print(menu, end="") + + def install_klipper(self): + klipper_setup.run_klipper_setup(install=True) + + def install_moonraker(self): + print("install_moonraker") + + def install_mainsail(self): + print("install_mainsail") + + def install_fluidd(self): + print("install_fluidd") + + def install_klipperscreen(self): + print("install_klipperscreen") + + def install_pretty_gcode(self): + print("install_pretty_gcode") + + def install_telegram_bot(self): + print("install_telegram_bot") + + def install_obico(self): + print("install_obico") + + def install_octoeverywhere(self): + print("install_octoeverywhere") + + def install_mobileraker(self): + print("install_mobileraker") + + def install_crowsnest(self): + print("install_crowsnest") diff --git a/kiauh/menus/main_menu.py b/kiauh/menus/main_menu.py new file mode 100644 index 0000000..97a029a --- /dev/null +++ b/kiauh/menus/main_menu.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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.menus.advanced_menu import AdvancedMenu +from kiauh.menus.base_menu import BaseMenu +from kiauh.menus.install_menu import InstallMenu +from kiauh.menus.remove_menu import RemoveMenu +from kiauh.menus.settings_menu import SettingsMenu +from kiauh.menus.update_menu import UpdateMenu +from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT + + +class MainMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={ + 0: self.test, + 1: InstallMenu, + 2: UpdateMenu, + 3: RemoveMenu, + 4: AdvancedMenu, + 5: None, + 6: SettingsMenu + }, + footer_type="quit" + ) + + def print_menu(self): + menu = textwrap.dedent(f""" + /=======================================================\\ + | {COLOR_CYAN}~~~~~~~~~~~~~~~ [ Main Menu ] ~~~~~~~~~~~~~~~{RESET_FORMAT} | + |-------------------------------------------------------| + | 0) [Log-Upload] | Klipper: | + | | Repo: | + | 1) [Install] | | + | 2) [Update] | Moonraker: | + | 3) [Remove] | Repo: | + | 4) [Advanced] | | + | 5) [Backup] | Mainsail: | + | | Fluidd: | + | 6) [Settings] | KlipperScreen: | + | | Mobileraker: | + | | | + | | Crowsnest: | + | | Telegram Bot: | + | | Obico: | + | | OctoEverywhere: | + |-------------------------------------------------------| + | {COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT} | Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT} | + """)[1:] + print(menu, end="") + + def test(self): + print("blub") diff --git a/kiauh/menus/remove_menu.py b/kiauh/menus/remove_menu.py new file mode 100644 index 0000000..9b414fa --- /dev/null +++ b/kiauh/menus/remove_menu.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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.menus.base_menu import BaseMenu +from kiauh.modules.klipper import klipper_setup +from kiauh.utils.constants import COLOR_RED, RESET_FORMAT + + +# noinspection PyMethodMayBeStatic +class RemoveMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={ + 1: self.remove_klipper, + 2: self.remove_moonraker, + 3: self.remove_mainsail, + 4: self.remove_mainsail_config, + 5: self.remove_fluidd, + 6: self.remove_fluidd_config, + 7: self.remove_klipperscreen, + 8: self.remove_crowsnest, + 9: self.remove_mjpgstreamer, + 10: self.remove_pretty_gcode, + 11: self.remove_telegram_bot, + 12: self.remove_obico, + 13: self.remove_octoeverywhere, + 14: self.remove_mobileraker, + 15: self.remove_nginx, + }, + footer_type="back" + ) + + def print_menu(self): + menu = textwrap.dedent(f""" + /=======================================================\\ + | {COLOR_RED}~~~~~~~~~~~~~~ [ Remove Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} | + |-------------------------------------------------------| + | INFO: Configurations and/or any backups will be kept! | + |-------------------------------------------------------| + | Firmware & API: | Webcam Streamer: | + | 1) [Klipper] | 8) [Crowsnest] | + | 2) [Moonraker] | 9) [MJPG-Streamer] | + | | | + | Klipper Webinterface: | Other: | + | 3) [Mainsail] | 10) [PrettyGCode] | + | 4) [Mainsail-Config] | 11) [Telegram Bot] | + | 5) [Fluidd] | 12) [Obico for Klipper] | + | 6) [Fluidd-Config] | 13) [OctoEverywhere] | + | | 14) [Mobileraker] | + | Touchscreen GUI: | 15) [NGINX] | + | 7) [KlipperScreen] | | + """)[1:] + print(menu, end="") + + def remove_klipper(self): + klipper_setup.run_klipper_setup(install=False) + + def remove_moonraker(self): + print("remove_moonraker") + + def remove_mainsail(self): + print("remove_mainsail") + + def remove_mainsail_config(self): + print("remove_mainsail_config") + + def remove_fluidd(self): + print("remove_fluidd") + + def remove_fluidd_config(self): + print("remove_fluidd_config") + + def remove_klipperscreen(self): + print("remove_klipperscreen") + + def remove_crowsnest(self): + print("remove_crowsnest") + + def remove_mjpgstreamer(self): + print("remove_mjpgstreamer") + + def remove_pretty_gcode(self): + print("remove_pretty_gcode") + + def remove_telegram_bot(self): + print("remove_telegram_bot") + + def remove_obico(self): + print("remove_obico") + + def remove_octoeverywhere(self): + print("remove_octoeverywhere") + + def remove_mobileraker(self): + print("remove_mobileraker") + + def remove_nginx(self): + print("remove_nginx") diff --git a/kiauh/menus/settings_menu.py b/kiauh/menus/settings_menu.py new file mode 100644 index 0000000..df2f6c4 --- /dev/null +++ b/kiauh/menus/settings_menu.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from kiauh.menus.base_menu import BaseMenu + + +# noinspection PyMethodMayBeStatic +class SettingsMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={} + ) + + def print_menu(self): + print("self") + + def execute_option_p(self): + # Implement the functionality for Option P + print("Executing Option P") + + def execute_option_q(self): + # Implement the functionality for Option Q + print("Executing Option Q") + + def execute_option_r(self): + # Implement the functionality for Option R + print("Executing Option R") diff --git a/kiauh/menus/update_menu.py b/kiauh/menus/update_menu.py new file mode 100644 index 0000000..f95865a --- /dev/null +++ b/kiauh/menus/update_menu.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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.menus.base_menu import BaseMenu +from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT + + +# noinspection PyMethodMayBeStatic +class UpdateMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={ + 0: self.update_all, + 1: self.update_klipper, + 2: self.update_moonraker, + 3: self.update_mainsail, + 4: self.update_fluidd, + 5: self.update_klipperscreen, + 6: self.update_pgc_for_klipper, + 7: self.update_telegram_bot, + 8: self.update_moonraker_obico, + 9: self.update_octoeverywhere, + 10: self.update_mobileraker, + 11: self.update_crowsnest, + 12: self.upgrade_system_packages, + }, + footer_type="back" + ) + + def print_menu(self): + menu = textwrap.dedent(f""" + /=======================================================\\ + | {COLOR_GREEN}~~~~~~~~~~~~~~ [ Update Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} | + |-------------------------------------------------------| + | 0) [Update all] | | | + | | Current: | Latest: | + | Klipper & API: |--------------|--------------| + | 1) [Klipper] | | | + | 2) [Moonraker] | | | + | | | | + | Klipper Webinterface: |--------------|--------------| + | 3) [Mainsail] | | | + | 4) [Fluidd] | | | + | | | | + | Touchscreen GUI: |--------------|--------------| + | 5) [KlipperScreen] | | | + | | | | + | Other: |--------------|--------------| + | 6) [PrettyGCode] | | | + | 7) [Telegram Bot] | | | + | 8) [Obico for Klipper] | | | + | 9) [OctoEverywhere] | | | + | 10) [Mobileraker] | | | + | 11) [Crowsnest] | | | + | |-----------------------------| + | 12) [System] | | | + """)[1:] + print(menu, end="") + + def update_all(self): + print("update_all") + + def update_klipper(self): + print("update_klipper") + + def update_moonraker(self): + print("update_moonraker") + + def update_mainsail(self): + print("update_mainsail") + + def update_fluidd(self): + print("update_fluidd") + + def update_klipperscreen(self): + print("update_klipperscreen") + + def update_pgc_for_klipper(self): + print("update_pgc_for_klipper") + + def update_telegram_bot(self): + print("update_telegram_bot") + + def update_moonraker_obico(self): + print("update_moonraker_obico") + + def update_octoeverywhere(self): + print("update_octoeverywhere") + + def update_mobileraker(self): + print("update_mobileraker") + + def update_crowsnest(self): + print("update_crowsnest") + + def upgrade_system_packages(self): + print("upgrade_system_packages") diff --git a/kiauh/modules/__init__.py b/kiauh/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/modules/klipper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py new file mode 100644 index 0000000..e7d2826 --- /dev/null +++ b/kiauh/modules/klipper/klipper.py @@ -0,0 +1,164 @@ +# !/usr/bin/env python + +# ======================================================================= # +# 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 os +import pwd +import shutil +import subprocess +from pathlib import Path +from typing import List + +from kiauh.instance_manager.base_instance import BaseInstance +from kiauh.utils.constants import SYSTEMD, KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import create_directory + + +# noinspection PyMethodMayBeStatic +class Klipper(BaseInstance): + @classmethod + def blacklist(cls) -> List[str]: + return ["None", "mcu"] + + def __init__(self, name: str): + super().__init__(name=name, + prefix="klipper", + user=pwd.getpwuid(os.getuid())[0], + data_dir_name=self._get_data_dir_from_name(name)) + self.klipper_dir = KLIPPER_DIR + self.env_dir = KLIPPER_ENV_DIR + self.cfg_file = f"{self.cfg_dir}/printer.cfg" + self.log = f"{self.log_dir}/klippy.log" + self.serial = f"{self.comms_dir}/klippy.serial" + self.uds = f"{self.comms_dir}/klippy.sock" + + def create(self) -> None: + Logger.print_info("Creating Klipper Instance") + module_path = os.path.dirname(os.path.abspath(__file__)) + service_template_path = os.path.join(module_path, "res", + "klipper.service") + env_template_file_path = os.path.join(module_path, "res", "klipper.env") + service_file_name = self.get_service_file_name(extension=True) + service_file_target = f"{SYSTEMD}/{service_file_name}" + env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env") + + # create folder structure + dirs = [self.data_dir, self.cfg_dir, self.log_dir, + self.comms_dir, self.sysd_dir] + for _dir in dirs: + create_directory(Path(_dir)) + + try: + # writing the klipper service file (requires sudo!) + service_content = self._prep_service_file(service_template_path, + env_file_target) + command = ["sudo", "tee", service_file_target] + subprocess.run(command, input=service_content.encode(), + stdout=subprocess.DEVNULL, check=True) + Logger.print_ok(f"Service file created: {service_file_target}") + + # writing the klipper.env file + env_file_content = self._prep_env_file(env_template_file_path) + with open(env_file_target, "w") as env_file: + env_file.write(env_file_content) + Logger.print_ok(f"Env file created: {env_file_target}") + + except subprocess.CalledProcessError as e: + Logger.print_error( + f"Error creating service file {service_file_target}: {e}") + raise + except OSError as e: + Logger.print_error( + f"Error creating env file {env_file_target}: {e}") + raise + + def read(self) -> None: + print("Reading Klipper Instance") + + def update(self) -> None: + print("Updating Klipper Instance") + + def delete(self, del_remnants: bool) -> None: + service_file = self.get_service_file_name(extension=True) + service_file_path = self._get_service_file_path() + + Logger.print_info(f"Deleting Klipper Instance: {service_file}") + + try: + command = ["sudo", "rm", "-f", service_file_path] + subprocess.run(command, check=True) + Logger.print_ok(f"Service file deleted: {service_file_path}") + except subprocess.CalledProcessError as e: + Logger.print_error(f"Error deleting service file: {e}") + raise + + if del_remnants: + self._delete_klipper_remnants() + + def _delete_klipper_remnants(self) -> None: + try: + Logger.print_info(f"Delete {self.klipper_dir} ...") + shutil.rmtree(Path(self.klipper_dir)) + Logger.print_info(f"Delete {self.env_dir} ...") + shutil.rmtree(Path(self.env_dir)) + except FileNotFoundError: + Logger.print_info("Cannot delete Klipper directories. Not found.") + except PermissionError as e: + Logger.print_error(f"Error deleting Klipper directories: {e}") + raise + + Logger.print_ok("Directories successfully deleted.") + + def get_service_file_name(self, extension=False) -> str: + name = self.prefix if self.name is None else self.prefix + '-' + self.name + return name if not extension else f"{name}.service" + + def _get_service_file_path(self): + return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}" + + def _get_data_dir_from_name(self, name: str) -> str: + if name is None: + return "printer" + elif int(name.isdigit()): + return f"printer_{name}" + else: + return name + + def _prep_service_file(self, service_template_path, env_file_path): + try: + with open(service_template_path, "r") as template_file: + template_content = template_file.read() + except FileNotFoundError: + Logger.print_error( + f"Unable to open {service_template_path} - File not found") + raise + service_content = template_content.replace("%USER%", self.user) + service_content = service_content.replace("%KLIPPER_DIR%", + self.klipper_dir) + service_content = service_content.replace("%ENV%", self.env_dir) + service_content = service_content.replace("%ENV_FILE%", env_file_path) + return service_content + + def _prep_env_file(self, env_template_file_path): + try: + with open(env_template_file_path, "r") as env_file: + env_template_file_content = env_file.read() + except FileNotFoundError: + Logger.print_error( + f"Unable to open {env_template_file_path} - File not found") + raise + env_file_content = env_template_file_content.replace("%KLIPPER_DIR%", + self.klipper_dir) + env_file_content = env_file_content.replace("%CFG%", self.cfg_file) + env_file_content = env_file_content.replace("%SERIAL%", self.serial) + env_file_content = env_file_content.replace("%LOG%", self.log) + env_file_content = env_file_content.replace("%UDS%", self.uds) + return env_file_content diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py new file mode 100644 index 0000000..8deab8a --- /dev/null +++ b/kiauh/modules/klipper/klipper_setup.py @@ -0,0 +1,236 @@ +# !/usr/bin/env python + +# ======================================================================= # +# 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 os +import re +import subprocess +from pathlib import Path +from typing import Optional, List, Union + +from kiauh.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.klipper.klipper_utils import print_instance_overview +from kiauh.utils.constants import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.utils.input_utils import get_user_confirm, get_user_number_input, \ + get_user_string_input, get_user_selection_input +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import parse_packages_from_file, \ + clone_repo, create_python_venv, \ + install_python_requirements, update_system_package_lists, \ + install_system_packages + + +def run_klipper_setup(install: bool) -> None: + instance_manager = InstanceManager(Klipper) + instance_list = instance_manager.get_instances() + instances_installed = len(instance_list) + + is_klipper_installed = check_klipper_installation(instance_manager) + if not install and not is_klipper_installed: + Logger.print_warn("Klipper not installed!") + return + + if install: + add_additional = handle_existing_instances(instance_manager) + if is_klipper_installed and not add_additional: + Logger.print_info("Exiting Klipper setup ...") + return + + install_klipper(instance_manager) + + if not install: + if instances_installed == 1: + remove_single_instance(instance_manager) + else: + remove_multi_instance(instance_manager) + + +def check_klipper_installation(instance_manager: InstanceManager) -> bool: + instance_list = instance_manager.get_instances() + instances_installed = len(instance_list) + + if instances_installed < 1: + return False + + return True + + +def handle_existing_instances(instance_manager: InstanceManager) -> bool: + instance_list = instance_manager.get_instances() + instance_count = len(instance_list) + + if instance_count > 0: + print_instance_overview(instance_list) + if not get_user_confirm("Add new instances?"): + return False + + return True + + +def install_klipper(instance_manager: InstanceManager) -> None: + instance_list = instance_manager.get_instances() + if_adding = " additional" if len(instance_list) > 0 else "" + install_count = get_user_number_input( + f"Number of{if_adding} Klipper instances to set up", + 1, default=1) + + instance_names = set_instance_names(instance_list, install_count) + + if len(instance_list) < 1: + setup_klipper_prerequesites() + + for name in instance_names: + current_instance = Klipper(name=name) + instance_manager.set_current_instance(current_instance) + instance_manager.create_instance() + instance_manager.enable_instance() + instance_manager.start_instance() + + instance_manager.reload_daemon() + + # step 4: check/handle conflicting packages/services + + # step 5: check for required group membership + + +def setup_klipper_prerequesites() -> None: + # clone klipper TODO: read branch and url from json to allow forks + url = "https://github.com/Klipper3D/klipper" + branch = "master" + clone_repo(Path(KLIPPER_DIR), url, branch) + + # install klipper dependencies and create python virtualenv + install_klipper_packages(Path(KLIPPER_DIR)) + create_python_venv(Path(KLIPPER_ENV_DIR)) + klipper_py_req = Path(f"{KLIPPER_DIR}/scripts/klippy-requirements.txt") + install_python_requirements(Path(KLIPPER_ENV_DIR), klipper_py_req) + + +def install_klipper_packages(klipper_dir: Path) -> None: + script = f"{klipper_dir}/scripts/install-debian.sh" + packages = parse_packages_from_file(script) + packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages] + # Add dfu-util for octopi-images + packages.append("dfu-util") + # Add dbus requirement for DietPi distro + if os.path.exists("/boot/dietpi/.version"): + packages.append("dbus") + + update_system_package_lists(silent=False) + install_system_packages(packages) + + +def set_instance_names(instance_list, install_count: int) -> List[ + Union[str, None]]: + instance_count = len(instance_list) + + # default single instance install + if instance_count == 0 and install_count == 1: + return [None] + + # new multi instance install + elif ((instance_count == 0 and install_count > 1) + # or convert single instance install to multi instance install + or (instance_count == 1 and install_count >= 1)): + if get_user_confirm("Assign custom names?", False): + return assign_custom_names(instance_count, install_count, None) + else: + _range = range(1, install_count + 1) + return [str(i) for i in _range] + + # existing multi instance install + elif instance_count > 1: + if has_custom_names(instance_list): + return assign_custom_names(instance_count, install_count, + instance_list) + else: + start = get_highest_index(instance_list) + 1 + _range = range(start, start + install_count) + return [str(i) for i in _range] + + +def has_custom_names(instance_list: List[Klipper]) -> bool: + pattern = re.compile("^\d+$") + for instance in instance_list: + if not pattern.match(instance.name): + return True + + return False + + +def assign_custom_names(instance_count: int, install_count: int, + instance_list: Optional[List[Klipper]]) -> List[str]: + instance_names = [] + exclude = Klipper.blacklist() + + # if an instance_list is provided, exclude all existing instance names + if instance_list is not None: + for instance in instance_list: + exclude.append(instance.name) + + for i in range(instance_count + install_count): + question = f"Enter name for instance {i + 1}" + name = get_user_string_input(question, exclude=exclude) + instance_names.append(name) + exclude.append(name) + + return instance_names + + +def get_highest_index(instance_list: List[Klipper]) -> int: + indices = [int(instance.name.split('-')[-1]) for instance in instance_list] + return max(indices) + + +def remove_single_instance(instance_manager: InstanceManager) -> None: + instance_list = instance_manager.get_instances() + try: + instance_manager.set_current_instance(instance_list[0]) + instance_manager.stop_instance() + instance_manager.disable_instance() + instance_manager.delete_instance(del_remnants=True) + instance_manager.reload_daemon() + except (OSError, subprocess.CalledProcessError): + Logger.print_error("Removing instance failed!") + return + + +def remove_multi_instance(instance_manager: InstanceManager) -> None: + instance_list = instance_manager.get_instances() + print_instance_overview(instance_list, show_index=True, + show_select_all=True) + + options = [str(i) for i in range(len(instance_list))] + options.extend(["a", "A", "b", "B"]) + + selection = get_user_selection_input( + "Select Klipper instance to remove", options) + print(selection) + + if selection == "b".lower(): + return + elif selection == "a".lower(): + Logger.print_info("Removing all Klipper instances ...") + for instance in instance_list: + instance_manager.set_current_instance(instance) + instance_manager.stop_instance() + instance_manager.disable_instance() + instance_manager.delete_instance(del_remnants=True) + else: + instance = instance_list[int(selection)] + Logger.print_info( + f"Removing Klipper instance: {instance.get_service_file_name()}") + instance_manager.set_current_instance(instance) + instance_manager.stop_instance() + instance_manager.disable_instance() + instance_manager.delete_instance(del_remnants=False) + + instance_manager.reload_daemon() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py new file mode 100644 index 0000000..c67eb8a --- /dev/null +++ b/kiauh/modules/klipper/klipper_utils.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from typing import List + +from kiauh.instance_manager.base_instance import BaseInstance +from kiauh.menus.base_menu import print_back_footer +from kiauh.utils.constants import COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, \ + RESET_FORMAT + + +def print_instance_overview(instances: List[BaseInstance], show_index=False, + show_select_all=False): + headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}" + + print("/=======================================================\\") + print(f"|{'{:^64}'.format(headline)}|") + print("|-------------------------------------------------------|") + + if show_select_all: + select_all = f" {COLOR_YELLOW}a) Select all{RESET_FORMAT}" + print(f"|{'{:64}'.format(select_all)}|") + print("| |") + + for i, s in enumerate(instances): + index = f"{i})" if show_index else "●" + instance = s.get_service_file_name() + line = f"{'{:53}'.format(f'{index} {instance}')}" + print(f"| {COLOR_CYAN}{line}{RESET_FORMAT}|") + + print_back_footer() diff --git a/kiauh/modules/klipper/res/klipper.env b/kiauh/modules/klipper/res/klipper.env new file mode 100644 index 0000000..b56553e --- /dev/null +++ b/kiauh/modules/klipper/res/klipper.env @@ -0,0 +1 @@ +KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %SERIAL% -l %LOG% -a %UDS%" diff --git a/kiauh/modules/klipper/res/klipper.service b/kiauh/modules/klipper/res/klipper.service new file mode 100644 index 0000000..b41788f --- /dev/null +++ b/kiauh/modules/klipper/res/klipper.service @@ -0,0 +1,18 @@ +[Unit] +Description=Klipper 3D Printer Firmware SV1 +Documentation=https://www.klipper3d.org/ +After=network-online.target +Wants=udev.target + +[Install] +WantedBy=multi-user.target + +[Service] +Type=simple +User=%USER% +RemainAfterExit=yes +WorkingDirectory=%KLIPPER_DIR% +EnvironmentFile=%ENV_FILE% +ExecStart=%ENV%/bin/python $KLIPPER_ARGS +Restart=always +RestartSec=10 diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py new file mode 100644 index 0000000..3c96aab --- /dev/null +++ b/kiauh/utils/constants.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from pathlib import Path + +# text colors and formats +COLOR_MAGENTA = "\033[35m" # magenta +COLOR_GREEN = "\033[92m" # bright green +COLOR_YELLOW = "\033[93m" # bright yellow +COLOR_RED = "\033[91m" # bright red +COLOR_CYAN = "\033[96m" # bright cyan +RESET_FORMAT = "\033[0m" # reset format + +SYSTEMD = "/etc/systemd/system" + +KLIPPER_DIR = f"{Path.home()}/klipper" +KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env" +MOONRAKER_DIR = f"{Path.home()}/moonraker" +MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env" +MAINSAIL_DIR = f"{Path.home()}/mainsail" +FLUIDD_DIR = f"{Path.home()}/fluidd" diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py new file mode 100644 index 0000000..c6327d6 --- /dev/null +++ b/kiauh/utils/input_utils.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from typing import Optional, List + +from kiauh.utils.logger import Logger +from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT + + +def get_user_confirm(question: str, default_choice=True) -> bool: + options_confirm = ["y", "yes"] + options_decline = ["n", "no"] + + if default_choice: + def_choice = "(Y/n)" + options_confirm.append("") + else: + def_choice = "(y/N)" + options_decline.append("") + + while True: + choice = ( + input(f"{COLOR_CYAN}###### {question} {def_choice} {RESET_FORMAT}") + .strip() + .lower()) + + if choice in options_confirm: + return True + elif choice in options_decline: + return False + else: + Logger.print_error("Invalid choice. Please select 'y' or 'n'.") + + +def get_user_number_input(question: str, min_count: int, max_count=None, + default=None) -> int: + _question = question + f" (default={default})" if default else question + _question = f"{COLOR_CYAN}###### {_question}: {RESET_FORMAT}" + while True: + try: + num = input(_question) + if num == "": + return default + + if max_count is not None: + if min_count <= int(num) <= max_count: + return int(num) + else: + raise ValueError + elif int(num) >= min_count: + return int(num) + else: + raise ValueError + except ValueError: + Logger.print_error("Invalid choice. Please select a valid number.") + + +def get_user_string_input(question: str, exclude=Optional[List]) -> str: + while True: + _input = (input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}") + .strip()) + + if _input.isalnum() and _input not in exclude: + return _input + + Logger.print_error("Invalid choice. Please enter a valid value.") + if _input in exclude: + Logger.print_error("This value is already in use/reserved.") + + +def get_user_selection_input(question: str, option_list: List) -> str: + while True: + _input = (input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}") + .strip()) + + if _input in option_list: + return _input + + Logger.print_error("Invalid choice. Please enter a valid value.") diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py new file mode 100644 index 0000000..f6adc0d --- /dev/null +++ b/kiauh/utils/logger.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from kiauh.utils.constants import COLOR_GREEN, COLOR_YELLOW, COLOR_RED, \ + COLOR_MAGENTA, RESET_FORMAT + + +class Logger: + + @staticmethod + def info(msg): + # log to kiauh.log + pass + + @staticmethod + def warn(msg): + # log to kiauh.log + pass + + @staticmethod + def error(msg): + # log to kiauh.log + pass + + @staticmethod + def print_ok(msg, prefix=True, end="\n") -> None: + message = f"[OK] {msg}" if prefix else msg + print(f"{COLOR_GREEN}{message}{RESET_FORMAT}", end=end) + + @staticmethod + def print_warn(msg, prefix=True, end="\n") -> None: + message = f"[WARN] {msg}" if prefix else msg + print(f"{COLOR_YELLOW}{message}{RESET_FORMAT}", end=end) + + @staticmethod + def print_error(msg, prefix=True, end="\n") -> None: + message = f"[ERROR] {msg}" if prefix else msg + print(f"{COLOR_RED}{message}{RESET_FORMAT}", end=end) + + @staticmethod + def print_info(msg, prefix=True, end="\n") -> None: + message = f"###### {msg}" if prefix else msg + print(f"{COLOR_MAGENTA}{message}{RESET_FORMAT}", end=end) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py new file mode 100644 index 0000000..c089e90 --- /dev/null +++ b/kiauh/utils/system_utils.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 os +import shutil +import subprocess +import sys +import time +from pathlib import Path +from typing import List + +from kiauh.utils.constants import COLOR_RED, RESET_FORMAT +from kiauh.utils.logger import Logger +from kiauh.utils.input_utils import get_user_confirm + + +def kill(opt_err_msg=None) -> None: + """ + Kill the application. + + Parameters + ---------- + opt_err_msg : str + optional, additional error message to display + + Returns + ---------- + None + """ + + if opt_err_msg: + Logger.print_error(opt_err_msg) + Logger.print_error("A critical error has occured. KIAUH was terminated.") + sys.exit(1) + + +def clone_repo(target_dir: Path, url: str, branch: str) -> None: + Logger.print_info(f"Cloning repository from {url}") + if not target_dir.exists(): + try: + command = ["git", "clone", f"{url}"] + subprocess.run(command, check=True) + + command = ["git", "checkout", f"{branch}"] + subprocess.run(command, cwd=target_dir, check=True) + + Logger.print_ok("Clone successfull!") + except subprocess.CalledProcessError as e: + print("Error cloning repository:", e.output.decode()) + else: + overwrite_target = get_user_confirm( + "Target directory already exists. Overwrite?") + if overwrite_target: + try: + shutil.rmtree(target_dir) + clone_repo(target_dir, url, branch) + except OSError as e: + print("Error removing existing repository:", e.strerror) + else: + print("Skipping re-clone of repository ...") + + +def parse_packages_from_file(source_file) -> List[str]: + packages = [] + print("Reading dependencies...") + with open(source_file, 'r') as file: + for line in file: + line = line.strip() + if line.startswith("PKGLIST="): + line = line.replace("\"", "") + line = line.replace("PKGLIST=", "") + line = line.replace('${PKGLIST}', '') + packages.extend(line.split()) + return packages + + +def create_python_venv(target: Path) -> None: + Logger.print_info("Set up Python virtual environment ...") + if not target.exists(): + try: + command = ["python3", "-m", "venv", f"{target}"] + result = subprocess.run(command, stderr=subprocess.PIPE, text=True) + if result.returncode != 0 or result.stderr: + print(f"{COLOR_RED}{result.stderr}{RESET_FORMAT}") + Logger.print_error("Setup of virtualenv failed!") + return + + Logger.print_ok("Setup of virtualenv successfull!") + except subprocess.CalledProcessError as e: + print("Error setting up virtualenv:", e.output.decode()) + else: + overwrite_venv = get_user_confirm( + "Virtualenv already exists. Re-create?") + if overwrite_venv: + try: + shutil.rmtree(target) + create_python_venv(target) + except OSError as e: + Logger.print_error( + f"Error removing existing virtualenv: {e.strerror}", + False) + else: + print("Skipping re-creation of virtualenv ...") + + +def update_python_pip(target: Path) -> None: + Logger.print_info("Updating pip ...") + try: + command = [f"{target}/bin/pip", "install", "-U", "pip"] + result = subprocess.run(command, stderr=subprocess.PIPE, text=True) + if result.returncode != 0 or result.stderr: + Logger.print_error(f"{result.stderr}", False) + Logger.print_error("Updating pip failed!") + return + + Logger.print_ok("Updating pip successfull!") + except subprocess.CalledProcessError as e: + print("Error updating pip:", e.output.decode()) + + +def install_python_requirements(target: Path, requirements: Path) -> None: + update_python_pip(target) + Logger.print_info("Installing Python requirements ...") + try: + command = [f"{target}/bin/pip", "install", "-r", f"{requirements}"] + result = subprocess.run(command, stderr=subprocess.PIPE, text=True) + if result.returncode != 0 or result.stderr: + Logger.print_error(f"{result.stderr}", False) + Logger.print_error("Installing Python requirements failed!") + return + + Logger.print_ok("Installing Python requirements successfull!") + except subprocess.CalledProcessError as e: + print("Error installing Python requirements:", e.output.decode()) + + +def update_system_package_lists(silent: bool, rls_info_change=False) -> None: + cache_mtime = 0 + cache_files = [ + "/var/lib/apt/periodic/update-success-stamp", + "/var/lib/apt/lists" + ] + for cache_file in cache_files: + if Path(cache_file).exists(): + cache_mtime = max(cache_mtime, os.path.getmtime(cache_file)) + + update_age = int(time.time() - cache_mtime) + update_interval = 6 * 3600 # 48hrs + + if update_age <= update_interval: + return + + if not silent: + print("Updating package list...") + + try: + command = ["sudo", "apt-get", "update"] + if rls_info_change: + command.append("--allow-releaseinfo-change") + + result = subprocess.run(command, stderr=subprocess.PIPE, text=True) + if result.returncode != 0 or result.stderr: + Logger.print_error(f"{result.stderr}", False) + Logger.print_error("Updating system package list failed!") + return + + Logger.print_ok("System package list updated successfully!") + except subprocess.CalledProcessError as e: + kill(f"Error updating system package list:\n{e.stderr.decode()}") + + +def install_system_packages(packages: List) -> None: + try: + command = ["sudo", "apt-get", "install", "-y"] + for pkg in packages: + command.append(pkg) + subprocess.run(command, stderr=subprocess.PIPE, check=True) + + Logger.print_ok("Packages installed successfully.") + except subprocess.CalledProcessError as e: + kill(f"Error installing packages:\n{e.stderr.decode()}") + + +def create_directory(_dir: Path) -> None: + try: + if not os.path.isdir(_dir): + Logger.print_info(f"Create directory: {_dir}") + os.makedirs(_dir, exist_ok=True) + Logger.print_ok("Directory created!") + else: + Logger.print_info( + f"Directory already exists: {_dir}\nSkip creation ...") + except OSError as e: + Logger.print_error(f"Error creating folder: {e}") + raise From 2a100c29344730959ad8de2d4e011a548c69b318 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 28 Oct 2023 19:33:18 +0200 Subject: [PATCH 002/137] feat(klipper): check for required user-groups Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper.py | 4 +-- kiauh/modules/klipper/klipper_setup.py | 35 ++++++++++++++++++++++++-- kiauh/modules/klipper/klipper_utils.py | 18 +++++++++++++ kiauh/utils/constants.py | 6 +++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index e7d2826..81f0ceb 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -17,7 +17,7 @@ from pathlib import Path from typing import List from kiauh.instance_manager.base_instance import BaseInstance -from kiauh.utils.constants import SYSTEMD, KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.utils.constants import CURRENT_USER, SYSTEMD, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.logger import Logger from kiauh.utils.system_utils import create_directory @@ -31,7 +31,7 @@ class Klipper(BaseInstance): def __init__(self, name: str): super().__init__(name=name, prefix="klipper", - user=pwd.getpwuid(os.getuid())[0], + user=CURRENT_USER, data_dir_name=self._get_data_dir_from_name(name)) self.klipper_dir = KLIPPER_DIR self.env_dir = KLIPPER_ENV_DIR diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 8deab8a..d20d294 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -11,14 +11,15 @@ import os import re +import grp import subprocess from pathlib import Path from typing import Optional, List, Union from kiauh.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_utils import print_instance_overview -from kiauh.utils.constants import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.modules.klipper.klipper_utils import print_instance_overview, print_missing_usergroup_dialog +from kiauh.utils.constants import CURRENT_USER, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.input_utils import get_user_confirm, get_user_number_input, \ get_user_string_input, get_user_selection_input from kiauh.utils.logger import Logger @@ -99,6 +100,7 @@ def install_klipper(instance_manager: InstanceManager) -> None: # step 4: check/handle conflicting packages/services # step 5: check for required group membership + check_user_groups() def setup_klipper_prerequesites() -> None: @@ -234,3 +236,32 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: instance_manager.delete_instance(del_remnants=False) instance_manager.reload_daemon() + +def check_user_groups(): + current_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()] + + missing_groups = [] + if "tty" not in current_groups: + missing_groups.append("tty") + if "dialout" not in current_groups: + missing_groups.append("dialout") + + if not missing_groups: + return + + print_missing_usergroup_dialog(missing_groups) + if not get_user_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"): + Logger.warn("Skipped adding user to required groups. You might encounter issues.") + return + + try: + for group in missing_groups: + Logger.print_info(f"Adding user '{CURRENT_USER}' to group {group} ...") + command = ["sudo", "usermod", "-a", "-G", group, CURRENT_USER] + subprocess.run(command, check=True) + Logger.print_ok(f"Group {group} assigned to user '{CURRENT_USER}'.") + except subprocess.CalledProcessError as e: + Logger.print_error(f"Unable to add user to usergroups: {e}") + raise + + Logger.print_warn("Remember to relog/restart this machine for the group(s) to be applied!") diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index c67eb8a..445e3ce 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -37,3 +37,21 @@ def print_instance_overview(instances: List[BaseInstance], show_index=False, print(f"| {COLOR_CYAN}{line}{RESET_FORMAT}|") print_back_footer() + +def print_missing_usergroup_dialog(missing_groups) -> None: + print("/=======================================================\\") + print(f"| {COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT} |") + if "tty" in missing_groups: + print(f"| {COLOR_CYAN}● tty{RESET_FORMAT} |") + if "dialout" in missing_groups: + print(f"| {COLOR_CYAN}● dialout{RESET_FORMAT} |") + print("| |") + print("| It is possible that you won't be able to successfully |") + print("| connect and/or flash the controller board without |") + print("| your user being a member of that group. |") + print("| If you want to add the current user to the group(s) |") + print("| listed above, answer with 'Y'. Else skip with 'n'. |") + print("| |") + print(f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |") + print(f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |") + print("\\=======================================================/") diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 3c96aab..f795697 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -9,6 +9,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import os +import pwd + from pathlib import Path # text colors and formats @@ -19,6 +22,9 @@ COLOR_RED = "\033[91m" # bright red COLOR_CYAN = "\033[96m" # bright cyan RESET_FORMAT = "\033[0m" # reset format +# current user +CURRENT_USER = pwd.getpwuid(os.getuid())[0] + SYSTEMD = "/etc/systemd/system" KLIPPER_DIR = f"{Path.home()}/klipper" From bfff3019cb3bed7a4b1dcdde8a0c3a6ac9bb3c81 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 28 Oct 2023 22:09:46 +0200 Subject: [PATCH 003/137] fix(InstanceManager): fix TypeError if instance name is None Signed-off-by: Dominik Willner --- kiauh/instance_manager/instance_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index 0bbdf14..c4847ef 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -148,4 +148,7 @@ class InstanceManager: return full_name.split("-")[-1] def _sort_instance_list(self, s): + if s is None: + return + return int(s) if s.isdigit() else s From 84a530be7deaf762f12582b81c3fcb6e1af26812 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 28 Oct 2023 23:52:51 +0200 Subject: [PATCH 004/137] fix(klipper): handle disruptive system packages/services Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 31 ++++++++++++++++++++++++-- kiauh/utils/system_utils.py | 11 ++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index d20d294..652a9f5 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -9,10 +9,11 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import grp import os import re -import grp import subprocess +import textwrap from pathlib import Path from typing import Optional, List, Union @@ -26,7 +27,7 @@ from kiauh.utils.logger import Logger from kiauh.utils.system_utils import parse_packages_from_file, \ clone_repo, create_python_venv, \ install_python_requirements, update_system_package_lists, \ - install_system_packages + install_system_packages, mask_system_service def run_klipper_setup(install: bool) -> None: @@ -98,6 +99,7 @@ def install_klipper(instance_manager: InstanceManager) -> None: instance_manager.reload_daemon() # step 4: check/handle conflicting packages/services + handle_disruptive_system_packages() # step 5: check for required group membership check_user_groups() @@ -237,6 +239,7 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: instance_manager.reload_daemon() + def check_user_groups(): current_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()] @@ -265,3 +268,27 @@ def check_user_groups(): raise Logger.print_warn("Remember to relog/restart this machine for the group(s) to be applied!") + + +def handle_disruptive_system_packages() -> None: + services = [] + brltty_status = subprocess.run(["systemctl", "is-enabled", "brltty"], capture_output=True, text=True) + modem_manager_status = subprocess.run(["systemctl", "is-enabled", "ModemManager"], capture_output=True, text=True) + + if "enabled" in brltty_status.stdout: + services.append("brltty") + if "enabled" in modem_manager_status.stdout: + services.append("ModemManager") + + for service in services if services else []: + try: + Logger.print_info(f"{service} service detected! Masking {service} service ...") + mask_system_service(service) + Logger.print_ok(f"{service} service masked!") + except subprocess.CalledProcessError: + warn_msg = textwrap.dedent(f""" + KIAUH was unable to mask the {service} system service. + Please fix the problem manually. Otherwise, this may have + undesirable effects on the operation of Klipper. + """)[1:] + Logger.print_warn(warn_msg) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index c089e90..af85d24 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -18,8 +18,8 @@ from pathlib import Path from typing import List from kiauh.utils.constants import COLOR_RED, RESET_FORMAT -from kiauh.utils.logger import Logger from kiauh.utils.input_utils import get_user_confirm +from kiauh.utils.logger import Logger def kill(opt_err_msg=None) -> None: @@ -201,3 +201,12 @@ def create_directory(_dir: Path) -> None: except OSError as e: Logger.print_error(f"Error creating folder: {e}") raise + + +def mask_system_service(service_name: str) -> None: + try: + command = ["sudo", "systemctl", "mask", service_name] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + Logger.print_error(f"Unable to mask system service {service_name}: {e.stderr.decode()}") + raise From 358c666da9bf6123cef6625006be4d16ae6ce45a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 29 Oct 2023 00:31:34 +0200 Subject: [PATCH 005/137] feat(style): use black code style / formatter Signed-off-by: Dominik Willner --- kiauh/instance_manager/base_instance.py | 12 ++- kiauh/instance_manager/instance_manager.py | 49 ++++++------ kiauh/main.py | 2 +- kiauh/menus/advanced_menu.py | 12 ++- kiauh/menus/base_menu.py | 75 ++++++++++++------- kiauh/menus/install_menu.py | 10 ++- kiauh/menus/main_menu.py | 10 ++- kiauh/menus/remove_menu.py | 8 +- kiauh/menus/settings_menu.py | 5 +- kiauh/menus/update_menu.py | 8 +- kiauh/modules/klipper/klipper.py | 58 +++++++++------ kiauh/modules/klipper/klipper_setup.py | 86 ++++++++++++++-------- kiauh/modules/klipper/klipper_utils.py | 29 +++++--- kiauh/utils/input_utils.py | 16 ++-- kiauh/utils/logger.py | 10 ++- kiauh/utils/system_utils.py | 28 ++++--- pyproject.toml | 13 ++++ 17 files changed, 261 insertions(+), 170 deletions(-) create mode 100644 pyproject.toml diff --git a/kiauh/instance_manager/base_instance.py b/kiauh/instance_manager/base_instance.py index 2f749b3..169ff74 100644 --- a/kiauh/instance_manager/base_instance.py +++ b/kiauh/instance_manager/base_instance.py @@ -19,8 +19,13 @@ class BaseInstance(ABC): def blacklist(cls) -> List[str]: return [] - def __init__(self, prefix: Optional[str], name: Optional[str], - user: Optional[str], data_dir_name: Optional[str]): + def __init__( + self, + prefix: Optional[str], + name: Optional[str], + user: Optional[str], + data_dir_name: Optional[str], + ): self._prefix = prefix self._name = name self._user = user @@ -82,4 +87,5 @@ class BaseInstance(ABC): @abstractmethod def get_service_file_name(self) -> str: raise NotImplementedError( - "Subclasses must implement the get_service_file_name method") + "Subclasses must implement the get_service_file_name method" + ) diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index c4847ef..b1924b1 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -22,11 +22,16 @@ from kiauh.utils.logger import Logger # noinspection PyMethodMayBeStatic class InstanceManager: - def __init__(self, instance_type: Type[BaseInstance], - current_instance: Optional[BaseInstance] = None) -> None: + def __init__( + self, + instance_type: Type[BaseInstance], + current_instance: Optional[BaseInstance] = None, + ) -> None: self.instance_type = instance_type self.current_instance = current_instance - self.instance_name = current_instance.name if current_instance is not None else None + self.instance_name = ( + current_instance.name if current_instance is not None else None + ) self.instances = [] def get_current_instance(self) -> BaseInstance: @@ -34,7 +39,9 @@ class InstanceManager: def set_current_instance(self, instance: BaseInstance) -> None: self.current_instance = instance - self.instance_name = f"{instance.prefix}-{instance.name}" if instance.name else instance.prefix + self.instance_name = ( + f"{instance.prefix}-{instance.name}" if instance.name else instance.prefix + ) def create_instance(self) -> None: if self.current_instance is not None: @@ -59,49 +66,41 @@ class InstanceManager: def enable_instance(self) -> None: Logger.print_info(f"Enabling {self.instance_name}.service ...") try: - command = ["sudo", "systemctl", "enable", - f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "enable", f"{self.instance_name}.service"] if subprocess.run(command, check=True): Logger.print_ok(f"{self.instance_name}.service enabled.") except subprocess.CalledProcessError as e: - Logger.print_error( - f"Error enabling service {self.instance_name}.service:") + Logger.print_error(f"Error enabling service {self.instance_name}.service:") Logger.print_error(f"{e}") def disable_instance(self) -> None: Logger.print_info(f"Disabling {self.instance_name}.service ...") try: - command = ["sudo", "systemctl", "disable", - f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "disable", f"{self.instance_name}.service"] if subprocess.run(command, check=True): Logger.print_ok(f"{self.instance_name}.service disabled.") except subprocess.CalledProcessError as e: - Logger.print_error( - f"Error disabling service {self.instance_name}.service:") + Logger.print_error(f"Error disabling service {self.instance_name}.service:") Logger.print_error(f"{e}") def start_instance(self) -> None: Logger.print_info(f"Starting {self.instance_name}.service ...") try: - command = ["sudo", "systemctl", "start", - f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "start", f"{self.instance_name}.service"] if subprocess.run(command, check=True): Logger.print_ok(f"{self.instance_name}.service started.") except subprocess.CalledProcessError as e: - Logger.print_error( - f"Error starting service {self.instance_name}.service:") + Logger.print_error(f"Error starting service {self.instance_name}.service:") Logger.print_error(f"{e}") def stop_instance(self) -> None: Logger.print_info(f"Stopping {self.instance_name}.service ...") try: - command = ["sudo", "systemctl", "stop", - f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "stop", f"{self.instance_name}.service"] if subprocess.run(command, check=True): Logger.print_ok(f"{self.instance_name}.service stopped.") except subprocess.CalledProcessError as e: - Logger.print_error( - f"Error stopping service {self.instance_name}.service:") + Logger.print_error(f"Error stopping service {self.instance_name}.service:") Logger.print_error(f"{e}") raise @@ -120,8 +119,7 @@ class InstanceManager: if not self.instances: self._find_instances() - return sorted(self.instances, - key=lambda x: self._sort_instance_list(x.name)) + return sorted(self.instances, key=lambda x: self._sort_instance_list(x.name)) def _find_instances(self) -> None: prefix = self.instance_type.__name__.lower() @@ -131,12 +129,13 @@ class InstanceManager: service_list = [ os.path.join(SYSTEMD, service) for service in os.listdir(SYSTEMD) - if pattern.search(service) - and not any(s in service for s in excluded)] + if pattern.search(service) and not any(s in service for s in excluded) + ] instance_list = [ self.instance_type(name=self._get_instance_name(Path(service))) - for service in service_list] + for service in service_list + ] self.instances = instance_list diff --git a/kiauh/main.py b/kiauh/main.py index 738b036..2bab667 100644 --- a/kiauh/main.py +++ b/kiauh/main.py @@ -15,6 +15,6 @@ from kiauh.utils.logger import Logger def main(): try: - MainMenu().start() + MainMenu().start() except KeyboardInterrupt: Logger.print_ok("\nHappy printing!\n", prefix=False) diff --git a/kiauh/menus/advanced_menu.py b/kiauh/menus/advanced_menu.py index ddc3315..9b0ca85 100644 --- a/kiauh/menus/advanced_menu.py +++ b/kiauh/menus/advanced_menu.py @@ -17,14 +17,11 @@ from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT class AdvancedMenu(BaseMenu): def __init__(self): - super().__init__( - header=True, - options={}, - footer_type="back" - ) + super().__init__(header=True, options={}, footer_type="back") def print_menu(self): - menu = textwrap.dedent(f""" + menu = textwrap.dedent( + f""" /=======================================================\\ | {COLOR_YELLOW}~~~~~~~~~~~~~ [ Advanced Menu ] ~~~~~~~~~~~~~{RESET_FORMAT} | |-------------------------------------------------------| @@ -36,5 +33,6 @@ class AdvancedMenu(BaseMenu): | 2) [Flash only] | | | 3) [Build + Flash] | Extras: | | 4) [Get MCU ID] | 7) [G-Code Shell Command] | - """)[1:] + """ + )[1:] print(menu, end="") diff --git a/kiauh/menus/base_menu.py b/kiauh/menus/base_menu.py index 6cb5be6..799b5af 100644 --- a/kiauh/menus/base_menu.py +++ b/kiauh/menus/base_menu.py @@ -15,8 +15,13 @@ import textwrap from abc import abstractmethod, ABC from typing import Dict, Any -from kiauh.utils.constants import COLOR_GREEN, COLOR_YELLOW, COLOR_RED, \ - COLOR_CYAN, RESET_FORMAT +from kiauh.utils.constants import ( + COLOR_GREEN, + COLOR_YELLOW, + COLOR_RED, + COLOR_CYAN, + RESET_FORMAT, +) def clear(): @@ -24,58 +29,70 @@ def clear(): def print_header(): - header = textwrap.dedent(f""" + header = textwrap.dedent( + f""" /=======================================================\\ | {COLOR_CYAN}~~~~~~~~~~~~~~~~~ [ KIAUH ] ~~~~~~~~~~~~~~~~~{RESET_FORMAT} | | {COLOR_CYAN} Klipper Installation And Update Helper {RESET_FORMAT} | | {COLOR_CYAN}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{RESET_FORMAT} | \=======================================================/ - """)[1:] + """ + )[1:] print(header, end="") def print_quit_footer(): - footer = textwrap.dedent(f""" + footer = textwrap.dedent( + f""" |-------------------------------------------------------| | {COLOR_RED}Q) Quit{RESET_FORMAT} | \=======================================================/ - """)[1:] + """ + )[1:] print(footer, end="") def print_back_footer(): - footer = textwrap.dedent(f""" + footer = textwrap.dedent( + f""" |-------------------------------------------------------| | {COLOR_GREEN}B) « Back{RESET_FORMAT} | \=======================================================/ - """)[1:] + """ + )[1:] print(footer, end="") def print_back_help_footer(): - footer = textwrap.dedent(f""" + footer = textwrap.dedent( + f""" |-------------------------------------------------------| | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | \=======================================================/ - """)[1:] + """ + )[1:] print(footer, end="") def print_back_quit_footer(): - footer = textwrap.dedent(f""" + footer = textwrap.dedent( + f""" |-------------------------------------------------------| | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} | \=======================================================/ - """)[1:] + """ + )[1:] print(footer, end="") def print_back_quit_help_footer(): - footer = textwrap.dedent(f""" + footer = textwrap.dedent( + f""" |-------------------------------------------------------| | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} | \=======================================================/ - """)[1:] + """ + )[1:] print(footer, end="") @@ -86,8 +103,9 @@ class BaseMenu(ABC): BACK_QUIT_FOOTER = "back_quit" BACK_QUIT_HELP_FOOTER = "back_quit_help" - def __init__(self, options: Dict[int, Any], options_offset=0, header=True, - footer_type="quit"): + def __init__( + self, options: Dict[int, Any], options_offset=0, header=True, footer_type="quit" + ): self.options = options self.options_offset = options_offset self.header = header @@ -95,8 +113,7 @@ class BaseMenu(ABC): @abstractmethod def print_menu(self): - raise NotImplementedError( - "Subclasses must implement the print_menu method") + raise NotImplementedError("Subclasses must implement the print_menu method") def print_footer(self): footer_type_map = { @@ -104,10 +121,9 @@ class BaseMenu(ABC): self.BACK_FOOTER: print_back_footer, self.BACK_HELP_FOOTER: print_back_help_footer, self.BACK_QUIT_FOOTER: print_back_quit_footer, - self.BACK_QUIT_HELP_FOOTER: print_back_quit_help_footer + self.BACK_QUIT_HELP_FOOTER: print_back_quit_help_footer, } - footer_function = footer_type_map.get(self.footer_type, - print_quit_footer) + footer_function = footer_type_map.get(self.footer_type, print_quit_footer) footer_function() def display(self): @@ -121,9 +137,11 @@ class BaseMenu(ABC): while True: choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}") - error_msg = f"{COLOR_RED}Invalid input.{RESET_FORMAT}" \ - if choice.isalpha() \ + error_msg = ( + f"{COLOR_RED}Invalid input.{RESET_FORMAT}" + if choice.isalpha() else f"{COLOR_RED}Invalid input. Select a number between {min(self.options)} and {max(self.options)}.{RESET_FORMAT}" + ) if choice.isdigit() and 0 <= int(choice) < len(self.options): return choice @@ -133,10 +151,12 @@ class BaseMenu(ABC): "back": ["b"], "back_help": ["b", "h"], "back_quit": ["b", "q"], - "back_quit_help": ["b", "q", "h"] + "back_quit_help": ["b", "q", "h"], } - if self.footer_type in allowed_input and choice.lower() in \ - allowed_input[self.footer_type]: + if ( + self.footer_type in allowed_input + and choice.lower() in allowed_input[self.footer_type] + ): return choice else: print(error_msg) @@ -169,7 +189,8 @@ class BaseMenu(ABC): raise NotImplementedError(f"No implementation for option {choice}") else: raise TypeError( - f"Type {type(option)} of option {choice} not of type BaseMenu or Method") + f"Type {type(option)} of option {choice} not of type BaseMenu or Method" + ) def navigate_to_submenu(self, submenu_class): submenu = submenu_class() diff --git a/kiauh/menus/install_menu.py b/kiauh/menus/install_menu.py index 91ae994..54b5148 100644 --- a/kiauh/menus/install_menu.py +++ b/kiauh/menus/install_menu.py @@ -32,13 +32,14 @@ class InstallMenu(BaseMenu): 8: self.install_obico, 9: self.install_octoeverywhere, 10: self.install_mobileraker, - 11: self.install_crowsnest + 11: self.install_crowsnest, }, - footer_type="back" + footer_type="back", ) def print_menu(self): - menu = textwrap.dedent(f""" + menu = textwrap.dedent( + f""" /=======================================================\\ | {COLOR_GREEN}~~~~~~~~~~~ [ Installation Menu ] ~~~~~~~~~~~{RESET_FORMAT} | |-------------------------------------------------------| @@ -56,7 +57,8 @@ class InstallMenu(BaseMenu): | | Webcam Streamer: | | Touchscreen GUI: | 11) [Crowsnest] | | 5) [KlipperScreen] | | - """)[1:] + """ + )[1:] print(menu, end="") def install_klipper(self): diff --git a/kiauh/menus/main_menu.py b/kiauh/menus/main_menu.py index 97a029a..9cffebb 100644 --- a/kiauh/menus/main_menu.py +++ b/kiauh/menus/main_menu.py @@ -31,13 +31,14 @@ class MainMenu(BaseMenu): 3: RemoveMenu, 4: AdvancedMenu, 5: None, - 6: SettingsMenu + 6: SettingsMenu, }, - footer_type="quit" + footer_type="quit", ) def print_menu(self): - menu = textwrap.dedent(f""" + menu = textwrap.dedent( + f""" /=======================================================\\ | {COLOR_CYAN}~~~~~~~~~~~~~~~ [ Main Menu ] ~~~~~~~~~~~~~~~{RESET_FORMAT} | |-------------------------------------------------------| @@ -58,7 +59,8 @@ class MainMenu(BaseMenu): | | OctoEverywhere: | |-------------------------------------------------------| | {COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT} | Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT} | - """)[1:] + """ + )[1:] print(menu, end="") def test(self): diff --git a/kiauh/menus/remove_menu.py b/kiauh/menus/remove_menu.py index 9b414fa..1fb2492 100644 --- a/kiauh/menus/remove_menu.py +++ b/kiauh/menus/remove_menu.py @@ -38,11 +38,12 @@ class RemoveMenu(BaseMenu): 14: self.remove_mobileraker, 15: self.remove_nginx, }, - footer_type="back" + footer_type="back", ) def print_menu(self): - menu = textwrap.dedent(f""" + menu = textwrap.dedent( + f""" /=======================================================\\ | {COLOR_RED}~~~~~~~~~~~~~~ [ Remove Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} | |-------------------------------------------------------| @@ -60,7 +61,8 @@ class RemoveMenu(BaseMenu): | | 14) [Mobileraker] | | Touchscreen GUI: | 15) [NGINX] | | 7) [KlipperScreen] | | - """)[1:] + """ + )[1:] print(menu, end="") def remove_klipper(self): diff --git a/kiauh/menus/settings_menu.py b/kiauh/menus/settings_menu.py index df2f6c4..dcef3df 100644 --- a/kiauh/menus/settings_menu.py +++ b/kiauh/menus/settings_menu.py @@ -15,10 +15,7 @@ from kiauh.menus.base_menu import BaseMenu # noinspection PyMethodMayBeStatic class SettingsMenu(BaseMenu): def __init__(self): - super().__init__( - header=True, - options={} - ) + super().__init__(header=True, options={}) def print_menu(self): print("self") diff --git a/kiauh/menus/update_menu.py b/kiauh/menus/update_menu.py index f95865a..0e9a78a 100644 --- a/kiauh/menus/update_menu.py +++ b/kiauh/menus/update_menu.py @@ -35,11 +35,12 @@ class UpdateMenu(BaseMenu): 11: self.update_crowsnest, 12: self.upgrade_system_packages, }, - footer_type="back" + footer_type="back", ) def print_menu(self): - menu = textwrap.dedent(f""" + menu = textwrap.dedent( + f""" /=======================================================\\ | {COLOR_GREEN}~~~~~~~~~~~~~~ [ Update Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} | |-------------------------------------------------------| @@ -65,7 +66,8 @@ class UpdateMenu(BaseMenu): | 11) [Crowsnest] | | | | |-----------------------------| | 12) [System] | | | - """)[1:] + """ + )[1:] print(menu, end="") def update_all(self): diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 81f0ceb..e21d6a0 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -10,7 +10,6 @@ # ======================================================================= # import os -import pwd import shutil import subprocess from pathlib import Path @@ -29,10 +28,12 @@ class Klipper(BaseInstance): return ["None", "mcu"] def __init__(self, name: str): - super().__init__(name=name, - prefix="klipper", - user=CURRENT_USER, - data_dir_name=self._get_data_dir_from_name(name)) + super().__init__( + name=name, + prefix="klipper", + user=CURRENT_USER, + data_dir_name=self._get_data_dir_from_name(name), + ) self.klipper_dir = KLIPPER_DIR self.env_dir = KLIPPER_ENV_DIR self.cfg_file = f"{self.cfg_dir}/printer.cfg" @@ -43,26 +44,35 @@ class Klipper(BaseInstance): def create(self) -> None: Logger.print_info("Creating Klipper Instance") module_path = os.path.dirname(os.path.abspath(__file__)) - service_template_path = os.path.join(module_path, "res", - "klipper.service") + service_template_path = os.path.join(module_path, "res", "klipper.service") env_template_file_path = os.path.join(module_path, "res", "klipper.env") service_file_name = self.get_service_file_name(extension=True) service_file_target = f"{SYSTEMD}/{service_file_name}" env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env") # create folder structure - dirs = [self.data_dir, self.cfg_dir, self.log_dir, - self.comms_dir, self.sysd_dir] + dirs = [ + self.data_dir, + self.cfg_dir, + self.log_dir, + self.comms_dir, + self.sysd_dir, + ] for _dir in dirs: create_directory(Path(_dir)) try: # writing the klipper service file (requires sudo!) - service_content = self._prep_service_file(service_template_path, - env_file_target) + service_content = self._prep_service_file( + service_template_path, env_file_target + ) command = ["sudo", "tee", service_file_target] - subprocess.run(command, input=service_content.encode(), - stdout=subprocess.DEVNULL, check=True) + subprocess.run( + command, + input=service_content.encode(), + stdout=subprocess.DEVNULL, + check=True, + ) Logger.print_ok(f"Service file created: {service_file_target}") # writing the klipper.env file @@ -73,11 +83,11 @@ class Klipper(BaseInstance): except subprocess.CalledProcessError as e: Logger.print_error( - f"Error creating service file {service_file_target}: {e}") + f"Error creating service file {service_file_target}: {e}" + ) raise except OSError as e: - Logger.print_error( - f"Error creating env file {env_file_target}: {e}") + Logger.print_error(f"Error creating env file {env_file_target}: {e}") raise def read(self) -> None: @@ -118,7 +128,7 @@ class Klipper(BaseInstance): Logger.print_ok("Directories successfully deleted.") def get_service_file_name(self, extension=False) -> str: - name = self.prefix if self.name is None else self.prefix + '-' + self.name + name = self.prefix if self.name is None else self.prefix + "-" + self.name return name if not extension else f"{name}.service" def _get_service_file_path(self): @@ -138,11 +148,11 @@ class Klipper(BaseInstance): template_content = template_file.read() except FileNotFoundError: Logger.print_error( - f"Unable to open {service_template_path} - File not found") + f"Unable to open {service_template_path} - File not found" + ) raise service_content = template_content.replace("%USER%", self.user) - service_content = service_content.replace("%KLIPPER_DIR%", - self.klipper_dir) + service_content = service_content.replace("%KLIPPER_DIR%", self.klipper_dir) service_content = service_content.replace("%ENV%", self.env_dir) service_content = service_content.replace("%ENV_FILE%", env_file_path) return service_content @@ -153,10 +163,12 @@ class Klipper(BaseInstance): env_template_file_content = env_file.read() except FileNotFoundError: Logger.print_error( - f"Unable to open {env_template_file_path} - File not found") + f"Unable to open {env_template_file_path} - File not found" + ) raise - env_file_content = env_template_file_content.replace("%KLIPPER_DIR%", - self.klipper_dir) + env_file_content = env_template_file_content.replace( + "%KLIPPER_DIR%", self.klipper_dir + ) env_file_content = env_file_content.replace("%CFG%", self.cfg_file) env_file_content = env_file_content.replace("%SERIAL%", self.serial) env_file_content = env_file_content.replace("%LOG%", self.log) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 652a9f5..e0a88cd 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -19,15 +19,27 @@ from typing import Optional, List, Union from kiauh.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_utils import print_instance_overview, print_missing_usergroup_dialog +from kiauh.modules.klipper.klipper_utils import ( + print_instance_overview, + print_missing_usergroup_dialog, +) from kiauh.utils.constants import CURRENT_USER, KLIPPER_DIR, KLIPPER_ENV_DIR -from kiauh.utils.input_utils import get_user_confirm, get_user_number_input, \ - get_user_string_input, get_user_selection_input +from kiauh.utils.input_utils import ( + get_user_confirm, + get_user_number_input, + get_user_string_input, + get_user_selection_input, +) from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import parse_packages_from_file, \ - clone_repo, create_python_venv, \ - install_python_requirements, update_system_package_lists, \ - install_system_packages, mask_system_service +from kiauh.utils.system_utils import ( + parse_packages_from_file, + clone_repo, + create_python_venv, + install_python_requirements, + update_system_package_lists, + install_system_packages, + mask_system_service, +) def run_klipper_setup(install: bool) -> None: @@ -81,8 +93,8 @@ def install_klipper(instance_manager: InstanceManager) -> None: instance_list = instance_manager.get_instances() if_adding = " additional" if len(instance_list) > 0 else "" install_count = get_user_number_input( - f"Number of{if_adding} Klipper instances to set up", - 1, default=1) + f"Number of{if_adding} Klipper instances to set up", 1, default=1 + ) instance_names = set_instance_names(instance_list, install_count) @@ -132,8 +144,7 @@ def install_klipper_packages(klipper_dir: Path) -> None: install_system_packages(packages) -def set_instance_names(instance_list, install_count: int) -> List[ - Union[str, None]]: +def set_instance_names(instance_list, install_count: int) -> List[Union[str, None]]: instance_count = len(instance_list) # default single instance install @@ -141,9 +152,11 @@ def set_instance_names(instance_list, install_count: int) -> List[ return [None] # new multi instance install - elif ((instance_count == 0 and install_count > 1) - # or convert single instance install to multi instance install - or (instance_count == 1 and install_count >= 1)): + elif ( + (instance_count == 0 and install_count > 1) + # or convert single instance install to multi instance install + or (instance_count == 1 and install_count >= 1) + ): if get_user_confirm("Assign custom names?", False): return assign_custom_names(instance_count, install_count, None) else: @@ -153,8 +166,7 @@ def set_instance_names(instance_list, install_count: int) -> List[ # existing multi instance install elif instance_count > 1: if has_custom_names(instance_list): - return assign_custom_names(instance_count, install_count, - instance_list) + return assign_custom_names(instance_count, install_count, instance_list) else: start = get_highest_index(instance_list) + 1 _range = range(start, start + install_count) @@ -170,8 +182,9 @@ def has_custom_names(instance_list: List[Klipper]) -> bool: return False -def assign_custom_names(instance_count: int, install_count: int, - instance_list: Optional[List[Klipper]]) -> List[str]: +def assign_custom_names( + instance_count: int, install_count: int, instance_list: Optional[List[Klipper]] +) -> List[str]: instance_names = [] exclude = Klipper.blacklist() @@ -190,7 +203,7 @@ def assign_custom_names(instance_count: int, install_count: int, def get_highest_index(instance_list: List[Klipper]) -> int: - indices = [int(instance.name.split('-')[-1]) for instance in instance_list] + indices = [int(instance.name.split("-")[-1]) for instance in instance_list] return max(indices) @@ -209,14 +222,12 @@ def remove_single_instance(instance_manager: InstanceManager) -> None: def remove_multi_instance(instance_manager: InstanceManager) -> None: instance_list = instance_manager.get_instances() - print_instance_overview(instance_list, show_index=True, - show_select_all=True) + print_instance_overview(instance_list, show_index=True, show_select_all=True) options = [str(i) for i in range(len(instance_list))] options.extend(["a", "A", "b", "B"]) - selection = get_user_selection_input( - "Select Klipper instance to remove", options) + selection = get_user_selection_input("Select Klipper instance to remove", options) print(selection) if selection == "b".lower(): @@ -231,7 +242,8 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: else: instance = instance_list[int(selection)] Logger.print_info( - f"Removing Klipper instance: {instance.get_service_file_name()}") + f"Removing Klipper instance: {instance.get_service_file_name()}" + ) instance_manager.set_current_instance(instance) instance_manager.stop_instance() instance_manager.disable_instance() @@ -254,7 +266,9 @@ def check_user_groups(): print_missing_usergroup_dialog(missing_groups) if not get_user_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"): - Logger.warn("Skipped adding user to required groups. You might encounter issues.") + Logger.warn( + "Skipped adding user to required groups. You might encounter issues." + ) return try: @@ -267,13 +281,19 @@ def check_user_groups(): Logger.print_error(f"Unable to add user to usergroups: {e}") raise - Logger.print_warn("Remember to relog/restart this machine for the group(s) to be applied!") + Logger.print_warn( + "Remember to relog/restart this machine for the group(s) to be applied!" + ) def handle_disruptive_system_packages() -> None: services = [] - brltty_status = subprocess.run(["systemctl", "is-enabled", "brltty"], capture_output=True, text=True) - modem_manager_status = subprocess.run(["systemctl", "is-enabled", "ModemManager"], capture_output=True, text=True) + brltty_status = subprocess.run( + ["systemctl", "is-enabled", "brltty"], capture_output=True, text=True + ) + modem_manager_status = subprocess.run( + ["systemctl", "is-enabled", "ModemManager"], capture_output=True, text=True + ) if "enabled" in brltty_status.stdout: services.append("brltty") @@ -282,13 +302,17 @@ def handle_disruptive_system_packages() -> None: for service in services if services else []: try: - Logger.print_info(f"{service} service detected! Masking {service} service ...") + Logger.print_info( + f"{service} service detected! Masking {service} service ..." + ) mask_system_service(service) Logger.print_ok(f"{service} service masked!") except subprocess.CalledProcessError: - warn_msg = textwrap.dedent(f""" + warn_msg = textwrap.dedent( + f""" KIAUH was unable to mask the {service} system service. Please fix the problem manually. Otherwise, this may have undesirable effects on the operation of Klipper. - """)[1:] + """ + )[1:] Logger.print_warn(warn_msg) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 445e3ce..0d42b86 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -13,12 +13,12 @@ from typing import List from kiauh.instance_manager.base_instance import BaseInstance from kiauh.menus.base_menu import print_back_footer -from kiauh.utils.constants import COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, \ - RESET_FORMAT +from kiauh.utils.constants import COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, RESET_FORMAT -def print_instance_overview(instances: List[BaseInstance], show_index=False, - show_select_all=False): +def print_instance_overview( + instances: List[BaseInstance], show_index=False, show_select_all=False +): headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}" print("/=======================================================\\") @@ -38,13 +38,20 @@ def print_instance_overview(instances: List[BaseInstance], show_index=False, print_back_footer() + def print_missing_usergroup_dialog(missing_groups) -> None: print("/=======================================================\\") - print(f"| {COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT} |") + print( + f"| {COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT} |" + ) if "tty" in missing_groups: - print(f"| {COLOR_CYAN}● tty{RESET_FORMAT} |") + print( + f"| {COLOR_CYAN}● tty{RESET_FORMAT} |" + ) if "dialout" in missing_groups: - print(f"| {COLOR_CYAN}● dialout{RESET_FORMAT} |") + print( + f"| {COLOR_CYAN}● dialout{RESET_FORMAT} |" + ) print("| |") print("| It is possible that you won't be able to successfully |") print("| connect and/or flash the controller board without |") @@ -52,6 +59,10 @@ def print_missing_usergroup_dialog(missing_groups) -> None: print("| If you want to add the current user to the group(s) |") print("| listed above, answer with 'Y'. Else skip with 'n'. |") print("| |") - print(f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |") - print(f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |") + print( + f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |" + ) + print( + f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |" + ) print("\\=======================================================/") diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index c6327d6..aac7a48 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -11,8 +11,8 @@ from typing import Optional, List -from kiauh.utils.logger import Logger from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT +from kiauh.utils.logger import Logger def get_user_confirm(question: str, default_choice=True) -> bool: @@ -30,7 +30,8 @@ def get_user_confirm(question: str, default_choice=True) -> bool: choice = ( input(f"{COLOR_CYAN}###### {question} {def_choice} {RESET_FORMAT}") .strip() - .lower()) + .lower() + ) if choice in options_confirm: return True @@ -40,8 +41,9 @@ def get_user_confirm(question: str, default_choice=True) -> bool: Logger.print_error("Invalid choice. Please select 'y' or 'n'.") -def get_user_number_input(question: str, min_count: int, max_count=None, - default=None) -> int: +def get_user_number_input( + question: str, min_count: int, max_count=None, default=None +) -> int: _question = question + f" (default={default})" if default else question _question = f"{COLOR_CYAN}###### {_question}: {RESET_FORMAT}" while True: @@ -65,8 +67,7 @@ def get_user_number_input(question: str, min_count: int, max_count=None, def get_user_string_input(question: str, exclude=Optional[List]) -> str: while True: - _input = (input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}") - .strip()) + _input = input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}").strip() if _input.isalnum() and _input not in exclude: return _input @@ -78,8 +79,7 @@ def get_user_string_input(question: str, exclude=Optional[List]) -> str: def get_user_selection_input(question: str, option_list: List) -> str: while True: - _input = (input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}") - .strip()) + _input = input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}").strip() if _input in option_list: return _input diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index f6adc0d..da54fb7 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -9,12 +9,16 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from kiauh.utils.constants import COLOR_GREEN, COLOR_YELLOW, COLOR_RED, \ - COLOR_MAGENTA, RESET_FORMAT +from kiauh.utils.constants import ( + COLOR_GREEN, + COLOR_YELLOW, + COLOR_RED, + COLOR_MAGENTA, + RESET_FORMAT, +) class Logger: - @staticmethod def info(msg): # log to kiauh.log diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index af85d24..7765a56 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -57,7 +57,8 @@ def clone_repo(target_dir: Path, url: str, branch: str) -> None: print("Error cloning repository:", e.output.decode()) else: overwrite_target = get_user_confirm( - "Target directory already exists. Overwrite?") + "Target directory already exists. Overwrite?" + ) if overwrite_target: try: shutil.rmtree(target_dir) @@ -71,13 +72,13 @@ def clone_repo(target_dir: Path, url: str, branch: str) -> None: def parse_packages_from_file(source_file) -> List[str]: packages = [] print("Reading dependencies...") - with open(source_file, 'r') as file: + with open(source_file, "r") as file: for line in file: line = line.strip() if line.startswith("PKGLIST="): - line = line.replace("\"", "") + line = line.replace('"', "") line = line.replace("PKGLIST=", "") - line = line.replace('${PKGLIST}', '') + line = line.replace("${PKGLIST}", "") packages.extend(line.split()) return packages @@ -97,16 +98,15 @@ def create_python_venv(target: Path) -> None: except subprocess.CalledProcessError as e: print("Error setting up virtualenv:", e.output.decode()) else: - overwrite_venv = get_user_confirm( - "Virtualenv already exists. Re-create?") + overwrite_venv = get_user_confirm("Virtualenv already exists. Re-create?") if overwrite_venv: try: shutil.rmtree(target) create_python_venv(target) except OSError as e: Logger.print_error( - f"Error removing existing virtualenv: {e.strerror}", - False) + f"Error removing existing virtualenv: {e.strerror}", False + ) else: print("Skipping re-creation of virtualenv ...") @@ -144,10 +144,7 @@ def install_python_requirements(target: Path, requirements: Path) -> None: def update_system_package_lists(silent: bool, rls_info_change=False) -> None: cache_mtime = 0 - cache_files = [ - "/var/lib/apt/periodic/update-success-stamp", - "/var/lib/apt/lists" - ] + cache_files = ["/var/lib/apt/periodic/update-success-stamp", "/var/lib/apt/lists"] for cache_file in cache_files: if Path(cache_file).exists(): cache_mtime = max(cache_mtime, os.path.getmtime(cache_file)) @@ -196,8 +193,7 @@ def create_directory(_dir: Path) -> None: os.makedirs(_dir, exist_ok=True) Logger.print_ok("Directory created!") else: - Logger.print_info( - f"Directory already exists: {_dir}\nSkip creation ...") + Logger.print_info(f"Directory already exists: {_dir}\nSkip creation ...") except OSError as e: Logger.print_error(f"Error creating folder: {e}") raise @@ -208,5 +204,7 @@ def mask_system_service(service_name: str) -> None: command = ["sudo", "systemctl", "mask", service_name] subprocess.run(command, stderr=subprocess.PIPE, check=True) except subprocess.CalledProcessError as e: - Logger.print_error(f"Unable to mask system service {service_name}: {e.stderr.decode()}") + Logger.print_error( + f"Unable to mask system service {service_name}: {e.stderr.decode()}" + ) raise diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..be1fddf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 88 +target-version = ['py38'] +include = '\.pyi?$' +exclude = ''' +( + \.git/ + | \.github/ + | docs/ + | resources/ + | scripts/ +) +''' From 1e0c74b54989c958dc678b453122e66fe28512b7 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 29 Oct 2023 01:29:27 +0200 Subject: [PATCH 006/137] style: rename input functions Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 20 ++++++++++---------- kiauh/utils/input_utils.py | 8 ++++---- kiauh/utils/system_utils.py | 8 +++----- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index e0a88cd..9098d2e 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -25,10 +25,10 @@ from kiauh.modules.klipper.klipper_utils import ( ) from kiauh.utils.constants import CURRENT_USER, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.input_utils import ( - get_user_confirm, - get_user_number_input, - get_user_string_input, - get_user_selection_input, + get_confirm, + get_number_input, + get_string_input, + get_selection_input, ) from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( @@ -83,7 +83,7 @@ def handle_existing_instances(instance_manager: InstanceManager) -> bool: if instance_count > 0: print_instance_overview(instance_list) - if not get_user_confirm("Add new instances?"): + if not get_confirm("Add new instances?"): return False return True @@ -92,7 +92,7 @@ def handle_existing_instances(instance_manager: InstanceManager) -> bool: def install_klipper(instance_manager: InstanceManager) -> None: instance_list = instance_manager.get_instances() if_adding = " additional" if len(instance_list) > 0 else "" - install_count = get_user_number_input( + install_count = get_number_input( f"Number of{if_adding} Klipper instances to set up", 1, default=1 ) @@ -157,7 +157,7 @@ def set_instance_names(instance_list, install_count: int) -> List[Union[str, Non # or convert single instance install to multi instance install or (instance_count == 1 and install_count >= 1) ): - if get_user_confirm("Assign custom names?", False): + if get_confirm("Assign custom names?", False): return assign_custom_names(instance_count, install_count, None) else: _range = range(1, install_count + 1) @@ -195,7 +195,7 @@ def assign_custom_names( for i in range(instance_count + install_count): question = f"Enter name for instance {i + 1}" - name = get_user_string_input(question, exclude=exclude) + name = get_string_input(question, exclude=exclude) instance_names.append(name) exclude.append(name) @@ -227,7 +227,7 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: options = [str(i) for i in range(len(instance_list))] options.extend(["a", "A", "b", "B"]) - selection = get_user_selection_input("Select Klipper instance to remove", options) + selection = get_selection_input("Select Klipper instance to remove", options) print(selection) if selection == "b".lower(): @@ -265,7 +265,7 @@ def check_user_groups(): return print_missing_usergroup_dialog(missing_groups) - if not get_user_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"): + if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"): Logger.warn( "Skipped adding user to required groups. You might encounter issues." ) diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index aac7a48..eaf97f6 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -15,7 +15,7 @@ from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT from kiauh.utils.logger import Logger -def get_user_confirm(question: str, default_choice=True) -> bool: +def get_confirm(question: str, default_choice=True) -> bool: options_confirm = ["y", "yes"] options_decline = ["n", "no"] @@ -41,7 +41,7 @@ def get_user_confirm(question: str, default_choice=True) -> bool: Logger.print_error("Invalid choice. Please select 'y' or 'n'.") -def get_user_number_input( +def get_number_input( question: str, min_count: int, max_count=None, default=None ) -> int: _question = question + f" (default={default})" if default else question @@ -65,7 +65,7 @@ def get_user_number_input( Logger.print_error("Invalid choice. Please select a valid number.") -def get_user_string_input(question: str, exclude=Optional[List]) -> str: +def get_string_input(question: str, exclude=Optional[List]) -> str: while True: _input = input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}").strip() @@ -77,7 +77,7 @@ def get_user_string_input(question: str, exclude=Optional[List]) -> str: Logger.print_error("This value is already in use/reserved.") -def get_user_selection_input(question: str, option_list: List) -> str: +def get_selection_input(question: str, option_list: List) -> str: while True: _input = input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}").strip() diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 7765a56..6a9bde6 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -18,7 +18,7 @@ from pathlib import Path from typing import List from kiauh.utils.constants import COLOR_RED, RESET_FORMAT -from kiauh.utils.input_utils import get_user_confirm +from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger @@ -56,9 +56,7 @@ def clone_repo(target_dir: Path, url: str, branch: str) -> None: except subprocess.CalledProcessError as e: print("Error cloning repository:", e.output.decode()) else: - overwrite_target = get_user_confirm( - "Target directory already exists. Overwrite?" - ) + overwrite_target = get_confirm("Target directory already exists. Overwrite?") if overwrite_target: try: shutil.rmtree(target_dir) @@ -98,7 +96,7 @@ def create_python_venv(target: Path) -> None: except subprocess.CalledProcessError as e: print("Error setting up virtualenv:", e.output.decode()) else: - overwrite_venv = get_user_confirm("Virtualenv already exists. Re-create?") + overwrite_venv = get_confirm("Virtualenv already exists. Re-create?") if overwrite_venv: try: shutil.rmtree(target) From 623bd7553bb862d32907bbe077b261eb9cb0eabd Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 29 Oct 2023 13:25:21 +0100 Subject: [PATCH 007/137] feat(RepoManager): implement RepoManager Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 11 ++- kiauh/repo_manager/__init__.py | 0 kiauh/repo_manager/repo_manager.py | 105 +++++++++++++++++++++++++ kiauh/utils/system_utils.py | 25 ------ 4 files changed, 112 insertions(+), 29 deletions(-) create mode 100644 kiauh/repo_manager/__init__.py create mode 100644 kiauh/repo_manager/repo_manager.py diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 9098d2e..ec9eb81 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -23,6 +23,7 @@ from kiauh.modules.klipper.klipper_utils import ( print_instance_overview, print_missing_usergroup_dialog, ) +from kiauh.repo_manager.repo_manager import RepoManager from kiauh.utils.constants import CURRENT_USER, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.input_utils import ( get_confirm, @@ -33,7 +34,6 @@ from kiauh.utils.input_utils import ( from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( parse_packages_from_file, - clone_repo, create_python_venv, install_python_requirements, update_system_package_lists, @@ -119,9 +119,12 @@ def install_klipper(instance_manager: InstanceManager) -> None: def setup_klipper_prerequesites() -> None: # clone klipper TODO: read branch and url from json to allow forks - url = "https://github.com/Klipper3D/klipper" - branch = "master" - clone_repo(Path(KLIPPER_DIR), url, branch) + repo_manager = RepoManager( + repo="https://github.com/Klipper3D/klipper", + branch="master", + target_dir=KLIPPER_DIR, + ) + repo_manager.clone_repo() # install klipper dependencies and create python virtualenv install_klipper_packages(Path(KLIPPER_DIR)) diff --git a/kiauh/repo_manager/__init__.py b/kiauh/repo_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/repo_manager/repo_manager.py b/kiauh/repo_manager/repo_manager.py new file mode 100644 index 0000000..dfffe31 --- /dev/null +++ b/kiauh/repo_manager/repo_manager.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 pathlib import Path +from typing import Union + +from kiauh.utils.input_utils import get_confirm +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class RepoManager: + def __init__(self, repo: str, branch: str, target_dir: str): + self._repo = repo + self._branch = branch + self._method = self._get_method() + self._target_dir = target_dir + + @property + def repo(self) -> str: + return self._repo + + @repo.setter + def repo(self, value) -> None: + self._repo = value + + @property + def branch(self) -> str: + return self._branch + + @branch.setter + def branch(self, value) -> None: + self._branch = value + + @property + def method(self) -> str: + return self._method + + @method.setter + def method(self, value) -> None: + self._method = value + + @property + def target_dir(self) -> str: + return self._target_dir + + @target_dir.setter + def target_dir(self, value) -> None: + self._target_dir = value + + def clone_repo(self): + log = f"Cloning repository from '{self.repo}' with method '{self.method}'" + Logger.print_info(log) + try: + question = "Target directory already exists. Overwrite?" + if Path(self.target_dir).exists() and get_confirm(question): + shutil.rmtree(self.target_dir) + else: + Logger.print_info("Skipping re-clone of repository ...") + return + + self._clone() + self._checkout() + except subprocess.CalledProcessError: + log = "An unexpected error occured during cloning of the repository." + Logger.print_error(log) + return + except OSError as e: + Logger.print_error(f"Error removing existing repository: {e.strerror}") + return + + def _clone(self): + try: + command = ["git", "clone", f"{self.repo}"] + subprocess.run(command, check=True) + + Logger.print_ok("Clone successfull!") + except subprocess.CalledProcessError as e: + log = f"Error cloning repository {self.repo}: {e.stderr.decode()}" + Logger.print_error(log) + raise + + def _checkout(self): + try: + command = ["git", "checkout", f"{self.branch}"] + subprocess.run(command, cwd=self.target_dir, check=True) + + Logger.print_ok("Checkout successfull!") + except subprocess.CalledProcessError as e: + log = f"Error checking out branch {self.branch}: {e.stderr.decode()}" + Logger.print_error(log) + raise + + def _get_method(self) -> str: + return "ssh" if self.repo.startswith("git") else "https" diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 6a9bde6..73ad1b0 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -42,31 +42,6 @@ def kill(opt_err_msg=None) -> None: sys.exit(1) -def clone_repo(target_dir: Path, url: str, branch: str) -> None: - Logger.print_info(f"Cloning repository from {url}") - if not target_dir.exists(): - try: - command = ["git", "clone", f"{url}"] - subprocess.run(command, check=True) - - command = ["git", "checkout", f"{branch}"] - subprocess.run(command, cwd=target_dir, check=True) - - Logger.print_ok("Clone successfull!") - except subprocess.CalledProcessError as e: - print("Error cloning repository:", e.output.decode()) - else: - overwrite_target = get_confirm("Target directory already exists. Overwrite?") - if overwrite_target: - try: - shutil.rmtree(target_dir) - clone_repo(target_dir, url, branch) - except OSError as e: - print("Error removing existing repository:", e.strerror) - else: - print("Skipping re-clone of repository ...") - - def parse_packages_from_file(source_file) -> List[str]: packages = [] print("Reading dependencies...") From 09e874214b6accae9dc1e6e77a3afd23d3f7302d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 29 Oct 2023 17:19:26 +0100 Subject: [PATCH 008/137] feat(ConfigManager): implement ConfigManager Signed-off-by: Dominik Willner --- .gitignore | 4 +- kiauh.cfg.example | 12 +++++ kiauh/config_manager/__init__.py | 0 kiauh/config_manager/config_manager.py | 61 ++++++++++++++++++++++++++ kiauh/modules/klipper/klipper_setup.py | 15 +++++-- kiauh/repo_manager/repo_manager.py | 14 +++--- klipper_repos.txt.example | 18 -------- 7 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 kiauh.cfg.example create mode 100644 kiauh/config_manager/__init__.py create mode 100644 kiauh/config_manager/config_manager.py delete mode 100644 klipper_repos.txt.example diff --git a/.gitignore b/.gitignore index 83a9457..8105baa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ .pytest_cache .kiauh-env *.code-workspace -klipper_repos.txt -klipper_repos.json - +kiauh.cfg diff --git a/kiauh.cfg.example b/kiauh.cfg.example new file mode 100644 index 0000000..0ba34db --- /dev/null +++ b/kiauh.cfg.example @@ -0,0 +1,12 @@ +[kiauh] +backup_before_update: False + +[klipper] +repository_url: https://github.com/Klipper3d/klipper +branch: master +method: https + +[moonraker] +repository_url: https://github.com/Klipper3d/klipper +branch: master +method: https diff --git a/kiauh/config_manager/__init__.py b/kiauh/config_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/config_manager/config_manager.py b/kiauh/config_manager/config_manager.py new file mode 100644 index 0000000..0f80647 --- /dev/null +++ b/kiauh/config_manager/config_manager.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 os +import configparser +from pathlib import Path +from typing import Union + +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class ConfigManager: + def __init__(self): + self.config_file = self._get_cfg_location() + self.config = configparser.ConfigParser() + + def read_config(self) -> None: + if not self.config_file: + Logger.print_error("Unable to read config file. File not found.") + return + + self.config.read_file(open(self.config_file, "r")) + + def write_config(self) -> None: + with open(self.config_file, "w") as cfg: + self.config.write(cfg) + + def get_value(self, section: str, key: str) -> Union[str, None]: + if not self.config.has_section(section): + log = f"Section not defined. Unable to read section: [{section}]." + Logger.print_error(log) + return None + + if not self.config.has_option(section, key): + log = f"Option not defined in section [{section}]. Unable to read option: '{key}'." + Logger.print_error(log) + return None + + return self.config.get(section, key) + + def set_value(self, section: str, key: str, value: str): + self.config.set(section, key, value) + + def check_config_exists(self) -> bool: + return True if self._get_cfg_location() else False + + def _get_cfg_location(self) -> str: + current_dir = os.path.dirname(os.path.abspath(__file__)) + project_dir = os.path.dirname(os.path.dirname(current_dir)) + cfg_path = os.path.join(project_dir, "kiauh.cfg") + + return cfg_path if Path(cfg_path).exists() else None diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index ec9eb81..865d2bf 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -17,6 +17,7 @@ import textwrap from pathlib import Path from typing import Optional, List, Union +from kiauh.config_manager.config_manager import ConfigManager from kiauh.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_utils import ( @@ -118,10 +119,18 @@ def install_klipper(instance_manager: InstanceManager) -> None: def setup_klipper_prerequesites() -> None: - # clone klipper TODO: read branch and url from json to allow forks + cm = ConfigManager() + cm.read_config() + + repo = ( + cm.get_value("klipper", "repository_url") + or "https://github.com/Klipper3D/klipper" + ) + branch = cm.get_value("klipper", "branch") or "master" + repo_manager = RepoManager( - repo="https://github.com/Klipper3D/klipper", - branch="master", + repo=repo, + branch=branch, target_dir=KLIPPER_DIR, ) repo_manager.clone_repo() diff --git a/kiauh/repo_manager/repo_manager.py b/kiauh/repo_manager/repo_manager.py index dfffe31..b1501d2 100644 --- a/kiauh/repo_manager/repo_manager.py +++ b/kiauh/repo_manager/repo_manager.py @@ -9,10 +9,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import os import shutil import subprocess -from pathlib import Path -from typing import Union from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger @@ -62,12 +61,11 @@ class RepoManager: log = f"Cloning repository from '{self.repo}' with method '{self.method}'" Logger.print_info(log) try: - question = "Target directory already exists. Overwrite?" - if Path(self.target_dir).exists() and get_confirm(question): + if os.path.exists(self.target_dir): + if not get_confirm("Target directory already exists. Overwrite?"): + Logger.print_info("Skipping re-clone of repository ...") + return shutil.rmtree(self.target_dir) - else: - Logger.print_info("Skipping re-clone of repository ...") - return self._clone() self._checkout() @@ -81,7 +79,7 @@ class RepoManager: def _clone(self): try: - command = ["git", "clone", f"{self.repo}"] + command = ["git", "clone", self.repo, self.target_dir] subprocess.run(command, check=True) Logger.print_ok("Clone successfull!") diff --git a/klipper_repos.txt.example b/klipper_repos.txt.example deleted file mode 100644 index 6cc3393..0000000 --- a/klipper_repos.txt.example +++ /dev/null @@ -1,18 +0,0 @@ -# This file acts as an example file. -# -# 1) Make a copy of this file and rename it to 'klipper_repos.txt' -# 2) Add your custom Klipper repository to the bottom of that copy -# 3) Save the file -# -# Back in KIAUH you can now go into -> [Settings] and use action '2' to set a different Klipper repository -# -# Make sure to always separate the repository and the branch with a ','. -# , -> https://github.com/Klipper3d/klipper,master -# If you omit a branch, it will always default to 'master' -# -# You are allowed to omit the 'https://github.com/' part of the repository URL -# Down below are now a few examples of what is considered as valid: -https://github.com/Klipper3d/klipper,master -https://github.com/Klipper3d/klipper -Klipper3d/klipper,master -Klipper3d/klipper From c9e8c4807e295c720aa79e54add06d54e8ea6ae0 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Tue, 31 Oct 2023 20:54:44 +0100 Subject: [PATCH 009/137] feat(klipper): convert single to multi instance Signed-off-by: Dominik Willner --- kiauh/instance_manager/base_instance.py | 8 - kiauh/modules/klipper/klipper.py | 84 ++++----- kiauh/modules/klipper/klipper_dialogs.py | 98 +++++++++++ kiauh/modules/klipper/klipper_setup.py | 182 +++++-------------- kiauh/modules/klipper/klipper_utils.py | 212 ++++++++++++++++++----- kiauh/utils/input_utils.py | 31 ++-- 6 files changed, 374 insertions(+), 241 deletions(-) create mode 100644 kiauh/modules/klipper/klipper_dialogs.py diff --git a/kiauh/instance_manager/base_instance.py b/kiauh/instance_manager/base_instance.py index 169ff74..3dcda97 100644 --- a/kiauh/instance_manager/base_instance.py +++ b/kiauh/instance_manager/base_instance.py @@ -72,14 +72,6 @@ class BaseInstance(ABC): def create(self) -> None: raise NotImplementedError("Subclasses must implement the create method") - @abstractmethod - def read(self) -> None: - raise NotImplementedError("Subclasses must implement the read method") - - @abstractmethod - def update(self) -> None: - raise NotImplementedError("Subclasses must implement the update method") - @abstractmethod def delete(self, del_remnants: bool) -> None: raise NotImplementedError("Subclasses must implement the delete method") diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index e21d6a0..5c187e2 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -50,36 +50,12 @@ class Klipper(BaseInstance): service_file_target = f"{SYSTEMD}/{service_file_name}" env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env") - # create folder structure - dirs = [ - self.data_dir, - self.cfg_dir, - self.log_dir, - self.comms_dir, - self.sysd_dir, - ] - for _dir in dirs: - create_directory(Path(_dir)) - try: - # writing the klipper service file (requires sudo!) - service_content = self._prep_service_file( - service_template_path, env_file_target + self.create_folder_structure() + self.write_service_file( + service_template_path, service_file_target, env_file_target ) - command = ["sudo", "tee", service_file_target] - subprocess.run( - command, - input=service_content.encode(), - stdout=subprocess.DEVNULL, - check=True, - ) - Logger.print_ok(f"Service file created: {service_file_target}") - - # writing the klipper.env file - env_file_content = self._prep_env_file(env_template_file_path) - with open(env_file_target, "w") as env_file: - env_file.write(env_file_content) - Logger.print_ok(f"Env file created: {env_file_target}") + self.write_env_file(env_template_file_path, env_file_target) except subprocess.CalledProcessError as e: Logger.print_error( @@ -90,12 +66,6 @@ class Klipper(BaseInstance): Logger.print_error(f"Error creating env file {env_file_target}: {e}") raise - def read(self) -> None: - print("Reading Klipper Instance") - - def update(self) -> None: - print("Updating Klipper Instance") - def delete(self, del_remnants: bool) -> None: service_file = self.get_service_file_name(extension=True) service_file_path = self._get_service_file_path() @@ -113,6 +83,45 @@ class Klipper(BaseInstance): if del_remnants: self._delete_klipper_remnants() + def create_folder_structure(self) -> None: + dirs = [ + self.data_dir, + self.cfg_dir, + self.log_dir, + self.comms_dir, + self.sysd_dir, + ] + for _dir in dirs: + create_directory(Path(_dir)) + + def write_service_file( + self, service_template_path: str, service_file_target: str, env_file_target: str + ): + service_content = self._prep_service_file( + service_template_path, env_file_target + ) + command = ["sudo", "tee", service_file_target] + subprocess.run( + command, + input=service_content.encode(), + stdout=subprocess.DEVNULL, + check=True, + ) + Logger.print_ok(f"Service file created: {service_file_target}") + + def write_env_file(self, env_template_file_path: str, env_file_target: str): + env_file_content = self._prep_env_file(env_template_file_path) + with open(env_file_target, "w") as env_file: + env_file.write(env_file_content) + Logger.print_ok(f"Env file created: {env_file_target}") + + def get_service_file_name(self, extension=False) -> str: + name = self.prefix if self.name is None else self.prefix + "-" + self.name + return name if not extension else f"{name}.service" + + def _get_service_file_path(self): + return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}" + def _delete_klipper_remnants(self) -> None: try: Logger.print_info(f"Delete {self.klipper_dir} ...") @@ -127,13 +136,6 @@ class Klipper(BaseInstance): Logger.print_ok("Directories successfully deleted.") - def get_service_file_name(self, extension=False) -> str: - name = self.prefix if self.name is None else self.prefix + "-" + self.name - return name if not extension else f"{name}.service" - - def _get_service_file_path(self): - return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}" - def _get_data_dir_from_name(self, name: str) -> str: if name is None: return "printer" diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py new file mode 100644 index 0000000..204ea43 --- /dev/null +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from typing import List + +from kiauh.instance_manager.base_instance import BaseInstance +from kiauh.menus.base_menu import print_back_footer +from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN + + +def print_instance_overview( + instances: List[BaseInstance], show_index=False, show_select_all=False +): + headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}" + + print("/=======================================================\\") + print(f"|{'{:^64}'.format(headline)}|") + print("|-------------------------------------------------------|") + + if show_select_all: + select_all = f" {COLOR_YELLOW}a) Select all{RESET_FORMAT}" + print(f"|{'{:64}'.format(select_all)}|") + print("| |") + + for i, s in enumerate(instances): + index = f"{i})" if show_index else "●" + instance = s.get_service_file_name() + line = f"{'{:53}'.format(f'{index} {instance}')}" + print(f"| {COLOR_CYAN}{line}{RESET_FORMAT}|") + + print_back_footer() + + +def print_select_instance_count_dialog(): + print("/=======================================================\\") + print("| Please select the number of Klipper instances to set |") + print("| up. The number of Klipper instances will determine |") + print("| the amount of printers you can run from this host. |") + print("| |") + print( + f"| {COLOR_YELLOW}WARNING:{RESET_FORMAT} |" + ) + print( + f"| {COLOR_YELLOW}Setting up too many instances may crash your system.{RESET_FORMAT} |" + ) + print_back_footer() + + +def print_select_custom_name_dialog(): + print("/=======================================================\\") + print("| You can now assign a custom name to each instance. |") + print("| If skipped, each instance will get an index assigned |") + print("| in ascending order, starting at index '1'. |") + print("| |") + print( + f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |" + ) + print( + f"| {COLOR_YELLOW}Only alphanumeric characters are allowed!{RESET_FORMAT} |" + ) + print_back_footer() + + +def print_missing_usergroup_dialog(missing_groups) -> None: + print("/=======================================================\\") + print( + f"| {COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT} |" + ) + if "tty" in missing_groups: + print( + f"| {COLOR_CYAN}● tty{RESET_FORMAT} |" + ) + if "dialout" in missing_groups: + print( + f"| {COLOR_CYAN}● dialout{RESET_FORMAT} |" + ) + print("| |") + print("| It is possible that you won't be able to successfully |") + print("| connect and/or flash the controller board without |") + print("| your user being a member of that group. |") + print("| If you want to add the current user to the group(s) |") + print("| listed above, answer with 'Y'. Else skip with 'n'. |") + print("| |") + print( + f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |" + ) + print( + f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |" + ) + print("\\=======================================================/") diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 865d2bf..5c41c40 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -9,27 +9,31 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import grp import os -import re import subprocess -import textwrap from pathlib import Path -from typing import Optional, List, Union +from typing import List, Union from kiauh.config_manager.config_manager import ConfigManager from kiauh.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_utils import ( +from kiauh.modules.klipper.klipper_dialogs import ( print_instance_overview, - print_missing_usergroup_dialog, + print_select_instance_count_dialog, +) +from kiauh.modules.klipper.klipper_utils import ( + handle_convert_single_to_multi_instance_names, + handle_new_multi_instance_names, + handle_existing_multi_instance_names, + handle_disruptive_system_packages, + check_user_groups, + handle_single_to_multi_conversion, ) from kiauh.repo_manager.repo_manager import RepoManager -from kiauh.utils.constants import CURRENT_USER, KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.utils.constants import KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.input_utils import ( get_confirm, get_number_input, - get_string_input, get_selection_input, ) from kiauh.utils.logger import Logger @@ -39,7 +43,6 @@ from kiauh.utils.system_utils import ( install_python_requirements, update_system_package_lists, install_system_packages, - mask_system_service, ) @@ -92,19 +95,37 @@ def handle_existing_instances(instance_manager: InstanceManager) -> bool: def install_klipper(instance_manager: InstanceManager) -> None: instance_list = instance_manager.get_instances() - if_adding = " additional" if len(instance_list) > 0 else "" - install_count = get_number_input( - f"Number of{if_adding} Klipper instances to set up", 1, default=1 - ) + + print_select_instance_count_dialog() + question = f"Number of{' additional' if len(instance_list) > 0 else ''} Klipper instances to set up" + install_count = get_number_input(question, 1, default=1, allow_go_back=True) + if install_count is None: + Logger.print_info("Exiting Klipper setup ...") + return instance_names = set_instance_names(instance_list, install_count) + if instance_names is None: + Logger.print_info("Exiting Klipper setup ...") + return if len(instance_list) < 1: setup_klipper_prerequesites() + convert_single_to_multi = ( + True + if len(instance_list) == 1 + and instance_list[0].name is None + and install_count >= 1 + else False + ) + for name in instance_names: - current_instance = Klipper(name=name) - instance_manager.set_current_instance(current_instance) + if convert_single_to_multi: + handle_single_to_multi_conversion(instance_manager, name) + convert_single_to_multi = False + else: + instance_manager.set_current_instance(Klipper(name=name)) + instance_manager.create_instance() instance_manager.enable_instance() instance_manager.start_instance() @@ -122,11 +143,11 @@ def setup_klipper_prerequesites() -> None: cm = ConfigManager() cm.read_config() - repo = ( + repo = str( cm.get_value("klipper", "repository_url") or "https://github.com/Klipper3D/klipper" ) - branch = cm.get_value("klipper", "branch") or "master" + branch = str(cm.get_value("klipper", "branch") or "master") repo_manager = RepoManager( repo=repo, @@ -159,64 +180,23 @@ def install_klipper_packages(klipper_dir: Path) -> None: def set_instance_names(instance_list, install_count: int) -> List[Union[str, None]]: instance_count = len(instance_list) - # default single instance install + # new single instance install if instance_count == 0 and install_count == 1: return [None] + # convert single instance install to multi install + elif instance_count == 1 and instance_list[0].name is None and install_count >= 1: + return handle_convert_single_to_multi_instance_names(install_count) + # new multi instance install - elif ( - (instance_count == 0 and install_count > 1) - # or convert single instance install to multi instance install - or (instance_count == 1 and install_count >= 1) - ): - if get_confirm("Assign custom names?", False): - return assign_custom_names(instance_count, install_count, None) - else: - _range = range(1, install_count + 1) - return [str(i) for i in _range] + elif instance_count == 0 and install_count > 1: + return handle_new_multi_instance_names(instance_count, install_count) # existing multi instance install elif instance_count > 1: - if has_custom_names(instance_list): - return assign_custom_names(instance_count, install_count, instance_list) - else: - start = get_highest_index(instance_list) + 1 - _range = range(start, start + install_count) - return [str(i) for i in _range] - - -def has_custom_names(instance_list: List[Klipper]) -> bool: - pattern = re.compile("^\d+$") - for instance in instance_list: - if not pattern.match(instance.name): - return True - - return False - - -def assign_custom_names( - instance_count: int, install_count: int, instance_list: Optional[List[Klipper]] -) -> List[str]: - instance_names = [] - exclude = Klipper.blacklist() - - # if an instance_list is provided, exclude all existing instance names - if instance_list is not None: - for instance in instance_list: - exclude.append(instance.name) - - for i in range(instance_count + install_count): - question = f"Enter name for instance {i + 1}" - name = get_string_input(question, exclude=exclude) - instance_names.append(name) - exclude.append(name) - - return instance_names - - -def get_highest_index(instance_list: List[Klipper]) -> int: - indices = [int(instance.name.split("-")[-1]) for instance in instance_list] - return max(indices) + return handle_existing_multi_instance_names( + instance_count, install_count, instance_list + ) def remove_single_instance(instance_manager: InstanceManager) -> None: @@ -262,69 +242,3 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: instance_manager.delete_instance(del_remnants=False) instance_manager.reload_daemon() - - -def check_user_groups(): - current_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()] - - missing_groups = [] - if "tty" not in current_groups: - missing_groups.append("tty") - if "dialout" not in current_groups: - missing_groups.append("dialout") - - if not missing_groups: - return - - print_missing_usergroup_dialog(missing_groups) - if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"): - Logger.warn( - "Skipped adding user to required groups. You might encounter issues." - ) - return - - try: - for group in missing_groups: - Logger.print_info(f"Adding user '{CURRENT_USER}' to group {group} ...") - command = ["sudo", "usermod", "-a", "-G", group, CURRENT_USER] - subprocess.run(command, check=True) - Logger.print_ok(f"Group {group} assigned to user '{CURRENT_USER}'.") - except subprocess.CalledProcessError as e: - Logger.print_error(f"Unable to add user to usergroups: {e}") - raise - - Logger.print_warn( - "Remember to relog/restart this machine for the group(s) to be applied!" - ) - - -def handle_disruptive_system_packages() -> None: - services = [] - brltty_status = subprocess.run( - ["systemctl", "is-enabled", "brltty"], capture_output=True, text=True - ) - modem_manager_status = subprocess.run( - ["systemctl", "is-enabled", "ModemManager"], capture_output=True, text=True - ) - - if "enabled" in brltty_status.stdout: - services.append("brltty") - if "enabled" in modem_manager_status.stdout: - services.append("ModemManager") - - for service in services if services else []: - try: - Logger.print_info( - f"{service} service detected! Masking {service} service ..." - ) - mask_system_service(service) - Logger.print_ok(f"{service} service masked!") - except subprocess.CalledProcessError: - warn_msg = textwrap.dedent( - f""" - KIAUH was unable to mask the {service} system service. - Please fix the problem manually. Otherwise, this may have - undesirable effects on the operation of Klipper. - """ - )[1:] - Logger.print_warn(warn_msg) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 0d42b86..e018250 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -9,60 +9,178 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from typing import List +import os +import re +import grp +import subprocess +import textwrap -from kiauh.instance_manager.base_instance import BaseInstance -from kiauh.menus.base_menu import print_back_footer -from kiauh.utils.constants import COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, RESET_FORMAT +from typing import List, Union + +from kiauh.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.klipper.klipper_dialogs import ( + print_missing_usergroup_dialog, + print_select_custom_name_dialog, +) +from kiauh.utils.constants import CURRENT_USER +from kiauh.utils.input_utils import get_confirm, get_string_input +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import mask_system_service -def print_instance_overview( - instances: List[BaseInstance], show_index=False, show_select_all=False -): - headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}" +def assign_custom_names( + instance_count: int, install_count: int, instance_list: List[Klipper] = None +) -> List[str]: + instance_names = [] + exclude = Klipper.blacklist() - print("/=======================================================\\") - print(f"|{'{:^64}'.format(headline)}|") - print("|-------------------------------------------------------|") + # if an instance_list is provided, exclude all existing instance names + if instance_list is not None: + for instance in instance_list: + exclude.append(instance.name) - if show_select_all: - select_all = f" {COLOR_YELLOW}a) Select all{RESET_FORMAT}" - print(f"|{'{:64}'.format(select_all)}|") - print("| |") + for i in range(instance_count + install_count): + question = f"Enter name for instance {i + 1}" + name = get_string_input(question, exclude=exclude) + instance_names.append(name) + exclude.append(name) - for i, s in enumerate(instances): - index = f"{i})" if show_index else "●" - instance = s.get_service_file_name() - line = f"{'{:53}'.format(f'{index} {instance}')}" - print(f"| {COLOR_CYAN}{line}{RESET_FORMAT}|") - - print_back_footer() + return instance_names -def print_missing_usergroup_dialog(missing_groups) -> None: - print("/=======================================================\\") - print( - f"| {COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT} |" +def handle_convert_single_to_multi_instance_names( + install_count: int, +) -> Union[List[str], None]: + print_select_custom_name_dialog() + choice = get_confirm("Assign custom names?", False, allow_go_back=True) + if choice is True: + # instance_count = 0 and install_count + 1 as we want to assign a new name to the existing single install + return assign_custom_names(0, install_count + 1) + elif choice is False: + # "install_count + 2" as we need to account for the existing single install + _range = range(1, install_count + 2) + return [str(i) for i in _range] + + return None + + +def handle_new_multi_instance_names( + instance_count: int, install_count: int +) -> Union[List[str], None]: + print_select_custom_name_dialog() + choice = get_confirm("Assign custom names?", False, allow_go_back=True) + if choice is True: + return assign_custom_names(instance_count, install_count) + elif choice is False: + _range = range(1, install_count + 1) + return [str(i) for i in _range] + + return None + + +def handle_existing_multi_instance_names( + instance_count: int, install_count: int, instance_list: List[Klipper] +) -> List[str]: + if has_custom_names(instance_list): + return assign_custom_names(instance_count, install_count, instance_list) + else: + start = get_highest_index(instance_list) + 1 + _range = range(start, start + install_count) + return [str(i) for i in _range] + + +def handle_single_to_multi_conversion( + instance_manager: InstanceManager, name: str +) -> None: + instance_list = instance_manager.get_instances() + instance_manager.set_current_instance(instance_list[0]) + old_data_dir_name = instance_manager.get_instances()[0].data_dir + instance_manager.stop_instance() + instance_manager.disable_instance() + instance_manager.delete_instance(del_remnants=False) + instance_manager.set_current_instance(Klipper(name=name)) + new_data_dir_name = instance_manager.get_current_instance().data_dir + try: + os.rename(old_data_dir_name, new_data_dir_name) + except OSError as e: + log = f"Cannot rename {old_data_dir_name} to {new_data_dir_name}:\n{e}" + Logger.print_error(log) + + +def check_user_groups(): + current_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()] + + missing_groups = [] + if "tty" not in current_groups: + missing_groups.append("tty") + if "dialout" not in current_groups: + missing_groups.append("dialout") + + if not missing_groups: + return + + print_missing_usergroup_dialog(missing_groups) + if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"): + log = "Skipped adding user to required groups. You might encounter issues." + Logger.warn(log) + return + + try: + for group in missing_groups: + Logger.print_info(f"Adding user '{CURRENT_USER}' to group {group} ...") + command = ["sudo", "usermod", "-a", "-G", group, CURRENT_USER] + subprocess.run(command, check=True) + Logger.print_ok(f"Group {group} assigned to user '{CURRENT_USER}'.") + except subprocess.CalledProcessError as e: + Logger.print_error(f"Unable to add user to usergroups: {e}") + raise + + log = "Remember to relog/restart this machine for the group(s) to be applied!" + Logger.print_warn(log) + + +def handle_disruptive_system_packages() -> None: + services = [] + brltty_status = subprocess.run( + ["systemctl", "is-enabled", "brltty"], capture_output=True, text=True ) - if "tty" in missing_groups: - print( - f"| {COLOR_CYAN}● tty{RESET_FORMAT} |" - ) - if "dialout" in missing_groups: - print( - f"| {COLOR_CYAN}● dialout{RESET_FORMAT} |" - ) - print("| |") - print("| It is possible that you won't be able to successfully |") - print("| connect and/or flash the controller board without |") - print("| your user being a member of that group. |") - print("| If you want to add the current user to the group(s) |") - print("| listed above, answer with 'Y'. Else skip with 'n'. |") - print("| |") - print( - f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |" + modem_manager_status = subprocess.run( + ["systemctl", "is-enabled", "ModemManager"], capture_output=True, text=True ) - print( - f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |" - ) - print("\\=======================================================/") + + if "enabled" in brltty_status.stdout: + services.append("brltty") + if "enabled" in modem_manager_status.stdout: + services.append("ModemManager") + + for service in services if services else []: + try: + Logger.print_info( + f"{service} service detected! Masking {service} service ..." + ) + mask_system_service(service) + Logger.print_ok(f"{service} service masked!") + except subprocess.CalledProcessError: + warn_msg = textwrap.dedent( + f""" + KIAUH was unable to mask the {service} system service. + Please fix the problem manually. Otherwise, this may have + undesirable effects on the operation of Klipper. + """ + )[1:] + Logger.print_warn(warn_msg) + + +def has_custom_names(instance_list: List[Klipper]) -> bool: + pattern = re.compile("^\d+$") + for instance in instance_list: + if not pattern.match(instance.name): + return True + + return False + + +def get_highest_index(instance_list: List[Klipper]) -> int: + indices = [int(instance.name.split("-")[-1]) for instance in instance_list] + return max(indices) diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index eaf97f6..5ab04c5 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -9,15 +9,18 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from typing import Optional, List +from typing import Optional, List, Union from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT from kiauh.utils.logger import Logger -def get_confirm(question: str, default_choice=True) -> bool: +def get_confirm( + question: str, default_choice=True, allow_go_back=False +) -> Union[bool, None]: options_confirm = ["y", "yes"] options_decline = ["n", "no"] + options_go_back = ["b", "B"] if default_choice: def_choice = "(Y/n)" @@ -28,7 +31,7 @@ def get_confirm(question: str, default_choice=True) -> bool: while True: choice = ( - input(f"{COLOR_CYAN}###### {question} {def_choice} {RESET_FORMAT}") + input(f"{COLOR_CYAN}###### {question} {def_choice}: {RESET_FORMAT}") .strip() .lower() ) @@ -37,28 +40,34 @@ def get_confirm(question: str, default_choice=True) -> bool: return True elif choice in options_decline: return False + elif allow_go_back and choice in options_go_back: + return None else: Logger.print_error("Invalid choice. Please select 'y' or 'n'.") def get_number_input( - question: str, min_count: int, max_count=None, default=None -) -> int: + question: str, min_count: int, max_count=None, default=None, allow_go_back=False +) -> Union[int, None]: + options_go_back = ["b", "B"] _question = question + f" (default={default})" if default else question _question = f"{COLOR_CYAN}###### {_question}: {RESET_FORMAT}" while True: try: - num = input(_question) - if num == "": + _input = input(_question) + if allow_go_back and _input in options_go_back: + return None + + if _input == "": return default if max_count is not None: - if min_count <= int(num) <= max_count: - return int(num) + if min_count <= int(_input) <= max_count: + return int(_input) else: raise ValueError - elif int(num) >= min_count: - return int(num) + elif int(_input) >= min_count: + return int(_input) else: raise ValueError except ValueError: From 093da73dd12a5e209a99043cd1ce64ef1f6157bf Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 2 Nov 2023 22:06:59 +0100 Subject: [PATCH 010/137] refactor(klipper): use constants for commonly used strings Signed-off-by: Dominik Willner --- kiauh/modules/klipper/__init__.py | 19 +++++++++++++++++++ kiauh/modules/klipper/klipper.py | 3 ++- kiauh/modules/klipper/klipper_setup.py | 21 ++++++++++++--------- kiauh/utils/constants.py | 11 ----------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/modules/klipper/__init__.py index e69de29..a9b4efc 100644 --- a/kiauh/modules/klipper/__init__.py +++ b/kiauh/modules/klipper/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from pathlib import Path + +KLIPPER_DIR = f"{Path.home()}/klipper" +KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env" +KLIPPER_REQUIREMENTS_TXT = f"{KLIPPER_DIR}/scripts/klippy-requirements.txt" +DEFAULT_KLIPPER_REPO_URL = "https://github.com/Klipper3D/klipper" + +EXIT_KLIPPER_SETUP = "Exiting Klipper setup ..." diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 5c187e2..01c92a0 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -16,7 +16,8 @@ from pathlib import Path from typing import List from kiauh.instance_manager.base_instance import BaseInstance -from kiauh.utils.constants import CURRENT_USER, SYSTEMD, KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.utils.constants import CURRENT_USER, SYSTEMD from kiauh.utils.logger import Logger from kiauh.utils.system_utils import create_directory diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 5c41c40..b9d6b43 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -16,6 +16,13 @@ from typing import List, Union from kiauh.config_manager.config_manager import ConfigManager from kiauh.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper import ( + EXIT_KLIPPER_SETUP, + DEFAULT_KLIPPER_REPO_URL, + KLIPPER_DIR, + KLIPPER_ENV_DIR, + KLIPPER_REQUIREMENTS_TXT, +) from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( print_instance_overview, @@ -30,7 +37,6 @@ from kiauh.modules.klipper.klipper_utils import ( handle_single_to_multi_conversion, ) from kiauh.repo_manager.repo_manager import RepoManager -from kiauh.utils.constants import KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.input_utils import ( get_confirm, get_number_input, @@ -59,7 +65,7 @@ def run_klipper_setup(install: bool) -> None: if install: add_additional = handle_existing_instances(instance_manager) if is_klipper_installed and not add_additional: - Logger.print_info("Exiting Klipper setup ...") + Logger.print_info(EXIT_KLIPPER_SETUP) return install_klipper(instance_manager) @@ -100,12 +106,12 @@ def install_klipper(instance_manager: InstanceManager) -> None: question = f"Number of{' additional' if len(instance_list) > 0 else ''} Klipper instances to set up" install_count = get_number_input(question, 1, default=1, allow_go_back=True) if install_count is None: - Logger.print_info("Exiting Klipper setup ...") + Logger.print_info(EXIT_KLIPPER_SETUP) return instance_names = set_instance_names(instance_list, install_count) if instance_names is None: - Logger.print_info("Exiting Klipper setup ...") + Logger.print_info(EXIT_KLIPPER_SETUP) return if len(instance_list) < 1: @@ -143,10 +149,7 @@ def setup_klipper_prerequesites() -> None: cm = ConfigManager() cm.read_config() - repo = str( - cm.get_value("klipper", "repository_url") - or "https://github.com/Klipper3D/klipper" - ) + repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL) branch = str(cm.get_value("klipper", "branch") or "master") repo_manager = RepoManager( @@ -159,7 +162,7 @@ def setup_klipper_prerequesites() -> None: # install klipper dependencies and create python virtualenv install_klipper_packages(Path(KLIPPER_DIR)) create_python_venv(Path(KLIPPER_ENV_DIR)) - klipper_py_req = Path(f"{KLIPPER_DIR}/scripts/klippy-requirements.txt") + klipper_py_req = Path(KLIPPER_REQUIREMENTS_TXT) install_python_requirements(Path(KLIPPER_ENV_DIR), klipper_py_req) diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index f795697..60662ce 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -12,8 +12,6 @@ import os import pwd -from pathlib import Path - # text colors and formats COLOR_MAGENTA = "\033[35m" # magenta COLOR_GREEN = "\033[92m" # bright green @@ -21,15 +19,6 @@ COLOR_YELLOW = "\033[93m" # bright yellow COLOR_RED = "\033[91m" # bright red COLOR_CYAN = "\033[96m" # bright cyan RESET_FORMAT = "\033[0m" # reset format - # current user CURRENT_USER = pwd.getpwuid(os.getuid())[0] - SYSTEMD = "/etc/systemd/system" - -KLIPPER_DIR = f"{Path.home()}/klipper" -KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env" -MOONRAKER_DIR = f"{Path.home()}/moonraker" -MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env" -MAINSAIL_DIR = f"{Path.home()}/mainsail" -FLUIDD_DIR = f"{Path.home()}/fluidd" From fb09acf660cd93a9e30732ceefd04721f8ba7b1d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 2 Nov 2023 22:52:34 +0100 Subject: [PATCH 011/137] refactor(utils): reduce complexity Signed-off-by: Dominik Willner --- kiauh/utils/__init__.py | 12 +++++++ kiauh/utils/input_utils.py | 66 +++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index e69de29..3c3f6a5 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +INVALID_CHOICE = "Invalid choice. Please select a valid value." diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index 5ab04c5..ba27ec9 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -11,6 +11,7 @@ from typing import Optional, List, Union +from kiauh.utils import INVALID_CHOICE from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT from kiauh.utils.logger import Logger @@ -31,9 +32,7 @@ def get_confirm( while True: choice = ( - input(f"{COLOR_CYAN}###### {question} {def_choice}: {RESET_FORMAT}") - .strip() - .lower() + input(format_question(question + f" {def_choice}", None)).strip().lower() ) if choice in options_confirm: @@ -43,54 +42,63 @@ def get_confirm( elif allow_go_back and choice in options_go_back: return None else: - Logger.print_error("Invalid choice. Please select 'y' or 'n'.") + Logger.print_error(INVALID_CHOICE) def get_number_input( question: str, min_count: int, max_count=None, default=None, allow_go_back=False ) -> Union[int, None]: options_go_back = ["b", "B"] - _question = question + f" (default={default})" if default else question - _question = f"{COLOR_CYAN}###### {_question}: {RESET_FORMAT}" + _question = format_question(question, default) while True: + _input = input(_question) + if allow_go_back and _input in options_go_back: + return None + + if _input == "": + return default + try: - _input = input(_question) - if allow_go_back and _input in options_go_back: - return None - - if _input == "": - return default - - if max_count is not None: - if min_count <= int(_input) <= max_count: - return int(_input) - else: - raise ValueError - elif int(_input) >= min_count: - return int(_input) - else: - raise ValueError + return validate_number_input(_input, min_count, max_count) except ValueError: - Logger.print_error("Invalid choice. Please select a valid number.") + Logger.print_error(INVALID_CHOICE) -def get_string_input(question: str, exclude=Optional[List]) -> str: +def get_string_input(question: str, exclude=Optional[List], default=None) -> str: while True: - _input = input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}").strip() + _input = input(format_question(question, default)).strip() if _input.isalnum() and _input not in exclude: return _input - Logger.print_error("Invalid choice. Please enter a valid value.") + Logger.print_error(INVALID_CHOICE) if _input in exclude: Logger.print_error("This value is already in use/reserved.") -def get_selection_input(question: str, option_list: List) -> str: +def get_selection_input(question: str, option_list: List, default=None) -> str: while True: - _input = input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}").strip() + _input = input(format_question(question, default)).strip() if _input in option_list: return _input - Logger.print_error("Invalid choice. Please enter a valid value.") + Logger.print_error(INVALID_CHOICE) + + +def format_question(question: str, default=None) -> str: + formatted_q = question + if default is not None: + formatted_q += f" (default={default})" + + return f"{COLOR_CYAN}###### {formatted_q}: {RESET_FORMAT}" + + +def validate_number_input(value: str, min_count: int, max_count: int) -> int: + if max_count is not None: + if min_count <= int(value) <= max_count: + return int(value) + elif int(value) >= min_count: + return int(value) + + raise ValueError From f9ecad0eca3977856d8b15ab530414ecb776514e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 5 Nov 2023 16:01:09 +0100 Subject: [PATCH 012/137] refactor(klipper): use name "klipper" for single instance setup Signed-off-by: Dominik Willner --- kiauh/instance_manager/instance_manager.py | 15 +++++++-------- kiauh/modules/klipper/klipper.py | 6 +++--- kiauh/modules/klipper/klipper_setup.py | 8 ++++++-- kiauh/utils/input_utils.py | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index b1924b1..f08f43e 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -39,9 +39,7 @@ class InstanceManager: def set_current_instance(self, instance: BaseInstance) -> None: self.current_instance = instance - self.instance_name = ( - f"{instance.prefix}-{instance.name}" if instance.name else instance.prefix - ) + self.instance_name = instance.name def create_instance(self) -> None: if self.current_instance is not None: @@ -123,13 +121,16 @@ class InstanceManager: def _find_instances(self) -> None: prefix = self.instance_type.__name__.lower() - pattern = re.compile(f"{prefix}(-[0-9a-zA-Z]+)?.service") + single_pattern = re.compile(f"^{prefix}.service$") + multi_pattern = re.compile(f"^{prefix}(-[0-9a-zA-Z]+)?.service$") excluded = self.instance_type.blacklist() service_list = [ os.path.join(SYSTEMD, service) for service in os.listdir(SYSTEMD) - if pattern.search(service) and not any(s in service for s in excluded) + if multi_pattern.search(service) + and not any(s in service for s in excluded) + or single_pattern.search(service) ] instance_list = [ @@ -141,10 +142,8 @@ class InstanceManager: def _get_instance_name(self, file_path: Path) -> Union[str, None]: full_name = str(file_path).split("/")[-1].split(".")[0] - if full_name.isalnum(): - return None - return full_name.split("-")[-1] + return full_name.split("-")[-1] if "-" in full_name else full_name def _sort_instance_list(self, s): if s is None: diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 01c92a0..326269f 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -26,7 +26,7 @@ from kiauh.utils.system_utils import create_directory class Klipper(BaseInstance): @classmethod def blacklist(cls) -> List[str]: - return ["None", "mcu"] + return ["None", "klipper", "mcu"] def __init__(self, name: str): super().__init__( @@ -117,7 +117,7 @@ class Klipper(BaseInstance): Logger.print_ok(f"Env file created: {env_file_target}") def get_service_file_name(self, extension=False) -> str: - name = self.prefix if self.name is None else self.prefix + "-" + self.name + name = "klipper" if self.name == self.prefix else self.prefix + "-" + self.name return name if not extension else f"{name}.service" def _get_service_file_path(self): @@ -138,7 +138,7 @@ class Klipper(BaseInstance): Logger.print_ok("Directories successfully deleted.") def _get_data_dir_from_name(self, name: str) -> str: - if name is None: + if name == "klipper": return "printer" elif int(name.isdigit()): return f"printer_{name}" diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index b9d6b43..7b343b8 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -185,10 +185,14 @@ def set_instance_names(instance_list, install_count: int) -> List[Union[str, Non # new single instance install if instance_count == 0 and install_count == 1: - return [None] + return ["klipper"] # convert single instance install to multi install - elif instance_count == 1 and instance_list[0].name is None and install_count >= 1: + elif ( + instance_count == 1 + and instance_list[0].name == "klipper" + and install_count >= 1 + ): return handle_convert_single_to_multi_instance_names(install_count) # new multi instance install diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index ba27ec9..280088d 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -68,7 +68,7 @@ def get_string_input(question: str, exclude=Optional[List], default=None) -> str while True: _input = input(format_question(question, default)).strip() - if _input.isalnum() and _input not in exclude: + if _input.isalnum() and _input.lower() not in exclude: return _input Logger.print_error(INVALID_CHOICE) From 515a42f0981009217ef581fdda8027cca221e6ca Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 5 Nov 2023 16:15:19 +0100 Subject: [PATCH 013/137] feat(klipper): implement update function Signed-off-by: Dominik Willner --- kiauh/instance_manager/instance_manager.py | 10 +++++++++ kiauh/menus/update_menu.py | 3 ++- kiauh/modules/klipper/klipper_dialogs.py | 17 ++++++++++++++ kiauh/modules/klipper/klipper_setup.py | 26 ++++++++++++++++++++++ kiauh/repo_manager/repo_manager.py | 18 +++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index f08f43e..5a775c1 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -91,6 +91,11 @@ class InstanceManager: Logger.print_error(f"Error starting service {self.instance_name}.service:") Logger.print_error(f"{e}") + def start_all_instance(self) -> None: + for instance in self.instances: + self.set_current_instance(instance) + self.start_instance() + def stop_instance(self) -> None: Logger.print_info(f"Stopping {self.instance_name}.service ...") try: @@ -102,6 +107,11 @@ class InstanceManager: Logger.print_error(f"{e}") raise + def stop_all_instance(self) -> None: + for instance in self.instances: + self.set_current_instance(instance) + self.stop_instance() + def reload_daemon(self) -> None: Logger.print_info("Reloading systemd manager configuration ...") try: diff --git a/kiauh/menus/update_menu.py b/kiauh/menus/update_menu.py index 0e9a78a..f617fe9 100644 --- a/kiauh/menus/update_menu.py +++ b/kiauh/menus/update_menu.py @@ -12,6 +12,7 @@ import textwrap from kiauh.menus.base_menu import BaseMenu +from kiauh.modules.klipper.klipper_setup import update_klipper from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT @@ -74,7 +75,7 @@ class UpdateMenu(BaseMenu): print("update_all") def update_klipper(self): - print("update_klipper") + update_klipper() def update_moonraker(self): print("update_moonraker") diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py index 204ea43..cc8d15c 100644 --- a/kiauh/modules/klipper/klipper_dialogs.py +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -96,3 +96,20 @@ def print_missing_usergroup_dialog(missing_groups) -> None: f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |" ) print("\\=======================================================/") + + +def print_update_warn_dialog(): + print("/=======================================================\\") + print( + f"| {COLOR_YELLOW}WARNING: {RESET_FORMAT}|" + ) + print( + f"| {COLOR_YELLOW}Do NOT continue if there are ongoing prints running! {RESET_FORMAT}|" + ) + print( + f"| {COLOR_YELLOW}All Klipper instances will be restarted during the {RESET_FORMAT}|" + ) + print( + f"| {COLOR_YELLOW}update process and any ongoing prints WILL FAIL. {RESET_FORMAT}|" + ) + print("\\=======================================================/") diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 7b343b8..d38b89f 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -27,6 +27,7 @@ from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( print_instance_overview, print_select_instance_count_dialog, + print_update_warn_dialog, ) from kiauh.modules.klipper.klipper_utils import ( handle_convert_single_to_multi_instance_names, @@ -249,3 +250,28 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: instance_manager.delete_instance(del_remnants=False) instance_manager.reload_daemon() + + +def update_klipper() -> None: + print_update_warn_dialog() + + if not get_confirm("Update Klipper now?"): + return + + instance_manager = InstanceManager(Klipper) + instance_manager.get_instances() + instance_manager.stop_all_instance() + + cm = ConfigManager() + cm.read_config() + + repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL) + branch = str(cm.get_value("klipper", "branch") or "master") + + repo_manager = RepoManager( + repo=repo, + branch=branch, + target_dir=KLIPPER_DIR, + ) + repo_manager.pull_repo() + instance_manager.start_all_instance() diff --git a/kiauh/repo_manager/repo_manager.py b/kiauh/repo_manager/repo_manager.py index b1501d2..4555920 100644 --- a/kiauh/repo_manager/repo_manager.py +++ b/kiauh/repo_manager/repo_manager.py @@ -77,6 +77,15 @@ class RepoManager: Logger.print_error(f"Error removing existing repository: {e.strerror}") return + def pull_repo(self) -> None: + Logger.print_info(f"Updating repository '{self.repo}' ...") + try: + self._pull() + except subprocess.CalledProcessError: + log = "An unexpected error occured during updating the repository." + Logger.print_error(log) + return + def _clone(self): try: command = ["git", "clone", self.repo, self.target_dir] @@ -99,5 +108,14 @@ class RepoManager: Logger.print_error(log) raise + def _pull(self) -> None: + try: + command = ["git", "pull"] + subprocess.run(command, cwd=self.target_dir, check=True) + except subprocess.CalledProcessError as e: + log = f"Error on git pull: {e.stderr.decode()}" + Logger.print_error(log) + raise + def _get_method(self) -> str: return "ssh" if self.repo.startswith("git") else "https" From e12e578098ef2b7612118c68ba79bb7390ec8b9a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 5 Nov 2023 22:56:56 +0100 Subject: [PATCH 014/137] refactor(klipper): rewrite dialogs Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_dialogs.py | 163 +++++++++++++---------- 1 file changed, 92 insertions(+), 71 deletions(-) diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py index cc8d15c..9bb46f1 100644 --- a/kiauh/modules/klipper/klipper_dialogs.py +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -9,6 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import textwrap from typing import List from kiauh.instance_manager.base_instance import BaseInstance @@ -20,96 +21,116 @@ def print_instance_overview( instances: List[BaseInstance], show_index=False, show_select_all=False ): headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}" - - print("/=======================================================\\") - print(f"|{'{:^64}'.format(headline)}|") - print("|-------------------------------------------------------|") + dialog = textwrap.dedent( + f""" + /=======================================================\\ + |{'{:^64}'.format(headline)}| + |-------------------------------------------------------| + """ + )[1:] if show_select_all: - select_all = f" {COLOR_YELLOW}a) Select all{RESET_FORMAT}" - print(f"|{'{:64}'.format(select_all)}|") - print("| |") + select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}" + dialog += f"| {'{:63}'.format(select_all)}|" + dialog += "| |" for i, s in enumerate(instances): - index = f"{i})" if show_index else "●" - instance = s.get_service_file_name() - line = f"{'{:53}'.format(f'{index} {instance}')}" - print(f"| {COLOR_CYAN}{line}{RESET_FORMAT}|") + line = f"{COLOR_CYAN}{'{i})' if show_index else '●'} {s.get_service_file_name()}{RESET_FORMAT}" + dialog += f"| {'{:63}'.format(line)}|" + print(dialog) print_back_footer() def print_select_instance_count_dialog(): - print("/=======================================================\\") - print("| Please select the number of Klipper instances to set |") - print("| up. The number of Klipper instances will determine |") - print("| the amount of printers you can run from this host. |") - print("| |") - print( - f"| {COLOR_YELLOW}WARNING:{RESET_FORMAT} |" - ) - print( - f"| {COLOR_YELLOW}Setting up too many instances may crash your system.{RESET_FORMAT} |" - ) + line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}Setting up too many instances may crash your system.{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | Please select the number of Klipper instances to set | + | up. The number of Klipper instances will determine | + | the amount of printers you can run from this host. | + | | + | {'{:63}'.format(line1)}| + | {'{:63}'.format(line2)}| + """ + )[1:] + + print(dialog, end="") print_back_footer() def print_select_custom_name_dialog(): - print("/=======================================================\\") - print("| You can now assign a custom name to each instance. |") - print("| If skipped, each instance will get an index assigned |") - print("| in ascending order, starting at index '1'. |") - print("| |") - print( - f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |" - ) - print( - f"| {COLOR_YELLOW}Only alphanumeric characters are allowed!{RESET_FORMAT} |" - ) + line1 = f"{COLOR_YELLOW}INFO:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}Only alphanumeric characters are allowed!{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | You can now assign a custom name to each instance. | + | If skipped, each instance will get an index assigned | + | in ascending order, starting at index '1'. | + | | + | {'{:63}'.format(line1)}| + | {'{:63}'.format(line2)}| + """ + )[1:] + + print(dialog, end="") print_back_footer() def print_missing_usergroup_dialog(missing_groups) -> None: - print("/=======================================================\\") - print( - f"| {COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT} |" - ) + line1 = f"{COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT}" + line2 = f"{COLOR_CYAN}● tty{RESET_FORMAT}" + line3 = f"{COLOR_CYAN}● dialout{RESET_FORMAT}" + line4 = f"{COLOR_YELLOW}INFO:{RESET_FORMAT}" + line5 = f"{COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT}" + + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | {'{:63}'.format(line1)}| + """ + )[1:] + if "tty" in missing_groups: - print( - f"| {COLOR_CYAN}● tty{RESET_FORMAT} |" - ) + dialog += f"| {'{:63}'.format(line2)}|\n" if "dialout" in missing_groups: - print( - f"| {COLOR_CYAN}● dialout{RESET_FORMAT} |" - ) - print("| |") - print("| It is possible that you won't be able to successfully |") - print("| connect and/or flash the controller board without |") - print("| your user being a member of that group. |") - print("| If you want to add the current user to the group(s) |") - print("| listed above, answer with 'Y'. Else skip with 'n'. |") - print("| |") - print( - f"| {COLOR_YELLOW}INFO:{RESET_FORMAT} |" - ) - print( - f"| {COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT} |" - ) - print("\\=======================================================/") + dialog += f"| {'{:63}'.format(line3)}|\n" + + dialog += textwrap.dedent( + f""" + | | + | It is possible that you won't be able to successfully | + | connect and/or flash the controller board without | + | your user being a member of that group. | + | If you want to add the current user to the group(s) | + | listed above, answer with 'Y'. Else skip with 'n'. | + | | + | {'{:63}'.format(line4)}| + | {'{:63}'.format(line5)}| + \\=======================================================/ + """ + )[1:] + + print(dialog, end="") def print_update_warn_dialog(): - print("/=======================================================\\") - print( - f"| {COLOR_YELLOW}WARNING: {RESET_FORMAT}|" - ) - print( - f"| {COLOR_YELLOW}Do NOT continue if there are ongoing prints running! {RESET_FORMAT}|" - ) - print( - f"| {COLOR_YELLOW}All Klipper instances will be restarted during the {RESET_FORMAT}|" - ) - print( - f"| {COLOR_YELLOW}update process and any ongoing prints WILL FAIL. {RESET_FORMAT}|" - ) - print("\\=======================================================/") + line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}Do NOT continue if there are ongoing prints running!{RESET_FORMAT}" + line3 = f"{COLOR_YELLOW}All Klipper instances will be restarted during the {RESET_FORMAT}" + line4 = f"{COLOR_YELLOW}update process and ongoing prints WILL FAIL.{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | {'{:63}'.format(line1)}| + | {'{:63}'.format(line2)}| + | {'{:63}'.format(line3)}| + | {'{:63}'.format(line4)}| + \\=======================================================/ + """ + )[1:] + + print(dialog, end="") From 44ed3b6ddf8a83a898a8574ae37966c26af66220 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 11 Nov 2023 23:53:42 +0100 Subject: [PATCH 015/137] feat(kiauh): add .iml to gitignore Signed-off-by: Dominik Willner --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8105baa..0da0757 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .pytest_cache .kiauh-env *.code-workspace +*.iml kiauh.cfg From 68369753fdb51f71e0cadea988ba5f10f33b811f Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 11 Nov 2023 23:56:27 +0100 Subject: [PATCH 016/137] refactor(InstanceManager): rework Signed-off-by: Dominik Willner --- kiauh/instance_manager/base_instance.py | 116 +++++++++++----- kiauh/instance_manager/instance_manager.py | 154 +++++++++++++-------- kiauh/modules/klipper/klipper.py | 32 +---- kiauh/modules/klipper/klipper_setup.py | 70 ++++------ kiauh/modules/klipper/klipper_utils.py | 18 +-- 5 files changed, 227 insertions(+), 163 deletions(-) diff --git a/kiauh/instance_manager/base_instance.py b/kiauh/instance_manager/base_instance.py index 3dcda97..df7e92f 100644 --- a/kiauh/instance_manager/base_instance.py +++ b/kiauh/instance_manager/base_instance.py @@ -11,7 +11,11 @@ from abc import abstractmethod, ABC from pathlib import Path -from typing import List, Optional +from typing import List, Union, Optional, Type, TypeVar + +from kiauh.utils.constants import SYSTEMD, CURRENT_USER + +B = TypeVar(name="B", bound="BaseInstance", covariant=True) class BaseInstance(ABC): @@ -21,43 +25,41 @@ class BaseInstance(ABC): def __init__( self, - prefix: Optional[str], - name: Optional[str], - user: Optional[str], - data_dir_name: Optional[str], + suffix: Optional[str], + instance_type: B = B, ): - self._prefix = prefix - self._name = name - self._user = user - self._data_dir_name = data_dir_name - self.data_dir = f"{Path.home()}/{self._data_dir_name}_data" - self.cfg_dir = f"{self.data_dir}/config" - self.log_dir = f"{self.data_dir}/logs" - self.comms_dir = f"{self.data_dir}/comms" - self.sysd_dir = f"{self.data_dir}/systemd" + self._instance_type = instance_type + self._suffix = suffix + self._user = CURRENT_USER + self._data_dir_name = self.get_data_dir_from_suffix() + self._data_dir = f"{Path.home()}/{self._data_dir_name}_data" + self._cfg_dir = f"{self.data_dir}/config" + self._log_dir = f"{self.data_dir}/logs" + self._comms_dir = f"{self.data_dir}/comms" + self._sysd_dir = f"{self.data_dir}/systemd" @property - def prefix(self) -> str: - return self._prefix + def instance_type(self) -> Type["BaseInstance"]: + return self._instance_type - @prefix.setter - def prefix(self, value) -> None: - self._prefix = value + @instance_type.setter + def instance_type(self, value: Type["BaseInstance"]) -> None: + self._instance_type = value @property - def name(self) -> str: - return self._name + def suffix(self) -> str: + return self._suffix - @name.setter - def name(self, value) -> None: - self._name = value + @suffix.setter + def suffix(self, value: Union[str, None]) -> None: + self._suffix = value @property def user(self) -> str: return self._user @user.setter - def user(self, value) -> None: + def user(self, value: str) -> None: self._user = value @property @@ -65,9 +67,49 @@ class BaseInstance(ABC): return self._data_dir_name @data_dir_name.setter - def data_dir_name(self, value) -> None: + def data_dir_name(self, value: str) -> None: self._data_dir_name = value + @property + def data_dir(self): + return self._data_dir + + @data_dir.setter + def data_dir(self, value: str): + self._data_dir = value + + @property + def cfg_dir(self): + return self._cfg_dir + + @cfg_dir.setter + def cfg_dir(self, value: str): + self._cfg_dir = value + + @property + def log_dir(self): + return self._log_dir + + @log_dir.setter + def log_dir(self, value: str): + self._log_dir = value + + @property + def comms_dir(self): + return self._comms_dir + + @comms_dir.setter + def comms_dir(self, value: str): + self._comms_dir = value + + @property + def sysd_dir(self): + return self._sysd_dir + + @sysd_dir.setter + def sysd_dir(self, value: str): + self._sysd_dir = value + @abstractmethod def create(self) -> None: raise NotImplementedError("Subclasses must implement the create method") @@ -76,8 +118,20 @@ class BaseInstance(ABC): def delete(self, del_remnants: bool) -> None: raise NotImplementedError("Subclasses must implement the delete method") - @abstractmethod - def get_service_file_name(self) -> str: - raise NotImplementedError( - "Subclasses must implement the get_service_file_name method" - ) + def get_service_file_name(self, extension: bool = False) -> str: + name = f"{self.__class__.__name__.lower()}" + if self.suffix is not None: + name += f"-{self.suffix}" + + return name if not extension else f"{name}.service" + + def get_service_file_path(self) -> str: + return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}" + + def get_data_dir_from_suffix(self) -> str: + if self._suffix is None: + return "printer" + elif self._suffix.isdigit(): + return f"printer_{self._suffix}" + else: + return self._suffix diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index 5a775c1..56b886b 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -12,34 +12,85 @@ import os import re import subprocess -from pathlib import Path -from typing import Optional, List, Type, Union +from typing import List, Optional, Union, TypeVar from kiauh.instance_manager.base_instance import BaseInstance from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger +I = TypeVar(name="I", bound=BaseInstance, covariant=True) + + # noinspection PyMethodMayBeStatic class InstanceManager: - def __init__( - self, - instance_type: Type[BaseInstance], - current_instance: Optional[BaseInstance] = None, - ) -> None: - self.instance_type = instance_type - self.current_instance = current_instance - self.instance_name = ( - current_instance.name if current_instance is not None else None - ) - self.instances = [] + def __init__(self, instance_type: I) -> None: + self._instance_type = instance_type + self._current_instance: Optional[I] = None + self._instance_suffix: Optional[str] = None + self._instance_service: Optional[str] = None + self._instance_service_path: Optional[str] = None + self._instances: List[I] = [] - def get_current_instance(self) -> BaseInstance: - return self.current_instance + @property + def instance_type(self) -> I: + return self._instance_type - def set_current_instance(self, instance: BaseInstance) -> None: - self.current_instance = instance - self.instance_name = instance.name + @instance_type.setter + def instance_type(self, value: I): + self._instance_type = value + + @property + def current_instance(self) -> I: + return self._current_instance + + @current_instance.setter + def current_instance(self, value: I) -> None: + self._current_instance = value + self.instance_suffix = value.suffix + self.instance_service = value.get_service_file_path() + self.instance_service_path = value.get_service_file_path() + + @property + def instance_suffix(self) -> str: + return self._instance_suffix + + @instance_suffix.setter + def instance_suffix(self, value: str): + self._instance_suffix = value + + @property + def instance_service(self) -> str: + return self._instance_service + + @instance_service.setter + def instance_service(self, value: str): + self._instance_service = value + + @property + def instance_service_path(self) -> str: + return self._instance_service_path + + @instance_service_path.setter + def instance_service_path(self, value: str): + self._instance_service_path = value + + @property + def instances(self) -> List[I]: + print("instances getter called") + if not self._instances: + print("instances none") + self._instances = self._find_instances() + + print("return instances") + print(self._instances) + for instance in self._instances: + print(type(instance), instance.suffix) + return sorted(self._instances, key=lambda x: self._sort_instance_list(x.suffix)) + + @instances.setter + def instances(self, value: List[I]): + self._instances = value def create_instance(self) -> None: if self.current_instance is not None: @@ -62,54 +113,56 @@ class InstanceManager: raise ValueError("current_instance cannot be None") def enable_instance(self) -> None: - Logger.print_info(f"Enabling {self.instance_name}.service ...") + Logger.print_info(f"Enabling {self.instance_service} ...") try: - command = ["sudo", "systemctl", "enable", f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "enable", self.instance_service] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_name}.service enabled.") + Logger.print_ok(f"{self.instance_suffix}.service enabled.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error enabling service {self.instance_name}.service:") + Logger.print_error( + f"Error enabling service {self.instance_suffix}.service:" + ) Logger.print_error(f"{e}") def disable_instance(self) -> None: - Logger.print_info(f"Disabling {self.instance_name}.service ...") + Logger.print_info(f"Disabling {self.instance_service} ...") try: - command = ["sudo", "systemctl", "disable", f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "disable", self.instance_service] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_name}.service disabled.") + Logger.print_ok(f"{self.instance_service} disabled.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error disabling service {self.instance_name}.service:") + Logger.print_error(f"Error disabling service {self.instance_service}:") Logger.print_error(f"{e}") def start_instance(self) -> None: - Logger.print_info(f"Starting {self.instance_name}.service ...") + Logger.print_info(f"Starting {self.instance_service} ...") try: - command = ["sudo", "systemctl", "start", f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "start", self.instance_service] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_name}.service started.") + Logger.print_ok(f"{self.instance_service} started.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error starting service {self.instance_name}.service:") + Logger.print_error(f"Error starting service {self.instance_service}:") Logger.print_error(f"{e}") def start_all_instance(self) -> None: for instance in self.instances: - self.set_current_instance(instance) + self.current_instance = instance self.start_instance() def stop_instance(self) -> None: - Logger.print_info(f"Stopping {self.instance_name}.service ...") + Logger.print_info(f"Stopping {self.instance_service} ...") try: - command = ["sudo", "systemctl", "stop", f"{self.instance_name}.service"] + command = ["sudo", "systemctl", "stop", self.instance_service] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_name}.service stopped.") + Logger.print_ok(f"{self.instance_service} stopped.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error stopping service {self.instance_name}.service:") + Logger.print_error(f"Error stopping service {self.instance_service}:") Logger.print_error(f"{e}") raise def stop_all_instance(self) -> None: for instance in self.instances: - self.set_current_instance(instance) + self.current_instance = instance self.stop_instance() def reload_daemon(self) -> None: @@ -123,39 +176,30 @@ class InstanceManager: Logger.print_error(f"{e}") raise - def get_instances(self) -> List[BaseInstance]: - if not self.instances: - self._find_instances() - - return sorted(self.instances, key=lambda x: self._sort_instance_list(x.name)) - - def _find_instances(self) -> None: - prefix = self.instance_type.__name__.lower() - single_pattern = re.compile(f"^{prefix}.service$") - multi_pattern = re.compile(f"^{prefix}(-[0-9a-zA-Z]+)?.service$") - + 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() + service_list = [ os.path.join(SYSTEMD, service) for service in os.listdir(SYSTEMD) - if multi_pattern.search(service) - and not any(s in service for s in excluded) - or single_pattern.search(service) + if pattern.search(service) and not any(s in service for s in excluded) ] instance_list = [ - self.instance_type(name=self._get_instance_name(Path(service))) + self.instance_type(suffix=self._get_instance_suffix(service)) for service in service_list ] - self.instances = instance_list + return instance_list - def _get_instance_name(self, file_path: Path) -> Union[str, None]: - full_name = str(file_path).split("/")[-1].split(".")[0] + def _get_instance_suffix(self, file_path: str) -> Union[str, None]: + full_name = file_path.split("/")[-1].split(".")[0] return full_name.split("-")[-1] if "-" in full_name else full_name - def _sort_instance_list(self, s): + def _sort_instance_list(self, s: Union[int, str, None]): if s is None: return diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 326269f..33c4ec3 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -17,7 +17,7 @@ from typing import List from kiauh.instance_manager.base_instance import BaseInstance from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR -from kiauh.utils.constants import CURRENT_USER, SYSTEMD +from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger from kiauh.utils.system_utils import create_directory @@ -26,15 +26,10 @@ from kiauh.utils.system_utils import create_directory class Klipper(BaseInstance): @classmethod def blacklist(cls) -> List[str]: - return ["None", "klipper", "mcu"] + return ["None", "mcu"] - def __init__(self, name: str): - super().__init__( - name=name, - prefix="klipper", - user=CURRENT_USER, - data_dir_name=self._get_data_dir_from_name(name), - ) + def __init__(self, suffix: str = None): + super().__init__(instance_type=self, suffix=suffix) self.klipper_dir = KLIPPER_DIR self.env_dir = KLIPPER_ENV_DIR self.cfg_file = f"{self.cfg_dir}/printer.cfg" @@ -43,7 +38,7 @@ class Klipper(BaseInstance): self.uds = f"{self.comms_dir}/klippy.sock" def create(self) -> None: - Logger.print_info("Creating Klipper Instance") + Logger.print_info("Creating new Klipper Instance ...") module_path = os.path.dirname(os.path.abspath(__file__)) service_template_path = os.path.join(module_path, "res", "klipper.service") env_template_file_path = os.path.join(module_path, "res", "klipper.env") @@ -69,7 +64,7 @@ class Klipper(BaseInstance): def delete(self, del_remnants: bool) -> None: service_file = self.get_service_file_name(extension=True) - service_file_path = self._get_service_file_path() + service_file_path = self.get_service_file_path() Logger.print_info(f"Deleting Klipper Instance: {service_file}") @@ -116,13 +111,6 @@ class Klipper(BaseInstance): env_file.write(env_file_content) Logger.print_ok(f"Env file created: {env_file_target}") - def get_service_file_name(self, extension=False) -> str: - name = "klipper" if self.name == self.prefix else self.prefix + "-" + self.name - return name if not extension else f"{name}.service" - - def _get_service_file_path(self): - return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}" - def _delete_klipper_remnants(self) -> None: try: Logger.print_info(f"Delete {self.klipper_dir} ...") @@ -137,14 +125,6 @@ class Klipper(BaseInstance): Logger.print_ok("Directories successfully deleted.") - def _get_data_dir_from_name(self, name: str) -> str: - if name == "klipper": - return "printer" - elif int(name.isdigit()): - return f"printer_{name}" - else: - return name - def _prep_service_file(self, service_template_path, env_file_path): try: with open(service_template_path, "r") as template_file: diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index d38b89f..e19fb99 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -55,41 +55,30 @@ from kiauh.utils.system_utils import ( def run_klipper_setup(install: bool) -> None: instance_manager = InstanceManager(Klipper) - instance_list = instance_manager.get_instances() + instance_list = instance_manager.instances instances_installed = len(instance_list) - is_klipper_installed = check_klipper_installation(instance_manager) + is_klipper_installed = instances_installed > 0 if not install and not is_klipper_installed: Logger.print_warn("Klipper not installed!") return if install: - add_additional = handle_existing_instances(instance_manager) + add_additional = handle_existing_instances(instance_list) if is_klipper_installed and not add_additional: Logger.print_info(EXIT_KLIPPER_SETUP) return - install_klipper(instance_manager) + install_klipper(instance_manager, instance_list) if not install: if instances_installed == 1: - remove_single_instance(instance_manager) + remove_single_instance(instance_manager, instance_list) else: - remove_multi_instance(instance_manager) + remove_multi_instance(instance_manager, instance_list) -def check_klipper_installation(instance_manager: InstanceManager) -> bool: - instance_list = instance_manager.get_instances() - instances_installed = len(instance_list) - - if instances_installed < 1: - return False - - return True - - -def handle_existing_instances(instance_manager: InstanceManager) -> bool: - instance_list = instance_manager.get_instances() +def handle_existing_instances(instance_list: List[Klipper]) -> bool: instance_count = len(instance_list) if instance_count > 0: @@ -100,9 +89,9 @@ def handle_existing_instances(instance_manager: InstanceManager) -> bool: return True -def install_klipper(instance_manager: InstanceManager) -> None: - instance_list = instance_manager.get_instances() - +def install_klipper( + instance_manager: InstanceManager, instance_list: List[Klipper] +) -> None: print_select_instance_count_dialog() question = f"Number of{' additional' if len(instance_list) > 0 else ''} Klipper instances to set up" install_count = get_number_input(question, 1, default=1, allow_go_back=True) @@ -110,7 +99,7 @@ def install_klipper(instance_manager: InstanceManager) -> None: Logger.print_info(EXIT_KLIPPER_SETUP) return - instance_names = set_instance_names(instance_list, install_count) + instance_names = set_instance_suffix(instance_list, install_count) if instance_names is None: Logger.print_info(EXIT_KLIPPER_SETUP) return @@ -119,11 +108,9 @@ def install_klipper(instance_manager: InstanceManager) -> None: setup_klipper_prerequesites() convert_single_to_multi = ( - True - if len(instance_list) == 1 - and instance_list[0].name is None + len(instance_list) == 1 + and instance_list[0].suffix is None and install_count >= 1 - else False ) for name in instance_names: @@ -131,7 +118,7 @@ def install_klipper(instance_manager: InstanceManager) -> None: handle_single_to_multi_conversion(instance_manager, name) convert_single_to_multi = False else: - instance_manager.set_current_instance(Klipper(name=name)) + instance_manager.current_instance = Klipper(suffix=name) instance_manager.create_instance() instance_manager.enable_instance() @@ -181,19 +168,17 @@ def install_klipper_packages(klipper_dir: Path) -> None: install_system_packages(packages) -def set_instance_names(instance_list, install_count: int) -> List[Union[str, None]]: +def set_instance_suffix( + instance_list: List[Klipper], install_count: int +) -> List[Union[str, None]]: instance_count = len(instance_list) # new single instance install if instance_count == 0 and install_count == 1: - return ["klipper"] + return [None] # convert single instance install to multi install - elif ( - instance_count == 1 - and instance_list[0].name == "klipper" - and install_count >= 1 - ): + elif instance_count == 1 and install_count >= 1 and instance_list[0].suffix is None: return handle_convert_single_to_multi_instance_names(install_count) # new multi instance install @@ -207,10 +192,11 @@ def set_instance_names(instance_list, install_count: int) -> List[Union[str, Non ) -def remove_single_instance(instance_manager: InstanceManager) -> None: - instance_list = instance_manager.get_instances() +def remove_single_instance( + instance_manager: InstanceManager, instance_list: List[Klipper] +) -> None: try: - instance_manager.set_current_instance(instance_list[0]) + instance_manager.current_instance = instance_list[0] instance_manager.stop_instance() instance_manager.disable_instance() instance_manager.delete_instance(del_remnants=True) @@ -220,8 +206,9 @@ def remove_single_instance(instance_manager: InstanceManager) -> None: return -def remove_multi_instance(instance_manager: InstanceManager) -> None: - instance_list = instance_manager.get_instances() +def remove_multi_instance( + instance_manager: InstanceManager, instance_list: List[Klipper] +) -> None: print_instance_overview(instance_list, show_index=True, show_select_all=True) options = [str(i) for i in range(len(instance_list))] @@ -235,7 +222,7 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: elif selection == "a".lower(): Logger.print_info("Removing all Klipper instances ...") for instance in instance_list: - instance_manager.set_current_instance(instance) + instance_manager.current_instance = instance instance_manager.stop_instance() instance_manager.disable_instance() instance_manager.delete_instance(del_remnants=True) @@ -244,7 +231,7 @@ def remove_multi_instance(instance_manager: InstanceManager) -> None: Logger.print_info( f"Removing Klipper instance: {instance.get_service_file_name()}" ) - instance_manager.set_current_instance(instance) + instance_manager.current_instance = instance instance_manager.stop_instance() instance_manager.disable_instance() instance_manager.delete_instance(del_remnants=False) @@ -259,7 +246,6 @@ def update_klipper() -> None: return instance_manager = InstanceManager(Klipper) - instance_manager.get_instances() instance_manager.stop_all_instance() cm = ConfigManager() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index e018250..3bb8b04 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -35,10 +35,10 @@ def assign_custom_names( instance_names = [] exclude = Klipper.blacklist() - # if an instance_list is provided, exclude all existing instance names + # if an instance_list is provided, exclude all existing instance suffixes if instance_list is not None: for instance in instance_list: - exclude.append(instance.name) + exclude.append(instance.suffix) for i in range(instance_count + install_count): question = f"Enter name for instance {i + 1}" @@ -93,14 +93,14 @@ def handle_existing_multi_instance_names( def handle_single_to_multi_conversion( instance_manager: InstanceManager, name: str ) -> None: - instance_list = instance_manager.get_instances() - instance_manager.set_current_instance(instance_list[0]) - old_data_dir_name = instance_manager.get_instances()[0].data_dir + instance_list = instance_manager.instances + instance_manager.current_instance = instance_list[0] + old_data_dir_name = instance_manager.instances[0].data_dir instance_manager.stop_instance() instance_manager.disable_instance() instance_manager.delete_instance(del_remnants=False) - instance_manager.set_current_instance(Klipper(name=name)) - new_data_dir_name = instance_manager.get_current_instance().data_dir + instance_manager.current_instance = Klipper(suffix=name) + new_data_dir_name = instance_manager.current_instance.data_dir try: os.rename(old_data_dir_name, new_data_dir_name) except OSError as e: @@ -175,12 +175,12 @@ def handle_disruptive_system_packages() -> None: def has_custom_names(instance_list: List[Klipper]) -> bool: pattern = re.compile("^\d+$") for instance in instance_list: - if not pattern.match(instance.name): + if not pattern.match(instance.suffix): return True return False def get_highest_index(instance_list: List[Klipper]) -> int: - indices = [int(instance.name.split("-")[-1]) for instance in instance_list] + indices = [int(instance.suffix.split("-")[-1]) for instance in instance_list] return max(indices) From 682be48e8d3be5f6379b0a9eb1f23cb8f1a26c3b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 00:01:21 +0100 Subject: [PATCH 017/137] fix(InstanceManager): instance_service should be service file name remove debug prints Signed-off-by: Dominik Willner --- kiauh/instance_manager/instance_manager.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index 56b886b..c2acb14 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -48,7 +48,7 @@ class InstanceManager: def current_instance(self, value: I) -> None: self._current_instance = value self.instance_suffix = value.suffix - self.instance_service = value.get_service_file_path() + self.instance_service = value.get_service_file_name() self.instance_service_path = value.get_service_file_path() @property @@ -77,15 +77,9 @@ class InstanceManager: @property def instances(self) -> List[I]: - print("instances getter called") if not self._instances: - print("instances none") self._instances = self._find_instances() - print("return instances") - print(self._instances) - for instance in self._instances: - print(type(instance), instance.suffix) return sorted(self._instances, key=lambda x: self._sort_instance_list(x.suffix)) @instances.setter From 2148d95cf43f1511fa1efce6e8a20a38998e0ea4 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 00:10:52 +0100 Subject: [PATCH 018/137] fix(InstanceManager): return None for suffix if there is none Signed-off-by: Dominik Willner --- kiauh/instance_manager/instance_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/instance_manager/instance_manager.py index c2acb14..da75ba9 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/instance_manager/instance_manager.py @@ -191,7 +191,7 @@ class InstanceManager: def _get_instance_suffix(self, file_path: str) -> Union[str, None]: full_name = file_path.split("/")[-1].split(".")[0] - return full_name.split("-")[-1] if "-" in full_name else full_name + return full_name.split("-")[-1] if "-" in full_name else None def _sort_instance_list(self, s: Union[int, str, None]): if s is None: From fe8767113b26e2b9ab0af187682dd2fa66085673 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 00:36:37 +0100 Subject: [PATCH 019/137] refactor(klipper): rework dialogs Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_dialogs.py | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py index 9bb46f1..1dacd59 100644 --- a/kiauh/modules/klipper/klipper_dialogs.py +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -24,21 +24,21 @@ def print_instance_overview( dialog = textwrap.dedent( f""" /=======================================================\\ - |{'{:^64}'.format(headline)}| + |{headline:^64}| |-------------------------------------------------------| """ )[1:] if show_select_all: select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}" - dialog += f"| {'{:63}'.format(select_all)}|" - dialog += "| |" + dialog += f"| {select_all:<63}|\n" + dialog += "| |\n" for i, s in enumerate(instances): - line = f"{COLOR_CYAN}{'{i})' if show_index else '●'} {s.get_service_file_name()}{RESET_FORMAT}" - dialog += f"| {'{:63}'.format(line)}|" + line = f"{COLOR_CYAN}{f'{i})' if show_index else '●'} {s.get_service_file_name()}{RESET_FORMAT}" + dialog += f"| {line:<63}|\n" - print(dialog) + print(dialog, end="") print_back_footer() @@ -52,8 +52,8 @@ def print_select_instance_count_dialog(): | up. The number of Klipper instances will determine | | the amount of printers you can run from this host. | | | - | {'{:63}'.format(line1)}| - | {'{:63}'.format(line2)}| + | {line1:<63}| + | {line2:<63}| """ )[1:] @@ -71,8 +71,8 @@ def print_select_custom_name_dialog(): | If skipped, each instance will get an index assigned | | in ascending order, starting at index '1'. | | | - | {'{:63}'.format(line1)}| - | {'{:63}'.format(line2)}| + | {line1:<63}| + | {line2:<63}| """ )[1:] @@ -90,14 +90,14 @@ def print_missing_usergroup_dialog(missing_groups) -> None: dialog = textwrap.dedent( f""" /=======================================================\\ - | {'{:63}'.format(line1)}| + | {line1:<63}| """ )[1:] if "tty" in missing_groups: - dialog += f"| {'{:63}'.format(line2)}|\n" + dialog += f"| {line2:<63}|\n" if "dialout" in missing_groups: - dialog += f"| {'{:63}'.format(line3)}|\n" + dialog += f"| {line3:<63}|\n" dialog += textwrap.dedent( f""" @@ -108,8 +108,8 @@ def print_missing_usergroup_dialog(missing_groups) -> None: | If you want to add the current user to the group(s) | | listed above, answer with 'Y'. Else skip with 'n'. | | | - | {'{:63}'.format(line4)}| - | {'{:63}'.format(line5)}| + | {line4:<63}| + | {line5:<63}| \\=======================================================/ """ )[1:] @@ -117,7 +117,7 @@ def print_missing_usergroup_dialog(missing_groups) -> None: print(dialog, end="") -def print_update_warn_dialog(): +def print_update_warn_dialog() -> None: line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" line2 = f"{COLOR_YELLOW}Do NOT continue if there are ongoing prints running!{RESET_FORMAT}" line3 = f"{COLOR_YELLOW}All Klipper instances will be restarted during the {RESET_FORMAT}" @@ -125,10 +125,10 @@ def print_update_warn_dialog(): dialog = textwrap.dedent( f""" /=======================================================\\ - | {'{:63}'.format(line1)}| - | {'{:63}'.format(line2)}| - | {'{:63}'.format(line3)}| - | {'{:63}'.format(line4)}| + | {line1:<63}| + | {line2:<63}| + | {line3:<63}| + | {line4:<63}| \\=======================================================/ """ )[1:] From eaf12db27ee43c1ccb631c381ef94fef72e546bd Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 00:39:10 +0100 Subject: [PATCH 020/137] fix(klipper): allow go back when asked for new instances Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index e19fb99..3e612aa 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -83,7 +83,7 @@ def handle_existing_instances(instance_list: List[Klipper]) -> bool: if instance_count > 0: print_instance_overview(instance_list) - if not get_confirm("Add new instances?"): + if not get_confirm("Add new instances?", allow_go_back=True): return False return True From be805c169b0d5974c4e7f5fd16d1595087592448 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 01:13:39 +0100 Subject: [PATCH 021/137] feat(klipper): allow keeping klipper and klipper-env dir during uninstall Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 3e612aa..c4a90a1 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -195,11 +195,17 @@ def set_instance_suffix( def remove_single_instance( instance_manager: InstanceManager, instance_list: List[Klipper] ) -> None: + question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?" + del_remnants = get_confirm(question, allow_go_back=True) + if del_remnants is None: + Logger.print_info("Exiting Klipper Uninstaller ...") + return + try: instance_manager.current_instance = instance_list[0] instance_manager.stop_instance() instance_manager.disable_instance() - instance_manager.delete_instance(del_remnants=True) + instance_manager.delete_instance(del_remnants=del_remnants) instance_manager.reload_daemon() except (OSError, subprocess.CalledProcessError): Logger.print_error("Removing instance failed!") @@ -215,22 +221,26 @@ def remove_multi_instance( options.extend(["a", "A", "b", "B"]) selection = get_selection_input("Select Klipper instance to remove", options) - print(selection) if selection == "b".lower(): return elif selection == "a".lower(): + question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?" + del_remnants = get_confirm(question, allow_go_back=True) + if del_remnants is None: + Logger.print_info("Exiting Klipper Uninstaller ...") + return + Logger.print_info("Removing all Klipper instances ...") for instance in instance_list: instance_manager.current_instance = instance instance_manager.stop_instance() instance_manager.disable_instance() - instance_manager.delete_instance(del_remnants=True) + instance_manager.delete_instance(del_remnants=del_remnants) else: instance = instance_list[int(selection)] - Logger.print_info( - f"Removing Klipper instance: {instance.get_service_file_name()}" - ) + log = f"Removing Klipper instance: {instance.get_service_file_name()}" + Logger.print_info(log) instance_manager.current_instance = instance instance_manager.stop_instance() instance_manager.disable_instance() From 6ed5395f17a5e99dabcfcbdde19b1eedc6ae603b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 01:26:43 +0100 Subject: [PATCH 022/137] feat(klipper): check for brltty-udev too Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_utils.py | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 3bb8b04..1a93037 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -142,32 +142,36 @@ def check_user_groups(): def handle_disruptive_system_packages() -> None: services = [] - brltty_status = subprocess.run( - ["systemctl", "is-enabled", "brltty"], capture_output=True, text=True - ) - modem_manager_status = subprocess.run( - ["systemctl", "is-enabled", "ModemManager"], capture_output=True, text=True - ) + + command = ["systemctl", "is-enabled", "brltty"] + brltty_status = subprocess.run(command, capture_output=True, text=True) + + command = ["systemctl", "is-enabled", "brltty-udev"] + brltty_udev_status = subprocess.run(command, capture_output=True, text=True) + + command = ["systemctl", "is-enabled", "ModemManager"] + modem_manager_status = subprocess.run(command, capture_output=True, text=True) if "enabled" in brltty_status.stdout: services.append("brltty") + if "enabled" in brltty_udev_status.stdout: + services.append("brltty-udev") if "enabled" in modem_manager_status.stdout: services.append("ModemManager") for service in services if services else []: try: - Logger.print_info( - f"{service} service detected! Masking {service} service ..." - ) + log = f"{service} service detected! Masking {service} service ..." + Logger.print_info(log) mask_system_service(service) Logger.print_ok(f"{service} service masked!") except subprocess.CalledProcessError: warn_msg = textwrap.dedent( f""" - KIAUH was unable to mask the {service} system service. - Please fix the problem manually. Otherwise, this may have - undesirable effects on the operation of Klipper. - """ + KIAUH was unable to mask the {service} system service. + Please fix the problem manually. Otherwise, this may have + undesirable effects on the operation of Klipper. + """ )[1:] Logger.print_warn(warn_msg) From d0d2404132fcc864bbf0339a946b94b7e1122e49 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 12 Nov 2023 23:28:05 +0100 Subject: [PATCH 023/137] refactor(kiauh): move core modules to core package Signed-off-by: Dominik Willner --- kiauh/{config_manager => core}/__init__.py | 0 .../config_manager}/__init__.py | 0 kiauh/{ => core}/config_manager/config_manager.py | 0 kiauh/{menus => core/instance_manager}/__init__.py | 0 kiauh/{ => core}/instance_manager/base_instance.py | 0 .../{ => core}/instance_manager/instance_manager.py | 2 +- kiauh/{repo_manager => core/menus}/__init__.py | 0 kiauh/{ => core}/menus/advanced_menu.py | 2 +- kiauh/{ => core}/menus/base_menu.py | 0 kiauh/{ => core}/menus/install_menu.py | 2 +- kiauh/{ => core}/menus/main_menu.py | 12 ++++++------ kiauh/{ => core}/menus/remove_menu.py | 2 +- kiauh/{ => core}/menus/settings_menu.py | 2 +- kiauh/{ => core}/menus/update_menu.py | 2 +- kiauh/core/repo_manager/__init__.py | 0 kiauh/{ => core}/repo_manager/repo_manager.py | 0 kiauh/main.py | 2 +- kiauh/modules/klipper/klipper.py | 2 +- kiauh/modules/klipper/klipper_dialogs.py | 4 ++-- kiauh/modules/klipper/klipper_setup.py | 6 +++--- kiauh/modules/klipper/klipper_utils.py | 2 +- 21 files changed, 20 insertions(+), 20 deletions(-) rename kiauh/{config_manager => core}/__init__.py (100%) rename kiauh/{instance_manager => core/config_manager}/__init__.py (100%) rename kiauh/{ => core}/config_manager/config_manager.py (100%) rename kiauh/{menus => core/instance_manager}/__init__.py (100%) rename kiauh/{ => core}/instance_manager/base_instance.py (100%) rename kiauh/{ => core}/instance_manager/instance_manager.py (99%) rename kiauh/{repo_manager => core/menus}/__init__.py (100%) rename kiauh/{ => core}/menus/advanced_menu.py (97%) rename kiauh/{ => core}/menus/base_menu.py (100%) rename kiauh/{ => core}/menus/install_menu.py (98%) rename kiauh/{ => core}/menus/main_menu.py (89%) rename kiauh/{ => core}/menus/remove_menu.py (98%) rename kiauh/{ => core}/menus/settings_menu.py (96%) rename kiauh/{ => core}/menus/update_menu.py (98%) create mode 100644 kiauh/core/repo_manager/__init__.py rename kiauh/{ => core}/repo_manager/repo_manager.py (100%) diff --git a/kiauh/config_manager/__init__.py b/kiauh/core/__init__.py similarity index 100% rename from kiauh/config_manager/__init__.py rename to kiauh/core/__init__.py diff --git a/kiauh/instance_manager/__init__.py b/kiauh/core/config_manager/__init__.py similarity index 100% rename from kiauh/instance_manager/__init__.py rename to kiauh/core/config_manager/__init__.py diff --git a/kiauh/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py similarity index 100% rename from kiauh/config_manager/config_manager.py rename to kiauh/core/config_manager/config_manager.py diff --git a/kiauh/menus/__init__.py b/kiauh/core/instance_manager/__init__.py similarity index 100% rename from kiauh/menus/__init__.py rename to kiauh/core/instance_manager/__init__.py diff --git a/kiauh/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py similarity index 100% rename from kiauh/instance_manager/base_instance.py rename to kiauh/core/instance_manager/base_instance.py diff --git a/kiauh/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py similarity index 99% rename from kiauh/instance_manager/instance_manager.py rename to kiauh/core/instance_manager/instance_manager.py index da75ba9..3318d45 100644 --- a/kiauh/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -14,7 +14,7 @@ import re import subprocess from typing import List, Optional, Union, TypeVar -from kiauh.instance_manager.base_instance import BaseInstance +from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger diff --git a/kiauh/repo_manager/__init__.py b/kiauh/core/menus/__init__.py similarity index 100% rename from kiauh/repo_manager/__init__.py rename to kiauh/core/menus/__init__.py diff --git a/kiauh/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py similarity index 97% rename from kiauh/menus/advanced_menu.py rename to kiauh/core/menus/advanced_menu.py index 9b0ca85..b029d4a 100644 --- a/kiauh/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -11,7 +11,7 @@ import textwrap -from kiauh.menus.base_menu import BaseMenu +from kiauh.core.menus.base_menu import BaseMenu from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT diff --git a/kiauh/menus/base_menu.py b/kiauh/core/menus/base_menu.py similarity index 100% rename from kiauh/menus/base_menu.py rename to kiauh/core/menus/base_menu.py diff --git a/kiauh/menus/install_menu.py b/kiauh/core/menus/install_menu.py similarity index 98% rename from kiauh/menus/install_menu.py rename to kiauh/core/menus/install_menu.py index 54b5148..f1b299e 100644 --- a/kiauh/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -11,7 +11,7 @@ import textwrap -from kiauh.menus.base_menu import BaseMenu +from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper import klipper_setup from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT diff --git a/kiauh/menus/main_menu.py b/kiauh/core/menus/main_menu.py similarity index 89% rename from kiauh/menus/main_menu.py rename to kiauh/core/menus/main_menu.py index 9cffebb..76bcd8b 100644 --- a/kiauh/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -11,12 +11,12 @@ import textwrap -from kiauh.menus.advanced_menu import AdvancedMenu -from kiauh.menus.base_menu import BaseMenu -from kiauh.menus.install_menu import InstallMenu -from kiauh.menus.remove_menu import RemoveMenu -from kiauh.menus.settings_menu import SettingsMenu -from kiauh.menus.update_menu import UpdateMenu +from kiauh.core.menus.advanced_menu import AdvancedMenu +from kiauh.core.menus.base_menu import BaseMenu +from kiauh.core.menus.install_menu import InstallMenu +from kiauh.core.menus.remove_menu import RemoveMenu +from kiauh.core.menus.settings_menu import SettingsMenu +from kiauh.core.menus.update_menu import UpdateMenu from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT diff --git a/kiauh/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py similarity index 98% rename from kiauh/menus/remove_menu.py rename to kiauh/core/menus/remove_menu.py index 1fb2492..861ca48 100644 --- a/kiauh/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -11,7 +11,7 @@ import textwrap -from kiauh.menus.base_menu import BaseMenu +from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper import klipper_setup from kiauh.utils.constants import COLOR_RED, RESET_FORMAT diff --git a/kiauh/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py similarity index 96% rename from kiauh/menus/settings_menu.py rename to kiauh/core/menus/settings_menu.py index dcef3df..d56f284 100644 --- a/kiauh/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -9,7 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from kiauh.menus.base_menu import BaseMenu +from kiauh.core.menus.base_menu import BaseMenu # noinspection PyMethodMayBeStatic diff --git a/kiauh/menus/update_menu.py b/kiauh/core/menus/update_menu.py similarity index 98% rename from kiauh/menus/update_menu.py rename to kiauh/core/menus/update_menu.py index f617fe9..eebe8ee 100644 --- a/kiauh/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -11,7 +11,7 @@ import textwrap -from kiauh.menus.base_menu import BaseMenu +from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper.klipper_setup import update_klipper from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT diff --git a/kiauh/core/repo_manager/__init__.py b/kiauh/core/repo_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py similarity index 100% rename from kiauh/repo_manager/repo_manager.py rename to kiauh/core/repo_manager/repo_manager.py diff --git a/kiauh/main.py b/kiauh/main.py index 2bab667..5f826c8 100644 --- a/kiauh/main.py +++ b/kiauh/main.py @@ -9,7 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from kiauh.menus.main_menu import MainMenu +from kiauh.core.menus.main_menu import MainMenu from kiauh.utils.logger import Logger diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 33c4ec3..2d6c82f 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -15,7 +15,7 @@ import subprocess from pathlib import Path from typing import List -from kiauh.instance_manager.base_instance import BaseInstance +from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py index 1dacd59..cad0cf7 100644 --- a/kiauh/modules/klipper/klipper_dialogs.py +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -12,8 +12,8 @@ import textwrap from typing import List -from kiauh.instance_manager.base_instance import BaseInstance -from kiauh.menus.base_menu import print_back_footer +from kiauh.core.instance_manager.base_instance import BaseInstance +from kiauh.core.menus.base_menu import print_back_footer from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index c4a90a1..070eca8 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -14,8 +14,8 @@ import subprocess from pathlib import Path from typing import List, Union -from kiauh.config_manager.config_manager import ConfigManager -from kiauh.instance_manager.instance_manager import InstanceManager +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper import ( EXIT_KLIPPER_SETUP, DEFAULT_KLIPPER_REPO_URL, @@ -37,7 +37,7 @@ from kiauh.modules.klipper.klipper_utils import ( check_user_groups, handle_single_to_multi_conversion, ) -from kiauh.repo_manager.repo_manager import RepoManager +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils.input_utils import ( get_confirm, get_number_input, diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 1a93037..7a1b241 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -17,7 +17,7 @@ import textwrap from typing import List, Union -from kiauh.instance_manager.instance_manager import InstanceManager +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, From 47121f68755083a906fd6e5743b0914e2889752a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 13 Nov 2023 20:06:48 +0100 Subject: [PATCH 024/137] refactor(utils): clean up, add comments Signed-off-by: Dominik Willner --- kiauh/utils/system_utils.py | 89 ++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 73ad1b0..6b22334 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -17,23 +17,15 @@ import time from pathlib import Path from typing import List -from kiauh.utils.constants import COLOR_RED, RESET_FORMAT from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger -def kill(opt_err_msg=None) -> None: +def kill(opt_err_msg: str = "") -> None: """ - Kill the application. - - Parameters - ---------- - opt_err_msg : str - optional, additional error message to display - - Returns - ---------- - None + Kills the application | + :param opt_err_msg: an optional, additional error message + :return: None """ if opt_err_msg: @@ -42,7 +34,14 @@ def kill(opt_err_msg=None) -> None: sys.exit(1) -def parse_packages_from_file(source_file) -> List[str]: +def parse_packages_from_file(source_file: Path) -> List[str]: + """ + Read the package names from bash scripts, when defined like: + PKGLIST="package1 package2 package3" | + :param source_file: path of the sourcefile to read from + :return: a list of package names + """ + packages = [] print("Reading dependencies...") with open(source_file, "r") as file: @@ -53,38 +52,47 @@ def parse_packages_from_file(source_file) -> List[str]: line = line.replace("PKGLIST=", "") line = line.replace("${PKGLIST}", "") packages.extend(line.split()) + return packages def create_python_venv(target: Path) -> None: + """ + Create a python 3 virtualenv at the provided target destination | + :param target: Path where to create the virtualenv at + :return: None + """ Logger.print_info("Set up Python virtual environment ...") if not target.exists(): try: command = ["python3", "-m", "venv", f"{target}"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) if result.returncode != 0 or result.stderr: - print(f"{COLOR_RED}{result.stderr}{RESET_FORMAT}") + Logger.print_error(f"{result.stderr}", prefix=False) Logger.print_error("Setup of virtualenv failed!") return Logger.print_ok("Setup of virtualenv successfull!") except subprocess.CalledProcessError as e: - print("Error setting up virtualenv:", e.output.decode()) + Logger.print_error(f"Error setting up virtualenv:\n{e.output.decode()}") else: - overwrite_venv = get_confirm("Virtualenv already exists. Re-create?") - if overwrite_venv: + if get_confirm("Virtualenv already exists. Re-create?"): try: shutil.rmtree(target) create_python_venv(target) except OSError as e: - Logger.print_error( - f"Error removing existing virtualenv: {e.strerror}", False - ) + log = f"Error removing existing virtualenv: {e.strerror}" + Logger.print_error(log, False) else: - print("Skipping re-creation of virtualenv ...") + Logger.print_info("Skipping re-creation of virtualenv ...") def update_python_pip(target: Path) -> None: + """ + Updates pip in the provided target destination | + :param target: Path of the virtualenv + :return: None + """ Logger.print_info("Updating pip ...") try: command = [f"{target}/bin/pip", "install", "-U", "pip"] @@ -96,10 +104,16 @@ def update_python_pip(target: Path) -> None: Logger.print_ok("Updating pip successfull!") except subprocess.CalledProcessError as e: - print("Error updating pip:", e.output.decode()) + Logger.print_error(f"Error updating pip:\n{e.output.decode()}") def install_python_requirements(target: Path, requirements: Path) -> None: + """ + Installs the python packages based on a provided requirements.txt | + :param target: Path of the virtualenv + :param requirements: Path to the requirements.txt file + :return: None + """ update_python_pip(target) Logger.print_info("Installing Python requirements ...") try: @@ -112,10 +126,17 @@ def install_python_requirements(target: Path, requirements: Path) -> None: Logger.print_ok("Installing Python requirements successfull!") except subprocess.CalledProcessError as e: - print("Error installing Python requirements:", e.output.decode()) + log = f"Error installing Python requirements:\n{e.output.decode()}" + Logger.print_error(log) def update_system_package_lists(silent: bool, rls_info_change=False) -> None: + """ + Updates the systems package list | + :param silent: Log info to the console or not + :param rls_info_change: Flag for "--allow-releaseinfo-change" + :return: None + """ cache_mtime = 0 cache_files = ["/var/lib/apt/periodic/update-success-stamp", "/var/lib/apt/lists"] for cache_file in cache_files: @@ -129,7 +150,7 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: return if not silent: - print("Updating package list...") + Logger.print_info("Updating package list...") try: command = ["sudo", "apt-get", "update"] @@ -148,6 +169,11 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: def install_system_packages(packages: List) -> None: + """ + Installs a list of system packages | + :param packages: List of system package names + :return: None + """ try: command = ["sudo", "apt-get", "install", "-y"] for pkg in packages: @@ -160,6 +186,11 @@ def install_system_packages(packages: List) -> None: def create_directory(_dir: Path) -> None: + """ + Helper function for creating a directory or skipping if it already exists | + :param _dir: the directory to create + :return: None + """ try: if not os.path.isdir(_dir): Logger.print_info(f"Create directory: {_dir}") @@ -173,11 +204,15 @@ def create_directory(_dir: Path) -> None: def mask_system_service(service_name: str) -> None: + """ + Mask a system service to prevent it from starting | + :param service_name: name of the service to mask + :return: None + """ try: command = ["sudo", "systemctl", "mask", service_name] subprocess.run(command, stderr=subprocess.PIPE, check=True) except subprocess.CalledProcessError as e: - Logger.print_error( - f"Unable to mask system service {service_name}: {e.stderr.decode()}" - ) + log = f"Unable to mask system service {service_name}: {e.stderr.decode()}" + Logger.print_error(log) raise From 1392ca9f82af2a6dbce6edda860c43de76b70698 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 13 Nov 2023 20:07:21 +0100 Subject: [PATCH 025/137] refactor(klipper): pass the script path as a Path to the parse function Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 070eca8..fea9b6d 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -155,7 +155,7 @@ def setup_klipper_prerequesites() -> None: def install_klipper_packages(klipper_dir: Path) -> None: - script = f"{klipper_dir}/scripts/install-debian.sh" + script = Path(f"{klipper_dir}/scripts/install-debian.sh") packages = parse_packages_from_file(script) packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages] # Add dfu-util for octopi-images From a4a3d5eecb96856c3d18544490cf3940d07d1750 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Tue, 14 Nov 2023 21:28:13 +0100 Subject: [PATCH 026/137] feat(BackupManager): implement simple backup manager Signed-off-by: Dominik Willner --- kiauh/__init__.py | 14 +++++ kiauh/core/backup_manager/__init__.py | 0 kiauh/core/backup_manager/backup_manager.py | 69 +++++++++++++++++++++ kiauh/core/config_manager/config_manager.py | 17 +++-- kiauh/modules/klipper/klipper_setup.py | 15 +++-- kiauh/utils/common.py | 25 ++++++++ kiauh/utils/constants.py | 3 + 7 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 kiauh/core/backup_manager/__init__.py create mode 100644 kiauh/core/backup_manager/backup_manager.py create mode 100644 kiauh/utils/common.py diff --git a/kiauh/__init__.py b/kiauh/__init__.py index e69de29..f670517 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -0,0 +1,14 @@ +# !/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from os.path import dirname, abspath + +APPLICATION_ROOT = dirname(dirname(abspath(__file__))) diff --git a/kiauh/core/backup_manager/__init__.py b/kiauh/core/backup_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py new file mode 100644 index 0000000..8a333a5 --- /dev/null +++ b/kiauh/core/backup_manager/backup_manager.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 +from pathlib import Path + +from kiauh.utils.common import get_current_date +from kiauh.utils.constants import KIAUH_BACKUP_DIR +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class BackupManager: + def __init__( + self, backup_name: str, source: Path, backup_dir: Path = KIAUH_BACKUP_DIR + ): + self._backup_name = backup_name + self._source = source + self._backup_dir = backup_dir + + @property + def backup_name(self) -> str: + return self._backup_name + + @backup_name.setter + def backup_name(self, value: str): + self._backup_name = value + + @property + def source(self) -> Path: + return self._source + + @source.setter + def source(self, value: Path): + self._source = value + + @property + def backup_dir(self) -> Path: + return self._backup_dir + + @backup_dir.setter + def backup_dir(self, value: Path): + self._backup_dir = value + + def backup(self) -> None: + if self._source is None or not Path(self._source).exists(): + raise OSError + + try: + log = f"Creating backup of {self.backup_name} in {self.backup_dir} ..." + Logger.print_info(log) + date = get_current_date() + dest = Path( + f"{self.backup_dir}/{self.backup_name}/{date.get('date')}-{date.get('time')}" + ) + shutil.copytree(src=self.source, dst=dest) + except OSError as e: + Logger.print_error(f"Unable to backup source directory. Not exist.\n{e}") + return + + Logger.print_ok("Backup successfull!") diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 0f80647..0237a27 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -14,6 +14,7 @@ import configparser from pathlib import Path from typing import Union +from kiauh import APPLICATION_ROOT from kiauh.utils.logger import Logger @@ -34,7 +35,7 @@ class ConfigManager: with open(self.config_file, "w") as cfg: self.config.write(cfg) - def get_value(self, section: str, key: str) -> Union[str, None]: + def get_value(self, section: str, key: str) -> Union[str, bool, None]: if not self.config.has_section(section): log = f"Section not defined. Unable to read section: [{section}]." Logger.print_error(log) @@ -45,17 +46,21 @@ class ConfigManager: Logger.print_error(log) return None - return self.config.get(section, key) + value = self.config.get(section, key) + if value == "True" or value == "true": + return True + elif value == "False" or value == "false": + return False + else: + return value def set_value(self, section: str, key: str, value: str): self.config.set(section, key, value) - def check_config_exists(self) -> bool: + def check_config_exist(self) -> bool: return True if self._get_cfg_location() else False def _get_cfg_location(self) -> str: - current_dir = os.path.dirname(os.path.abspath(__file__)) - project_dir = os.path.dirname(os.path.dirname(current_dir)) - cfg_path = os.path.join(project_dir, "kiauh.cfg") + cfg_path = os.path.join(APPLICATION_ROOT, "kiauh.cfg") return cfg_path if Path(cfg_path).exists() else None diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index fea9b6d..e7c4d52 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -14,6 +14,7 @@ import subprocess from pathlib import Path from typing import List, Union +from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper import ( @@ -251,16 +252,22 @@ def remove_multi_instance( def update_klipper() -> None: print_update_warn_dialog() - if not get_confirm("Update Klipper now?"): return - instance_manager = InstanceManager(Klipper) - instance_manager.stop_all_instance() - cm = ConfigManager() cm.read_config() + if cm.get_value("kiauh", "backup_before_update"): + backup_manager = BackupManager(source=KLIPPER_DIR, backup_name="klipper") + backup_manager.backup() + backup_manager.backup_name = "klippy-env" + backup_manager.source = KLIPPER_ENV_DIR + backup_manager.backup() + + instance_manager = InstanceManager(Klipper) + instance_manager.stop_all_instance() + repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL) branch = str(cm.get_value("klipper", "branch") or "master") diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py new file mode 100644 index 0000000..bc9a9fb --- /dev/null +++ b/kiauh/utils/common.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +# ======================================================================= # +# 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 # +# ======================================================================= # + +from datetime import datetime +from typing import Dict, Literal + + +def get_current_date() -> Dict[Literal["date", "time"], str]: + """ + Get the current date | + :return: a Dict holding a date and time key:value pair + """ + now: datetime = datetime.today() + date: str = now.strftime("%Y-%m-%d") + time: str = now.strftime("%H-%M-%S") + + return {"date": date, "time": time} diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 60662ce..08a4398 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -11,6 +11,7 @@ import os import pwd +from pathlib import Path # text colors and formats COLOR_MAGENTA = "\033[35m" # magenta @@ -19,6 +20,8 @@ COLOR_YELLOW = "\033[93m" # bright yellow COLOR_RED = "\033[91m" # bright red COLOR_CYAN = "\033[96m" # bright cyan RESET_FORMAT = "\033[0m" # reset format +# kiauh +KIAUH_BACKUP_DIR = f"{Path.home()}/kiauh-backups" # current user CURRENT_USER = pwd.getpwuid(os.getuid())[0] SYSTEMD = "/etc/systemd/system" From 279d000bb050b22755ffd89611ee5f2941d3ed5a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Tue, 14 Nov 2023 21:31:25 +0100 Subject: [PATCH 027/137] refactor(kiauh): specify python3 in shebang Signed-off-by: Dominik Willner --- kiauh.py | 2 +- kiauh/__init__.py | 2 +- kiauh/core/backup_manager/backup_manager.py | 2 +- kiauh/core/config_manager/config_manager.py | 2 +- kiauh/core/instance_manager/base_instance.py | 2 +- kiauh/core/instance_manager/instance_manager.py | 2 +- kiauh/core/menus/advanced_menu.py | 2 +- kiauh/core/menus/base_menu.py | 2 +- kiauh/core/menus/install_menu.py | 2 +- kiauh/core/menus/main_menu.py | 2 +- kiauh/core/menus/remove_menu.py | 2 +- kiauh/core/menus/settings_menu.py | 2 +- kiauh/core/menus/update_menu.py | 2 +- kiauh/core/repo_manager/repo_manager.py | 2 +- kiauh/main.py | 2 +- kiauh/modules/klipper/__init__.py | 2 +- kiauh/modules/klipper/klipper.py | 2 +- kiauh/modules/klipper/klipper_dialogs.py | 2 +- kiauh/modules/klipper/klipper_setup.py | 2 +- kiauh/modules/klipper/klipper_utils.py | 2 +- kiauh/utils/__init__.py | 2 +- kiauh/utils/common.py | 2 +- kiauh/utils/constants.py | 2 +- kiauh/utils/input_utils.py | 2 +- kiauh/utils/logger.py | 2 +- kiauh/utils/system_utils.py | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/kiauh.py b/kiauh.py index ec12ca5..227775f 100644 --- a/kiauh.py +++ b/kiauh.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/__init__.py b/kiauh/__init__.py index f670517..d30786d 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -1,4 +1,4 @@ -# !/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 8a333a5..613ca61 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 0237a27..a1cd757 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index df7e92f..2fdb188 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 3318d45..a497425 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index b029d4a..6cda33f 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 799b5af..7bb39aa 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index f1b299e..f30b676 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 76bcd8b..be292da 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 861ca48..83f0c07 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index d56f284..8cb7723 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index eebe8ee..bf5bbd3 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index 4555920..9b166cf 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/main.py b/kiauh/main.py index 5f826c8..e064aec 100644 --- a/kiauh/main.py +++ b/kiauh/main.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/modules/klipper/__init__.py index a9b4efc..2b0d427 100644 --- a/kiauh/modules/klipper/__init__.py +++ b/kiauh/modules/klipper/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 2d6c82f..e071566 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -1,4 +1,4 @@ -# !/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py index cad0cf7..d3b86bd 100644 --- a/kiauh/modules/klipper/klipper_dialogs.py +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index e7c4d52..725a370 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -1,4 +1,4 @@ -# !/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 7a1b241..59059ca 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index 3c3f6a5..203c66a 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index bc9a9fb..4b6a48a 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 08a4398..1c0b90e 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index 280088d..dcddda0 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index da54fb7..7c5e7f5 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 6b22334..868078b 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # From 6128e35d451fd8c9c9c0721964fc4745acda4e55 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 15 Nov 2023 00:25:19 +0100 Subject: [PATCH 028/137] refactor(kiauh): rework menu formatting logic Signed-off-by: Dominik Willner --- kiauh/core/menus/__init__.py | 14 ++++++ kiauh/core/menus/advanced_menu.py | 8 +++- kiauh/core/menus/base_menu.py | 77 ++++++++++++++----------------- kiauh/core/menus/install_menu.py | 12 ++--- kiauh/core/menus/main_menu.py | 17 ++++--- kiauh/core/menus/remove_menu.py | 8 +++- kiauh/core/menus/update_menu.py | 8 +++- 7 files changed, 82 insertions(+), 62 deletions(-) diff --git a/kiauh/core/menus/__init__.py b/kiauh/core/menus/__init__.py index e69de29..23cd836 100644 --- a/kiauh/core/menus/__init__.py +++ b/kiauh/core/menus/__init__.py @@ -0,0 +1,14 @@ +#!/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 # +# ======================================================================= # + +QUIT_FOOTER = "quit" +BACK_FOOTER = "back" +BACK_HELP_FOOTER = "back_help" diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 6cda33f..038409a 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -11,19 +11,23 @@ import textwrap +from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT class AdvancedMenu(BaseMenu): def __init__(self): - super().__init__(header=True, options={}, footer_type="back") + super().__init__(header=True, options={}, footer_type=BACK_FOOTER) def print_menu(self): + header = " [ Advanced Menu ] " + color = COLOR_YELLOW + count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( f""" /=======================================================\\ - | {COLOR_YELLOW}~~~~~~~~~~~~~ [ Advanced Menu ] ~~~~~~~~~~~~~{RESET_FORMAT} | + | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| | Klipper & API: | Mainsail: | | 0) [Rollback] | 5) [Theme installer] | diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 7bb39aa..1950e4b 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -13,8 +13,9 @@ import subprocess import sys import textwrap from abc import abstractmethod, ABC -from typing import Dict, Any +from typing import Dict, Any, Literal +from kiauh.core.menus import QUIT_FOOTER, BACK_FOOTER, BACK_HELP_FOOTER from kiauh.utils.constants import ( COLOR_GREEN, COLOR_YELLOW, @@ -29,12 +30,17 @@ def clear(): def print_header(): + line1 = " [ KIAUH ] " + line2 = "Klipper Installation And Update Helper" + line3 = "" + color = COLOR_CYAN + count = 62 - len(color) - len(RESET_FORMAT) header = textwrap.dedent( f""" /=======================================================\\ - | {COLOR_CYAN}~~~~~~~~~~~~~~~~~ [ KIAUH ] ~~~~~~~~~~~~~~~~~{RESET_FORMAT} | - | {COLOR_CYAN} Klipper Installation And Update Helper {RESET_FORMAT} | - | {COLOR_CYAN}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{RESET_FORMAT} | + | {color}{line1:~^{count}}{RESET_FORMAT} | + | {color}{line2:^{count}}{RESET_FORMAT} | + | {color}{line3:~^{count}}{RESET_FORMAT} | \=======================================================/ """ )[1:] @@ -42,10 +48,13 @@ def print_header(): def print_quit_footer(): + text = "Q) Quit" + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) footer = textwrap.dedent( f""" |-------------------------------------------------------| - | {COLOR_RED}Q) Quit{RESET_FORMAT} | + | {color}{text:^{count}}{RESET_FORMAT} | \=======================================================/ """ )[1:] @@ -53,10 +62,13 @@ def print_quit_footer(): def print_back_footer(): + text = "B) « Back" + color = COLOR_GREEN + count = 62 - len(color) - len(RESET_FORMAT) footer = textwrap.dedent( f""" |-------------------------------------------------------| - | {COLOR_GREEN}B) « Back{RESET_FORMAT} | + | {color}{text:^{count}}{RESET_FORMAT} | \=======================================================/ """ )[1:] @@ -64,32 +76,15 @@ def print_back_footer(): def print_back_help_footer(): + text1 = "B) « Back" + text2 = "H) Help [?]" + color1 = COLOR_GREEN + color2 = COLOR_YELLOW + count = 34 - len(color1) - len(RESET_FORMAT) footer = textwrap.dedent( f""" |-------------------------------------------------------| - | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | - \=======================================================/ - """ - )[1:] - print(footer, end="") - - -def print_back_quit_footer(): - footer = textwrap.dedent( - f""" - |-------------------------------------------------------| - | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} | - \=======================================================/ - """ - )[1:] - print(footer, end="") - - -def print_back_quit_help_footer(): - footer = textwrap.dedent( - f""" - |-------------------------------------------------------| - | {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} | + | {color1}{text1:^{count}}{RESET_FORMAT} | {color2}{text2:^{count}}{RESET_FORMAT} | \=======================================================/ """ )[1:] @@ -97,14 +92,14 @@ def print_back_quit_help_footer(): class BaseMenu(ABC): - QUIT_FOOTER = "quit" - BACK_FOOTER = "back" - BACK_HELP_FOOTER = "back_help" - BACK_QUIT_FOOTER = "back_quit" - BACK_QUIT_HELP_FOOTER = "back_quit_help" - def __init__( - self, options: Dict[int, Any], options_offset=0, header=True, footer_type="quit" + self, + options: Dict[int, Any], + options_offset: int = 0, + header: bool = True, + footer_type: Literal[ + "QUIT_FOOTER", "BACK_FOOTER", "BACK_HELP_FOOTER" + ] = QUIT_FOOTER, ): self.options = options self.options_offset = options_offset @@ -117,11 +112,9 @@ class BaseMenu(ABC): def print_footer(self): footer_type_map = { - self.QUIT_FOOTER: print_quit_footer, - self.BACK_FOOTER: print_back_footer, - self.BACK_HELP_FOOTER: print_back_help_footer, - self.BACK_QUIT_FOOTER: print_back_quit_footer, - self.BACK_QUIT_HELP_FOOTER: print_back_quit_help_footer, + QUIT_FOOTER: print_quit_footer, + BACK_FOOTER: print_back_footer, + BACK_HELP_FOOTER: print_back_help_footer, } footer_function = footer_type_map.get(self.footer_type, print_quit_footer) footer_function() @@ -150,8 +143,6 @@ class BaseMenu(ABC): "quit": ["q"], "back": ["b"], "back_help": ["b", "h"], - "back_quit": ["b", "q"], - "back_quit_help": ["b", "q", "h"], } if ( self.footer_type in allowed_input diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index f30b676..c1e2646 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -11,6 +11,7 @@ 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.utils.constants import COLOR_GREEN, RESET_FORMAT @@ -34,18 +35,17 @@ class InstallMenu(BaseMenu): 10: self.install_mobileraker, 11: self.install_crowsnest, }, - footer_type="back", + footer_type=BACK_FOOTER, ) def print_menu(self): + header = " [ Installation Menu ] " + color = COLOR_GREEN + count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( f""" /=======================================================\\ - | {COLOR_GREEN}~~~~~~~~~~~ [ Installation Menu ] ~~~~~~~~~~~{RESET_FORMAT} | - |-------------------------------------------------------| - | You need this menu usually only for installing | - | all necessary dependencies for the various | - | functions on a completely fresh system. | + | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| | Firmware & API: | Other: | | 1) [Klipper] | 6) [PrettyGCode] | diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index be292da..bc77121 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -11,6 +11,7 @@ import textwrap +from kiauh.core.menus import QUIT_FOOTER from kiauh.core.menus.advanced_menu import AdvancedMenu from kiauh.core.menus.base_menu import BaseMenu from kiauh.core.menus.install_menu import InstallMenu @@ -25,7 +26,7 @@ class MainMenu(BaseMenu): super().__init__( header=True, options={ - 0: self.test, + 0: None, 1: InstallMenu, 2: UpdateMenu, 3: RemoveMenu, @@ -33,14 +34,19 @@ class MainMenu(BaseMenu): 5: None, 6: SettingsMenu, }, - footer_type="quit", + footer_type=QUIT_FOOTER, ) def print_menu(self): + header = " [ Main Menu ] " + footer1 = "KIAUH v6.0.0" + footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}" + color = COLOR_CYAN + count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( f""" /=======================================================\\ - | {COLOR_CYAN}~~~~~~~~~~~~~~~ [ Main Menu ] ~~~~~~~~~~~~~~~{RESET_FORMAT} | + | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| | 0) [Log-Upload] | Klipper: | | | Repo: | @@ -58,10 +64,7 @@ class MainMenu(BaseMenu): | | Obico: | | | OctoEverywhere: | |-------------------------------------------------------| - | {COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT} | Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT} | + | {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} | """ )[1:] print(menu, end="") - - def test(self): - print("blub") diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 83f0c07..51b3d44 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -11,6 +11,7 @@ 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.utils.constants import COLOR_RED, RESET_FORMAT @@ -38,14 +39,17 @@ class RemoveMenu(BaseMenu): 14: self.remove_mobileraker, 15: self.remove_nginx, }, - footer_type="back", + footer_type=BACK_FOOTER, ) def print_menu(self): + header = " [ Remove Menu ] " + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( f""" /=======================================================\\ - | {COLOR_RED}~~~~~~~~~~~~~~ [ Remove Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} | + | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| | INFO: Configurations and/or any backups will be kept! | |-------------------------------------------------------| diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index bf5bbd3..3413616 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -11,6 +11,7 @@ import textwrap +from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper.klipper_setup import update_klipper from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT @@ -36,14 +37,17 @@ class UpdateMenu(BaseMenu): 11: self.update_crowsnest, 12: self.upgrade_system_packages, }, - footer_type="back", + footer_type=BACK_FOOTER, ) def print_menu(self): + header = " [ Update Menu ] " + color = COLOR_GREEN + count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( f""" /=======================================================\\ - | {COLOR_GREEN}~~~~~~~~~~~~~~ [ Update Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} | + | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| | 0) [Update all] | | | | | Current: | Latest: | From 458c89a78a6d3bb9cb8ac5ece64202e305f57e3d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 2 Dec 2023 16:52:30 +0100 Subject: [PATCH 029/137] fix(InstanceManager): print service name instead of suffix only Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/instance_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index a497425..eb046fa 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -111,10 +111,10 @@ class InstanceManager: try: command = ["sudo", "systemctl", "enable", self.instance_service] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_suffix}.service enabled.") + Logger.print_ok(f"{self.instance_service}.service enabled.") except subprocess.CalledProcessError as e: Logger.print_error( - f"Error enabling service {self.instance_suffix}.service:" + f"Error enabling service {self.instance_service}.service:" ) Logger.print_error(f"{e}") From bfb10c742bd2c527a1b2f7ab1789014fa49bd199 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Dec 2023 23:06:30 +0100 Subject: [PATCH 030/137] refactor(kiauh): reword print_info to print_status and implement new print_info method Signed-off-by: Dominik Willner --- kiauh/core/backup_manager/backup_manager.py | 2 +- .../core/instance_manager/instance_manager.py | 41 ++++++++++--------- kiauh/core/repo_manager/repo_manager.py | 10 +++-- kiauh/modules/klipper/klipper.py | 10 ++--- kiauh/modules/klipper/klipper_setup.py | 14 +++---- kiauh/modules/klipper/klipper_utils.py | 4 +- kiauh/utils/constants.py | 1 + kiauh/utils/logger.py | 10 ++++- kiauh/utils/system_utils.py | 15 +++---- 9 files changed, 58 insertions(+), 49 deletions(-) diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 613ca61..ee6b782 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -56,7 +56,7 @@ class BackupManager: try: log = f"Creating backup of {self.backup_name} in {self.backup_dir} ..." - Logger.print_info(log) + Logger.print_status(log) date = get_current_date() dest = Path( f"{self.backup_dir}/{self.backup_name}/{date.get('date')}-{date.get('time')}" diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index eb046fa..fe6455f 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -29,6 +29,7 @@ class InstanceManager: self._current_instance: Optional[I] = None self._instance_suffix: Optional[str] = None self._instance_service: Optional[str] = None + self._instance_service_full: Optional[str] = None self._instance_service_path: Optional[str] = None self._instances: List[I] = [] @@ -67,6 +68,10 @@ class InstanceManager: def instance_service(self, value: str): self._instance_service = value + @property + def instance_service_full(self) -> str: + return f"{self._instance_service}.service" + @property def instance_service_path(self) -> str: return self._instance_service_path @@ -107,35 +112,33 @@ class InstanceManager: raise ValueError("current_instance cannot be None") def enable_instance(self) -> None: - Logger.print_info(f"Enabling {self.instance_service} ...") + Logger.print_status(f"Enabling {self.instance_service_full} ...") try: - command = ["sudo", "systemctl", "enable", self.instance_service] + command = ["sudo", "systemctl", "enable", self.instance_service_full] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_service}.service enabled.") + Logger.print_ok(f"{self.instance_service_full} enabled.") except subprocess.CalledProcessError as e: - Logger.print_error( - f"Error enabling service {self.instance_service}.service:" - ) + Logger.print_error(f"Error enabling service {self.instance_service_full}:") Logger.print_error(f"{e}") def disable_instance(self) -> None: - Logger.print_info(f"Disabling {self.instance_service} ...") + Logger.print_status(f"Disabling {self.instance_service_full} ...") try: - command = ["sudo", "systemctl", "disable", self.instance_service] + command = ["sudo", "systemctl", "disable", self.instance_service_full] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_service} disabled.") + Logger.print_ok(f"{self.instance_service_full} disabled.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error disabling service {self.instance_service}:") + Logger.print_error(f"Error disabling {self.instance_service_full}:") Logger.print_error(f"{e}") def start_instance(self) -> None: - Logger.print_info(f"Starting {self.instance_service} ...") + Logger.print_status(f"Starting {self.instance_service_full} ...") try: - command = ["sudo", "systemctl", "start", self.instance_service] + command = ["sudo", "systemctl", "start", self.instance_service_full] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_service} started.") + Logger.print_ok(f"{self.instance_service_full} started.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error starting service {self.instance_service}:") + Logger.print_error(f"Error starting {self.instance_service_full}:") Logger.print_error(f"{e}") def start_all_instance(self) -> None: @@ -144,13 +147,13 @@ class InstanceManager: self.start_instance() def stop_instance(self) -> None: - Logger.print_info(f"Stopping {self.instance_service} ...") + Logger.print_status(f"Stopping {self.instance_service_full} ...") try: - command = ["sudo", "systemctl", "stop", self.instance_service] + command = ["sudo", "systemctl", "stop", self.instance_service_full] if subprocess.run(command, check=True): - Logger.print_ok(f"{self.instance_service} stopped.") + Logger.print_ok(f"{self.instance_service_full} stopped.") except subprocess.CalledProcessError as e: - Logger.print_error(f"Error stopping service {self.instance_service}:") + Logger.print_error(f"Error stopping {self.instance_service_full}:") Logger.print_error(f"{e}") raise @@ -160,7 +163,7 @@ class InstanceManager: self.stop_instance() def reload_daemon(self) -> None: - Logger.print_info("Reloading systemd manager configuration ...") + Logger.print_status("Reloading systemd manager configuration ...") try: command = ["sudo", "systemctl", "daemon-reload"] if subprocess.run(command, check=True): diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index 9b166cf..ee84c62 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -59,11 +59,13 @@ class RepoManager: def clone_repo(self): log = f"Cloning repository from '{self.repo}' with method '{self.method}'" - Logger.print_info(log) + Logger.print_status(log) try: if os.path.exists(self.target_dir): - if not get_confirm("Target directory already exists. Overwrite?"): - Logger.print_info("Skipping re-clone of repository ...") + if not get_confirm( + "Target directory already exists. Overwrite?", default_choice=False + ): + Logger.print_info("Skipping re-clone of repository.") return shutil.rmtree(self.target_dir) @@ -78,7 +80,7 @@ class RepoManager: return def pull_repo(self) -> None: - Logger.print_info(f"Updating repository '{self.repo}' ...") + Logger.print_status(f"Updating repository '{self.repo}' ...") try: self._pull() except subprocess.CalledProcessError: diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index e071566..e4aa570 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -38,7 +38,7 @@ class Klipper(BaseInstance): self.uds = f"{self.comms_dir}/klippy.sock" def create(self) -> None: - Logger.print_info("Creating new Klipper Instance ...") + Logger.print_status("Creating new Klipper Instance ...") module_path = os.path.dirname(os.path.abspath(__file__)) service_template_path = os.path.join(module_path, "res", "klipper.service") env_template_file_path = os.path.join(module_path, "res", "klipper.env") @@ -66,7 +66,7 @@ class Klipper(BaseInstance): service_file = self.get_service_file_name(extension=True) service_file_path = self.get_service_file_path() - Logger.print_info(f"Deleting Klipper Instance: {service_file}") + Logger.print_status(f"Deleting Klipper Instance: {service_file}") try: command = ["sudo", "rm", "-f", service_file_path] @@ -113,12 +113,12 @@ class Klipper(BaseInstance): def _delete_klipper_remnants(self) -> None: try: - Logger.print_info(f"Delete {self.klipper_dir} ...") + Logger.print_status(f"Delete {self.klipper_dir} ...") shutil.rmtree(Path(self.klipper_dir)) - Logger.print_info(f"Delete {self.env_dir} ...") + Logger.print_status(f"Delete {self.env_dir} ...") shutil.rmtree(Path(self.env_dir)) except FileNotFoundError: - Logger.print_info("Cannot delete Klipper directories. Not found.") + Logger.print_status("Cannot delete Klipper directories. Not found.") except PermissionError as e: Logger.print_error(f"Error deleting Klipper directories: {e}") raise diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 725a370..fb00924 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -67,7 +67,7 @@ def run_klipper_setup(install: bool) -> None: if install: add_additional = handle_existing_instances(instance_list) if is_klipper_installed and not add_additional: - Logger.print_info(EXIT_KLIPPER_SETUP) + Logger.print_status(EXIT_KLIPPER_SETUP) return install_klipper(instance_manager, instance_list) @@ -97,12 +97,12 @@ def install_klipper( question = f"Number of{' additional' if len(instance_list) > 0 else ''} Klipper instances to set up" install_count = get_number_input(question, 1, default=1, allow_go_back=True) if install_count is None: - Logger.print_info(EXIT_KLIPPER_SETUP) + Logger.print_status(EXIT_KLIPPER_SETUP) return instance_names = set_instance_suffix(instance_list, install_count) if instance_names is None: - Logger.print_info(EXIT_KLIPPER_SETUP) + Logger.print_status(EXIT_KLIPPER_SETUP) return if len(instance_list) < 1: @@ -199,7 +199,7 @@ def remove_single_instance( question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?" del_remnants = get_confirm(question, allow_go_back=True) if del_remnants is None: - Logger.print_info("Exiting Klipper Uninstaller ...") + Logger.print_status("Exiting Klipper Uninstaller ...") return try: @@ -229,10 +229,10 @@ def remove_multi_instance( question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?" del_remnants = get_confirm(question, allow_go_back=True) if del_remnants is None: - Logger.print_info("Exiting Klipper Uninstaller ...") + Logger.print_status("Exiting Klipper Uninstaller ...") return - Logger.print_info("Removing all Klipper instances ...") + Logger.print_status("Removing all Klipper instances ...") for instance in instance_list: instance_manager.current_instance = instance instance_manager.stop_instance() @@ -241,7 +241,7 @@ def remove_multi_instance( else: instance = instance_list[int(selection)] log = f"Removing Klipper instance: {instance.get_service_file_name()}" - Logger.print_info(log) + Logger.print_status(log) instance_manager.current_instance = instance instance_manager.stop_instance() instance_manager.disable_instance() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 59059ca..aa11124 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -128,7 +128,7 @@ def check_user_groups(): try: for group in missing_groups: - Logger.print_info(f"Adding user '{CURRENT_USER}' to group {group} ...") + Logger.print_status(f"Adding user '{CURRENT_USER}' to group {group} ...") command = ["sudo", "usermod", "-a", "-G", group, CURRENT_USER] subprocess.run(command, check=True) Logger.print_ok(f"Group {group} assigned to user '{CURRENT_USER}'.") @@ -162,7 +162,7 @@ def handle_disruptive_system_packages() -> None: for service in services if services else []: try: log = f"{service} service detected! Masking {service} service ..." - Logger.print_info(log) + Logger.print_status(log) mask_system_service(service) Logger.print_ok(f"{service} service masked!") except subprocess.CalledProcessError: diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 1c0b90e..37d4a33 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -14,6 +14,7 @@ import pwd from pathlib import Path # text colors and formats +COLOR_WHITE = "\033[37m" # white COLOR_MAGENTA = "\033[35m" # magenta COLOR_GREEN = "\033[92m" # bright green COLOR_YELLOW = "\033[93m" # bright yellow diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index 7c5e7f5..864bd77 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -10,6 +10,7 @@ # ======================================================================= # from kiauh.utils.constants import ( + COLOR_WHITE, COLOR_GREEN, COLOR_YELLOW, COLOR_RED, @@ -34,6 +35,11 @@ class Logger: # log to kiauh.log pass + @staticmethod + def print_info(msg, prefix=True, end="\n") -> None: + message = f"[INFO] {msg}" if prefix else msg + print(f"{COLOR_WHITE}{message}{RESET_FORMAT}", end=end) + @staticmethod def print_ok(msg, prefix=True, end="\n") -> None: message = f"[OK] {msg}" if prefix else msg @@ -50,6 +56,6 @@ class Logger: print(f"{COLOR_RED}{message}{RESET_FORMAT}", end=end) @staticmethod - def print_info(msg, prefix=True, end="\n") -> None: - message = f"###### {msg}" if prefix else msg + def print_status(msg, prefix=True, end="\n") -> None: + message = f"\n###### {msg}" if prefix else msg print(f"{COLOR_MAGENTA}{message}{RESET_FORMAT}", end=end) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 868078b..d0d9276 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -62,7 +62,7 @@ def create_python_venv(target: Path) -> None: :param target: Path where to create the virtualenv at :return: None """ - Logger.print_info("Set up Python virtual environment ...") + Logger.print_status("Set up Python virtual environment ...") if not target.exists(): try: command = ["python3", "-m", "venv", f"{target}"] @@ -76,7 +76,7 @@ def create_python_venv(target: Path) -> None: except subprocess.CalledProcessError as e: Logger.print_error(f"Error setting up virtualenv:\n{e.output.decode()}") else: - if get_confirm("Virtualenv already exists. Re-create?"): + if get_confirm("Virtualenv already exists. Re-create?", default_choice=False): try: shutil.rmtree(target) create_python_venv(target) @@ -93,7 +93,7 @@ def update_python_pip(target: Path) -> None: :param target: Path of the virtualenv :return: None """ - Logger.print_info("Updating pip ...") + Logger.print_status("Updating pip ...") try: command = [f"{target}/bin/pip", "install", "-U", "pip"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) @@ -115,7 +115,7 @@ def install_python_requirements(target: Path, requirements: Path) -> None: :return: None """ update_python_pip(target) - Logger.print_info("Installing Python requirements ...") + Logger.print_status("Installing Python requirements ...") try: command = [f"{target}/bin/pip", "install", "-r", f"{requirements}"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) @@ -150,7 +150,7 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: return if not silent: - Logger.print_info("Updating package list...") + Logger.print_status("Updating package list...") try: command = ["sudo", "apt-get", "update"] @@ -193,11 +193,8 @@ def create_directory(_dir: Path) -> None: """ try: if not os.path.isdir(_dir): - Logger.print_info(f"Create directory: {_dir}") os.makedirs(_dir, exist_ok=True) - Logger.print_ok("Directory created!") - else: - Logger.print_info(f"Directory already exists: {_dir}\nSkip creation ...") + Logger.print_ok(f"Created directory: {_dir}") except OSError as e: Logger.print_error(f"Error creating folder: {e}") raise From e35e44a76a52baaaadab7190dc4a8a28b8a7b508 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Dec 2023 23:08:41 +0100 Subject: [PATCH 031/137] refactor(kiauh): move create_folders to BaseInstance Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/base_instance.py | 25 ++++++++++++++++++++ kiauh/modules/klipper/klipper.py | 14 +---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 2fdb188..9e8143f 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -14,6 +14,7 @@ from pathlib import Path from typing import List, Union, Optional, Type, TypeVar from kiauh.utils.constants import SYSTEMD, CURRENT_USER +from kiauh.utils.system_utils import create_directory B = TypeVar(name="B", bound="BaseInstance", covariant=True) @@ -37,6 +38,7 @@ class BaseInstance(ABC): self._log_dir = f"{self.data_dir}/logs" self._comms_dir = f"{self.data_dir}/comms" self._sysd_dir = f"{self.data_dir}/systemd" + self._gcodes_dir = f"{self.data_dir}/gcodes" @property def instance_type(self) -> Type["BaseInstance"]: @@ -110,6 +112,14 @@ class BaseInstance(ABC): def sysd_dir(self, value: str): self._sysd_dir = value + @property + def gcodes_dir(self): + return self._gcodes_dir + + @gcodes_dir.setter + def gcodes_dir(self, value: str): + self._gcodes_dir = value + @abstractmethod def create(self) -> None: raise NotImplementedError("Subclasses must implement the create method") @@ -118,6 +128,21 @@ class BaseInstance(ABC): def delete(self, del_remnants: bool) -> None: raise NotImplementedError("Subclasses must implement the delete method") + def create_folders(self, add_dirs: List[str] = None) -> None: + dirs = [ + self.data_dir, + self.cfg_dir, + self.log_dir, + self.comms_dir, + self.sysd_dir, + ] + + if add_dirs: + dirs.extend(add_dirs) + + for _dir in dirs: + create_directory(Path(_dir)) + def get_service_file_name(self, extension: bool = False) -> str: name = f"{self.__class__.__name__.lower()}" if self.suffix is not None: diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index e4aa570..636d173 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -19,7 +19,6 @@ from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import create_directory # noinspection PyMethodMayBeStatic @@ -47,7 +46,7 @@ class Klipper(BaseInstance): env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env") try: - self.create_folder_structure() + self.create_folders() self.write_service_file( service_template_path, service_file_target, env_file_target ) @@ -79,17 +78,6 @@ class Klipper(BaseInstance): if del_remnants: self._delete_klipper_remnants() - def create_folder_structure(self) -> None: - dirs = [ - self.data_dir, - self.cfg_dir, - self.log_dir, - self.comms_dir, - self.sysd_dir, - ] - for _dir in dirs: - create_directory(Path(_dir)) - def write_service_file( self, service_template_path: str, service_file_target: str, env_file_target: str ): From 57f34b07c60361682afde58bf61b859920a37a6e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Dec 2023 23:09:43 +0100 Subject: [PATCH 032/137] refactor(utils): add more util functions Signed-off-by: Dominik Willner --- kiauh/utils/system_utils.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index d0d9276..ec1026e 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -14,6 +14,7 @@ import shutil import subprocess import sys import time +import socket from pathlib import Path from typing import List @@ -213,3 +214,33 @@ def mask_system_service(service_name: str) -> None: log = f"Unable to mask system service {service_name}: {e.stderr.decode()}" Logger.print_error(log) raise + + +def check_file_exists(file_path: Path) -> bool: + """ + Helper function for checking the existence of a file where + elevated permissions are required | + :param file_path: the absolute path of the file to check + :return: True if file exists, otherwise False + """ + try: + command = ["sudo", "find", file_path] + subprocess.check_output(command, stderr=subprocess.DEVNULL) + return True + except subprocess.CalledProcessError: + return False + + +# this feels hacky and not quite right, but for now it works +# see: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib +def get_ipv4_addr() -> str: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0) + try: + # doesn't even have to be reachable + s.connect(("192.255.255.255", 1)) + return s.getsockname()[0] + except Exception: + return "127.0.0.1" + finally: + s.close() From de20f0c4122a25ce3f60287c08ff99288e136439 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Dec 2023 23:10:43 +0100 Subject: [PATCH 033/137] refactor(ConfigManager): allow to take in any config file Signed-off-by: Dominik Willner --- kiauh/__init__.py | 3 ++- kiauh/core/config_manager/config_manager.py | 15 ++------------- kiauh/modules/klipper/klipper_setup.py | 5 +++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/kiauh/__init__.py b/kiauh/__init__.py index d30786d..662cd28 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -9,6 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from os.path import dirname, abspath +from os.path import join, dirname, abspath APPLICATION_ROOT = dirname(dirname(abspath(__file__))) +KIAUH_CFG = join(APPLICATION_ROOT, "kiauh.cfg") diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index a1cd757..593773f 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -9,19 +9,16 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import configparser -from pathlib import Path from typing import Union -from kiauh import APPLICATION_ROOT from kiauh.utils.logger import Logger # noinspection PyMethodMayBeStatic class ConfigManager: - def __init__(self): - self.config_file = self._get_cfg_location() + def __init__(self, cfg_file: str): + self.config_file = cfg_file self.config = configparser.ConfigParser() def read_config(self) -> None: @@ -56,11 +53,3 @@ class ConfigManager: def set_value(self, section: str, key: str, value: str): self.config.set(section, key, value) - - def check_config_exist(self) -> bool: - return True if self._get_cfg_location() else False - - def _get_cfg_location(self) -> str: - cfg_path = os.path.join(APPLICATION_ROOT, "kiauh.cfg") - - return cfg_path if Path(cfg_path).exists() else None diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index fb00924..13008c3 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -14,6 +14,7 @@ import subprocess from pathlib import Path from typing import List, Union +from kiauh import KIAUH_CFG from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager @@ -135,7 +136,7 @@ def install_klipper( def setup_klipper_prerequesites() -> None: - cm = ConfigManager() + cm = ConfigManager(cfg_file=KIAUH_CFG) cm.read_config() repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL) @@ -255,7 +256,7 @@ def update_klipper() -> None: if not get_confirm("Update Klipper now?"): return - cm = ConfigManager() + cm = ConfigManager(cfg_file=KIAUH_CFG) cm.read_config() if cm.get_value("kiauh", "backup_before_update"): From 420b193f4bcb3af5b193dbf9f3dd8ee9ab51380b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Dec 2023 23:13:28 +0100 Subject: [PATCH 034/137] feat(Moonraker): implement Moonraker Signed-off-by: Dominik Willner --- kiauh/core/menus/install_menu.py | 3 +- kiauh/core/menus/remove_menu.py | 3 +- kiauh/modules/moonraker/__init__.py | 31 ++ kiauh/modules/moonraker/moonraker.py | 162 ++++++++ kiauh/modules/moonraker/moonraker_dialogs.py | 72 ++++ kiauh/modules/moonraker/moonraker_setup.py | 346 ++++++++++++++++++ kiauh/modules/moonraker/res/moonraker.conf | 30 ++ kiauh/modules/moonraker/res/moonraker.env | 1 + kiauh/modules/moonraker/res/moonraker.service | 19 + 9 files changed, 665 insertions(+), 2 deletions(-) create mode 100644 kiauh/modules/moonraker/__init__.py create mode 100644 kiauh/modules/moonraker/moonraker.py create mode 100644 kiauh/modules/moonraker/moonraker_dialogs.py create mode 100644 kiauh/modules/moonraker/moonraker_setup.py create mode 100644 kiauh/modules/moonraker/res/moonraker.conf create mode 100644 kiauh/modules/moonraker/res/moonraker.env create mode 100644 kiauh/modules/moonraker/res/moonraker.service diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index c1e2646..03c82f1 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -14,6 +14,7 @@ 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.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT @@ -65,7 +66,7 @@ class InstallMenu(BaseMenu): klipper_setup.run_klipper_setup(install=True) def install_moonraker(self): - print("install_moonraker") + moonraker_setup.run_moonraker_setup(install=True) def install_mainsail(self): print("install_mainsail") diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 51b3d44..1271557 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -14,6 +14,7 @@ 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.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_RED, RESET_FORMAT @@ -73,7 +74,7 @@ class RemoveMenu(BaseMenu): klipper_setup.run_klipper_setup(install=False) def remove_moonraker(self): - print("remove_moonraker") + moonraker_setup.run_moonraker_setup(install=False) def remove_mainsail(self): print("remove_mainsail") diff --git a/kiauh/modules/moonraker/__init__.py b/kiauh/modules/moonraker/__init__.py new file mode 100644 index 0000000..af0e88f --- /dev/null +++ b/kiauh/modules/moonraker/__init__.py @@ -0,0 +1,31 @@ +#!/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 os +from pathlib import Path + +MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) + +MOONRAKER_DIR = f"{Path.home()}/moonraker" +MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env" +MOONRAKER_REQUIREMENTS_TXT = f"{MOONRAKER_DIR}/scripts/moonraker-requirements.txt" +DEFAULT_MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker" +DEFAULT_MOONRAKER_PORT = 7125 + +# introduced due to +# https://github.com/Arksine/moonraker/issues/349 +# https://github.com/Arksine/moonraker/pull/346 +POLKIT_LEGACY_FILE = "/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla" +POLKIT_FILE = "/etc/polkit-1/rules.d/moonraker.rules" +POLKIT_USR_FILE = "/usr/share/polkit-1/rules.d/moonraker.rules" +POLKIT_SCRIPT = f"{Path.home()}/moonraker/scripts/set-policykit-rules.sh" + +EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..." diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py new file mode 100644 index 0000000..df648cf --- /dev/null +++ b/kiauh/modules/moonraker/moonraker.py @@ -0,0 +1,162 @@ +#!/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 os +import shutil +import subprocess +from pathlib import Path +from typing import List, Union + +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.base_instance import BaseInstance +from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH +from kiauh.utils.constants import SYSTEMD +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class Moonraker(BaseInstance): + @classmethod + def blacklist(cls) -> List[str]: + return ["None", "mcu"] + + def __init__(self, suffix: str = None): + super().__init__(instance_type=self, suffix=suffix) + self.moonraker_dir = MOONRAKER_DIR + self.env_dir = MOONRAKER_ENV_DIR + self.cfg_file = self._get_cfg() + self.port = self._get_port() + self.backup_dir = f"{self.data_dir}/backup" + self.certs_dir = f"{self.data_dir}/certs" + self.db_dir = f"{self.data_dir}/database" + self.log = f"{self.log_dir}/moonraker.log" + + def create(self, create_example_cfg: bool = False) -> None: + Logger.print_status("Creating new Moonraker Instance ...") + service_template_path = os.path.join(MODULE_PATH, "res", "moonraker.service") + env_template_file_path = os.path.join(MODULE_PATH, "res", "moonraker.env") + service_file_name = self.get_service_file_name(extension=True) + service_file_target = f"{SYSTEMD}/{service_file_name}" + env_file_target = os.path.abspath(f"{self.sysd_dir}/moonraker.env") + + try: + self.create_folders([self.backup_dir, self.certs_dir, self.db_dir]) + self.write_service_file( + service_template_path, service_file_target, env_file_target + ) + self.write_env_file(env_template_file_path, env_file_target) + + except subprocess.CalledProcessError as e: + Logger.print_error( + f"Error creating service file {service_file_target}: {e}" + ) + raise + except OSError as e: + Logger.print_error(f"Error writing file: {e}") + raise + + def delete(self, del_remnants: bool) -> None: + service_file = self.get_service_file_name(extension=True) + service_file_path = self.get_service_file_path() + + Logger.print_status(f"Deleting Moonraker Instance: {service_file}") + + try: + command = ["sudo", "rm", "-f", service_file_path] + subprocess.run(command, check=True) + Logger.print_ok(f"Service file deleted: {service_file_path}") + except subprocess.CalledProcessError as e: + 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: str, service_file_target: str, env_file_target: str + ): + service_content = self._prep_service_file( + service_template_path, env_file_target + ) + command = ["sudo", "tee", service_file_target] + subprocess.run( + command, + input=service_content.encode(), + stdout=subprocess.DEVNULL, + check=True, + ) + Logger.print_ok(f"Service file created: {service_file_target}") + + def write_env_file(self, env_template_file_path: str, env_file_target: str): + env_file_content = self._prep_env_file(env_template_file_path) + with open(env_file_target, "w") as env_file: + 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(Path(self.moonraker_dir)) + Logger.print_status(f"Delete {self.env_dir} ...") + shutil.rmtree(Path(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, env_file_path): + try: + with open(service_template_path, "r") as template_file: + template_content = template_file.read() + except FileNotFoundError: + Logger.print_error( + f"Unable to open {service_template_path} - File not found" + ) + raise + service_content = template_content.replace("%USER%", self.user) + service_content = service_content.replace("%MOONRAKER_DIR%", self.moonraker_dir) + service_content = service_content.replace("%ENV%", self.env_dir) + service_content = service_content.replace("%ENV_FILE%", env_file_path) + return service_content + + def _prep_env_file(self, env_template_file_path): + try: + with open(env_template_file_path, "r") as env_file: + env_template_file_content = env_file.read() + except FileNotFoundError: + Logger.print_error( + f"Unable to open {env_template_file_path} - File not found" + ) + raise + env_file_content = env_template_file_content.replace( + "%MOONRAKER_DIR%", self.moonraker_dir + ) + env_file_content = env_file_content.replace("%PRINTER_DATA%", self.data_dir) + return env_file_content + + def _get_cfg(self): + cfg_file_loc = f"{self.cfg_dir}/moonraker.conf" + if Path(cfg_file_loc).is_file(): + return cfg_file_loc + return None + + def _get_port(self) -> Union[int, None]: + if self.cfg_file is None: + return None + + cm = ConfigManager(cfg_file=self.cfg_file) + cm.read_config() + port = cm.get_value("server", "port") + + return int(port) if port is not None else port diff --git a/kiauh/modules/moonraker/moonraker_dialogs.py b/kiauh/modules/moonraker/moonraker_dialogs.py new file mode 100644 index 0000000..6e79b49 --- /dev/null +++ b/kiauh/modules/moonraker/moonraker_dialogs.py @@ -0,0 +1,72 @@ +#!/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 typing import List + +from kiauh.core.menus.base_menu import print_back_footer +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN + + +def print_moonraker_overview( + klipper_instances: List[Klipper], + moonraker_instances: List[Moonraker], + show_index=False, + show_select_all=False, +): + headline = f"{COLOR_GREEN}The following instances were found:{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + |{headline:^64}| + |-------------------------------------------------------| + """ + )[1:] + + if show_select_all: + select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}" + dialog += f"| {select_all:<63}|\n" + dialog += "| |\n" + + instance_map = { + k.get_service_file_name(): k.get_service_file_name().replace( + "klipper", "moonraker" + ) + if k.suffix in [m.suffix for m in moonraker_instances] + else "" + for k in klipper_instances + } + + for i, k in enumerate(instance_map): + mr_name = instance_map.get(k) + m = f"<-> {mr_name}" if mr_name != "" else "" + line = f"{COLOR_CYAN}{f'{i})' if show_index else '●'} {k} {m} {RESET_FORMAT}" + dialog += f"| {line:<63}|\n" + + warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}" + warn_l2 = f"{COLOR_YELLOW}If you select an instance with an existing Moonraker{RESET_FORMAT}" + warn_l3 = f"{COLOR_YELLOW}instance, that Moonraker instance will be re-created!{RESET_FORMAT}" + warning = textwrap.dedent( + f""" + | | + |-------------------------------------------------------| + | {warn_l1:<63}| + | {warn_l2:<63}| + | {warn_l3:<63}| + """ + )[1:] + + dialog += warning + + print(dialog, end="") + print_back_footer() diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py new file mode 100644 index 0000000..0ca2e2f --- /dev/null +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -0,0 +1,346 @@ +#!/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 os +import subprocess +from pathlib import Path +from typing import List + +from kiauh import KIAUH_CFG +from kiauh.core.backup_manager.backup_manager import BackupManager +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.klipper.klipper_dialogs import ( + print_instance_overview, + print_update_warn_dialog, +) +from kiauh.core.repo_manager.repo_manager import RepoManager +from kiauh.modules.moonraker import ( + EXIT_MOONRAKER_SETUP, + DEFAULT_MOONRAKER_REPO_URL, + MOONRAKER_DIR, + MOONRAKER_ENV_DIR, + MOONRAKER_REQUIREMENTS_TXT, + POLKIT_LEGACY_FILE, + POLKIT_FILE, + POLKIT_USR_FILE, + POLKIT_SCRIPT, + DEFAULT_MOONRAKER_PORT, + MODULE_PATH, +) +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.modules.moonraker.moonraker_dialogs import print_moonraker_overview +from kiauh.utils.input_utils import ( + get_confirm, + get_selection_input, +) +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import ( + parse_packages_from_file, + create_python_venv, + install_python_requirements, + update_system_package_lists, + install_system_packages, + check_file_exists, + get_ipv4_addr, +) + + +def run_moonraker_setup(install: bool) -> None: + 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) + + is_klipper_installed = kl_instance_count > 0 + if install and not is_klipper_installed: + Logger.print_warn("Klipper not installed!") + Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") + return + + is_moonraker_installed = mr_instance_count > 0 + if not install and not is_moonraker_installed: + Logger.print_warn("Moonraker not installed!") + return + + 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: + print_moonraker_overview( + klipper_instances, moonraker_instances, show_index=True, show_select_all=True + ) + + options = [str(i) for i in range(len(klipper_instances))] + options.extend(["a", "A", "b", "B"]) + question = "Select Klipper instance to setup Moonraker for" + selection = get_selection_input(question, options).lower() + + instance_names = [] + if selection == "b": + Logger.print_status(EXIT_MOONRAKER_SETUP) + return + + elif selection == "a": + for instance in klipper_instances: + instance_names.append(instance.suffix) + + else: + index = int(selection) + instance_names.append(klipper_instances[index].suffix) + + create_example_cfg = get_confirm("Create example moonraker.conf?") + setup_moonraker_prerequesites() + install_moonraker_polkit() + + ports_in_use = [ + instance.port for instance in moonraker_instances if instance.port is not None + ] + for name in instance_names: + current_instance = Moonraker(suffix=name) + + instance_manager.current_instance = current_instance + instance_manager.create_instance() + instance_manager.enable_instance() + + if create_example_cfg: + cfg_dir = current_instance.cfg_dir + Logger.print_status(f"Creating example moonraker.conf in '{cfg_dir}'") + if current_instance.cfg_file is None: + create_example_moonraker_conf(current_instance, ports_in_use) + Logger.print_ok(f"Example moonraker.conf created in '{cfg_dir}'") + else: + Logger.print_info(f"moonraker.conf in '{cfg_dir}' already exists.") + + instance_manager.start_instance() + + instance_manager.reload_daemon() + + +def setup_moonraker_prerequesites() -> None: + cm = ConfigManager(cfg_file=KIAUH_CFG) + cm.read_config() + + repo = str( + cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL + ) + branch = str(cm.get_value("moonraker", "branch") or "master") + + repo_manager = RepoManager( + repo=repo, + branch=branch, + target_dir=MOONRAKER_DIR, + ) + repo_manager.clone_repo() + + # install moonraker dependencies and create python virtualenv + install_moonraker_packages(Path(MOONRAKER_DIR)) + create_python_venv(Path(MOONRAKER_ENV_DIR)) + moonraker_py_req = Path(MOONRAKER_REQUIREMENTS_TXT) + install_python_requirements(Path(MOONRAKER_ENV_DIR), moonraker_py_req) + + +def install_moonraker_packages(moonraker_dir: Path) -> None: + script = Path(f"{moonraker_dir}/scripts/install-moonraker.sh") + packages = parse_packages_from_file(script) + update_system_package_lists(silent=False) + install_system_packages(packages) + + +def install_moonraker_polkit() -> None: + Logger.print_status("Installing Moonraker policykit rules ...") + + legacy_file_exists = check_file_exists(Path(POLKIT_LEGACY_FILE)) + polkit_file_exists = check_file_exists(Path(POLKIT_FILE)) + usr_file_exists = check_file_exists(Path(POLKIT_USR_FILE)) + + if legacy_file_exists or (polkit_file_exists and usr_file_exists): + Logger.print_info("Moonraker policykit rules are already installed.") + return + + try: + command = [POLKIT_SCRIPT, "--disable-systemctl"] + result = subprocess.run( + command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True + ) + if result.returncode != 0 or result.stderr: + Logger.print_error(f"{result.stderr}", False) + Logger.print_error("Installing Moonraker policykit rules failed!") + return + + Logger.print_ok("Moonraker policykit rules successfully installed!") + except subprocess.CalledProcessError as e: + log = f"Error while installing Moonraker policykit rules: {e.stderr.decode()}" + Logger.print_error(log) + + +def remove_moonraker( + instance_manager: InstanceManager, instance_list: List[Moonraker] +) -> None: + print_instance_overview(instance_list, True, True) + + options = [str(i) for i in range(len(instance_list))] + options.extend(["a", "A", "b", "B"]) + + 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 Path(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 update_moonraker() -> None: + print_update_warn_dialog() + if not get_confirm("Update Moonraker now?"): + return + + cm = ConfigManager(cfg_file=KIAUH_CFG) + cm.read_config() + + if cm.get_value("kiauh", "backup_before_update"): + backup_manager = BackupManager(source=MOONRAKER_DIR, backup_name="moonraker") + backup_manager.backup() + backup_manager.backup_name = "moonraker-env" + backup_manager.source = MOONRAKER_ENV_DIR + backup_manager.backup() + + instance_manager = InstanceManager(Moonraker) + instance_manager.stop_all_instance() + + repo = str( + cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL + ) + branch = str(cm.get_value("moonraker", "branch") or "master") + + repo_manager = RepoManager( + repo=repo, + branch=branch, + target_dir=MOONRAKER_DIR, + ) + repo_manager.pull_repo() + instance_manager.start_all_instance() + + +def create_example_moonraker_conf(instance: Moonraker, ports: List[int]) -> None: + port = max(ports) + 1 if ports else DEFAULT_MOONRAKER_PORT + ports.append(port) + instance.port = port + example_cfg_path = os.path.join(MODULE_PATH, "res", "moonraker.conf") + + with open(f"{instance.cfg_dir}/moonraker.conf", "w") as cfg: + cfg.write(_prep_example_moonraker_conf(instance, example_cfg_path)) + + +def _prep_example_moonraker_conf(instance: Moonraker, example_cfg_path: str) -> str: + try: + with open(example_cfg_path, "r") as cfg: + example_cfg_content = cfg.read() + except FileNotFoundError: + Logger.print_error(f"Unable to open {example_cfg_path} - File not found") + raise + + example_cfg_content = example_cfg_content.replace("%PORT%", str(instance.port)) + example_cfg_content = example_cfg_content.replace( + "%UDS%", f"{instance.comms_dir}/klippy.sock" + ) + + ip = get_ipv4_addr().split(".")[:2] + ip.extend(["0", "0/16"]) + example_cfg_content = example_cfg_content.replace("%LAN%", ".".join(ip)) + + return example_cfg_content diff --git a/kiauh/modules/moonraker/res/moonraker.conf b/kiauh/modules/moonraker/res/moonraker.conf new file mode 100644 index 0000000..d6eadd8 --- /dev/null +++ b/kiauh/modules/moonraker/res/moonraker.conf @@ -0,0 +1,30 @@ +[server] +host: 0.0.0.0 +port: %PORT% +klippy_uds_address: %UDS% + +[authorization] +trusted_clients: + %LAN% + 10.0.0.0/8 + 127.0.0.0/8 + 169.254.0.0/16 + 172.16.0.0/12 + 192.168.0.0/16 + FE80::/10 + ::1/128 +cors_domains: + *.lan + *.local + *://localhost + *://localhost:* + *://my.mainsail.xyz + *://app.fluidd.xyz + +[octoprint_compat] + +[history] + +[update_manager] +channel: dev +refresh_interval: 168 diff --git a/kiauh/modules/moonraker/res/moonraker.env b/kiauh/modules/moonraker/res/moonraker.env new file mode 100644 index 0000000..bca6af5 --- /dev/null +++ b/kiauh/modules/moonraker/res/moonraker.env @@ -0,0 +1 @@ +MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%" \ No newline at end of file diff --git a/kiauh/modules/moonraker/res/moonraker.service b/kiauh/modules/moonraker/res/moonraker.service new file mode 100644 index 0000000..696d7ba --- /dev/null +++ b/kiauh/modules/moonraker/res/moonraker.service @@ -0,0 +1,19 @@ +[Unit] +Description=API Server for Klipper SV1 +Documentation=https://moonraker.readthedocs.io/ +Requires=network-online.target +After=network-online.target + +[Install] +WantedBy=multi-user.target + +[Service] +Type=simple +User=%USER% +SupplementaryGroups=moonraker-admin +RemainAfterExit=yes +WorkingDirectory=%MOONRAKER_DIR% +EnvironmentFile=%ENV_FILE% +ExecStart=%ENV%/bin/python $MOONRAKER_ARGS +Restart=always +RestartSec=10 From 9a1a66aa64bc2359724ebd842ccf767187c044de Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 4 Dec 2023 21:33:35 +0100 Subject: [PATCH 035/137] docs(utils): add docstrings Signed-off-by: Dominik Willner --- kiauh/__init__.py | 2 + kiauh/core/backup_manager/backup_manager.py | 2 +- kiauh/utils/common.py | 2 +- kiauh/utils/constants.py | 3 -- kiauh/utils/input_utils.py | 44 +++++++++++++++++++++ kiauh/utils/system_utils.py | 9 ++++- 6 files changed, 55 insertions(+), 7 deletions(-) diff --git a/kiauh/__init__.py b/kiauh/__init__.py index 662cd28..8fc1d9a 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -10,6 +10,8 @@ # ======================================================================= # from os.path import join, dirname, abspath +from pathlib import Path APPLICATION_ROOT = dirname(dirname(abspath(__file__))) KIAUH_CFG = join(APPLICATION_ROOT, "kiauh.cfg") +KIAUH_BACKUP_DIR = f"{Path.home()}/kiauh-backups" diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index ee6b782..db82162 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -12,8 +12,8 @@ import shutil from pathlib import Path +from kiauh import KIAUH_BACKUP_DIR from kiauh.utils.common import get_current_date -from kiauh.utils.constants import KIAUH_BACKUP_DIR from kiauh.utils.logger import Logger diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 4b6a48a..b6a8e87 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -16,7 +16,7 @@ from typing import Dict, Literal def get_current_date() -> Dict[Literal["date", "time"], str]: """ Get the current date | - :return: a Dict holding a date and time key:value pair + :return: Dict holding a date and time key:value pair """ now: datetime = datetime.today() date: str = now.strftime("%Y-%m-%d") diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 37d4a33..66b00ff 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -11,7 +11,6 @@ import os import pwd -from pathlib import Path # text colors and formats COLOR_WHITE = "\033[37m" # white @@ -21,8 +20,6 @@ COLOR_YELLOW = "\033[93m" # bright yellow COLOR_RED = "\033[91m" # bright red COLOR_CYAN = "\033[96m" # bright cyan RESET_FORMAT = "\033[0m" # reset format -# kiauh -KIAUH_BACKUP_DIR = f"{Path.home()}/kiauh-backups" # current user CURRENT_USER = pwd.getpwuid(os.getuid())[0] SYSTEMD = "/etc/systemd/system" diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index dcddda0..7f9f1f5 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -19,6 +19,13 @@ from kiauh.utils.logger import Logger def get_confirm( question: str, default_choice=True, allow_go_back=False ) -> Union[bool, None]: + """ + Helper method for validating confirmation (yes/no) user input. | + :param question: The question to display + :param default_choice: A default if input was submitted without input + :param allow_go_back: Navigate back to a previous dialog + :return: Either True or False, or None on go_back + """ options_confirm = ["y", "yes"] options_decline = ["n", "no"] options_go_back = ["b", "B"] @@ -48,6 +55,15 @@ def get_confirm( def get_number_input( question: str, min_count: int, max_count=None, default=None, allow_go_back=False ) -> Union[int, None]: + """ + Helper method to get a number input from the user + :param question: The question to display + :param min_count: The lowest allowed value + :param max_count: The highest allowed value (or None) + :param default: Optional default value + :param allow_go_back: Navigate back to a previous dialog + :return: Either the validated number input, or None on go_back + """ options_go_back = ["b", "B"] _question = format_question(question, default) while True: @@ -65,6 +81,13 @@ def get_number_input( def get_string_input(question: str, exclude=Optional[List], default=None) -> str: + """ + Helper method to get a string input from the user + :param question: The question to display + :param exclude: List of strings which are not allowed + :param default: Optional default value + :return: The validated string value + """ while True: _input = input(format_question(question, default)).strip() @@ -77,6 +100,13 @@ def get_string_input(question: str, exclude=Optional[List], default=None) -> str def get_selection_input(question: str, option_list: List, default=None) -> str: + """ + Helper method to get a selection from a list of options from the user + :param question: The question to display + :param option_list: The list of options the user can select from + :param default: Optional default value + :return: The option that was selected by the user + """ while True: _input = input(format_question(question, default)).strip() @@ -87,6 +117,12 @@ def get_selection_input(question: str, option_list: List, default=None) -> str: def format_question(question: str, default=None) -> str: + """ + Helper method to have a standardized formatting of questions | + :param question: The question to display + :param default: If defined, the default option will be displayed to the user + :return: The formatted question string + """ formatted_q = question if default is not None: formatted_q += f" (default={default})" @@ -95,6 +131,14 @@ def format_question(question: str, default=None) -> str: def validate_number_input(value: str, min_count: int, max_count: int) -> int: + """ + Helper method for a simple number input validation. | + :param value: The value to validate + :param min_count: The lowest allowed value + :param max_count: The highest allowed value (or None) + :return: The validated value as Integer + :raises: ValueError if value is invalid + """ if max_count is not None: if min_count <= int(value) <= max_count: return int(value) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index ec1026e..34a2985 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -40,7 +40,7 @@ def parse_packages_from_file(source_file: Path) -> List[str]: Read the package names from bash scripts, when defined like: PKGLIST="package1 package2 package3" | :param source_file: path of the sourcefile to read from - :return: a list of package names + :return: A list of package names """ packages = [] @@ -169,7 +169,7 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: kill(f"Error updating system package list:\n{e.stderr.decode()}") -def install_system_packages(packages: List) -> None: +def install_system_packages(packages: List[str]) -> None: """ Installs a list of system packages | :param packages: List of system package names @@ -234,6 +234,11 @@ def check_file_exists(file_path: Path) -> bool: # this feels hacky and not quite right, but for now it works # see: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib def get_ipv4_addr() -> str: + """ + Helper function that returns the IPv4 of the current machine + by opening a socket and sending a package to an arbitrary IP. | + :return: Local IPv4 of the current machine + """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(0) try: From e121ba8a629768bace1c7bf9c35472a69eb704bb Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 4 Dec 2023 22:35:36 +0100 Subject: [PATCH 036/137] feat(Moonraker): add python version check Signed-off-by: Dominik Willner --- kiauh/modules/moonraker/moonraker_setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 0ca2e2f..24f8a60 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -11,6 +11,7 @@ import os import subprocess +import sys from pathlib import Path from typing import List @@ -63,6 +64,11 @@ def run_moonraker_setup(install: bool) -> None: mr_instance_list = mr_im.instances mr_instance_count = len(mr_instance_list) + if not (sys.version_info.major >= 4 and sys.version_info.minor >= 7): + Logger.print_error("Versioncheck failed!") + Logger.print_error("Python 3. or newer required to run Moonraker.") + return + is_klipper_installed = kl_instance_count > 0 if install and not is_klipper_installed: Logger.print_warn("Klipper not installed!") From f62c10dc8bd2a546adb54bfbc4c1b345847b0145 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 16 Dec 2023 15:38:23 +0100 Subject: [PATCH 037/137] feat(kiauh): add helper methods to check for installed packages Signed-off-by: Dominik Willner --- kiauh/utils/common.py | 22 +++++++++++++++++++++- kiauh/utils/system_utils.py | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index b6a8e87..949d6a4 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -10,7 +10,11 @@ # ======================================================================= # from datetime import datetime -from typing import Dict, Literal +from typing import Dict, Literal, List + +from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import check_package_install, install_system_packages def get_current_date() -> Dict[Literal["date", "time"], str]: @@ -23,3 +27,19 @@ def get_current_date() -> Dict[Literal["date", "time"], str]: time: str = now.strftime("%H-%M-%S") return {"date": date, "time": time} + + +def check_install_dependencies(deps: List[str]) -> None: + """ + Common helper method to check if dependencies are installed + and if not, install them automatically | + :param deps: List of strings of package names to check if installed + :return: None + """ + requirements = check_package_install(deps) + if requirements: + Logger.print_status("Installing dependencies ...") + Logger.print_info("The following packages need installation:") + for _ in requirements: + print(f"{COLOR_CYAN}● {_}{RESET_FORMAT}") + install_system_packages(requirements) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 34a2985..39d9cac 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -169,6 +169,26 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: kill(f"Error updating system package list:\n{e.stderr.decode()}") +def check_package_install(packages: List[str]) -> List[str]: + """ + Checks the system for installed packages | + :param packages: List of strings of package names + :return: A list containing the names of packages that are not installed + """ + not_installed = [] + for package in packages: + command = ["dpkg-query", "f'${Status}'", "--show", package] + result = subprocess.run( + command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True + ) + if "installed" not in result.stdout.strip("'").split(): + not_installed.append(package) + else: + Logger.print_ok(f"{package} already installed.") + + return not_installed + + def install_system_packages(packages: List[str]) -> None: """ Installs a list of system packages | From f709cf84e7125faecc109bc297643d5bd434fadb Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 16 Dec 2023 15:41:48 +0100 Subject: [PATCH 038/137] feat(kiauh): add helper methods for downloading files Signed-off-by: Dominik Willner --- kiauh/utils/system_utils.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 39d9cac..588f48d 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -11,10 +11,12 @@ import os import shutil +import socket import subprocess import sys import time -import socket +import urllib.error +import urllib.request from pathlib import Path from typing import List @@ -269,3 +271,32 @@ def get_ipv4_addr() -> str: return "127.0.0.1" finally: s.close() + + +def download_file(url: str, target_folder: str, target_name: str, show_progress=True): + target_path = os.path.join(target_folder, target_name) + try: + if show_progress: + urllib.request.urlretrieve(url, target_path, download_progress) + else: + urllib.request.urlretrieve(url, target_path) + except urllib.error.HTTPError as e: + Logger.print_error(f"Download failed! HTTP error occured: {e}") + raise + except urllib.error.URLError as e: + Logger.print_error(f"Download failed! URL error occured: {e}") + raise + except Exception as e: + Logger.print_error(f"Download failed! An error occured: {e}") + raise + + +def download_progress(block_num, block_size, total_size): + downloaded = block_num * block_size + percent = 100 if downloaded >= total_size else downloaded / total_size * 100 + mb = 1024 * 1024 + progress = int(percent / 5) + remaining = "-" * (20 - progress) + dl = f"\rDownloading: [{'#' * progress}{remaining}]{percent:.2f}% ({downloaded/mb:.2f}/{total_size/mb:.2f}MB)" + sys.stdout.write(dl) + sys.stdout.flush() From 545397f5983218577b9cea20ed7770497aeb4b72 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 16 Dec 2023 17:38:01 +0100 Subject: [PATCH 039/137] feat(kiauh): fix typo in check_package_install Signed-off-by: Dominik Willner --- kiauh/utils/system_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 588f48d..9b6fbce 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -179,7 +179,7 @@ def check_package_install(packages: List[str]) -> List[str]: """ not_installed = [] for package in packages: - command = ["dpkg-query", "f'${Status}'", "--show", package] + command = ["dpkg-query", "-f'${Status}'", "--show", package] result = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True ) From 1836beab42b10dacd7f773742e413121bbf1d765 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 00:01:25 +0100 Subject: [PATCH 040/137] feat(klipper): add getter for specific properties Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 636d173..ba9e1b5 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -31,10 +31,26 @@ class Klipper(BaseInstance): super().__init__(instance_type=self, suffix=suffix) self.klipper_dir = KLIPPER_DIR self.env_dir = KLIPPER_ENV_DIR - self.cfg_file = f"{self.cfg_dir}/printer.cfg" - self.log = f"{self.log_dir}/klippy.log" - self.serial = f"{self.comms_dir}/klippy.serial" - self.uds = f"{self.comms_dir}/klippy.sock" + self._cfg_file = f"{self.cfg_dir}/printer.cfg" + self._log = f"{self.log_dir}/klippy.log" + self._serial = f"{self.comms_dir}/klippy.serial" + self._uds = f"{self.comms_dir}/klippy.sock" + + @property + def cfg_file(self) -> str: + return self._cfg_file + + @property + def log(self) -> str: + return self._log + + @property + def serial(self) -> str: + return self._serial + + @property + def uds(self) -> str: + return self._uds def create(self) -> None: Logger.print_status("Creating new Klipper Instance ...") @@ -140,8 +156,8 @@ class Klipper(BaseInstance): env_file_content = env_template_file_content.replace( "%KLIPPER_DIR%", self.klipper_dir ) - env_file_content = env_file_content.replace("%CFG%", self.cfg_file) - env_file_content = env_file_content.replace("%SERIAL%", self.serial) - env_file_content = env_file_content.replace("%LOG%", self.log) - env_file_content = env_file_content.replace("%UDS%", self.uds) + env_file_content = env_file_content.replace("%CFG%", self._cfg_file) + env_file_content = env_file_content.replace("%SERIAL%", self._serial) + env_file_content = env_file_content.replace("%LOG%", self._log) + env_file_content = env_file_content.replace("%UDS%", self._uds) return env_file_content From b20613819e173755e01d783006fff53eb4166bca Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 13:00:46 +0100 Subject: [PATCH 041/137] feat(Logger): add "start" parameter Signed-off-by: Dominik Willner --- kiauh/utils/logger.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index 864bd77..8732878 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -36,26 +36,26 @@ class Logger: pass @staticmethod - def print_info(msg, prefix=True, end="\n") -> None: + def print_info(msg, prefix=True, start="", end="\n") -> None: message = f"[INFO] {msg}" if prefix else msg - print(f"{COLOR_WHITE}{message}{RESET_FORMAT}", end=end) + print(f"{COLOR_WHITE}{start}{message}{RESET_FORMAT}", end=end) @staticmethod - def print_ok(msg, prefix=True, end="\n") -> None: + def print_ok(msg, prefix=True, start="", end="\n") -> None: message = f"[OK] {msg}" if prefix else msg - print(f"{COLOR_GREEN}{message}{RESET_FORMAT}", end=end) + print(f"{COLOR_GREEN}{start}{message}{RESET_FORMAT}", end=end) @staticmethod - def print_warn(msg, prefix=True, end="\n") -> None: + def print_warn(msg, prefix=True, start="", end="\n") -> None: message = f"[WARN] {msg}" if prefix else msg - print(f"{COLOR_YELLOW}{message}{RESET_FORMAT}", end=end) + print(f"{COLOR_YELLOW}{start}{message}{RESET_FORMAT}", end=end) @staticmethod - def print_error(msg, prefix=True, end="\n") -> None: + def print_error(msg, prefix=True, start="", end="\n") -> None: message = f"[ERROR] {msg}" if prefix else msg - print(f"{COLOR_RED}{message}{RESET_FORMAT}", end=end) + print(f"{COLOR_RED}{start}{message}{RESET_FORMAT}", end=end) @staticmethod - def print_status(msg, prefix=True, end="\n") -> None: + def print_status(msg, prefix=True, start="", end="\n") -> None: message = f"\n###### {msg}" if prefix else msg - print(f"{COLOR_MAGENTA}{message}{RESET_FORMAT}", end=end) + print(f"{COLOR_MAGENTA}{start}{message}{RESET_FORMAT}", end=end) From 78cefddb2e35a20f002c31d7f0951437b3ba744a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 13:26:22 +0100 Subject: [PATCH 042/137] feat(InstanceManager): add restart service method Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/instance_manager.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index fe6455f..6891139 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -141,11 +141,26 @@ class InstanceManager: Logger.print_error(f"Error starting {self.instance_service_full}:") Logger.print_error(f"{e}") + def restart_instance(self) -> None: + Logger.print_status(f"Restarting {self.instance_service_full} ...") + try: + command = ["sudo", "systemctl", "restart", self.instance_service_full] + if subprocess.run(command, check=True): + Logger.print_ok(f"{self.instance_service_full} restarted.") + except subprocess.CalledProcessError as e: + Logger.print_error(f"Error restarting {self.instance_service_full}:") + Logger.print_error(f"{e}") + def start_all_instance(self) -> None: for instance in self.instances: self.current_instance = instance self.start_instance() + def restart_all_instance(self) -> None: + for instance in self.instances: + self.current_instance = instance + self.restart_instance() + def stop_instance(self) -> None: Logger.print_status(f"Stopping {self.instance_service_full} ...") try: From a80f0bb0e822e46886c16c1c16ae13401e95c185 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 14:42:53 +0100 Subject: [PATCH 043/137] feat(utils): add several util methods Signed-off-by: Dominik Willner --- kiauh/utils/__init__.py | 7 ++ kiauh/utils/res/common_vars.conf | 6 ++ kiauh/utils/res/nginx_cfg | 96 +++++++++++++++++ kiauh/utils/res/upstreams.conf | 25 +++++ kiauh/utils/system_utils.py | 179 ++++++++++++++++++++++++++++++- 5 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 kiauh/utils/res/common_vars.conf create mode 100644 kiauh/utils/res/nginx_cfg create mode 100644 kiauh/utils/res/upstreams.conf diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index 203c66a..108b521 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -8,5 +8,12 @@ # # # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import os +MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) INVALID_CHOICE = "Invalid choice. Please select a valid value." + +# ================== NGINX =====================# +NGINX_SITES_AVAILABLE = "/etc/nginx/sites-available" +NGINX_SITES_ENABLED = "/etc/nginx/sites-enabled" +NGINX_CONFD = "/etc/nginx/conf.d" diff --git a/kiauh/utils/res/common_vars.conf b/kiauh/utils/res/common_vars.conf new file mode 100644 index 0000000..9c3f85e --- /dev/null +++ b/kiauh/utils/res/common_vars.conf @@ -0,0 +1,6 @@ +# /etc/nginx/conf.d/common_vars.conf + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} \ No newline at end of file diff --git a/kiauh/utils/res/nginx_cfg b/kiauh/utils/res/nginx_cfg new file mode 100644 index 0000000..6303674 --- /dev/null +++ b/kiauh/utils/res/nginx_cfg @@ -0,0 +1,96 @@ +# /etc/nginx/sites-available/%NAME% + +server { + listen %PORT%; + + access_log /var/log/nginx/%NAME%-access.log; + error_log /var/log/nginx/%NAME%-error.log; + + # disable this section on smaller hardware like a pi zero + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_proxied expired no-cache no-store private auth; + gzip_comp_level 4; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml; + + # web_path from %NAME% static files + root %ROOT_DIR%; + + index index.html; + server_name _; + + # disable max upload size checks + client_max_body_size 0; + + # disable proxy request buffering + proxy_request_buffering off; + + location / { + try_files $uri $uri/ /index.html; + } + + location = /index.html { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + location /websocket { + proxy_pass http://apiserver/websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 86400; + } + + location ~ ^/(printer|api|access|machine|server)/ { + proxy_pass http://apiserver$request_uri; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + proxy_read_timeout 600; + } + + location /webcam/ { + postpone_output 0; + proxy_buffering off; + proxy_ignore_headers X-Accel-Buffering; + access_log off; + error_log off; + proxy_pass http://mjpgstreamer1/; + } + + location /webcam2/ { + postpone_output 0; + proxy_buffering off; + proxy_ignore_headers X-Accel-Buffering; + access_log off; + error_log off; + proxy_pass http://mjpgstreamer2/; + } + + location /webcam3/ { + postpone_output 0; + proxy_buffering off; + proxy_ignore_headers X-Accel-Buffering; + access_log off; + error_log off; + proxy_pass http://mjpgstreamer3/; + } + + location /webcam4/ { + postpone_output 0; + proxy_buffering off; + proxy_ignore_headers X-Accel-Buffering; + access_log off; + error_log off; + proxy_pass http://mjpgstreamer4/; + } +} diff --git a/kiauh/utils/res/upstreams.conf b/kiauh/utils/res/upstreams.conf new file mode 100644 index 0000000..d04e04a --- /dev/null +++ b/kiauh/utils/res/upstreams.conf @@ -0,0 +1,25 @@ +# /etc/nginx/conf.d/upstreams.conf +upstream apiserver { + ip_hash; + server 127.0.0.1:7125; +} + +upstream mjpgstreamer1 { + ip_hash; + server 127.0.0.1:8080; +} + +upstream mjpgstreamer2 { + ip_hash; + server 127.0.0.1:8081; +} + +upstream mjpgstreamer3 { + ip_hash; + server 127.0.0.1:8082; +} + +upstream mjpgstreamer4 { + ip_hash; + server 127.0.0.1:8083; +} \ No newline at end of file diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 9b6fbce..f14fbb2 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -18,8 +18,15 @@ import time import urllib.error import urllib.request from pathlib import Path -from typing import List +from typing import List, Literal +from zipfile import ZipFile +from kiauh.utils import ( + NGINX_CONFD, + MODULE_PATH, + NGINX_SITES_AVAILABLE, + NGINX_SITES_ENABLED, +) from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger @@ -273,11 +280,22 @@ def get_ipv4_addr() -> str: s.close() -def download_file(url: str, target_folder: str, target_name: str, show_progress=True): +def download_file( + url: str, target_folder: str, target_name: str, show_progress=True +) -> None: + """ + Helper method for downloading files from a provided URL | + :param url: the url to the file + :param target_folder: the target folder to download the file into + :param target_name: the name of the downloaded file + :param show_progress: show download progress or not + :return: None + """ target_path = os.path.join(target_folder, target_name) try: if show_progress: urllib.request.urlretrieve(url, target_path, download_progress) + sys.stdout.write("\n") else: urllib.request.urlretrieve(url, target_path) except urllib.error.HTTPError as e: @@ -291,7 +309,14 @@ def download_file(url: str, target_folder: str, target_name: str, show_progress= raise -def download_progress(block_num, block_size, total_size): +def download_progress(block_num, block_size, total_size) -> None: + """ + Reporthook method for urllib.request.urlretrieve() method call in download_file() | + :param block_num: + :param block_size: + :param total_size: total filesize in bytes + :return: None + """ downloaded = block_num * block_size percent = 100 if downloaded >= total_size else downloaded / total_size * 100 mb = 1024 * 1024 @@ -300,3 +325,151 @@ def download_progress(block_num, block_size, total_size): dl = f"\rDownloading: [{'#' * progress}{remaining}]{percent:.2f}% ({downloaded/mb:.2f}/{total_size/mb:.2f}MB)" sys.stdout.write(dl) sys.stdout.flush() + + +def unzip(file: str, target_dir: str) -> None: + """ + Helper function to unzip a zip-archive into a target directory | + :param file: the zip-file to unzip + :param target_dir: the target directory to extract the files into + :return: None + """ + with ZipFile(file, "r") as _zip: + _zip.extractall(target_dir) + + +def create_upstream_nginx_cfg() -> None: + """ + Creates an upstream.conf in /etc/nginx/conf.d + :return: None + """ + source = os.path.join(MODULE_PATH, "res", "upstreams.conf") + target = os.path.join(NGINX_CONFD, "upstreams.conf") + try: + command = ["sudo", "cp", source, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create upstreams.conf: {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def create_common_vars_nginx_cfg() -> None: + """ + Creates a common_vars.conf in /etc/nginx/conf.d + :return: None + """ + source = os.path.join(MODULE_PATH, "res", "common_vars.conf") + target = os.path.join(NGINX_CONFD, "common_vars.conf") + try: + command = ["sudo", "cp", source, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create upstreams.conf: {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def create_nginx_cfg(name: str, port: int, root_dir: str) -> None: + """ + Creates an NGINX config from a template file and replaces all placeholders + :param name: name of the config to create + :param port: listen port + :param root_dir: directory of the static files + :return: None + """ + tmp = f"{Path.home()}/{name}.tmp" + shutil.copy(os.path.join(MODULE_PATH, "res", "nginx_cfg"), tmp) + with open(tmp, "r+") as f: + content = f.read() + content = content.replace("%NAME%", name) + content = content.replace("%PORT%", str(port)) + content = content.replace("%ROOT_DIR%", root_dir) + f.seek(0) + f.write(content) + f.truncate() + + target = os.path.join(NGINX_SITES_AVAILABLE, name) + try: + command = ["sudo", "mv", tmp, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create '{target}': {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def delete_default_nginx_cfg() -> None: + """ + Deletes a default NGINX config + :return: None + """ + default_cfg = Path("/etc/nginx/sites-enabled/default") + if not check_file_exists(default_cfg): + return + + try: + command = ["sudo", "rm", default_cfg] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to delete '{default_cfg}': {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def enable_nginx_cfg(name: str) -> None: + """ + Helper method to enable an NGINX config | + :param name: name of the config to enable + :return: None + """ + source = os.path.join(NGINX_SITES_AVAILABLE, name) + target = os.path.join(NGINX_SITES_ENABLED, name) + if check_file_exists(Path(target)): + return + + try: + command = ["sudo", "ln", "-s", source, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create symlink: {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def set_nginx_permissions() -> None: + """ + Check if permissions of the users home directory + grant execution rights to group and other and set them if not set. + Required permissions for NGINX to be able to serve Mainsail/Fluidd. + This seems to have become necessary with Ubuntu 21+. | + :return: None + """ + cmd1 = f"ls -ld {Path.home()} | cut -d' ' -f1" + homedir_perm = subprocess.run(cmd1, shell=True, stdout=subprocess.PIPE, text=True) + homedir_perm = homedir_perm.stdout + + if homedir_perm.count("x") < 3: + Logger.print_status("Granting NGINX the required permissions ...") + subprocess.run(["chmod", "og+x", Path.home()]) + Logger.print_ok("Permissions granted.") + + +def control_systemd_service( + name: str, action: Literal["start", "stop", "restart", "disable"] +) -> None: + """ + Helper method to execute several actions for a specific systemd service. | + :param name: the service name + :param action: Either "start", "stop", "restart" or "disable" + :return: None + """ + try: + Logger.print_status(f"{action.capitalize()} {name}.service ...") + command = ["sudo", "systemctl", action, f"{name}.service"] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + Logger.print_ok(f"OK!") + except subprocess.CalledProcessError as e: + log = f"Failed to {action} {name}.service: {e.stderr.decode()}" + Logger.print_error(log) + raise From 80a953a58751ffce8c901b72d0df6e0ca6024070 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 16:10:20 +0100 Subject: [PATCH 044/137] fix(Moonraker): typo in python version check Signed-off-by: Dominik Willner --- kiauh/modules/moonraker/moonraker_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 24f8a60..47df121 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -64,9 +64,9 @@ def run_moonraker_setup(install: bool) -> None: mr_instance_list = mr_im.instances mr_instance_count = len(mr_instance_list) - if not (sys.version_info.major >= 4 and sys.version_info.minor >= 7): + if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7): Logger.print_error("Versioncheck failed!") - Logger.print_error("Python 3. or newer required to run Moonraker.") + Logger.print_error("Python 3.7 or newer required to run Moonraker.") return is_klipper_installed = kl_instance_count > 0 From 59d8867c8c8e912f8755013db1f1570482894edb Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 16:12:43 +0100 Subject: [PATCH 045/137] fix(kiauh): copy&paste issue in repo url for Moonraker Signed-off-by: Dominik Willner --- kiauh.cfg.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh.cfg.example b/kiauh.cfg.example index 0ba34db..72f4a21 100644 --- a/kiauh.cfg.example +++ b/kiauh.cfg.example @@ -7,6 +7,6 @@ branch: master method: https [moonraker] -repository_url: https://github.com/Klipper3d/klipper +repository_url: https://github.com/Arksine/moonraker branch: master method: https From 1178d3c73009464ea4de76dd089dfc9bee1e916b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 16:18:44 +0100 Subject: [PATCH 046/137] refactor(Moonraker): skip selection dialog if there is only 1 klipper instance Signed-off-by: Dominik Willner --- kiauh/modules/moonraker/moonraker_setup.py | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 47df121..df2eb7c 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -103,26 +103,30 @@ def install_moonraker( moonraker_instances: List[Moonraker], klipper_instances: List[Klipper], ) -> None: - print_moonraker_overview( - klipper_instances, moonraker_instances, show_index=True, show_select_all=True - ) - - options = [str(i) for i in range(len(klipper_instances))] - options.extend(["a", "A", "b", "B"]) - question = "Select Klipper instance to setup Moonraker for" - selection = get_selection_input(question, options).lower() + selected_klipper_instance = 0 + if len(klipper_instances) > 1: + print_moonraker_overview( + klipper_instances, + moonraker_instances, + show_index=True, + show_select_all=True, + ) + options = [str(i) for i in range(len(klipper_instances))] + options.extend(["a", "A", "b", "B"]) + question = "Select Klipper instance to setup Moonraker for" + selected_klipper_instance = get_selection_input(question, options).lower() instance_names = [] - if selection == "b": + if selected_klipper_instance == "b": Logger.print_status(EXIT_MOONRAKER_SETUP) return - elif selection == "a": + elif selected_klipper_instance == "a": for instance in klipper_instances: instance_names.append(instance.suffix) else: - index = int(selection) + index = int(selected_klipper_instance) instance_names.append(klipper_instances[index].suffix) create_example_cfg = get_confirm("Create example moonraker.conf?") From 30b44144696c32531f92ada0457d90147d68f90c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 18:00:16 +0100 Subject: [PATCH 047/137] feat(Klipper): create example printer.cfg if wanted Signed-off-by: Dominik Willner --- kiauh/modules/klipper/__init__.py | 3 +++ kiauh/modules/klipper/klipper.py | 12 ++++++++++-- kiauh/modules/klipper/klipper_setup.py | 18 ++++++++++++++++-- kiauh/modules/klipper/klipper_utils.py | 21 ++++++++++++++++++++- kiauh/modules/klipper/res/printer.cfg | 11 +++++++++++ 5 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 kiauh/modules/klipper/res/printer.cfg diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/modules/klipper/__init__.py index 2b0d427..f355f5d 100644 --- a/kiauh/modules/klipper/__init__.py +++ b/kiauh/modules/klipper/__init__.py @@ -9,8 +9,11 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import os from pathlib import Path +MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) + KLIPPER_DIR = f"{Path.home()}/klipper" KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env" KLIPPER_REQUIREMENTS_TXT = f"{KLIPPER_DIR}/scripts/klippy-requirements.txt" diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index ba9e1b5..02eb639 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -31,7 +31,7 @@ class Klipper(BaseInstance): super().__init__(instance_type=self, suffix=suffix) self.klipper_dir = KLIPPER_DIR self.env_dir = KLIPPER_ENV_DIR - self._cfg_file = f"{self.cfg_dir}/printer.cfg" + self._cfg_file = self._get_cfg() self._log = f"{self.log_dir}/klippy.log" self._serial = f"{self.comms_dir}/klippy.serial" self._uds = f"{self.comms_dir}/klippy.sock" @@ -156,8 +156,16 @@ class Klipper(BaseInstance): env_file_content = env_template_file_content.replace( "%KLIPPER_DIR%", self.klipper_dir ) - env_file_content = env_file_content.replace("%CFG%", self._cfg_file) + env_file_content = env_file_content.replace( + "%CFG%", f"{self.cfg_dir}/printer.cfg" + ) env_file_content = env_file_content.replace("%SERIAL%", self._serial) env_file_content = env_file_content.replace("%LOG%", self._log) env_file_content = env_file_content.replace("%UDS%", self._uds) return env_file_content + + def _get_cfg(self): + cfg_file_loc = f"{self.cfg_dir}/printer.cfg" + if Path(cfg_file_loc).is_file(): + return cfg_file_loc + return None diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 13008c3..dba98c2 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -38,6 +38,7 @@ from kiauh.modules.klipper.klipper_utils import ( handle_disruptive_system_packages, check_user_groups, handle_single_to_multi_conversion, + create_example_printer_cfg, ) from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils.input_utils import ( @@ -106,6 +107,8 @@ def install_klipper( Logger.print_status(EXIT_KLIPPER_SETUP) return + create_example_cfg = get_confirm("Create example printer.cfg?") + if len(instance_list) < 1: setup_klipper_prerequesites() @@ -117,13 +120,24 @@ def install_klipper( for name in instance_names: if convert_single_to_multi: - handle_single_to_multi_conversion(instance_manager, name) + current_instance = handle_single_to_multi_conversion(instance_manager, name) convert_single_to_multi = False else: - instance_manager.current_instance = Klipper(suffix=name) + current_instance = Klipper(suffix=name) + instance_manager.current_instance = current_instance instance_manager.create_instance() instance_manager.enable_instance() + + if create_example_cfg: + cfg_dir = current_instance.cfg_dir + Logger.print_status(f"Creating example printer.cfg in '{cfg_dir}'") + if current_instance.cfg_file is None: + create_example_printer_cfg(current_instance) + Logger.print_ok(f"Example printer.cfg created in '{cfg_dir}'") + else: + Logger.print_info(f"printer.cfg in '{cfg_dir}' already exists.") + instance_manager.start_instance() instance_manager.reload_daemon() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index aa11124..fcbc99a 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -12,12 +12,15 @@ import os import re import grp +import shutil import subprocess import textwrap from typing import List, Union +from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper import MODULE_PATH from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, @@ -92,7 +95,7 @@ def handle_existing_multi_instance_names( def handle_single_to_multi_conversion( instance_manager: InstanceManager, name: str -) -> None: +) -> Klipper: instance_list = instance_manager.instances instance_manager.current_instance = instance_list[0] old_data_dir_name = instance_manager.instances[0].data_dir @@ -103,6 +106,7 @@ def handle_single_to_multi_conversion( new_data_dir_name = instance_manager.current_instance.data_dir try: os.rename(old_data_dir_name, new_data_dir_name) + return instance_manager.current_instance except OSError as e: log = f"Cannot rename {old_data_dir_name} to {new_data_dir_name}:\n{e}" Logger.print_error(log) @@ -188,3 +192,18 @@ def has_custom_names(instance_list: List[Klipper]) -> bool: def get_highest_index(instance_list: List[Klipper]) -> int: indices = [int(instance.suffix.split("-")[-1]) for instance in instance_list] return max(indices) + + +def create_example_printer_cfg(instance: Klipper) -> None: + source = os.path.join(MODULE_PATH, "res", "printer.cfg") + target = os.path.join(instance.cfg_dir, "printer.cfg") + try: + shutil.copy(source, target) + except OSError as e: + Logger.print_error(f"Unable to create example printer.cfg:\n{e}") + return + + cm = ConfigManager(target) + cm.read_config() + cm.set_value("virtual_sdcard", "path", instance.gcodes_dir) + cm.write_config() diff --git a/kiauh/modules/klipper/res/printer.cfg b/kiauh/modules/klipper/res/printer.cfg new file mode 100644 index 0000000..88fe7df --- /dev/null +++ b/kiauh/modules/klipper/res/printer.cfg @@ -0,0 +1,11 @@ +[mcu] +serial: /dev/serial/by-id/ + +[virtual_sdcard] +path: %GCODES_DIR% +on_error_gcode: CANCEL_PRINT + +[printer] +kinematics: none +max_velocity: 1000 +max_accel: 1000 From b83f642a13a37a23838842e4d8d70b461e209491 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 18:03:42 +0100 Subject: [PATCH 048/137] refactor(ConfigManager): logging can be silenced Signed-off-by: Dominik Willner --- kiauh/core/config_manager/config_manager.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 593773f..00cc41b 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -32,15 +32,17 @@ class ConfigManager: with open(self.config_file, "w") as cfg: self.config.write(cfg) - def get_value(self, section: str, key: str) -> Union[str, bool, None]: + def get_value(self, section: str, key: str, silent=False) -> Union[str, bool, None]: if not self.config.has_section(section): - log = f"Section not defined. Unable to read section: [{section}]." - Logger.print_error(log) + if not silent: + log = f"Section not defined. Unable to read section: [{section}]." + Logger.print_error(log) return None if not self.config.has_option(section, key): - log = f"Option not defined in section [{section}]. Unable to read option: '{key}'." - Logger.print_error(log) + if not silent: + log = f"Option not defined in section [{section}]. Unable to read option: '{key}'." + Logger.print_error(log) return None value = self.config.get(section, key) From 3865266da15b2da044648a5386370e187968bfe8 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 18:07:18 +0100 Subject: [PATCH 049/137] refactor(RepoManager): default to master branch if none is provided Signed-off-by: Dominik Willner --- kiauh/core/repo_manager/repo_manager.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index ee84c62..feda37f 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -19,9 +19,14 @@ from kiauh.utils.logger import Logger # noinspection PyMethodMayBeStatic class RepoManager: - def __init__(self, repo: str, branch: str, target_dir: str): + def __init__( + self, + repo: str, + target_dir: str, + branch: str = None, + ): self._repo = repo - self._branch = branch + self._branch = branch if branch is not None else "master" self._method = self._get_method() self._target_dir = target_dir @@ -62,9 +67,8 @@ class RepoManager: Logger.print_status(log) try: if os.path.exists(self.target_dir): - if not get_confirm( - "Target directory already exists. Overwrite?", default_choice=False - ): + question = f"'{self.target_dir}' already exists. Overwrite?" + if not get_confirm(question, default_choice=False): Logger.print_info("Skipping re-clone of repository.") return shutil.rmtree(self.target_dir) From c2e7ee98dfe309c13a3b512e863d4b187349feda Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 18:08:18 +0100 Subject: [PATCH 050/137] feat(Mainsail): implement Mainsail installer Signed-off-by: Dominik Willner --- kiauh.cfg.example | 4 + kiauh/core/menus/install_menu.py | 3 +- kiauh/modules/mainsail/__init__.py | 26 ++ kiauh/modules/mainsail/mainsail_dialogs.py | 95 ++++++ kiauh/modules/mainsail/mainsail_setup.py | 272 ++++++++++++++++++ .../mainsail/res/mainsail-config-updater.conf | 6 + .../mainsail/res/mainsail-updater.conf | 5 + 7 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 kiauh/modules/mainsail/__init__.py create mode 100644 kiauh/modules/mainsail/mainsail_dialogs.py create mode 100644 kiauh/modules/mainsail/mainsail_setup.py create mode 100644 kiauh/modules/mainsail/res/mainsail-config-updater.conf create mode 100644 kiauh/modules/mainsail/res/mainsail-updater.conf diff --git a/kiauh.cfg.example b/kiauh.cfg.example index 72f4a21..e1724d2 100644 --- a/kiauh.cfg.example +++ b/kiauh.cfg.example @@ -10,3 +10,7 @@ method: https repository_url: https://github.com/Arksine/moonraker branch: master method: https + +[mainsail] +default_port: 80 +unstable_releases: False diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 03c82f1..cd72444 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -14,6 +14,7 @@ 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.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT @@ -69,7 +70,7 @@ class InstallMenu(BaseMenu): moonraker_setup.run_moonraker_setup(install=True) def install_mainsail(self): - print("install_mainsail") + mainsail_setup.run_mainsail_setup(install=True) def install_fluidd(self): print("install_fluidd") diff --git a/kiauh/modules/mainsail/__init__.py b/kiauh/modules/mainsail/__init__.py new file mode 100644 index 0000000..06f2027 --- /dev/null +++ b/kiauh/modules/mainsail/__init__.py @@ -0,0 +1,26 @@ +#!/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 # +# ======================================================================= # + +from pathlib import Path + +import os + +MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) +MAINSAIL_DIR = os.path.join(Path.home(), "mainsail") +MAINSAIL_CONFIG_DIR = os.path.join(Path.home(), "mainsail-config") +MAINSAIL_CONFIG_JSON = os.path.join(MAINSAIL_DIR, "config.json") +MAINSAIL_URL = ( + "https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip" +) +MAINSAIL_UNSTABLE_URL = ( + "https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip" +) +MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git" diff --git a/kiauh/modules/mainsail/mainsail_dialogs.py b/kiauh/modules/mainsail/mainsail_dialogs.py new file mode 100644 index 0000000..0c60bbf --- /dev/null +++ b/kiauh/modules/mainsail/mainsail_dialogs.py @@ -0,0 +1,95 @@ +#!/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.base_menu import print_back_footer +from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN + + +def print_moonraker_not_found_dialog(): + line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}No local Moonraker installation was found!{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | {line1:<63}| + | {line2:<63}| + |-------------------------------------------------------| + | It is possible to install Mainsail without a local | + | Moonraker installation. If you continue, you need to | + | make sure, that Moonraker is installed on another | + | machine in your network. Otherwise Mainsail will NOT | + | work correctly. | + """ + )[1:] + + print(dialog, end="") + print_back_footer() + + +def print_mainsail_already_installed_dialog(): + line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}Mainsail seems to be already installed!{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | {line1:<63}| + | {line2:<63}| + |-------------------------------------------------------| + | If you continue, your current Mainsail installation | + | will be overwritten. You will not loose any printer | + | configurations and the Moonraker database will remain | + | untouched. | + """ + )[1:] + + print(dialog, end="") + print_back_footer() + + +def print_install_mainsail_config_dialog(): + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | It is recommended to use special macros in order to | + | have Mainsail fully functional and working. | + | | + | The recommended macros for Mainsail can be seen here: | + | https://github.com/mainsail-crew/mainsail-config | + | | + | If you already use these macros skip this step. | + | Otherwise you should consider to answer with 'Y' to | + | download the recommended macros. | + \\=======================================================/ + """ + )[1:] + + print(dialog, end="") + + +def print_mainsail_port_select_dialog(port: str): + port = f"{COLOR_CYAN}{port}{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | Please select the port, Mainsail should be served on. | + | If you are unsure what to select, hit Enter to apply | + | the suggested value of: {port:38} | + | | + | In case you need Mainsail to be served on a specific | + | port, you can set it now. Make sure the port is not | + | used by any other application on your system! | + \\=======================================================/ + """ + )[1:] + + print(dialog, end="") diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py new file mode 100644 index 0000000..3dc3607 --- /dev/null +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -0,0 +1,272 @@ +#!/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 json +import os.path +import shutil +from pathlib import Path +from typing import List + +from kiauh import KIAUH_CFG +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.repo_manager.repo_manager import RepoManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.mainsail import ( + MAINSAIL_URL, + MAINSAIL_DIR, + MAINSAIL_CONFIG_DIR, + MAINSAIL_CONFIG_REPO_URL, + MAINSAIL_CONFIG_JSON, + MODULE_PATH, +) +from kiauh.modules.mainsail.mainsail_dialogs import ( + print_moonraker_not_found_dialog, + print_mainsail_already_installed_dialog, + print_install_mainsail_config_dialog, + print_mainsail_port_select_dialog, +) +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.common import check_install_dependencies +from kiauh.utils.input_utils import get_confirm, get_number_input +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import ( + download_file, + unzip, + create_upstream_nginx_cfg, + create_nginx_cfg, + delete_default_nginx_cfg, + enable_nginx_cfg, + set_nginx_permissions, + get_ipv4_addr, + control_systemd_service, + create_common_vars_nginx_cfg, +) + + +def run_mainsail_setup(install: bool) -> None: + im_mr = InstanceManager(Moonraker) + is_moonraker_installed = len(im_mr.instances) > 0 + + enable_remotemode = False + if not is_moonraker_installed: + print_moonraker_not_found_dialog() + do_continue = get_confirm("Continue Mainsail installation?", allow_go_back=True) + if do_continue: + enable_remotemode = True + else: + return + + is_mainsail_installed = Path(f"{Path.home()}/mainsail").exists() + do_reinstall = False + if is_mainsail_installed: + print_mainsail_already_installed_dialog() + do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True) + if do_reinstall: + backup_config_json() + else: + return + + im_kl = InstanceManager(Klipper) + is_klipper_installed = len(im_kl.instances) > 0 + install_ms_config = False + if is_klipper_installed: + print_install_mainsail_config_dialog() + question = "Download the recommended macros?" + install_ms_config = get_confirm(question, allow_go_back=False) + + cm = ConfigManager(cfg_file=KIAUH_CFG) + cm.read_config() + default_port = cm.get_value("mainsail", "default_port") + mainsail_port = default_port if default_port else 80 + if not default_port: + print_mainsail_port_select_dialog(f"{mainsail_port}") + mainsail_port = get_number_input( + "Configure Mainsail for port", + min_count=mainsail_port, + default=mainsail_port, + ) + + check_install_dependencies(["nginx"]) + + try: + download_mainsail() + if do_reinstall: + restore_config_json() + if enable_remotemode: + enable_mainsail_remotemode() + if is_moonraker_installed: + patch_moonraker_conf( + im_mr.instances, + "Mainsail", + "update_manager mainsail", + "mainsail-updater.conf", + ) + im_mr.restart_all_instance() + if is_klipper_installed and install_ms_config: + download_mainsail_config() + patch_moonraker_conf( + im_mr.instances, + "mainsail-config", + "update_manager mainsail-config", + "mainsail-config-updater.conf", + ) + patch_printer_config(im_kl.instances) + im_kl.restart_all_instance() + + create_upstream_nginx_cfg() + create_common_vars_nginx_cfg() + create_mainsail_nginx_cfg(mainsail_port) + if is_klipper_installed: + symlink_webui_nginx_log(im_kl.instances) + control_systemd_service("nginx", "restart") + + except Exception as e: + Logger.print_error(f"Mainsail installation failed!\n{e}") + return + + log = f"Open Mainsail now on: http://{get_ipv4_addr()}:{mainsail_port}" + Logger.print_ok("Mainsail installation complete!", start="\n") + Logger.print_ok(log, prefix=False, end="\n\n") + + +def download_mainsail() -> None: + try: + Logger.print_status("Downloading Mainsail ...") + download_file(MAINSAIL_URL, f"{Path.home()}", "mainsail.zip") + Logger.print_ok("Download complete!") + + Logger.print_status("Extracting mainsail.zip ...") + unzip(f"{Path.home()}/mainsail.zip", MAINSAIL_DIR) + Logger.print_ok("OK!") + + except Exception: + Logger.print_error("Downloading Mainsail failed!") + raise + + +def download_mainsail_config() -> None: + try: + Logger.print_status("Downloading mainsail-config ...") + rm = RepoManager(MAINSAIL_CONFIG_REPO_URL, target_dir=MAINSAIL_CONFIG_DIR) + rm.clone_repo() + except Exception: + Logger.print_error("Downloading mainsail-config failed!") + raise + + +def create_mainsail_nginx_cfg(port: int) -> None: + try: + Logger.print_status("Creating NGINX config for Mainsail ...") + root_dir = MAINSAIL_DIR + delete_default_nginx_cfg() + create_nginx_cfg("mainsail", port, root_dir) + enable_nginx_cfg("mainsail") + set_nginx_permissions() + Logger.print_ok("NGINX config for Mainsail successfully created.") + except Exception: + Logger.print_error("Creating NGINX config for Mainsail failed!") + raise + + +def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: + Logger.print_status("Link NGINX logs into log directory ...") + access_log = Path("/var/log/nginx/mainsail-access.log") + error_log = Path("/var/log/nginx/mainsail-error.log") + + for instance in klipper_instances: + desti_access = Path(instance.log_dir).joinpath("mainsail-access.log") + if not desti_access.exists(): + desti_access.symlink_to(access_log) + + desti_error = Path(instance.log_dir).joinpath("mainsail-error.log") + if not desti_error.exists(): + desti_error.symlink_to(error_log) + + +def patch_moonraker_conf( + moonraker_instances: List[Moonraker], + name: str, + section_name: str, + template_file: str, +) -> None: + for instance in moonraker_instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Add {name} update section to '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + return + + cm = ConfigManager(cfg_file) + cm.read_config() + if cm.config.has_section(section_name): + Logger.print_info("Section already exist. Skipped ...") + return + + template = os.path.join(MODULE_PATH, "res", template_file) + with open(template, "r") as t: + template_content = "\n" + template_content += t.read() + + with open(cfg_file, "a") as f: + f.write(template_content) + + +def patch_printer_config(klipper_instances: List[Klipper]) -> None: + for instance in klipper_instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Including mainsail-config in '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + return + + cm = ConfigManager(cfg_file) + cm.read_config() + if cm.config.has_section("include mainsail.cfg"): + Logger.print_info("Section already exist. Skipped ...") + return + + with open(cfg_file, "a") as f: + f.write("\n[include mainsail.cfg]") + + +def backup_config_json() -> None: + try: + Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") + target = os.path.join(Path.home(), "config.json.kiauh.bak") + shutil.copy(MAINSAIL_CONFIG_JSON, target) + except OSError: + Logger.print_info(f"Unable to backup config.json. Skipped ...") + + +def restore_config_json() -> None: + try: + Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") + source = os.path.join(Path.home(), "config.json.kiauh.bak") + shutil.copy(source, MAINSAIL_CONFIG_JSON) + except OSError: + Logger.print_info(f"Unable to restore config.json. Skipped ...") + + +def enable_mainsail_remotemode() -> None: + with open(MAINSAIL_CONFIG_JSON, "r") as f: + config_data = json.load(f) + + if config_data["instancesDB"] == "browser": + return + + Logger.print_status("Setting instance storage location to 'browser' ...") + config_data["instancesDB"] = "browser" + + with open(MAINSAIL_CONFIG_JSON, "w") as f: + json.dump(config_data, f, indent=4) diff --git a/kiauh/modules/mainsail/res/mainsail-config-updater.conf b/kiauh/modules/mainsail/res/mainsail-config-updater.conf new file mode 100644 index 0000000..02bb789 --- /dev/null +++ b/kiauh/modules/mainsail/res/mainsail-config-updater.conf @@ -0,0 +1,6 @@ +[update_manager mainsail-config] +type: git_repo +primary_branch: master +path: ~/mainsail-config +origin: https://github.com/mainsail-crew/mainsail-config.git +managed_services: klipper diff --git a/kiauh/modules/mainsail/res/mainsail-updater.conf b/kiauh/modules/mainsail/res/mainsail-updater.conf new file mode 100644 index 0000000..f668332 --- /dev/null +++ b/kiauh/modules/mainsail/res/mainsail-updater.conf @@ -0,0 +1,5 @@ +[update_manager mainsail] +type: web +channel: stable +repo: mainsail-crew/mainsail +path: ~/mainsail From 926ba1acb415402ca3603aa8d6fe36b1dff2e1bb Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 21:49:09 +0100 Subject: [PATCH 051/137] feat(ConfigManager): implement own ConfigParser write() method Signed-off-by: Dominik Willner --- kiauh/core/config_manager/config_manager.py | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 00cc41b..4470575 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -19,7 +19,7 @@ from kiauh.utils.logger import Logger class ConfigManager: def __init__(self, cfg_file: str): self.config_file = cfg_file - self.config = configparser.ConfigParser() + self.config = CustomConfigParser() def read_config(self) -> None: if not self.config_file: @@ -55,3 +55,27 @@ class ConfigManager: def set_value(self, section: str, key: str, value: str): self.config.set(section, key, value) + + +class CustomConfigParser(configparser.ConfigParser): + """ + A custom ConfigParser class overwriting the write() method of configparser.Configparser. + Key and value will be delimited by a ": ". + Note the whitespace AFTER the colon, which is the whole reason for that overwrite. + """ + + def write(self, fp, space_around_delimiters=False): + if self._defaults: + fp.write("[%s]\n" % configparser.DEFAULTSECT) + for key, value in self._defaults.items(): + fp.write("%s: %s\n" % (key, str(value).replace("\n", "\n\t"))) + fp.write("\n") + for section in self._sections: + fp.write("[%s]\n" % section) + for key, value in self._sections[section].items(): + if key == "__name__": + continue + if (value is not None) or (self._optcre == self.OPTCRE): + key = ": ".join((key, str(value).replace("\n", "\n\t"))) + fp.write("%s\n" % key) + fp.write("\n") From 5fb4444f03e27baa774bc3f332335f6727602ec6 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 21:50:21 +0100 Subject: [PATCH 052/137] refactor(Moonraker): refactor example moonraker.conf creation Signed-off-by: Dominik Willner --- kiauh/modules/moonraker/moonraker_setup.py | 49 ++------------ kiauh/modules/moonraker/moonraker_utils.py | 75 ++++++++++++++++++++++ kiauh/modules/moonraker/res/moonraker.conf | 1 - 3 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 kiauh/modules/moonraker/moonraker_utils.py diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index df2eb7c..c0dbb73 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import subprocess import sys from pathlib import Path @@ -35,11 +34,10 @@ from kiauh.modules.moonraker import ( POLKIT_FILE, POLKIT_USR_FILE, POLKIT_SCRIPT, - DEFAULT_MOONRAKER_PORT, - MODULE_PATH, ) from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.modules.moonraker.moonraker_dialogs import print_moonraker_overview +from kiauh.modules.moonraker.moonraker_utils import create_example_moonraker_conf from kiauh.utils.input_utils import ( get_confirm, get_selection_input, @@ -52,7 +50,6 @@ from kiauh.utils.system_utils import ( update_system_package_lists, install_system_packages, check_file_exists, - get_ipv4_addr, ) @@ -133,9 +130,9 @@ def install_moonraker( setup_moonraker_prerequesites() install_moonraker_polkit() - ports_in_use = [ - instance.port for instance in moonraker_instances if instance.port is not None - ] + used_ports_map = { + instance.suffix: instance.port for instance in moonraker_instances + } for name in instance_names: current_instance = Moonraker(suffix=name) @@ -144,13 +141,7 @@ def install_moonraker( instance_manager.enable_instance() if create_example_cfg: - cfg_dir = current_instance.cfg_dir - Logger.print_status(f"Creating example moonraker.conf in '{cfg_dir}'") - if current_instance.cfg_file is None: - create_example_moonraker_conf(current_instance, ports_in_use) - Logger.print_ok(f"Example moonraker.conf created in '{cfg_dir}'") - else: - Logger.print_info(f"moonraker.conf in '{cfg_dir}' already exists.") + create_example_moonraker_conf(current_instance, used_ports_map) instance_manager.start_instance() @@ -324,33 +315,3 @@ def update_moonraker() -> None: ) repo_manager.pull_repo() instance_manager.start_all_instance() - - -def create_example_moonraker_conf(instance: Moonraker, ports: List[int]) -> None: - port = max(ports) + 1 if ports else DEFAULT_MOONRAKER_PORT - ports.append(port) - instance.port = port - example_cfg_path = os.path.join(MODULE_PATH, "res", "moonraker.conf") - - with open(f"{instance.cfg_dir}/moonraker.conf", "w") as cfg: - cfg.write(_prep_example_moonraker_conf(instance, example_cfg_path)) - - -def _prep_example_moonraker_conf(instance: Moonraker, example_cfg_path: str) -> str: - try: - with open(example_cfg_path, "r") as cfg: - example_cfg_content = cfg.read() - except FileNotFoundError: - Logger.print_error(f"Unable to open {example_cfg_path} - File not found") - raise - - example_cfg_content = example_cfg_content.replace("%PORT%", str(instance.port)) - example_cfg_content = example_cfg_content.replace( - "%UDS%", f"{instance.comms_dir}/klippy.sock" - ) - - ip = get_ipv4_addr().split(".")[:2] - ip.extend(["0", "0/16"]) - example_cfg_content = example_cfg_content.replace("%LAN%", ".".join(ip)) - - return example_cfg_content diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py new file mode 100644 index 0000000..2c08f5e --- /dev/null +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -0,0 +1,75 @@ +#!/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 os +import shutil +from typing import List, Dict + +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.modules.moonraker import ( + DEFAULT_MOONRAKER_PORT, + MODULE_PATH, +) +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import ( + get_ipv4_addr, +) + + +def create_example_moonraker_conf( + instance: Moonraker, ports_map: Dict[str, int] +) -> None: + Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'") + if instance.cfg_file is not None: + Logger.print_info(f"moonraker.conf in '{instance.cfg_dir}' already exists.") + return + + source = os.path.join(MODULE_PATH, "res", "moonraker.conf") + target = os.path.join(instance.cfg_dir, "moonraker.conf") + try: + shutil.copy(source, target) + except OSError as e: + Logger.print_error(f"Unable to create example moonraker.conf:\n{e}") + return + + cm = ConfigManager(target) + cm.read_config() + + ports = [ + ports_map.get(instance) + for instance in ports_map + if ports_map.get(instance) is not None + ] + if ports_map.get(instance.suffix) is None: + # this could be improved to not increment the max value of the ports list and assign it as the port + # as it can lead to situation where the port for e.g. instance moonraker-2 becomes 7128 if the port + # of moonraker-1 is 7125 and moonraker-3 is 7127 and there are moonraker.conf files for moonraker-1 + # and moonraker-3 already. though, there does not seem to be a very reliable way of always assigning + # the correct port to each instance and the user will likely be required to correct the value manually. + port = max(ports) + 1 if ports else DEFAULT_MOONRAKER_PORT + else: + port = ports_map.get(instance.suffix) + + ports_map[instance.suffix] = port + + uds = f"{instance.comms_dir}/klippy.sock" + ip = get_ipv4_addr().split(".")[:2] + ip.extend(["0", "0/16"]) + trusted_clients = f"\n{'.'.join(ip)}" + trusted_clients += cm.get_value("authorization", "trusted_clients") + + cm.set_value("server", "port", str(port)) + cm.set_value("server", "klippy_uds_address", uds) + cm.set_value("authorization", "trusted_clients", trusted_clients) + + cm.write_config() + Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'") diff --git a/kiauh/modules/moonraker/res/moonraker.conf b/kiauh/modules/moonraker/res/moonraker.conf index d6eadd8..d985233 100644 --- a/kiauh/modules/moonraker/res/moonraker.conf +++ b/kiauh/modules/moonraker/res/moonraker.conf @@ -5,7 +5,6 @@ klippy_uds_address: %UDS% [authorization] trusted_clients: - %LAN% 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 From b8640f45a626a01db880eca8ef2502ee847b7596 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 17 Dec 2023 23:30:38 +0100 Subject: [PATCH 053/137] refactor(Klipper): refactor example printer.cfg creation Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 8 +------- kiauh/modules/klipper/klipper_utils.py | 6 ++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index dba98c2..78bc282 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -130,13 +130,7 @@ def install_klipper( instance_manager.enable_instance() if create_example_cfg: - cfg_dir = current_instance.cfg_dir - Logger.print_status(f"Creating example printer.cfg in '{cfg_dir}'") - if current_instance.cfg_file is None: - create_example_printer_cfg(current_instance) - Logger.print_ok(f"Example printer.cfg created in '{cfg_dir}'") - else: - Logger.print_info(f"printer.cfg in '{cfg_dir}' already exists.") + create_example_printer_cfg(current_instance) instance_manager.start_instance() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index fcbc99a..e3439af 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -195,6 +195,11 @@ def get_highest_index(instance_list: List[Klipper]) -> int: def create_example_printer_cfg(instance: Klipper) -> None: + Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'") + if instance.cfg_file is not None: + Logger.print_info(f"printer.cfg in '{instance.cfg_dir}' already exists.") + return + source = os.path.join(MODULE_PATH, "res", "printer.cfg") target = os.path.join(instance.cfg_dir, "printer.cfg") try: @@ -207,3 +212,4 @@ def create_example_printer_cfg(instance: Klipper) -> None: cm.read_config() cm.set_value("virtual_sdcard", "path", instance.gcodes_dir) cm.write_config() + Logger.print_ok(f"Example printer.cfg created in '{instance.cfg_dir}'") From cd38970bbdc74eca18392efc3f1a1ba3f6980787 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 18 Dec 2023 20:58:27 +0100 Subject: [PATCH 054/137] refactor(Mainsail): move some functions to a mainsail utils module Signed-off-by: Dominik Willner --- kiauh/core/menus/install_menu.py | 2 +- kiauh/modules/mainsail/mainsail_setup.py | 58 +++----------------- kiauh/modules/mainsail/mainsail_utils.py | 67 ++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 52 deletions(-) create mode 100644 kiauh/modules/mainsail/mainsail_utils.py diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index cd72444..b30e4ee 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -70,7 +70,7 @@ class InstallMenu(BaseMenu): moonraker_setup.run_moonraker_setup(install=True) def install_mainsail(self): - mainsail_setup.run_mainsail_setup(install=True) + mainsail_setup.run_mainsail_installation() def install_fluidd(self): print("install_fluidd") diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 3dc3607..d5f934e 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -9,9 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import json import os.path -import shutil from pathlib import Path from typing import List @@ -25,7 +23,6 @@ from kiauh.modules.mainsail import ( MAINSAIL_DIR, MAINSAIL_CONFIG_DIR, MAINSAIL_CONFIG_REPO_URL, - MAINSAIL_CONFIG_JSON, MODULE_PATH, ) from kiauh.modules.mainsail.mainsail_dialogs import ( @@ -34,6 +31,12 @@ from kiauh.modules.mainsail.mainsail_dialogs import ( print_install_mainsail_config_dialog, print_mainsail_port_select_dialog, ) +from kiauh.modules.mainsail.mainsail_utils import ( + restore_config_json, + enable_mainsail_remotemode, + backup_config_json, + symlink_webui_nginx_log, +) from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.utils.common import check_install_dependencies from kiauh.utils.input_utils import get_confirm, get_number_input @@ -52,7 +55,7 @@ from kiauh.utils.system_utils import ( ) -def run_mainsail_setup(install: bool) -> None: +def run_mainsail_installation() -> None: im_mr = InstanceManager(Moonraker) is_moonraker_installed = len(im_mr.instances) > 0 @@ -177,21 +180,6 @@ def create_mainsail_nginx_cfg(port: int) -> None: raise -def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: - Logger.print_status("Link NGINX logs into log directory ...") - access_log = Path("/var/log/nginx/mainsail-access.log") - error_log = Path("/var/log/nginx/mainsail-error.log") - - for instance in klipper_instances: - desti_access = Path(instance.log_dir).joinpath("mainsail-access.log") - if not desti_access.exists(): - desti_access.symlink_to(access_log) - - desti_error = Path(instance.log_dir).joinpath("mainsail-error.log") - if not desti_error.exists(): - desti_error.symlink_to(error_log) - - def patch_moonraker_conf( moonraker_instances: List[Moonraker], name: str, @@ -238,35 +226,3 @@ def patch_printer_config(klipper_instances: List[Klipper]) -> None: with open(cfg_file, "a") as f: f.write("\n[include mainsail.cfg]") - - -def backup_config_json() -> None: - try: - Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") - target = os.path.join(Path.home(), "config.json.kiauh.bak") - shutil.copy(MAINSAIL_CONFIG_JSON, target) - except OSError: - Logger.print_info(f"Unable to backup config.json. Skipped ...") - - -def restore_config_json() -> None: - try: - Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") - source = os.path.join(Path.home(), "config.json.kiauh.bak") - shutil.copy(source, MAINSAIL_CONFIG_JSON) - except OSError: - Logger.print_info(f"Unable to restore config.json. Skipped ...") - - -def enable_mainsail_remotemode() -> None: - with open(MAINSAIL_CONFIG_JSON, "r") as f: - config_data = json.load(f) - - if config_data["instancesDB"] == "browser": - return - - Logger.print_status("Setting instance storage location to 'browser' ...") - config_data["instancesDB"] = "browser" - - with open(MAINSAIL_CONFIG_JSON, "w") as f: - json.dump(config_data, f, indent=4) diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py new file mode 100644 index 0000000..92f7a61 --- /dev/null +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -0,0 +1,67 @@ +#!/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 json +import os +import shutil +from pathlib import Path +from typing import List + +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON +from kiauh.utils.logger import Logger + + +def backup_config_json() -> None: + try: + Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") + target = os.path.join(Path.home(), "config.json.kiauh.bak") + shutil.copy(MAINSAIL_CONFIG_JSON, target) + except OSError: + Logger.print_info(f"Unable to backup config.json. Skipped ...") + + +def restore_config_json() -> None: + try: + Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") + source = os.path.join(Path.home(), "config.json.kiauh.bak") + shutil.copy(source, MAINSAIL_CONFIG_JSON) + except OSError: + Logger.print_info(f"Unable to restore config.json. Skipped ...") + + +def enable_mainsail_remotemode() -> None: + with open(MAINSAIL_CONFIG_JSON, "r") as f: + config_data = json.load(f) + + if config_data["instancesDB"] == "browser": + return + + Logger.print_status("Setting instance storage location to 'browser' ...") + config_data["instancesDB"] = "browser" + + with open(MAINSAIL_CONFIG_JSON, "w") as f: + json.dump(config_data, f, indent=4) + + +def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: + Logger.print_status("Link NGINX logs into log directory ...") + access_log = Path("/var/log/nginx/mainsail-access.log") + error_log = Path("/var/log/nginx/mainsail-error.log") + + for instance in klipper_instances: + desti_access = Path(instance.log_dir).joinpath("mainsail-access.log") + if not desti_access.exists(): + desti_access.symlink_to(access_log) + + desti_error = Path(instance.log_dir).joinpath("mainsail-error.log") + if not desti_error.exists(): + desti_error.symlink_to(error_log) From 4915896099d123c8f7a6d5d2e4d584630177677c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 21 Dec 2023 22:53:41 +0100 Subject: [PATCH 055/137] feat(Mainsail): remove Mainsail Signed-off-by: Dominik Willner --- kiauh/core/menus/remove_menu.py | 6 +- kiauh/modules/mainsail/mainsail_remove.py | 150 ++++++++++++++++++ kiauh/modules/mainsail/mainsail_utils.py | 2 + kiauh/modules/mainsail/menus/__init__.py | 0 .../mainsail/menus/mainsail_remove_menu.py | 121 ++++++++++++++ kiauh/utils/filesystem_utils.py | 26 +++ 6 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 kiauh/modules/mainsail/mainsail_remove.py create mode 100644 kiauh/modules/mainsail/menus/__init__.py create mode 100644 kiauh/modules/mainsail/menus/mainsail_remove_menu.py create mode 100644 kiauh/utils/filesystem_utils.py diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 1271557..4082c8f 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -14,6 +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.utils.constants import COLOR_RED, RESET_FORMAT @@ -26,7 +28,7 @@ class RemoveMenu(BaseMenu): options={ 1: self.remove_klipper, 2: self.remove_moonraker, - 3: self.remove_mainsail, + 3: MainsailRemoveMenu, 4: self.remove_mainsail_config, 5: self.remove_fluidd, 6: self.remove_fluidd_config, @@ -77,7 +79,7 @@ class RemoveMenu(BaseMenu): moonraker_setup.run_moonraker_setup(install=False) def remove_mainsail(self): - print("remove_mainsail") + mainsail_setup.run_mainsail_removal() def remove_mainsail_config(self): print("remove_mainsail_config") diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py new file mode 100644 index 0000000..525ad3f --- /dev/null +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -0,0 +1,150 @@ +#!/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 pathlib import Path + +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR +from kiauh.modules.mainsail.mainsail_utils import backup_config_json +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED +from kiauh.utils.filesystem_utils import remove_file +from kiauh.utils.logger import Logger + + +def run_mainsail_removal( + remove_mainsail: bool, + remove_ms_config: bool, + backup_ms_config_json: bool, + remove_mr_updater_section: bool, + remove_msc_printer_cfg_include: bool, +) -> None: + if backup_ms_config_json: + backup_config_json() + if remove_mainsail: + remove_mainsail_dir() + remove_nginx_config() + remove_nginx_logs() + if remove_mr_updater_section: + remove_updater_section("update_manager mainsail") + if remove_ms_config: + remove_ms_config_dir() + if remove_mr_updater_section: + remove_updater_section("update_manager mainsail-config") + if remove_msc_printer_cfg_include: + remove_printer_cfg_include() + + +def remove_mainsail_dir() -> None: + Logger.print_status("Removing Mainsail ...") + if not Path(MAINSAIL_DIR).exists(): + Logger.print_info(f"'{MAINSAIL_DIR}' does not exist. Skipping ...") + return + + try: + shutil.rmtree(MAINSAIL_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{MAINSAIL_DIR}':\n{e}") + + +def remove_nginx_config() -> None: + Logger.print_status("Removing Mainsails NGINX config ...") + try: + remove_file(Path(NGINX_SITES_AVAILABLE).joinpath("mainsail"), True) + remove_file(Path(NGINX_SITES_ENABLED).joinpath("mainsail"), True) + + except subprocess.CalledProcessError as e: + log = f"Unable to remove Mainsail NGINX config:\n{e.stderr.decode()}" + Logger.print_error(log) + + +def remove_nginx_logs() -> None: + Logger.print_status("Removing Mainsails NGINX logs ...") + try: + remove_file(Path("/var/log/nginx/mainsail-access.log"), True) + remove_file(Path("/var/log/nginx/mainsail-error.log"), True) + + im = InstanceManager(Klipper) + if not im.instances: + return + + for instance in im.instances: + remove_file(Path(instance.log_dir).joinpath("mainsail-access.log")) + remove_file(Path(instance.log_dir).joinpath("mainsail-error.log")) + + except (OSError, subprocess.CalledProcessError) as e: + Logger.print_error(f"Unable to NGINX logs:\n{e}") + + +def remove_updater_section(name: str) -> None: + Logger.print_status("Remove updater section from moonraker.conf ...") + im = InstanceManager(Moonraker) + if not im.instances: + Logger.print_info("Moonraker not installed. Skipping ...") + return + + for instance in im.instances: + log = f"Remove section '{name}' in '{instance.cfg_file}' ..." + Logger.print_status(log) + + if not Path(instance.cfg_file).exists(): + Logger.print_info("Section not present. Skipping ...") + continue + + cm = ConfigManager(instance.cfg_file) + cm.read_config() + if not cm.config.has_section(name): + Logger.print_info("Section not present. Skipped ...") + continue + + cm.config.remove_section(name) + cm.write_config() + + +def remove_ms_config_dir() -> None: + Logger.print_status("Removing mainsail-config ...") + if not Path(MAINSAIL_CONFIG_DIR).exists(): + Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...") + return + + try: + shutil.rmtree(MAINSAIL_CONFIG_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{MAINSAIL_CONFIG_DIR}':\n{e}") + + +def remove_printer_cfg_include() -> None: + Logger.print_status("Remove mainsail-config include from printer.cfg ...") + im = InstanceManager(Klipper) + if not im.instances: + Logger.print_info("Klipper not installed. Skipping ...") + return + + for instance in im.instances: + log = f"Removing include from '{instance.cfg_file}' ..." + Logger.print_status(log) + + if not Path(instance.cfg_file).exists(): + continue + + cm = ConfigManager(instance.cfg_file) + cm.read_config() + if not cm.config.has_section("include mainsail.cfg"): + Logger.print_info("Section not present. Skipped ...") + continue + + cm.config.remove_section("include mainsail.cfg") + cm.write_config() diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index 92f7a61..4cfc9d5 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -20,6 +20,8 @@ from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON from kiauh.utils.logger import Logger +# TODO: give this method an optional target dir to backup to +# alteratively use the BackupManager for handling this task (probably best) def backup_config_json() -> None: try: Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") diff --git a/kiauh/modules/mainsail/menus/__init__.py b/kiauh/modules/mainsail/menus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py new file mode 100644 index 0000000..fd56a60 --- /dev/null +++ b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py @@ -0,0 +1,121 @@ +#!/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.mainsail import mainsail_remove +from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN + + +class MainsailRemoveMenu(BaseMenu): + def __init__(self): + super().__init__( + header=False, + options={ + 0: self.toggle_all, + 1: self.toggle_remove_mainsail, + 2: self.toggle_remove_ms_config, + 3: self.toggle_backup_config_json, + 4: self.toggle_remove_updater_section, + 5: self.toggle_remove_printer_cfg_include, + 6: self.run_removal_process, + }, + footer_type=BACK_HELP_FOOTER, + ) + self.remove_mainsail = False + self.remove_ms_config = False + self.backup_config_json = False + self.remove_updater_section = False + self.remove_printer_cfg_include = False + + def print_menu(self) -> None: + header = " [ Remove Mainsail ] " + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) + checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + unchecked = "[ ]" + o1 = checked if self.remove_mainsail else unchecked + o2 = checked if self.remove_ms_config else unchecked + o3 = checked if self.backup_config_json else unchecked + o4 = checked if self.remove_updater_section else unchecked + o5 = checked if self.remove_printer_cfg_include 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) Everything | + |-------------------------------------------------------| + | 1) {o1} Remove Mainsail Web UI | + | 2) {o2} Remove mainsail-config | + | 3) {o3} Backup Mainsail config.json | + | | + | printer.cfg & moonraker.conf | + | 4) {o4} Remove Moonraker updater section | + | 5) {o5} Remove printer.cfg include | + |-------------------------------------------------------| + | 6) Start removal | + """ + )[1:] + print(menu, end="") + + def toggle_all(self) -> None: + self.remove_mainsail = True + self.remove_ms_config = True + self.backup_config_json = True + self.remove_updater_section = True + self.remove_printer_cfg_include = True + + def toggle_remove_mainsail(self) -> None: + self.remove_mainsail = not self.remove_mainsail + + def toggle_remove_ms_config(self) -> None: + self.remove_ms_config = not self.remove_ms_config + + def toggle_backup_config_json(self) -> None: + self.backup_config_json = not self.backup_config_json + + def toggle_remove_updater_section(self) -> None: + self.remove_updater_section = not self.remove_updater_section + + def toggle_remove_printer_cfg_include(self) -> None: + self.remove_printer_cfg_include = not self.remove_printer_cfg_include + + def run_removal_process(self) -> None: + if ( + not self.remove_mainsail + and not self.remove_ms_config + and not self.backup_config_json + and not self.remove_updater_section + and not self.remove_printer_cfg_include + ): + error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" + print(error) + return + + mainsail_remove.run_mainsail_removal( + remove_mainsail=self.remove_mainsail, + remove_ms_config=self.remove_ms_config, + backup_ms_config_json=self.backup_config_json, + remove_mr_updater_section=self.remove_updater_section, + remove_msc_printer_cfg_include=self.remove_printer_cfg_include, + ) + + self.remove_mainsail = False + self.remove_ms_config = False + self.backup_config_json = False + self.remove_updater_section = False + self.remove_printer_cfg_include = False diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py new file mode 100644 index 0000000..b89e939 --- /dev/null +++ b/kiauh/utils/filesystem_utils.py @@ -0,0 +1,26 @@ +#!/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 subprocess +from pathlib import Path + + +from kiauh.utils.logger import Logger + + +def remove_file(file_path: Path, sudo=False) -> None: + try: + command = f"{'sudo ' if sudo else ''}rm -f {file_path}" + subprocess.run(command, stderr=subprocess.PIPE, check=True, shell=True) + except subprocess.CalledProcessError as e: + log = f"Cannot remove file {file_path}: {e.stderr.decode()}" + Logger.print_error(log) + raise From 12bd8eb79930de0ffbc9c059e7532af8de6edf09 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Dec 2023 21:25:20 +0100 Subject: [PATCH 056/137] feat(KIAUH): move filesystem related methods to own module Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/base_instance.py | 2 +- kiauh/modules/mainsail/mainsail_setup.py | 16 +- kiauh/modules/moonraker/moonraker_setup.py | 8 +- kiauh/utils/filesystem_utils.py | 145 ++++++++++++++++++- kiauh/utils/system_utils.py | 141 +----------------- 5 files changed, 155 insertions(+), 157 deletions(-) diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 9e8143f..0251694 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -14,7 +14,7 @@ from pathlib import Path from typing import List, Union, Optional, Type, TypeVar from kiauh.utils.constants import SYSTEMD, CURRENT_USER -from kiauh.utils.system_utils import create_directory +from kiauh.utils.filesystem_utils import create_directory B = TypeVar(name="B", bound="BaseInstance", covariant=True) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index d5f934e..0b6a45d 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -24,35 +24,31 @@ from kiauh.modules.mainsail import ( MAINSAIL_CONFIG_DIR, MAINSAIL_CONFIG_REPO_URL, MODULE_PATH, -) + ) from kiauh.modules.mainsail.mainsail_dialogs import ( print_moonraker_not_found_dialog, print_mainsail_already_installed_dialog, print_install_mainsail_config_dialog, print_mainsail_port_select_dialog, -) + ) from kiauh.modules.mainsail.mainsail_utils import ( restore_config_json, enable_mainsail_remotemode, backup_config_json, symlink_webui_nginx_log, -) + ) from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.utils.common import check_install_dependencies +from kiauh.utils.filesystem_utils import unzip, create_upstream_nginx_cfg, create_common_vars_nginx_cfg, \ + delete_default_nginx_cfg, create_nginx_cfg, enable_nginx_cfg from kiauh.utils.input_utils import get_confirm, get_number_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( download_file, - unzip, - create_upstream_nginx_cfg, - create_nginx_cfg, - delete_default_nginx_cfg, - enable_nginx_cfg, set_nginx_permissions, get_ipv4_addr, control_systemd_service, - create_common_vars_nginx_cfg, -) + ) def run_mainsail_installation() -> None: diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index c0dbb73..4f5a2a3 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -38,6 +38,7 @@ from kiauh.modules.moonraker import ( from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.modules.moonraker.moonraker_dialogs import print_moonraker_overview from kiauh.modules.moonraker.moonraker_utils import create_example_moonraker_conf +from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import ( get_confirm, get_selection_input, @@ -49,7 +50,6 @@ from kiauh.utils.system_utils import ( install_python_requirements, update_system_package_lists, install_system_packages, - check_file_exists, ) @@ -181,9 +181,9 @@ def install_moonraker_packages(moonraker_dir: Path) -> None: def install_moonraker_polkit() -> None: Logger.print_status("Installing Moonraker policykit rules ...") - legacy_file_exists = check_file_exists(Path(POLKIT_LEGACY_FILE)) - polkit_file_exists = check_file_exists(Path(POLKIT_FILE)) - usr_file_exists = check_file_exists(Path(POLKIT_USR_FILE)) + legacy_file_exists = check_file_exist(Path(POLKIT_LEGACY_FILE)) + polkit_file_exists = check_file_exist(Path(POLKIT_FILE)) + usr_file_exists = check_file_exist(Path(POLKIT_USR_FILE)) if legacy_file_exists or (polkit_file_exists and usr_file_exists): Logger.print_info("Moonraker policykit rules are already installed.") diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index b89e939..727cc8f 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # # # @@ -9,13 +8,46 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import os +import shutil import subprocess from pathlib import Path +from zipfile import ZipFile - +from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED, MODULE_PATH, NGINX_CONFD from kiauh.utils.logger import Logger +def check_file_exist(file_path: Path) -> bool: + """ + Helper function for checking the existence of a file where + elevated permissions are required | + :param file_path: the absolute path of the file to check + :return: True if file exists, otherwise False + """ + try: + command = ["sudo", "find", file_path] + subprocess.check_output(command, stderr=subprocess.DEVNULL) + return True + except subprocess.CalledProcessError: + return False + + +def create_directory(_dir: Path) -> None: + """ + Helper function for creating a directory or skipping if it already exists | + :param _dir: the directory to create + :return: None + """ + try: + if not os.path.isdir(_dir): + os.makedirs(_dir, exist_ok=True) + Logger.print_ok(f"Created directory: {_dir}") + except OSError as e: + Logger.print_error(f"Error creating folder: {e}") + raise + + def remove_file(file_path: Path, sudo=False) -> None: try: command = f"{'sudo ' if sudo else ''}rm -f {file_path}" @@ -24,3 +56,112 @@ def remove_file(file_path: Path, sudo=False) -> None: log = f"Cannot remove file {file_path}: {e.stderr.decode()}" Logger.print_error(log) raise + +def unzip(file: str, target_dir: str) -> None: + """ + Helper function to unzip a zip-archive into a target directory | + :param file: the zip-file to unzip + :param target_dir: the target directory to extract the files into + :return: None + """ + with ZipFile(file, "r") as _zip: + _zip.extractall(target_dir) + + +def create_upstream_nginx_cfg() -> None: + """ + Creates an upstream.conf in /etc/nginx/conf.d + :return: None + """ + source = os.path.join(MODULE_PATH, "res", "upstreams.conf") + target = os.path.join(NGINX_CONFD, "upstreams.conf") + try: + command = ["sudo", "cp", source, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create upstreams.conf: {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def create_common_vars_nginx_cfg() -> None: + """ + Creates a common_vars.conf in /etc/nginx/conf.d + :return: None + """ + source = os.path.join(MODULE_PATH, "res", "common_vars.conf") + target = os.path.join(NGINX_CONFD, "common_vars.conf") + try: + command = ["sudo", "cp", source, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create upstreams.conf: {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def create_nginx_cfg(name: str, port: int, root_dir: str) -> None: + """ + Creates an NGINX config from a template file and replaces all placeholders + :param name: name of the config to create + :param port: listen port + :param root_dir: directory of the static files + :return: None + """ + tmp = f"{Path.home()}/{name}.tmp" + shutil.copy(os.path.join(MODULE_PATH, "res", "nginx_cfg"), tmp) + with open(tmp, "r+") as f: + content = f.read() + content = content.replace("%NAME%", name) + content = content.replace("%PORT%", str(port)) + content = content.replace("%ROOT_DIR%", root_dir) + f.seek(0) + f.write(content) + f.truncate() + + target = os.path.join(NGINX_SITES_AVAILABLE, name) + try: + command = ["sudo", "mv", tmp, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create '{target}': {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def delete_default_nginx_cfg() -> None: + """ + Deletes a default NGINX config + :return: None + """ + default_cfg = Path("/etc/nginx/sites-enabled/default") + if not check_file_exist(default_cfg): + return + + try: + command = ["sudo", "rm", default_cfg] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to delete '{default_cfg}': {e.stderr.decode()}" + Logger.print_error(log) + raise + + +def enable_nginx_cfg(name: str) -> None: + """ + Helper method to enable an NGINX config | + :param name: name of the config to enable + :return: None + """ + source = os.path.join(NGINX_SITES_AVAILABLE, name) + target = os.path.join(NGINX_SITES_ENABLED, name) + if check_file_exist(Path(target)): + return + + try: + command = ["sudo", "ln", "-s", source, target] + subprocess.run(command, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + log = f"Unable to create symlink: {e.stderr.decode()}" + Logger.print_error(log) + raise diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index f14fbb2..4925707 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -27,6 +27,7 @@ from kiauh.utils import ( NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED, ) +from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger @@ -215,21 +216,6 @@ def install_system_packages(packages: List[str]) -> None: kill(f"Error installing packages:\n{e.stderr.decode()}") -def create_directory(_dir: Path) -> None: - """ - Helper function for creating a directory or skipping if it already exists | - :param _dir: the directory to create - :return: None - """ - try: - if not os.path.isdir(_dir): - os.makedirs(_dir, exist_ok=True) - Logger.print_ok(f"Created directory: {_dir}") - except OSError as e: - Logger.print_error(f"Error creating folder: {e}") - raise - - def mask_system_service(service_name: str) -> None: """ Mask a system service to prevent it from starting | @@ -245,21 +231,6 @@ def mask_system_service(service_name: str) -> None: raise -def check_file_exists(file_path: Path) -> bool: - """ - Helper function for checking the existence of a file where - elevated permissions are required | - :param file_path: the absolute path of the file to check - :return: True if file exists, otherwise False - """ - try: - command = ["sudo", "find", file_path] - subprocess.check_output(command, stderr=subprocess.DEVNULL) - return True - except subprocess.CalledProcessError: - return False - - # this feels hacky and not quite right, but for now it works # see: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib def get_ipv4_addr() -> str: @@ -327,116 +298,6 @@ def download_progress(block_num, block_size, total_size) -> None: sys.stdout.flush() -def unzip(file: str, target_dir: str) -> None: - """ - Helper function to unzip a zip-archive into a target directory | - :param file: the zip-file to unzip - :param target_dir: the target directory to extract the files into - :return: None - """ - with ZipFile(file, "r") as _zip: - _zip.extractall(target_dir) - - -def create_upstream_nginx_cfg() -> None: - """ - Creates an upstream.conf in /etc/nginx/conf.d - :return: None - """ - source = os.path.join(MODULE_PATH, "res", "upstreams.conf") - target = os.path.join(NGINX_CONFD, "upstreams.conf") - try: - command = ["sudo", "cp", source, target] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to create upstreams.conf: {e.stderr.decode()}" - Logger.print_error(log) - raise - - -def create_common_vars_nginx_cfg() -> None: - """ - Creates a common_vars.conf in /etc/nginx/conf.d - :return: None - """ - source = os.path.join(MODULE_PATH, "res", "common_vars.conf") - target = os.path.join(NGINX_CONFD, "common_vars.conf") - try: - command = ["sudo", "cp", source, target] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to create upstreams.conf: {e.stderr.decode()}" - Logger.print_error(log) - raise - - -def create_nginx_cfg(name: str, port: int, root_dir: str) -> None: - """ - Creates an NGINX config from a template file and replaces all placeholders - :param name: name of the config to create - :param port: listen port - :param root_dir: directory of the static files - :return: None - """ - tmp = f"{Path.home()}/{name}.tmp" - shutil.copy(os.path.join(MODULE_PATH, "res", "nginx_cfg"), tmp) - with open(tmp, "r+") as f: - content = f.read() - content = content.replace("%NAME%", name) - content = content.replace("%PORT%", str(port)) - content = content.replace("%ROOT_DIR%", root_dir) - f.seek(0) - f.write(content) - f.truncate() - - target = os.path.join(NGINX_SITES_AVAILABLE, name) - try: - command = ["sudo", "mv", tmp, target] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to create '{target}': {e.stderr.decode()}" - Logger.print_error(log) - raise - - -def delete_default_nginx_cfg() -> None: - """ - Deletes a default NGINX config - :return: None - """ - default_cfg = Path("/etc/nginx/sites-enabled/default") - if not check_file_exists(default_cfg): - return - - try: - command = ["sudo", "rm", default_cfg] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to delete '{default_cfg}': {e.stderr.decode()}" - Logger.print_error(log) - raise - - -def enable_nginx_cfg(name: str) -> None: - """ - Helper method to enable an NGINX config | - :param name: name of the config to enable - :return: None - """ - source = os.path.join(NGINX_SITES_AVAILABLE, name) - target = os.path.join(NGINX_SITES_ENABLED, name) - if check_file_exists(Path(target)): - return - - try: - command = ["sudo", "ln", "-s", source, target] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to create symlink: {e.stderr.decode()}" - Logger.print_error(log) - raise - - def set_nginx_permissions() -> None: """ Check if permissions of the users home directory From 22e8e314dbda2ba98370da33e0fc0a93ac19c90d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Dec 2023 21:41:15 +0100 Subject: [PATCH 057/137] fix(Mainsail): implement missing mainsail cfg symlinking Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_setup.py | 39 ++++++++++++++++++------ kiauh/utils/filesystem_utils.py | 19 +++++++++++- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 0b6a45d..0970251 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -10,6 +10,7 @@ # ======================================================================= # import os.path +import subprocess from pathlib import Path from typing import List @@ -24,23 +25,30 @@ from kiauh.modules.mainsail import ( MAINSAIL_CONFIG_DIR, MAINSAIL_CONFIG_REPO_URL, MODULE_PATH, - ) +) from kiauh.modules.mainsail.mainsail_dialogs import ( print_moonraker_not_found_dialog, print_mainsail_already_installed_dialog, print_install_mainsail_config_dialog, print_mainsail_port_select_dialog, - ) +) from kiauh.modules.mainsail.mainsail_utils import ( restore_config_json, enable_mainsail_remotemode, backup_config_json, symlink_webui_nginx_log, - ) +) from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.utils.common import check_install_dependencies -from kiauh.utils.filesystem_utils import unzip, create_upstream_nginx_cfg, create_common_vars_nginx_cfg, \ - delete_default_nginx_cfg, create_nginx_cfg, enable_nginx_cfg +from kiauh.utils.filesystem_utils import ( + unzip, + create_upstream_nginx_cfg, + create_common_vars_nginx_cfg, + delete_default_nginx_cfg, + create_nginx_cfg, + enable_nginx_cfg, + create_symlink, +) from kiauh.utils.input_utils import get_confirm, get_number_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( @@ -48,7 +56,7 @@ from kiauh.utils.system_utils import ( set_nginx_permissions, get_ipv4_addr, control_systemd_service, - ) +) def run_mainsail_installation() -> None: @@ -110,8 +118,9 @@ def run_mainsail_installation() -> None: "mainsail-updater.conf", ) im_mr.restart_all_instance() - if is_klipper_installed and install_ms_config: - download_mainsail_config() + if install_ms_config and is_klipper_installed: + download_mainsail_cfg() + create_mainsail_cfg_symlink(im_kl.instances) patch_moonraker_conf( im_mr.instances, "mainsail-config", @@ -152,7 +161,7 @@ def download_mainsail() -> None: raise -def download_mainsail_config() -> None: +def download_mainsail_cfg() -> None: try: Logger.print_status("Downloading mainsail-config ...") rm = RepoManager(MAINSAIL_CONFIG_REPO_URL, target_dir=MAINSAIL_CONFIG_DIR) @@ -162,6 +171,18 @@ def download_mainsail_config() -> None: raise +def create_mainsail_cfg_symlink(klipper_instances: List[Klipper]) -> None: + Logger.print_status("Create symlink of mainsail.cfg ...") + source = Path(MAINSAIL_CONFIG_DIR, "mainsail.cfg") + for instance in klipper_instances: + target = instance.cfg_dir + Logger.print_status(f"Linking {source} to {target}") + try: + create_symlink(source, target) + except subprocess.CalledProcessError: + Logger.print_error("Creating symlink failed!") + + def create_mainsail_nginx_cfg(port: int) -> None: try: Logger.print_status("Creating NGINX config for Mainsail ...") diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 727cc8f..ad4e868 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -14,7 +14,12 @@ import subprocess from pathlib import Path from zipfile import ZipFile -from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED, MODULE_PATH, NGINX_CONFD +from kiauh.utils import ( + NGINX_SITES_AVAILABLE, + NGINX_SITES_ENABLED, + MODULE_PATH, + NGINX_CONFD, +) from kiauh.utils.logger import Logger @@ -48,6 +53,17 @@ def create_directory(_dir: Path) -> None: raise +def create_symlink(source: Path, target: Path, sudo=False) -> None: + try: + cmd = ["ln", "-sf", source, target] + if sudo: + cmd.insert(0, "sudo") + subprocess.run(cmd, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError as e: + Logger.print_error(f"Failed to create symlink: {e}") + raise + + def remove_file(file_path: Path, sudo=False) -> None: try: command = f"{'sudo ' if sudo else ''}rm -f {file_path}" @@ -57,6 +73,7 @@ def remove_file(file_path: Path, sudo=False) -> None: Logger.print_error(log) raise + def unzip(file: str, target_dir: str) -> None: """ Helper function to unzip a zip-archive into a target directory | From 8ff0b9d81d20baa1959bd88be7392cf66efe94a0 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Dec 2023 22:39:43 +0100 Subject: [PATCH 058/137] refactor(Mainsail): refactor methods for removing and checking files Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_remove.py | 24 ++++++++++---- kiauh/modules/mainsail/mainsail_setup.py | 8 ++--- kiauh/modules/moonraker/moonraker_setup.py | 6 ++-- kiauh/utils/filesystem_utils.py | 38 +++++++++++++--------- kiauh/utils/system_utils.py | 8 ----- 5 files changed, 47 insertions(+), 37 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py index 525ad3f..d09fdb6 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -41,7 +41,8 @@ def run_mainsail_removal( if remove_mr_updater_section: remove_updater_section("update_manager mainsail") if remove_ms_config: - remove_ms_config_dir() + remove_mainsail_cfg_dir() + remove_mainsail_cfg_symlink() if remove_mr_updater_section: remove_updater_section("update_manager mainsail-config") if remove_msc_printer_cfg_include: @@ -63,8 +64,8 @@ def remove_mainsail_dir() -> None: def remove_nginx_config() -> None: Logger.print_status("Removing Mainsails NGINX config ...") try: - remove_file(Path(NGINX_SITES_AVAILABLE).joinpath("mainsail"), True) - remove_file(Path(NGINX_SITES_ENABLED).joinpath("mainsail"), True) + remove_file(Path(NGINX_SITES_AVAILABLE, "mainsail"), True) + remove_file(Path(NGINX_SITES_ENABLED, "mainsail"), True) except subprocess.CalledProcessError as e: log = f"Unable to remove Mainsail NGINX config:\n{e.stderr.decode()}" @@ -82,8 +83,8 @@ def remove_nginx_logs() -> None: return for instance in im.instances: - remove_file(Path(instance.log_dir).joinpath("mainsail-access.log")) - remove_file(Path(instance.log_dir).joinpath("mainsail-error.log")) + remove_file(Path(instance.log_dir, "mainsail-access.log")) + remove_file(Path(instance.log_dir, "mainsail-error.log")) except (OSError, subprocess.CalledProcessError) as e: Logger.print_error(f"Unable to NGINX logs:\n{e}") @@ -114,7 +115,7 @@ def remove_updater_section(name: str) -> None: cm.write_config() -def remove_ms_config_dir() -> None: +def remove_mainsail_cfg_dir() -> None: Logger.print_status("Removing mainsail-config ...") if not Path(MAINSAIL_CONFIG_DIR).exists(): Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...") @@ -126,6 +127,17 @@ def remove_ms_config_dir() -> None: Logger.print_error(f"Unable to delete '{MAINSAIL_CONFIG_DIR}':\n{e}") +def remove_mainsail_cfg_symlink() -> None: + Logger.print_status("Removing mainsail.cfg symlinks ...") + im = InstanceManager(Moonraker) + for instance in im.instances: + Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...") + try: + remove_file(Path(instance.cfg_dir, "mainsail.cfg")) + except subprocess.CalledProcessError: + Logger.print_error("Failed to remove symlink!") + + def remove_printer_cfg_include() -> None: Logger.print_status("Remove mainsail-config include from printer.cfg ...") im = InstanceManager(Klipper) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 0970251..5558137 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -42,8 +42,8 @@ from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.utils.common import check_install_dependencies from kiauh.utils.filesystem_utils import ( unzip, - create_upstream_nginx_cfg, - create_common_vars_nginx_cfg, + copy_upstream_nginx_cfg, + copy_common_vars_nginx_cfg, delete_default_nginx_cfg, create_nginx_cfg, enable_nginx_cfg, @@ -130,8 +130,8 @@ def run_mainsail_installation() -> None: patch_printer_config(im_kl.instances) im_kl.restart_all_instance() - create_upstream_nginx_cfg() - create_common_vars_nginx_cfg() + copy_upstream_nginx_cfg() + copy_common_vars_nginx_cfg() create_mainsail_nginx_cfg(mainsail_port) if is_klipper_installed: symlink_webui_nginx_log(im_kl.instances) diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 4f5a2a3..cde127e 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -181,9 +181,9 @@ def install_moonraker_packages(moonraker_dir: Path) -> None: def install_moonraker_polkit() -> None: Logger.print_status("Installing Moonraker policykit rules ...") - legacy_file_exists = check_file_exist(Path(POLKIT_LEGACY_FILE)) - polkit_file_exists = check_file_exist(Path(POLKIT_FILE)) - usr_file_exists = check_file_exist(Path(POLKIT_USR_FILE)) + legacy_file_exists = check_file_exist(Path(POLKIT_LEGACY_FILE), True) + polkit_file_exists = check_file_exist(Path(POLKIT_FILE), True) + usr_file_exists = check_file_exist(Path(POLKIT_USR_FILE), True) if legacy_file_exists or (polkit_file_exists and usr_file_exists): Logger.print_info("Moonraker policykit rules are already installed.") diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index ad4e868..721290c 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -23,19 +23,25 @@ from kiauh.utils import ( from kiauh.utils.logger import Logger -def check_file_exist(file_path: Path) -> bool: +def check_file_exist(file_path: Path, sudo=False) -> bool: """ - Helper function for checking the existence of a file where - elevated permissions are required | + Helper function for checking the existence of a file | :param file_path: the absolute path of the file to check - :return: True if file exists, otherwise False + :param sudo: use sudo if required + :return: True, if file exists, otherwise False """ - try: - command = ["sudo", "find", file_path] - subprocess.check_output(command, stderr=subprocess.DEVNULL) - return True - except subprocess.CalledProcessError: - return False + if sudo: + try: + command = ["sudo", "find", file_path] + subprocess.check_output(command, stderr=subprocess.DEVNULL) + return True + except subprocess.CalledProcessError: + return False + else: + if Path(file_path).exists(): + return True + else: + return False def create_directory(_dir: Path) -> None: @@ -66,8 +72,8 @@ def create_symlink(source: Path, target: Path, sudo=False) -> None: def remove_file(file_path: Path, sudo=False) -> None: try: - command = f"{'sudo ' if sudo else ''}rm -f {file_path}" - subprocess.run(command, stderr=subprocess.PIPE, check=True, shell=True) + cmd = f"{'sudo ' if sudo else ''}rm -f {file_path}" + subprocess.run(cmd, stderr=subprocess.PIPE, check=True, shell=True) except subprocess.CalledProcessError as e: log = f"Cannot remove file {file_path}: {e.stderr.decode()}" Logger.print_error(log) @@ -85,7 +91,7 @@ def unzip(file: str, target_dir: str) -> None: _zip.extractall(target_dir) -def create_upstream_nginx_cfg() -> None: +def copy_upstream_nginx_cfg() -> None: """ Creates an upstream.conf in /etc/nginx/conf.d :return: None @@ -101,7 +107,7 @@ def create_upstream_nginx_cfg() -> None: raise -def create_common_vars_nginx_cfg() -> None: +def copy_common_vars_nginx_cfg() -> None: """ Creates a common_vars.conf in /etc/nginx/conf.d :return: None @@ -152,7 +158,7 @@ def delete_default_nginx_cfg() -> None: :return: None """ default_cfg = Path("/etc/nginx/sites-enabled/default") - if not check_file_exist(default_cfg): + if not check_file_exist(default_cfg, True): return try: @@ -172,7 +178,7 @@ def enable_nginx_cfg(name: str) -> None: """ source = os.path.join(NGINX_SITES_AVAILABLE, name) target = os.path.join(NGINX_SITES_ENABLED, name) - if check_file_exist(Path(target)): + if check_file_exist(Path(target), True): return try: diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 4925707..0b07655 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -19,15 +19,7 @@ import urllib.error import urllib.request from pathlib import Path from typing import List, Literal -from zipfile import ZipFile -from kiauh.utils import ( - NGINX_CONFD, - MODULE_PATH, - NGINX_SITES_AVAILABLE, - NGINX_SITES_ENABLED, -) -from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger From edd5f5c6fdb2a4e388e6415f6b7b11b42a8e3c11 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Dec 2023 22:43:29 +0100 Subject: [PATCH 059/137] refactor(KIAUH): refactor RemoveMenu Signed-off-by: Dominik Willner --- kiauh/core/menus/remove_menu.py | 44 ++++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 4082c8f..4883609 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -29,18 +29,16 @@ class RemoveMenu(BaseMenu): 1: self.remove_klipper, 2: self.remove_moonraker, 3: MainsailRemoveMenu, - 4: self.remove_mainsail_config, 5: self.remove_fluidd, - 6: self.remove_fluidd_config, - 7: self.remove_klipperscreen, - 8: self.remove_crowsnest, - 9: self.remove_mjpgstreamer, - 10: self.remove_pretty_gcode, - 11: self.remove_telegram_bot, - 12: self.remove_obico, - 13: self.remove_octoeverywhere, - 14: self.remove_mobileraker, - 15: self.remove_nginx, + 6: self.remove_klipperscreen, + 7: self.remove_crowsnest, + 8: self.remove_mjpgstreamer, + 9: self.remove_pretty_gcode, + 10: self.remove_telegram_bot, + 11: self.remove_obico, + 12: self.remove_octoeverywhere, + 13: self.remove_mobileraker, + 14: self.remove_nginx, }, footer_type=BACK_FOOTER, ) @@ -57,17 +55,17 @@ class RemoveMenu(BaseMenu): | INFO: Configurations and/or any backups will be kept! | |-------------------------------------------------------| | Firmware & API: | Webcam Streamer: | - | 1) [Klipper] | 8) [Crowsnest] | - | 2) [Moonraker] | 9) [MJPG-Streamer] | + | 1) [Klipper] | 6) [Crowsnest] | + | 2) [Moonraker] | 7) [MJPG-Streamer] | | | | | Klipper Webinterface: | Other: | - | 3) [Mainsail] | 10) [PrettyGCode] | - | 4) [Mainsail-Config] | 11) [Telegram Bot] | - | 5) [Fluidd] | 12) [Obico for Klipper] | - | 6) [Fluidd-Config] | 13) [OctoEverywhere] | - | | 14) [Mobileraker] | - | Touchscreen GUI: | 15) [NGINX] | - | 7) [KlipperScreen] | | + | 3) [Mainsail] | 8) [PrettyGCode] | + | 4) [Fluidd] | 9) [Telegram Bot] | + | | 10) [Obico for Klipper] | + | Touchscreen GUI: | 11) [OctoEverywhere] | + | 5) [KlipperScreen] | 12) [Mobileraker] | + | | 13) [NGINX] | + | | | """ )[1:] print(menu, end="") @@ -78,12 +76,6 @@ class RemoveMenu(BaseMenu): def remove_moonraker(self): moonraker_setup.run_moonraker_setup(install=False) - def remove_mainsail(self): - mainsail_setup.run_mainsail_removal() - - def remove_mainsail_config(self): - print("remove_mainsail_config") - def remove_fluidd(self): print("remove_fluidd") From 3d5e83d5abf982f9628f6e258af238e220785854 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Dec 2023 22:54:29 +0100 Subject: [PATCH 060/137] refactor(Mainsail): remove specific methods and replace by generic ones Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_setup.py | 12 +++++--- kiauh/utils/filesystem_utils.py | 39 ------------------------ 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 5558137..b8e6615 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -39,15 +39,15 @@ from kiauh.modules.mainsail.mainsail_utils import ( symlink_webui_nginx_log, ) from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from kiauh.utils.common import check_install_dependencies from kiauh.utils.filesystem_utils import ( unzip, copy_upstream_nginx_cfg, copy_common_vars_nginx_cfg, - delete_default_nginx_cfg, create_nginx_cfg, - enable_nginx_cfg, create_symlink, + remove_file, ) from kiauh.utils.input_utils import get_confirm, get_number_input from kiauh.utils.logger import Logger @@ -184,12 +184,14 @@ def create_mainsail_cfg_symlink(klipper_instances: List[Klipper]) -> None: def create_mainsail_nginx_cfg(port: int) -> None: + root_dir = MAINSAIL_DIR + source = Path(NGINX_SITES_AVAILABLE, "mainsail") + target = Path(NGINX_SITES_ENABLED, "mainsail") try: Logger.print_status("Creating NGINX config for Mainsail ...") - root_dir = MAINSAIL_DIR - delete_default_nginx_cfg() + remove_file(Path("/etc/nginx/sites-enabled/default"), True) create_nginx_cfg("mainsail", port, root_dir) - enable_nginx_cfg("mainsail") + create_symlink(source, target, True) set_nginx_permissions() Logger.print_ok("NGINX config for Mainsail successfully created.") except Exception: diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 721290c..b623be2 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -16,7 +16,6 @@ from zipfile import ZipFile from kiauh.utils import ( NGINX_SITES_AVAILABLE, - NGINX_SITES_ENABLED, MODULE_PATH, NGINX_CONFD, ) @@ -150,41 +149,3 @@ def create_nginx_cfg(name: str, port: int, root_dir: str) -> None: log = f"Unable to create '{target}': {e.stderr.decode()}" Logger.print_error(log) raise - - -def delete_default_nginx_cfg() -> None: - """ - Deletes a default NGINX config - :return: None - """ - default_cfg = Path("/etc/nginx/sites-enabled/default") - if not check_file_exist(default_cfg, True): - return - - try: - command = ["sudo", "rm", default_cfg] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to delete '{default_cfg}': {e.stderr.decode()}" - Logger.print_error(log) - raise - - -def enable_nginx_cfg(name: str) -> None: - """ - Helper method to enable an NGINX config | - :param name: name of the config to enable - :return: None - """ - source = os.path.join(NGINX_SITES_AVAILABLE, name) - target = os.path.join(NGINX_SITES_ENABLED, name) - if check_file_exist(Path(target), True): - return - - try: - command = ["sudo", "ln", "-s", source, target] - subprocess.run(command, stderr=subprocess.PIPE, check=True) - except subprocess.CalledProcessError as e: - log = f"Unable to create symlink: {e.stderr.decode()}" - Logger.print_error(log) - raise From 14132fc34b6d8ae977e944e380ab3d773e7b4a01 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 23 Dec 2023 17:21:50 +0100 Subject: [PATCH 061/137] refactor(ConfigManager): automatically read config upon ConfigManager instance init Signed-off-by: Dominik Willner --- kiauh/core/config_manager/config_manager.py | 6 +++++- kiauh/modules/klipper/klipper_setup.py | 4 ---- kiauh/modules/klipper/klipper_utils.py | 1 - kiauh/modules/mainsail/mainsail_remove.py | 2 -- kiauh/modules/mainsail/mainsail_setup.py | 3 --- kiauh/modules/moonraker/moonraker.py | 1 - kiauh/modules/moonraker/moonraker_setup.py | 4 ---- kiauh/modules/moonraker/moonraker_utils.py | 7 +++---- 8 files changed, 8 insertions(+), 20 deletions(-) diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 4470575..24858c8 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -10,6 +10,7 @@ # ======================================================================= # import configparser +from pathlib import Path from typing import Union from kiauh.utils.logger import Logger @@ -21,6 +22,9 @@ class ConfigManager: self.config_file = cfg_file self.config = CustomConfigParser() + if Path(cfg_file).is_file(): + self.read_config() + def read_config(self) -> None: if not self.config_file: Logger.print_error("Unable to read config file. File not found.") @@ -32,7 +36,7 @@ class ConfigManager: with open(self.config_file, "w") as cfg: self.config.write(cfg) - def get_value(self, section: str, key: str, silent=False) -> Union[str, bool, None]: + def get_value(self, section: str, key: str, silent=True) -> Union[str, bool, None]: if not self.config.has_section(section): if not silent: log = f"Section not defined. Unable to read section: [{section}]." diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 78bc282..01c3a94 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -145,8 +145,6 @@ def install_klipper( def setup_klipper_prerequesites() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) - cm.read_config() - repo = str(cm.get_value("klipper", "repository_url") or DEFAULT_KLIPPER_REPO_URL) branch = str(cm.get_value("klipper", "branch") or "master") @@ -265,8 +263,6 @@ def update_klipper() -> None: return cm = ConfigManager(cfg_file=KIAUH_CFG) - cm.read_config() - if cm.get_value("kiauh", "backup_before_update"): backup_manager = BackupManager(source=KLIPPER_DIR, backup_name="klipper") backup_manager.backup() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index e3439af..26d20a6 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -209,7 +209,6 @@ def create_example_printer_cfg(instance: Klipper) -> None: return cm = ConfigManager(target) - cm.read_config() cm.set_value("virtual_sdcard", "path", instance.gcodes_dir) cm.write_config() Logger.print_ok(f"Example printer.cfg created in '{instance.cfg_dir}'") diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py index d09fdb6..6e80fc1 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -106,7 +106,6 @@ def remove_updater_section(name: str) -> None: continue cm = ConfigManager(instance.cfg_file) - cm.read_config() if not cm.config.has_section(name): Logger.print_info("Section not present. Skipped ...") continue @@ -153,7 +152,6 @@ def remove_printer_cfg_include() -> None: continue cm = ConfigManager(instance.cfg_file) - cm.read_config() if not cm.config.has_section("include mainsail.cfg"): Logger.print_info("Section not present. Skipped ...") continue diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index b8e6615..8de5f50 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -91,7 +91,6 @@ def run_mainsail_installation() -> None: install_ms_config = get_confirm(question, allow_go_back=False) cm = ConfigManager(cfg_file=KIAUH_CFG) - cm.read_config() default_port = cm.get_value("mainsail", "default_port") mainsail_port = default_port if default_port else 80 if not default_port: @@ -214,7 +213,6 @@ def patch_moonraker_conf( return cm = ConfigManager(cfg_file) - cm.read_config() if cm.config.has_section(section_name): Logger.print_info("Section already exist. Skipped ...") return @@ -238,7 +236,6 @@ def patch_printer_config(klipper_instances: List[Klipper]) -> None: return cm = ConfigManager(cfg_file) - cm.read_config() if cm.config.has_section("include mainsail.cfg"): Logger.print_info("Section already exist. Skipped ...") return diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py index df648cf..d3dbfc6 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/modules/moonraker/moonraker.py @@ -156,7 +156,6 @@ class Moonraker(BaseInstance): return None cm = ConfigManager(cfg_file=self.cfg_file) - cm.read_config() port = cm.get_value("server", "port") return int(port) if port is not None else port diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index cde127e..cfddacd 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -150,8 +150,6 @@ def install_moonraker( def setup_moonraker_prerequesites() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) - cm.read_config() - repo = str( cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL ) @@ -291,8 +289,6 @@ def update_moonraker() -> None: return cm = ConfigManager(cfg_file=KIAUH_CFG) - cm.read_config() - if cm.get_value("kiauh", "backup_before_update"): backup_manager = BackupManager(source=MOONRAKER_DIR, backup_name="moonraker") backup_manager.backup() diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index 2c08f5e..ce3d99e 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -41,9 +41,6 @@ def create_example_moonraker_conf( Logger.print_error(f"Unable to create example moonraker.conf:\n{e}") return - cm = ConfigManager(target) - cm.read_config() - ports = [ ports_map.get(instance) for instance in ports_map @@ -61,9 +58,11 @@ def create_example_moonraker_conf( ports_map[instance.suffix] = port - uds = f"{instance.comms_dir}/klippy.sock" ip = get_ipv4_addr().split(".")[:2] ip.extend(["0", "0/16"]) + uds = f"{instance.comms_dir}/klippy.sock" + + cm = ConfigManager(target) trusted_clients = f"\n{'.'.join(ip)}" trusted_clients += cm.get_value("authorization", "trusted_clients") From b9479db76609d62071d7bf5109d45f0bbfbed202 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 23 Dec 2023 19:42:34 +0100 Subject: [PATCH 062/137] feat(KIAUH): show installation status of Klipper and Moonraker in MainMenu Signed-off-by: Dominik Willner --- kiauh/core/menus/main_menu.py | 67 +++++++++++++++++++++++++++-------- kiauh/utils/common.py | 55 ++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 17 deletions(-) diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index bc77121..908d77d 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -18,7 +18,12 @@ from kiauh.core.menus.install_menu import InstallMenu from kiauh.core.menus.remove_menu import RemoveMenu from kiauh.core.menus.settings_menu import SettingsMenu from kiauh.core.menus.update_menu import UpdateMenu -from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT +from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.common import get_repo_name, get_install_status_common +from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT, COLOR_RED class MainMenu(BaseMenu): @@ -36,8 +41,40 @@ class MainMenu(BaseMenu): }, footer_type=QUIT_FOOTER, ) + self.kl_status = None + self.kl_repo = None + self.mr_status = None + self.mr_repo = None + self.ms_status = None + self.fl_status = None + self.ks_status = None + self.mb_status = None + self.cn_status = None + self.tg_status = None + self.ob_status = None + self.oe_status = None + self.init_status() + + def init_status(self) -> None: + status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn", "tg", "ob", "oe"] + for var in status_vars: + setattr(self, f"{var}_status", f"{COLOR_RED}Not installed!{RESET_FORMAT}") + + def fetch_status(self) -> None: + # klipper + self.kl_status = get_install_status_common( + Klipper, KLIPPER_DIR, KLIPPER_ENV_DIR + ) + self.kl_repo = get_repo_name(KLIPPER_DIR) + # moonraker + self.mr_status = get_install_status_common( + Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR + ) + self.mr_repo = get_repo_name(MOONRAKER_DIR) def print_menu(self): + self.fetch_status() + header = " [ Main Menu ] " footer1 = "KIAUH v6.0.0" footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}" @@ -48,21 +85,21 @@ class MainMenu(BaseMenu): /=======================================================\\ | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| - | 0) [Log-Upload] | Klipper: | - | | Repo: | - | 1) [Install] | | - | 2) [Update] | Moonraker: | - | 3) [Remove] | Repo: | - | 4) [Advanced] | | - | 5) [Backup] | Mainsail: | - | | Fluidd: | - | 6) [Settings] | KlipperScreen: | - | | Mobileraker: | + | 0) [Log-Upload] | Klipper: {self.kl_status:<32} | + | | Repo: {self.kl_repo:<32} | + | 1) [Install] |------------------------------------| + | 2) [Update] | Moonraker: {self.mr_status:<32} | + | 3) [Remove] | Repo: {self.mr_repo:<32} | + | 4) [Advanced] |------------------------------------| + | 5) [Backup] | Mainsail: {self.ms_status:<26} | + | | Fluidd: {self.fl_status:<26} | + | 6) [Settings] | KlipperScreen: {self.ks_status:<26} | + | | Mobileraker: {self.mb_status:<26} | | | | - | | Crowsnest: | - | | Telegram Bot: | - | | Obico: | - | | OctoEverywhere: | + | | Crowsnest: {self.cn_status:<26} | + | | Telegram Bot: {self.tg_status:<26} | + | | Obico: {self.ob_status:<26} | + | | OctoEverywhere: {self.oe_status:<26} | |-------------------------------------------------------| | {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} | """ diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 949d6a4..04aa746 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import subprocess # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # @@ -10,9 +11,18 @@ # ======================================================================= # from datetime import datetime -from typing import Dict, Literal, List +from pathlib import Path +from typing import Dict, Literal, List, Type -from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT +from kiauh.core.instance_manager.base_instance import BaseInstance +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.utils.constants import ( + COLOR_CYAN, + RESET_FORMAT, + COLOR_YELLOW, + COLOR_GREEN, + COLOR_RED, +) from kiauh.utils.logger import Logger from kiauh.utils.system_utils import check_package_install, install_system_packages @@ -43,3 +53,44 @@ def check_install_dependencies(deps: List[str]) -> None: for _ in requirements: print(f"{COLOR_CYAN}● {_}{RESET_FORMAT}") install_system_packages(requirements) + + +def get_repo_name(repo_dir: str) -> str: + """ + Helper method to extract the organisation and name of a repository | + :param repo_dir: repository to extract the values from + :return: String in form of "/" + """ + try: + cmd = ["git", "-C", repo_dir, "config", "--get", "remote.origin.url"] + result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) + result = "/".join(result.decode().strip().split("/")[-2:]) + return f"{COLOR_CYAN}{result}{RESET_FORMAT}" + except subprocess.CalledProcessError: + return f"{COLOR_YELLOW}Unknown{RESET_FORMAT}" + + +def get_install_status_common( + instance_type: Type[BaseInstance], repo_dir: str, env_dir: str +) -> str: + """ + Helper method to get the installation status of software components, + which only consist of 3 major parts and if those parts exist, the + component can be considered as "installed". Typically, Klipper or + Moonraker match that criteria. + :param instance_type: The component type + :param repo_dir: the repository directory + :param env_dir: the python environment directory + :return: formatted string, containing the status + """ + im = InstanceManager(instance_type) + dir_exist = Path(repo_dir).exists() + env_dir_exist = Path(env_dir).exists() + instances_exist = len(im.instances) > 0 + status = [dir_exist, env_dir_exist, instances_exist] + if all(status): + return f"{COLOR_GREEN}Installed: {len(im.instances)}{RESET_FORMAT}" + elif not any(status): + return f"{COLOR_RED}Not installed!{RESET_FORMAT}" + else: + return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" From 2f0feb317e419874f0c50e764ee7e5586f8def6a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 24 Dec 2023 12:58:41 +0100 Subject: [PATCH 063/137] refactor(BackupManager): rework backup structure and implement single file backup method Signed-off-by: Dominik Willner --- kiauh/core/backup_manager/backup_manager.py | 65 ++++++++++----------- kiauh/modules/klipper/klipper_setup.py | 8 +-- kiauh/modules/moonraker/moonraker_setup.py | 8 +-- kiauh/utils/common.py | 6 +- 4 files changed, 40 insertions(+), 47 deletions(-) diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index db82162..1ba0beb 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -11,6 +11,7 @@ import shutil from pathlib import Path +from typing import List from kiauh import KIAUH_BACKUP_DIR from kiauh.utils.common import get_current_date @@ -19,51 +20,47 @@ from kiauh.utils.logger import Logger # noinspection PyMethodMayBeStatic class BackupManager: - def __init__( - self, backup_name: str, source: Path, backup_dir: Path = KIAUH_BACKUP_DIR - ): - self._backup_name = backup_name - self._source = source - self._backup_dir = backup_dir + def __init__(self, backup_root_dir: Path = KIAUH_BACKUP_DIR): + self._backup_root_dir = backup_root_dir @property - def backup_name(self) -> str: - return self._backup_name + def backup_root_dir(self) -> Path: + return self._backup_root_dir - @backup_name.setter - def backup_name(self, value: str): - self._backup_name = value + @backup_root_dir.setter + def backup_root_dir(self, value: Path): + self._backup_root_dir = value - @property - def source(self) -> Path: - return self._source + def backup_file(self, files: List[Path] = None, target: Path = None): + if not files: + raise ValueError("Parameter 'files' cannot be None or an empty List!") - @source.setter - def source(self, value: Path): - self._source = value + target = self.backup_root_dir if target is None else target + for file in files: + if Path(file).is_file(): + date = get_current_date().get("date") + time = get_current_date().get("time") + filename = f"{file.stem}-{date}-{time}{file.suffix}" + try: + Path(target).mkdir(exist_ok=True) + shutil.copyfile(file, Path(target, filename)) + except OSError as e: + Logger.print_error(f"Unable to backup '{file}':\n{e}") + continue - @property - def backup_dir(self) -> Path: - return self._backup_dir - - @backup_dir.setter - def backup_dir(self, value: Path): - self._backup_dir = value - - def backup(self) -> None: - if self._source is None or not Path(self._source).exists(): + def backup_directory(self, name: str, source: Path, target: Path = None) -> None: + if source is None or not Path(source).exists(): raise OSError + target = self.backup_root_dir if target is None else target try: - log = f"Creating backup of {self.backup_name} in {self.backup_dir} ..." + log = f"Creating backup of {name} in {target} ..." Logger.print_status(log) - date = get_current_date() - dest = Path( - f"{self.backup_dir}/{self.backup_name}/{date.get('date')}-{date.get('time')}" - ) - shutil.copytree(src=self.source, dst=dest) + date = get_current_date().get("date") + time = get_current_date().get("time") + shutil.copytree(source, Path(target, f"{name}-{date}-{time}")) except OSError as e: - Logger.print_error(f"Unable to backup source directory. Not exist.\n{e}") + Logger.print_error(f"Unable to backup directory '{source}':\n{e}") return Logger.print_ok("Backup successfull!") diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 01c3a94..0e33e51 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -264,11 +264,9 @@ def update_klipper() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) if cm.get_value("kiauh", "backup_before_update"): - backup_manager = BackupManager(source=KLIPPER_DIR, backup_name="klipper") - backup_manager.backup() - backup_manager.backup_name = "klippy-env" - backup_manager.source = KLIPPER_ENV_DIR - backup_manager.backup() + bm = BackupManager() + bm.backup_directory("klipper", KLIPPER_DIR) + bm.backup_directory("klippy-env", KLIPPER_ENV_DIR) instance_manager = InstanceManager(Klipper) instance_manager.stop_all_instance() diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index cfddacd..98b000e 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -290,11 +290,9 @@ def update_moonraker() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) if cm.get_value("kiauh", "backup_before_update"): - backup_manager = BackupManager(source=MOONRAKER_DIR, backup_name="moonraker") - backup_manager.backup() - backup_manager.backup_name = "moonraker-env" - backup_manager.source = MOONRAKER_ENV_DIR - backup_manager.backup() + bm = BackupManager() + bm.backup_directory("moonraker", MOONRAKER_DIR) + bm.backup_directory("moonraker-env", MOONRAKER_ENV_DIR) instance_manager = InstanceManager(Moonraker) instance_manager.stop_all_instance() diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 04aa746..144a4dc 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import subprocess # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # @@ -10,6 +9,7 @@ import subprocess # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import subprocess from datetime import datetime from pathlib import Path from typing import Dict, Literal, List, Type @@ -33,8 +33,8 @@ def get_current_date() -> Dict[Literal["date", "time"], str]: :return: Dict holding a date and time key:value pair """ now: datetime = datetime.today() - date: str = now.strftime("%Y-%m-%d") - time: str = now.strftime("%H-%M-%S") + date: str = now.strftime("%Y%m%d") + time: str = now.strftime("%H%M%S") return {"date": date, "time": time} From 7a6590e86a4b5c1fbc4d4d54077ecf41115a1a9b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 24 Dec 2023 13:53:55 +0100 Subject: [PATCH 064/137] refactor(Mainsail): rework config.json backup Signed-off-by: Dominik Willner --- kiauh/core/backup_manager/backup_manager.py | 8 +++++++- kiauh/modules/mainsail/mainsail_setup.py | 2 +- kiauh/modules/mainsail/mainsail_utils.py | 20 ++++++++++---------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 1ba0beb..608d418 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -31,22 +31,28 @@ class BackupManager: def backup_root_dir(self, value: Path): self._backup_root_dir = value - def backup_file(self, files: List[Path] = None, target: Path = None): + def backup_file( + self, files: List[Path] = None, target: Path = None, custom_filename=None + ): if not files: raise ValueError("Parameter 'files' cannot be None or an empty List!") target = self.backup_root_dir if target is None else target for file in files: + Logger.print_status(f"Creating backup of {file} ...") if Path(file).is_file(): date = get_current_date().get("date") time = get_current_date().get("time") filename = f"{file.stem}-{date}-{time}{file.suffix}" + filename = custom_filename if custom_filename is not None else filename try: Path(target).mkdir(exist_ok=True) shutil.copyfile(file, Path(target, filename)) except OSError as e: Logger.print_error(f"Unable to backup '{file}':\n{e}") continue + else: + Logger.print_info(f"File '{file}' not found ...") def backup_directory(self, name: str, source: Path, target: Path = None) -> None: if source is None or not Path(source).exists(): diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 8de5f50..d1fbc54 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -78,7 +78,7 @@ def run_mainsail_installation() -> None: print_mainsail_already_installed_dialog() do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True) if do_reinstall: - backup_config_json() + backup_config_json(is_temp=True) else: return diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index 4cfc9d5..052809e 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -15,20 +15,20 @@ import shutil from pathlib import Path from typing import List +from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON from kiauh.utils.logger import Logger -# TODO: give this method an optional target dir to backup to -# alteratively use the BackupManager for handling this task (probably best) -def backup_config_json() -> None: - try: - Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") - target = os.path.join(Path.home(), "config.json.kiauh.bak") - shutil.copy(MAINSAIL_CONFIG_JSON, target) - except OSError: - Logger.print_info(f"Unable to backup config.json. Skipped ...") +def backup_config_json(is_temp=False) -> None: + Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") + bm = BackupManager() + if is_temp: + fn = Path(Path.home(), "config.json.kiauh.bak") + bm.backup_file([MAINSAIL_CONFIG_JSON], custom_filename=fn) + else: + bm.backup_file([MAINSAIL_CONFIG_JSON]) def restore_config_json() -> None: @@ -37,7 +37,7 @@ def restore_config_json() -> None: source = os.path.join(Path.home(), "config.json.kiauh.bak") shutil.copy(source, MAINSAIL_CONFIG_JSON) except OSError: - Logger.print_info(f"Unable to restore config.json. Skipped ...") + Logger.print_info("Unable to restore config.json. Skipped ...") def enable_mainsail_remotemode() -> None: From 8cb075429685a1bcc7e7123d355968f7132d305f Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 24 Dec 2023 14:23:57 +0100 Subject: [PATCH 065/137] feat(KIAUH): show Mainsail install status Signed-off-by: Dominik Willner --- kiauh/core/menus/main_menu.py | 8 +++++++- kiauh/modules/mainsail/mainsail_utils.py | 13 +++++++++++- kiauh/utils/common.py | 26 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 908d77d..ea0c317 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -20,9 +20,13 @@ from kiauh.core.menus.settings_menu import SettingsMenu from kiauh.core.menus.update_menu import UpdateMenu from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.mainsail.mainsail_utils import get_mainsail_status from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR from kiauh.modules.moonraker.moonraker import Moonraker -from kiauh.utils.common import get_repo_name, get_install_status_common +from kiauh.utils.common import ( + get_repo_name, + get_install_status_common, +) from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT, COLOR_RED @@ -71,6 +75,8 @@ class MainMenu(BaseMenu): Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR ) self.mr_repo = get_repo_name(MOONRAKER_DIR) + # mainsail + self.ms_status = get_mainsail_status() def print_menu(self): self.fetch_status() diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index 052809e..02c0984 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -17,10 +17,21 @@ from typing import List from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON +from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON, MAINSAIL_DIR +from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_CONFD +from kiauh.utils.common import get_install_status_webui from kiauh.utils.logger import Logger +def get_mainsail_status() -> str: + return get_install_status_webui( + MAINSAIL_DIR, + Path(NGINX_SITES_AVAILABLE, "mainsail"), + Path(NGINX_CONFD, "upstreams.conf"), + Path(NGINX_CONFD, "common_vars.conf"), + ) + + def backup_config_json(is_temp=False) -> None: Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") bm = BackupManager() diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 144a4dc..856534d 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -23,6 +23,7 @@ from kiauh.utils.constants import ( COLOR_GREEN, COLOR_RED, ) +from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.logger import Logger from kiauh.utils.system_utils import check_package_install, install_system_packages @@ -94,3 +95,28 @@ def get_install_status_common( return f"{COLOR_RED}Not installed!{RESET_FORMAT}" else: return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" + + +def get_install_status_webui( + install_dir: Path, nginx_cfg: Path, upstreams_cfg: Path, common_cfg: Path +) -> str: + """ + Helper method to get the installation status of webuis + like Mainsail or Fluidd | + :param install_dir: folder of the static webui files + :param nginx_cfg: the webuis NGINX config + :param upstreams_cfg: the required upstreams.conf + :param common_cfg: the required common_vars.conf + :return: formatted string, containing the status + """ + dir_exist = Path(install_dir).exists() + nginx_cfg_exist = check_file_exist(nginx_cfg) + upstreams_cfg_exist = check_file_exist(upstreams_cfg) + common_cfg_exist = check_file_exist(common_cfg) + status = [dir_exist, nginx_cfg_exist, upstreams_cfg_exist, common_cfg_exist] + if all(status): + return f"{COLOR_GREEN}Installed!{RESET_FORMAT}" + elif not any(status): + return f"{COLOR_RED}Not installed!{RESET_FORMAT}" + else: + return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" From da533fdd672b8afc4cec3a963003f3543100bff8 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 24 Dec 2023 14:35:25 +0100 Subject: [PATCH 066/137] refactor(KIAUH): use util functions for Klipper and Moonraker to get their status Signed-off-by: Dominik Willner --- kiauh/core/menus/main_menu.py | 22 ++++++---------------- kiauh/modules/klipper/klipper_utils.py | 12 ++++++++++-- kiauh/modules/moonraker/moonraker_utils.py | 14 +++++++++++++- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index ea0c317..b62b312 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -18,15 +18,9 @@ from kiauh.core.menus.install_menu import InstallMenu from kiauh.core.menus.remove_menu import RemoveMenu from kiauh.core.menus.settings_menu import SettingsMenu from kiauh.core.menus.update_menu import UpdateMenu -from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR -from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.klipper.klipper_utils import get_klipper_status from kiauh.modules.mainsail.mainsail_utils import get_mainsail_status -from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR -from kiauh.modules.moonraker.moonraker import Moonraker -from kiauh.utils.common import ( - get_repo_name, - get_install_status_common, -) +from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT, COLOR_RED @@ -66,15 +60,11 @@ class MainMenu(BaseMenu): def fetch_status(self) -> None: # klipper - self.kl_status = get_install_status_common( - Klipper, KLIPPER_DIR, KLIPPER_ENV_DIR - ) - self.kl_repo = get_repo_name(KLIPPER_DIR) + self.kl_status = get_klipper_status().get("status") + self.kl_repo = get_klipper_status().get("repo") # moonraker - self.mr_status = get_install_status_common( - Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR - ) - self.mr_repo = get_repo_name(MOONRAKER_DIR) + self.mr_status = get_moonraker_status().get("status") + self.mr_repo = get_moonraker_status().get("repo") # mainsail self.ms_status = get_mainsail_status() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 26d20a6..6e059ce 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -16,22 +16,30 @@ import shutil import subprocess import textwrap -from typing import List, Union +from typing import List, Union, Literal, Dict from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.modules.klipper import MODULE_PATH +from kiauh.modules.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, print_select_custom_name_dialog, ) +from kiauh.utils.common import get_install_status_common, get_repo_name from kiauh.utils.constants import CURRENT_USER from kiauh.utils.input_utils import get_confirm, get_string_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import mask_system_service +def get_klipper_status() -> Dict[Literal["status", "repo"], str]: + return { + "status": get_install_status_common(Klipper, KLIPPER_DIR, KLIPPER_ENV_DIR), + "repo": get_repo_name(KLIPPER_DIR), + } + + def assign_custom_names( instance_count: int, install_count: int, instance_list: List[Klipper] = None ) -> List[str]: diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index ce3d99e..394a84f 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -11,20 +11,32 @@ import os import shutil -from typing import List, Dict +from typing import Dict, Literal from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.modules.moonraker import ( DEFAULT_MOONRAKER_PORT, MODULE_PATH, + MOONRAKER_DIR, + MOONRAKER_ENV_DIR, ) from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.common import get_install_status_common, get_repo_name from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( get_ipv4_addr, ) +def get_moonraker_status() -> Dict[Literal["status", "repo"], str]: + return { + "status": get_install_status_common( + Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR + ), + "repo": get_repo_name(MOONRAKER_DIR), + } + + def create_example_moonraker_conf( instance: Moonraker, ports_map: Dict[str, int] ) -> None: From 8aeb01aca0a9bdd6ab730b0067ce80f3aeb28be3 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 00:59:00 +0100 Subject: [PATCH 067/137] refactor(KIAUH): use pathlib instead of os where possible. consistent use of pathlib. Signed-off-by: Dominik Willner --- kiauh/__init__.py | 7 +- kiauh/core/backup_manager/backup_manager.py | 4 +- kiauh/core/config_manager/config_manager.py | 4 +- kiauh/core/instance_manager/base_instance.py | 49 +++++++------ .../core/instance_manager/instance_manager.py | 13 ++-- kiauh/core/repo_manager/repo_manager.py | 4 +- kiauh/modules/klipper/__init__.py | 9 ++- kiauh/modules/klipper/klipper.py | 71 ++++++++++--------- kiauh/modules/klipper/klipper_setup.py | 5 +- kiauh/modules/klipper/klipper_utils.py | 9 +-- kiauh/modules/mainsail/__init__.py | 10 ++- kiauh/modules/mainsail/mainsail_remove.py | 5 +- kiauh/modules/mainsail/mainsail_setup.py | 17 +++-- kiauh/modules/mainsail/mainsail_utils.py | 15 ++-- kiauh/modules/moonraker/__init__.py | 19 ++--- kiauh/modules/moonraker/moonraker.py | 62 +++++++++------- kiauh/modules/moonraker/moonraker_setup.py | 17 +++-- kiauh/modules/moonraker/moonraker_utils.py | 9 ++- kiauh/utils/__init__.py | 11 +-- kiauh/utils/common.py | 10 ++- kiauh/utils/constants.py | 3 +- kiauh/utils/filesystem_utils.py | 42 ++++------- kiauh/utils/system_utils.py | 21 +++--- 23 files changed, 207 insertions(+), 209 deletions(-) diff --git a/kiauh/__init__.py b/kiauh/__init__.py index 8fc1d9a..e5fd81f 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -9,9 +9,8 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from os.path import join, dirname, abspath from pathlib import Path -APPLICATION_ROOT = dirname(dirname(abspath(__file__))) -KIAUH_CFG = join(APPLICATION_ROOT, "kiauh.cfg") -KIAUH_BACKUP_DIR = f"{Path.home()}/kiauh-backups" +APPLICATION_ROOT = Path(__file__).resolve().parent.parent +KIAUH_CFG = APPLICATION_ROOT.joinpath("kiauh.cfg") +KIAUH_BACKUP_DIR = Path.home().joinpath("kiauh-backups") diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 608d418..68fa338 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -47,7 +47,7 @@ class BackupManager: filename = custom_filename if custom_filename is not None else filename try: Path(target).mkdir(exist_ok=True) - shutil.copyfile(file, Path(target, filename)) + shutil.copyfile(file, target.joinpath(filename)) except OSError as e: Logger.print_error(f"Unable to backup '{file}':\n{e}") continue @@ -64,7 +64,7 @@ class BackupManager: Logger.print_status(log) date = get_current_date().get("date") time = get_current_date().get("time") - shutil.copytree(source, Path(target, f"{name}-{date}-{time}")) + shutil.copytree(source, target.joinpath(f"{name}-{date}-{time}")) except OSError as e: Logger.print_error(f"Unable to backup directory '{source}':\n{e}") return diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 24858c8..5e56626 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -18,11 +18,11 @@ from kiauh.utils.logger import Logger # noinspection PyMethodMayBeStatic class ConfigManager: - def __init__(self, cfg_file: str): + def __init__(self, cfg_file: Path): self.config_file = cfg_file self.config = CustomConfigParser() - if Path(cfg_file).is_file(): + if cfg_file.is_file(): self.read_config() def read_config(self) -> None: diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 0251694..55f6941 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -14,7 +14,6 @@ from pathlib import Path from typing import List, Union, Optional, Type, TypeVar from kiauh.utils.constants import SYSTEMD, CURRENT_USER -from kiauh.utils.filesystem_utils import create_directory B = TypeVar(name="B", bound="BaseInstance", covariant=True) @@ -32,13 +31,13 @@ class BaseInstance(ABC): self._instance_type = instance_type self._suffix = suffix self._user = CURRENT_USER - self._data_dir_name = self.get_data_dir_from_suffix() - self._data_dir = f"{Path.home()}/{self._data_dir_name}_data" - self._cfg_dir = f"{self.data_dir}/config" - self._log_dir = f"{self.data_dir}/logs" - self._comms_dir = f"{self.data_dir}/comms" - self._sysd_dir = f"{self.data_dir}/systemd" - self._gcodes_dir = f"{self.data_dir}/gcodes" + self._data_dir_name = self.get_data_dir_name_from_suffix() + self._data_dir = Path.home().joinpath(f"{self._data_dir_name}_data") + self._cfg_dir = self.data_dir.joinpath("config") + self._log_dir = self.data_dir.joinpath("logs") + self._comms_dir = self.data_dir.joinpath("comms") + self._sysd_dir = self.data_dir.joinpath("systemd") + self._gcodes_dir = self.data_dir.joinpath("gcodes") @property def instance_type(self) -> Type["BaseInstance"]: @@ -73,51 +72,51 @@ class BaseInstance(ABC): self._data_dir_name = value @property - def data_dir(self): + def data_dir(self) -> Path: return self._data_dir @data_dir.setter - def data_dir(self, value: str): + def data_dir(self, value: str) -> None: self._data_dir = value @property - def cfg_dir(self): + def cfg_dir(self) -> Path: return self._cfg_dir @cfg_dir.setter - def cfg_dir(self, value: str): + def cfg_dir(self, value: str) -> None: self._cfg_dir = value @property - def log_dir(self): + def log_dir(self) -> Path: return self._log_dir @log_dir.setter - def log_dir(self, value: str): + def log_dir(self, value: str) -> None: self._log_dir = value @property - def comms_dir(self): + def comms_dir(self) -> Path: return self._comms_dir @comms_dir.setter - def comms_dir(self, value: str): + def comms_dir(self, value: str) -> None: self._comms_dir = value @property - def sysd_dir(self): + def sysd_dir(self) -> Path: return self._sysd_dir @sysd_dir.setter - def sysd_dir(self, value: str): + def sysd_dir(self, value: str) -> None: self._sysd_dir = value @property - def gcodes_dir(self): + def gcodes_dir(self) -> Path: return self._gcodes_dir @gcodes_dir.setter - def gcodes_dir(self, value: str): + def gcodes_dir(self, value: str) -> None: self._gcodes_dir = value @abstractmethod @@ -128,7 +127,7 @@ class BaseInstance(ABC): def delete(self, del_remnants: bool) -> None: raise NotImplementedError("Subclasses must implement the delete method") - def create_folders(self, add_dirs: List[str] = None) -> None: + def create_folders(self, add_dirs: List[Path] = None) -> None: dirs = [ self.data_dir, self.cfg_dir, @@ -141,7 +140,7 @@ class BaseInstance(ABC): dirs.extend(add_dirs) for _dir in dirs: - create_directory(Path(_dir)) + _dir.mkdir(exist_ok=True) def get_service_file_name(self, extension: bool = False) -> str: name = f"{self.__class__.__name__.lower()}" @@ -150,10 +149,10 @@ class BaseInstance(ABC): return name if not extension else f"{name}.service" - def get_service_file_path(self) -> str: - return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}" + def get_service_file_path(self) -> Path: + return SYSTEMD.joinpath(self.get_service_file_name(extension=True)) - def get_data_dir_from_suffix(self) -> str: + def get_data_dir_name_from_suffix(self) -> str: if self._suffix is None: return "printer" elif self._suffix.isdigit(): diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 6891139..adc72de 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -9,9 +9,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import re import subprocess +from pathlib import Path from typing import List, Optional, Union, TypeVar from kiauh.core.instance_manager.base_instance import BaseInstance @@ -194,9 +194,10 @@ class InstanceManager: excluded = self.instance_type.blacklist() service_list = [ - os.path.join(SYSTEMD, service) - for service in os.listdir(SYSTEMD) - if pattern.search(service) and not any(s in service for s in excluded) + Path(SYSTEMD, service) + for service in SYSTEMD.iterdir() + if pattern.search(service.name) + and not any(s in service.name for s in excluded) ] instance_list = [ @@ -206,8 +207,8 @@ class InstanceManager: return instance_list - def _get_instance_suffix(self, file_path: str) -> Union[str, None]: - full_name = file_path.split("/")[-1].split(".")[0] + def _get_instance_suffix(self, file_path: Path) -> Union[str, None]: + full_name = file_path.name.split(".")[0] return full_name.split("-")[-1] if "-" in full_name else None diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index feda37f..a3343e8 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -9,9 +9,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import shutil import subprocess +from pathlib import Path from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger @@ -66,7 +66,7 @@ class RepoManager: log = f"Cloning repository from '{self.repo}' with method '{self.method}'" Logger.print_status(log) try: - if os.path.exists(self.target_dir): + if Path(self.target_dir).exists(): question = f"'{self.target_dir}' already exists. Overwrite?" if not get_confirm(question, default_choice=False): Logger.print_info("Skipping re-clone of repository.") diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/modules/klipper/__init__.py index f355f5d..e4d32b6 100644 --- a/kiauh/modules/klipper/__init__.py +++ b/kiauh/modules/klipper/__init__.py @@ -9,14 +9,13 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os from pathlib import Path -MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) +MODULE_PATH = Path(__file__).resolve().parent -KLIPPER_DIR = f"{Path.home()}/klipper" -KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env" -KLIPPER_REQUIREMENTS_TXT = f"{KLIPPER_DIR}/scripts/klippy-requirements.txt" +KLIPPER_DIR = Path.home().joinpath("klipper") +KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") +KLIPPER_REQUIREMENTS_TXT = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt") DEFAULT_KLIPPER_REPO_URL = "https://github.com/Klipper3D/klipper" EXIT_KLIPPER_SETUP = "Exiting Klipper setup ..." diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 02eb639..ef064a8 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -9,14 +9,13 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import shutil import subprocess from pathlib import Path -from typing import List +from typing import List, Union from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger @@ -29,37 +28,36 @@ class Klipper(BaseInstance): def __init__(self, suffix: str = None): super().__init__(instance_type=self, suffix=suffix) - self.klipper_dir = KLIPPER_DIR - self.env_dir = KLIPPER_ENV_DIR + self.klipper_dir: Path = KLIPPER_DIR + self.env_dir: Path = KLIPPER_ENV_DIR self._cfg_file = self._get_cfg() - self._log = f"{self.log_dir}/klippy.log" - self._serial = f"{self.comms_dir}/klippy.serial" - self._uds = f"{self.comms_dir}/klippy.sock" + self._log = self.log_dir.joinpath("klippy.log") + self._serial = self.comms_dir.joinpath("klippy.serial") + self._uds = self.comms_dir.joinpath("klippy.sock") @property - def cfg_file(self) -> str: + def cfg_file(self) -> Path: return self._cfg_file @property - def log(self) -> str: + def log(self) -> Path: return self._log @property - def serial(self) -> str: + def serial(self) -> Path: return self._serial @property - def uds(self) -> str: + def uds(self) -> Path: return self._uds def create(self) -> None: Logger.print_status("Creating new Klipper Instance ...") - module_path = os.path.dirname(os.path.abspath(__file__)) - service_template_path = os.path.join(module_path, "res", "klipper.service") - env_template_file_path = os.path.join(module_path, "res", "klipper.env") + service_template_path = MODULE_PATH.joinpath("res/klipper.service") service_file_name = self.get_service_file_name(extension=True) - service_file_target = f"{SYSTEMD}/{service_file_name}" - env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env") + service_file_target = SYSTEMD.joinpath(service_file_name) + env_template_file_path = MODULE_PATH.joinpath("res/klipper.env") + env_file_target = self.sysd_dir.joinpath("klipper.env") try: self.create_folders() @@ -95,8 +93,11 @@ class Klipper(BaseInstance): self._delete_klipper_remnants() def write_service_file( - self, service_template_path: str, service_file_target: str, env_file_target: str - ): + self, + service_template_path: Path, + service_file_target: Path, + env_file_target: Path, + ) -> None: service_content = self._prep_service_file( service_template_path, env_file_target ) @@ -109,7 +110,9 @@ class Klipper(BaseInstance): ) Logger.print_ok(f"Service file created: {service_file_target}") - def write_env_file(self, env_template_file_path: str, env_file_target: str): + def write_env_file( + self, env_template_file_path: Path, env_file_target: Path + ) -> None: env_file_content = self._prep_env_file(env_template_file_path) with open(env_file_target, "w") as env_file: env_file.write(env_file_content) @@ -129,7 +132,9 @@ class Klipper(BaseInstance): Logger.print_ok("Directories successfully deleted.") - def _prep_service_file(self, service_template_path, env_file_path): + def _prep_service_file( + self, service_template_path: Path, env_file_path: Path + ) -> str: try: with open(service_template_path, "r") as template_file: template_content = template_file.read() @@ -139,12 +144,14 @@ class Klipper(BaseInstance): ) raise service_content = template_content.replace("%USER%", self.user) - service_content = service_content.replace("%KLIPPER_DIR%", self.klipper_dir) - service_content = service_content.replace("%ENV%", self.env_dir) - service_content = service_content.replace("%ENV_FILE%", env_file_path) + service_content = service_content.replace( + "%KLIPPER_DIR%", str(self.klipper_dir) + ) + service_content = service_content.replace("%ENV%", str(self.env_dir)) + service_content = service_content.replace("%ENV_FILE%", str(env_file_path)) return service_content - def _prep_env_file(self, env_template_file_path): + def _prep_env_file(self, env_template_file_path: Path) -> str: try: with open(env_template_file_path, "r") as env_file: env_template_file_content = env_file.read() @@ -154,18 +161,18 @@ class Klipper(BaseInstance): ) raise env_file_content = env_template_file_content.replace( - "%KLIPPER_DIR%", self.klipper_dir + "%KLIPPER_DIR%", str(self.klipper_dir) ) env_file_content = env_file_content.replace( "%CFG%", f"{self.cfg_dir}/printer.cfg" ) - env_file_content = env_file_content.replace("%SERIAL%", self._serial) - env_file_content = env_file_content.replace("%LOG%", self._log) - env_file_content = env_file_content.replace("%UDS%", self._uds) + env_file_content = env_file_content.replace("%SERIAL%", str(self.serial)) + env_file_content = env_file_content.replace("%LOG%", str(self.log)) + env_file_content = env_file_content.replace("%UDS%", str(self.uds)) return env_file_content - def _get_cfg(self): - cfg_file_loc = f"{self.cfg_dir}/printer.cfg" - if Path(cfg_file_loc).is_file(): + def _get_cfg(self) -> Union[Path, None]: + cfg_file_loc = self.cfg_dir.joinpath("printer.cfg") + if cfg_file_loc.is_file(): return cfg_file_loc return None diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 0e33e51..6d6fd66 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import subprocess from pathlib import Path from typing import List, Union @@ -163,13 +162,13 @@ def setup_klipper_prerequesites() -> None: def install_klipper_packages(klipper_dir: Path) -> None: - script = Path(f"{klipper_dir}/scripts/install-debian.sh") + script = klipper_dir.joinpath("scripts/install-debian.sh") packages = parse_packages_from_file(script) packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages] # Add dfu-util for octopi-images packages.append("dfu-util") # Add dbus requirement for DietPi distro - if os.path.exists("/boot/dietpi/.version"): + if Path("/boot/dietpi/.version").exists(): packages.append("dbus") update_system_package_lists(silent=False) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 6e059ce..bd929c6 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -15,6 +15,7 @@ import grp import shutil import subprocess import textwrap +from pathlib import Path from typing import List, Union, Literal, Dict @@ -113,7 +114,7 @@ def handle_single_to_multi_conversion( instance_manager.current_instance = Klipper(suffix=name) new_data_dir_name = instance_manager.current_instance.data_dir try: - os.rename(old_data_dir_name, new_data_dir_name) + Path(old_data_dir_name).rename(new_data_dir_name) return instance_manager.current_instance except OSError as e: log = f"Cannot rename {old_data_dir_name} to {new_data_dir_name}:\n{e}" @@ -208,8 +209,8 @@ def create_example_printer_cfg(instance: Klipper) -> None: Logger.print_info(f"printer.cfg in '{instance.cfg_dir}' already exists.") return - source = os.path.join(MODULE_PATH, "res", "printer.cfg") - target = os.path.join(instance.cfg_dir, "printer.cfg") + source = MODULE_PATH.joinpath("res/printer.cfg") + target = instance.cfg_dir.joinpath("printer.cfg") try: shutil.copy(source, target) except OSError as e: @@ -217,6 +218,6 @@ def create_example_printer_cfg(instance: Klipper) -> None: return cm = ConfigManager(target) - cm.set_value("virtual_sdcard", "path", instance.gcodes_dir) + cm.set_value("virtual_sdcard", "path", str(instance.gcodes_dir)) cm.write_config() Logger.print_ok(f"Example printer.cfg created in '{instance.cfg_dir}'") diff --git a/kiauh/modules/mainsail/__init__.py b/kiauh/modules/mainsail/__init__.py index 06f2027..87dc2f5 100644 --- a/kiauh/modules/mainsail/__init__.py +++ b/kiauh/modules/mainsail/__init__.py @@ -11,12 +11,10 @@ from pathlib import Path -import os - -MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) -MAINSAIL_DIR = os.path.join(Path.home(), "mainsail") -MAINSAIL_CONFIG_DIR = os.path.join(Path.home(), "mainsail-config") -MAINSAIL_CONFIG_JSON = os.path.join(MAINSAIL_DIR, "config.json") +MODULE_PATH = Path(__file__).resolve().parent +MAINSAIL_DIR = Path(Path.home(), "mainsail") +MAINSAIL_CONFIG_DIR = Path(Path.home(), "mainsail-config") +MAINSAIL_CONFIG_JSON = Path(MAINSAIL_DIR, "config.json") MAINSAIL_URL = ( "https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip" ) diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py index 6e80fc1..275ce1f 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -13,6 +13,7 @@ import shutil import subprocess from pathlib import Path +from typing import List from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager @@ -64,8 +65,8 @@ def remove_mainsail_dir() -> None: def remove_nginx_config() -> None: Logger.print_status("Removing Mainsails NGINX config ...") try: - remove_file(Path(NGINX_SITES_AVAILABLE, "mainsail"), True) - remove_file(Path(NGINX_SITES_ENABLED, "mainsail"), True) + remove_file(NGINX_SITES_AVAILABLE.joinpath("mainsail"), True) + remove_file(NGINX_SITES_ENABLED.joinpath("mainsail"), True) except subprocess.CalledProcessError as e: log = f"Unable to remove Mainsail NGINX config:\n{e.stderr.decode()}" diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index d1fbc54..758c39a 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os.path import subprocess from pathlib import Path from typing import List @@ -72,7 +71,7 @@ def run_mainsail_installation() -> None: else: return - is_mainsail_installed = Path(f"{Path.home()}/mainsail").exists() + is_mainsail_installed = Path.home().joinpath("mainsail").exists() do_reinstall = False if is_mainsail_installed: print_mainsail_already_installed_dialog() @@ -92,9 +91,9 @@ def run_mainsail_installation() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) default_port = cm.get_value("mainsail", "default_port") - mainsail_port = default_port if default_port else 80 + mainsail_port = default_port if default_port else "80" if not default_port: - print_mainsail_port_select_dialog(f"{mainsail_port}") + print_mainsail_port_select_dialog(mainsail_port) mainsail_port = get_number_input( "Configure Mainsail for port", min_count=mainsail_port, @@ -148,11 +147,11 @@ def run_mainsail_installation() -> None: def download_mainsail() -> None: try: Logger.print_status("Downloading Mainsail ...") - download_file(MAINSAIL_URL, f"{Path.home()}", "mainsail.zip") + download_file(MAINSAIL_URL, Path.home(), "mainsail.zip") Logger.print_ok("Download complete!") Logger.print_status("Extracting mainsail.zip ...") - unzip(f"{Path.home()}/mainsail.zip", MAINSAIL_DIR) + unzip(Path.home().joinpath("mainsail.zip"), MAINSAIL_DIR) Logger.print_ok("OK!") except Exception: @@ -184,8 +183,8 @@ def create_mainsail_cfg_symlink(klipper_instances: List[Klipper]) -> None: def create_mainsail_nginx_cfg(port: int) -> None: root_dir = MAINSAIL_DIR - source = Path(NGINX_SITES_AVAILABLE, "mainsail") - target = Path(NGINX_SITES_ENABLED, "mainsail") + source = NGINX_SITES_AVAILABLE.joinpath("mainsail") + target = NGINX_SITES_ENABLED.joinpath("mainsail") try: Logger.print_status("Creating NGINX config for Mainsail ...") remove_file(Path("/etc/nginx/sites-enabled/default"), True) @@ -217,7 +216,7 @@ def patch_moonraker_conf( Logger.print_info("Section already exist. Skipped ...") return - template = os.path.join(MODULE_PATH, "res", template_file) + template = MODULE_PATH.joinpath("res", template_file) with open(template, "r") as t: template_content = "\n" template_content += t.read() diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index 02c0984..196e27a 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -10,7 +10,6 @@ # ======================================================================= # import json -import os import shutil from pathlib import Path from typing import List @@ -26,9 +25,9 @@ from kiauh.utils.logger import Logger def get_mainsail_status() -> str: return get_install_status_webui( MAINSAIL_DIR, - Path(NGINX_SITES_AVAILABLE, "mainsail"), - Path(NGINX_CONFD, "upstreams.conf"), - Path(NGINX_CONFD, "common_vars.conf"), + NGINX_SITES_AVAILABLE.joinpath("mainsail"), + NGINX_CONFD.joinpath("upstreams.conf"), + NGINX_CONFD.joinpath("common_vars.conf"), ) @@ -36,7 +35,7 @@ def backup_config_json(is_temp=False) -> None: Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") bm = BackupManager() if is_temp: - fn = Path(Path.home(), "config.json.kiauh.bak") + fn = Path.home().joinpath("config.json.kiauh.bak") bm.backup_file([MAINSAIL_CONFIG_JSON], custom_filename=fn) else: bm.backup_file([MAINSAIL_CONFIG_JSON]) @@ -45,7 +44,7 @@ def backup_config_json(is_temp=False) -> None: def restore_config_json() -> None: try: Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") - source = os.path.join(Path.home(), "config.json.kiauh.bak") + source = Path.home().joinpath("config.json.kiauh.bak") shutil.copy(source, MAINSAIL_CONFIG_JSON) except OSError: Logger.print_info("Unable to restore config.json. Skipped ...") @@ -71,10 +70,10 @@ def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: error_log = Path("/var/log/nginx/mainsail-error.log") for instance in klipper_instances: - desti_access = Path(instance.log_dir).joinpath("mainsail-access.log") + desti_access = instance.log_dir.joinpath("mainsail-access.log") if not desti_access.exists(): desti_access.symlink_to(access_log) - desti_error = Path(instance.log_dir).joinpath("mainsail-error.log") + desti_error = instance.log_dir.joinpath("mainsail-error.log") if not desti_error.exists(): desti_error.symlink_to(error_log) diff --git a/kiauh/modules/moonraker/__init__.py b/kiauh/modules/moonraker/__init__.py index af0e88f..9e7231e 100644 --- a/kiauh/modules/moonraker/__init__.py +++ b/kiauh/modules/moonraker/__init__.py @@ -9,23 +9,24 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os from pathlib import Path -MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) +MODULE_PATH = Path(__file__).resolve().parent -MOONRAKER_DIR = f"{Path.home()}/moonraker" -MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env" -MOONRAKER_REQUIREMENTS_TXT = f"{MOONRAKER_DIR}/scripts/moonraker-requirements.txt" +MOONRAKER_DIR = Path.home().joinpath("moonraker") +MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env") +MOONRAKER_REQUIREMENTS_TXT = MOONRAKER_DIR.joinpath( + "scripts/moonraker-requirements.txt" +) DEFAULT_MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker" DEFAULT_MOONRAKER_PORT = 7125 # introduced due to # https://github.com/Arksine/moonraker/issues/349 # https://github.com/Arksine/moonraker/pull/346 -POLKIT_LEGACY_FILE = "/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla" -POLKIT_FILE = "/etc/polkit-1/rules.d/moonraker.rules" -POLKIT_USR_FILE = "/usr/share/polkit-1/rules.d/moonraker.rules" -POLKIT_SCRIPT = f"{Path.home()}/moonraker/scripts/set-policykit-rules.sh" +POLKIT_LEGACY_FILE = Path("/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla") +POLKIT_FILE = Path("/etc/polkit-1/rules.d/moonraker.rules") +POLKIT_USR_FILE = Path("/usr/share/polkit-1/rules.d/moonraker.rules") +POLKIT_SCRIPT = Path.home().joinpath("moonraker/scripts/set-policykit-rules.sh") EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..." diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py index d3dbfc6..8c6777c 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/modules/moonraker/moonraker.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import shutil import subprocess from pathlib import Path @@ -30,22 +29,22 @@ class Moonraker(BaseInstance): def __init__(self, suffix: str = None): super().__init__(instance_type=self, suffix=suffix) - self.moonraker_dir = MOONRAKER_DIR - self.env_dir = MOONRAKER_ENV_DIR + self.moonraker_dir: Path = MOONRAKER_DIR + self.env_dir: Path = MOONRAKER_ENV_DIR self.cfg_file = self._get_cfg() self.port = self._get_port() - self.backup_dir = f"{self.data_dir}/backup" - self.certs_dir = f"{self.data_dir}/certs" - self.db_dir = f"{self.data_dir}/database" - self.log = f"{self.log_dir}/moonraker.log" + self.backup_dir = self.data_dir.joinpath("backup") + self.certs_dir = self.data_dir.joinpath("certs") + self.db_dir = self.data_dir.joinpath("database") + self.log = self.log_dir.joinpath("moonraker.log") def create(self, create_example_cfg: bool = False) -> None: Logger.print_status("Creating new Moonraker Instance ...") - service_template_path = os.path.join(MODULE_PATH, "res", "moonraker.service") - env_template_file_path = os.path.join(MODULE_PATH, "res", "moonraker.env") + service_template_path = MODULE_PATH.joinpath("res/moonraker.service") + env_template_file_path = MODULE_PATH.joinpath("res/moonraker.env") service_file_name = self.get_service_file_name(extension=True) - service_file_target = f"{SYSTEMD}/{service_file_name}" - env_file_target = os.path.abspath(f"{self.sysd_dir}/moonraker.env") + service_file_target = SYSTEMD.joinpath(service_file_name) + env_file_target = self.sysd_dir.joinpath("moonraker.env") try: self.create_folders([self.backup_dir, self.certs_dir, self.db_dir]) @@ -81,8 +80,11 @@ class Moonraker(BaseInstance): self._delete_moonraker_remnants() def write_service_file( - self, service_template_path: str, service_file_target: str, env_file_target: str - ): + self, + service_template_path: Path, + service_file_target: Path, + env_file_target: Path, + ) -> None: service_content = self._prep_service_file( service_template_path, env_file_target ) @@ -95,7 +97,9 @@ class Moonraker(BaseInstance): ) Logger.print_ok(f"Service file created: {service_file_target}") - def write_env_file(self, env_template_file_path: str, env_file_target: str): + def write_env_file( + self, env_template_file_path: Path, env_file_target: Path + ) -> None: env_file_content = self._prep_env_file(env_template_file_path) with open(env_file_target, "w") as env_file: env_file.write(env_file_content) @@ -104,9 +108,9 @@ class Moonraker(BaseInstance): def _delete_moonraker_remnants(self) -> None: try: Logger.print_status(f"Delete {self.moonraker_dir} ...") - shutil.rmtree(Path(self.moonraker_dir)) + shutil.rmtree(self.moonraker_dir) Logger.print_status(f"Delete {self.env_dir} ...") - shutil.rmtree(Path(self.env_dir)) + shutil.rmtree(self.env_dir) except FileNotFoundError: Logger.print_status("Cannot delete Moonraker directories. Not found.") except PermissionError as e: @@ -115,7 +119,9 @@ class Moonraker(BaseInstance): Logger.print_ok("Directories successfully deleted.") - def _prep_service_file(self, service_template_path, env_file_path): + def _prep_service_file( + self, service_template_path: Path, env_file_path: Path + ) -> str: try: with open(service_template_path, "r") as template_file: template_content = template_file.read() @@ -125,12 +131,14 @@ class Moonraker(BaseInstance): ) raise service_content = template_content.replace("%USER%", self.user) - service_content = service_content.replace("%MOONRAKER_DIR%", self.moonraker_dir) - service_content = service_content.replace("%ENV%", self.env_dir) - service_content = service_content.replace("%ENV_FILE%", env_file_path) + service_content = service_content.replace( + "%MOONRAKER_DIR%", str(self.moonraker_dir) + ) + service_content = service_content.replace("%ENV%", str(self.env_dir)) + service_content = service_content.replace("%ENV_FILE%", str(env_file_path)) return service_content - def _prep_env_file(self, env_template_file_path): + def _prep_env_file(self, env_template_file_path: Path) -> str: try: with open(env_template_file_path, "r") as env_file: env_template_file_content = env_file.read() @@ -140,14 +148,16 @@ class Moonraker(BaseInstance): ) raise env_file_content = env_template_file_content.replace( - "%MOONRAKER_DIR%", self.moonraker_dir + "%MOONRAKER_DIR%", str(self.moonraker_dir) + ) + env_file_content = env_file_content.replace( + "%PRINTER_DATA%", str(self.data_dir) ) - env_file_content = env_file_content.replace("%PRINTER_DATA%", self.data_dir) return env_file_content - def _get_cfg(self): - cfg_file_loc = f"{self.cfg_dir}/moonraker.conf" - if Path(cfg_file_loc).is_file(): + def _get_cfg(self) -> Union[Path, None]: + cfg_file_loc = self.cfg_dir.joinpath("moonraker.conf") + if cfg_file_loc.is_file(): return cfg_file_loc return None diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 98b000e..a883071 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -163,14 +163,13 @@ def setup_moonraker_prerequesites() -> None: repo_manager.clone_repo() # install moonraker dependencies and create python virtualenv - install_moonraker_packages(Path(MOONRAKER_DIR)) - create_python_venv(Path(MOONRAKER_ENV_DIR)) - moonraker_py_req = Path(MOONRAKER_REQUIREMENTS_TXT) - install_python_requirements(Path(MOONRAKER_ENV_DIR), moonraker_py_req) + install_moonraker_packages(MOONRAKER_DIR) + create_python_venv(MOONRAKER_ENV_DIR) + install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQUIREMENTS_TXT) def install_moonraker_packages(moonraker_dir: Path) -> None: - script = Path(f"{moonraker_dir}/scripts/install-moonraker.sh") + script = moonraker_dir.joinpath("scripts/install-moonraker.sh") packages = parse_packages_from_file(script) update_system_package_lists(silent=False) install_system_packages(packages) @@ -179,9 +178,9 @@ def install_moonraker_packages(moonraker_dir: Path) -> None: def install_moonraker_polkit() -> None: Logger.print_status("Installing Moonraker policykit rules ...") - legacy_file_exists = check_file_exist(Path(POLKIT_LEGACY_FILE), True) - polkit_file_exists = check_file_exist(Path(POLKIT_FILE), True) - usr_file_exists = check_file_exist(Path(POLKIT_USR_FILE), True) + legacy_file_exists = check_file_exist(POLKIT_LEGACY_FILE, True) + polkit_file_exists = check_file_exist(POLKIT_FILE, True) + usr_file_exists = check_file_exist(POLKIT_USR_FILE, True) if legacy_file_exists or (polkit_file_exists and usr_file_exists): Logger.print_info("Moonraker policykit rules are already installed.") @@ -267,7 +266,7 @@ def remove_instances( def remove_polkit_rules() -> None: Logger.print_status("Removing all Moonraker policykit rules ...") - if not Path(MOONRAKER_DIR).exists(): + if not MOONRAKER_DIR.exists(): log = "Cannot remove policykit rules. Moonraker directory not found." Logger.print_warn(log) return diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index 394a84f..01ee7d7 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import shutil from typing import Dict, Literal @@ -45,8 +44,8 @@ def create_example_moonraker_conf( Logger.print_info(f"moonraker.conf in '{instance.cfg_dir}' already exists.") return - source = os.path.join(MODULE_PATH, "res", "moonraker.conf") - target = os.path.join(instance.cfg_dir, "moonraker.conf") + source = MODULE_PATH.joinpath("res/moonraker.conf") + target = instance.cfg_dir.joinpath("moonraker.conf") try: shutil.copy(source, target) except OSError as e: @@ -72,14 +71,14 @@ def create_example_moonraker_conf( ip = get_ipv4_addr().split(".")[:2] ip.extend(["0", "0/16"]) - uds = f"{instance.comms_dir}/klippy.sock" + uds = instance.comms_dir.joinpath("klippy.sock") cm = ConfigManager(target) trusted_clients = f"\n{'.'.join(ip)}" trusted_clients += cm.get_value("authorization", "trusted_clients") cm.set_value("server", "port", str(port)) - cm.set_value("server", "klippy_uds_address", uds) + cm.set_value("server", "klippy_uds_address", str(uds)) cm.set_value("authorization", "trusted_clients", trusted_clients) cm.write_config() diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index 108b521..f44410e 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -8,12 +8,13 @@ # # # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os -MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) +from pathlib import Path + +MODULE_PATH = Path(__file__).resolve().parent INVALID_CHOICE = "Invalid choice. Please select a valid value." # ================== NGINX =====================# -NGINX_SITES_AVAILABLE = "/etc/nginx/sites-available" -NGINX_SITES_ENABLED = "/etc/nginx/sites-enabled" -NGINX_CONFD = "/etc/nginx/conf.d" +NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available") +NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled") +NGINX_CONFD = Path("/etc/nginx/conf.d") diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 856534d..8cb20c8 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -56,7 +56,7 @@ def check_install_dependencies(deps: List[str]) -> None: install_system_packages(requirements) -def get_repo_name(repo_dir: str) -> str: +def get_repo_name(repo_dir: Path) -> str: """ Helper method to extract the organisation and name of a repository | :param repo_dir: repository to extract the values from @@ -72,7 +72,7 @@ def get_repo_name(repo_dir: str) -> str: def get_install_status_common( - instance_type: Type[BaseInstance], repo_dir: str, env_dir: str + instance_type: Type[BaseInstance], repo_dir: Path, env_dir: Path ) -> str: """ Helper method to get the installation status of software components, @@ -85,10 +85,8 @@ def get_install_status_common( :return: formatted string, containing the status """ im = InstanceManager(instance_type) - dir_exist = Path(repo_dir).exists() - env_dir_exist = Path(env_dir).exists() instances_exist = len(im.instances) > 0 - status = [dir_exist, env_dir_exist, instances_exist] + status = [repo_dir.exists(), env_dir.exists(), instances_exist] if all(status): return f"{COLOR_GREEN}Installed: {len(im.instances)}{RESET_FORMAT}" elif not any(status): @@ -109,7 +107,7 @@ def get_install_status_webui( :param common_cfg: the required common_vars.conf :return: formatted string, containing the status """ - dir_exist = Path(install_dir).exists() + dir_exist = install_dir.exists() nginx_cfg_exist = check_file_exist(nginx_cfg) upstreams_cfg_exist = check_file_exist(upstreams_cfg) common_cfg_exist = check_file_exist(common_cfg) diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 66b00ff..647b55b 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -11,6 +11,7 @@ import os import pwd +from pathlib import Path # text colors and formats COLOR_WHITE = "\033[37m" # white @@ -22,4 +23,4 @@ COLOR_CYAN = "\033[96m" # bright cyan RESET_FORMAT = "\033[0m" # reset format # current user CURRENT_USER = pwd.getpwuid(os.getuid())[0] -SYSTEMD = "/etc/systemd/system" +SYSTEMD = Path("/etc/systemd/system") diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index b623be2..505c1f1 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -8,7 +8,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import os import shutil import subprocess from pathlib import Path @@ -37,27 +36,12 @@ def check_file_exist(file_path: Path, sudo=False) -> bool: except subprocess.CalledProcessError: return False else: - if Path(file_path).exists(): + if file_path.exists(): return True else: return False -def create_directory(_dir: Path) -> None: - """ - Helper function for creating a directory or skipping if it already exists | - :param _dir: the directory to create - :return: None - """ - try: - if not os.path.isdir(_dir): - os.makedirs(_dir, exist_ok=True) - Logger.print_ok(f"Created directory: {_dir}") - except OSError as e: - Logger.print_error(f"Error creating folder: {e}") - raise - - def create_symlink(source: Path, target: Path, sudo=False) -> None: try: cmd = ["ln", "-sf", source, target] @@ -79,14 +63,14 @@ def remove_file(file_path: Path, sudo=False) -> None: raise -def unzip(file: str, target_dir: str) -> None: +def unzip(filepath: Path, target_dir: Path) -> None: """ Helper function to unzip a zip-archive into a target directory | - :param file: the zip-file to unzip + :param filepath: the path to the zip-file to unzip :param target_dir: the target directory to extract the files into :return: None """ - with ZipFile(file, "r") as _zip: + with ZipFile(filepath, "r") as _zip: _zip.extractall(target_dir) @@ -95,8 +79,8 @@ def copy_upstream_nginx_cfg() -> None: Creates an upstream.conf in /etc/nginx/conf.d :return: None """ - source = os.path.join(MODULE_PATH, "res", "upstreams.conf") - target = os.path.join(NGINX_CONFD, "upstreams.conf") + source = MODULE_PATH.joinpath("res/upstreams.conf") + target = NGINX_CONFD.joinpath("upstreams.conf") try: command = ["sudo", "cp", source, target] subprocess.run(command, stderr=subprocess.PIPE, check=True) @@ -111,8 +95,8 @@ def copy_common_vars_nginx_cfg() -> None: Creates a common_vars.conf in /etc/nginx/conf.d :return: None """ - source = os.path.join(MODULE_PATH, "res", "common_vars.conf") - target = os.path.join(NGINX_CONFD, "common_vars.conf") + source = MODULE_PATH.joinpath("res/common_vars.conf") + target = NGINX_CONFD.joinpath("common_vars.conf") try: command = ["sudo", "cp", source, target] subprocess.run(command, stderr=subprocess.PIPE, check=True) @@ -122,7 +106,7 @@ def copy_common_vars_nginx_cfg() -> None: raise -def create_nginx_cfg(name: str, port: int, root_dir: str) -> None: +def create_nginx_cfg(name: str, port: int, root_dir: Path) -> None: """ Creates an NGINX config from a template file and replaces all placeholders :param name: name of the config to create @@ -130,18 +114,18 @@ def create_nginx_cfg(name: str, port: int, root_dir: str) -> None: :param root_dir: directory of the static files :return: None """ - tmp = f"{Path.home()}/{name}.tmp" - shutil.copy(os.path.join(MODULE_PATH, "res", "nginx_cfg"), tmp) + tmp = Path.home().joinpath(f"{name}.tmp") + shutil.copy(MODULE_PATH.joinpath("res/nginx_cfg"), tmp) with open(tmp, "r+") as f: content = f.read() content = content.replace("%NAME%", name) content = content.replace("%PORT%", str(port)) - content = content.replace("%ROOT_DIR%", root_dir) + content = content.replace("%ROOT_DIR%", str(root_dir)) f.seek(0) f.write(content) f.truncate() - target = os.path.join(NGINX_SITES_AVAILABLE, name) + target = NGINX_SITES_AVAILABLE.joinpath(name) try: command = ["sudo", "mv", tmp, target] subprocess.run(command, stderr=subprocess.PIPE, check=True) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 0b07655..be317ea 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -98,7 +98,7 @@ def update_python_pip(target: Path) -> None: """ Logger.print_status("Updating pip ...") try: - command = [f"{target}/bin/pip", "install", "-U", "pip"] + command = [target.joinpath("bin/pip"), "install", "-U", "pip"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) if result.returncode != 0 or result.stderr: Logger.print_error(f"{result.stderr}", False) @@ -120,7 +120,7 @@ def install_python_requirements(target: Path, requirements: Path) -> None: update_python_pip(target) Logger.print_status("Installing Python requirements ...") try: - command = [f"{target}/bin/pip", "install", "-r", f"{requirements}"] + command = [target.joinpath("bin/pip"), "install", "-r", f"{requirements}"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) if result.returncode != 0 or result.stderr: Logger.print_error(f"{result.stderr}", False) @@ -141,9 +141,12 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: :return: None """ cache_mtime = 0 - cache_files = ["/var/lib/apt/periodic/update-success-stamp", "/var/lib/apt/lists"] + cache_files = [ + Path("/var/lib/apt/periodic/update-success-stamp"), + Path("/var/lib/apt/lists"), + ] for cache_file in cache_files: - if Path(cache_file).exists(): + if cache_file.exists(): cache_mtime = max(cache_mtime, os.path.getmtime(cache_file)) update_age = int(time.time() - cache_mtime) @@ -244,7 +247,7 @@ def get_ipv4_addr() -> str: def download_file( - url: str, target_folder: str, target_name: str, show_progress=True + url: str, target_folder: Path, target_name: str, show_progress=True ) -> None: """ Helper method for downloading files from a provided URL | @@ -254,7 +257,7 @@ def download_file( :param show_progress: show download progress or not :return: None """ - target_path = os.path.join(target_folder, target_name) + target_path = target_folder.joinpath(target_name) try: if show_progress: urllib.request.urlretrieve(url, target_path, download_progress) @@ -298,8 +301,8 @@ def set_nginx_permissions() -> None: This seems to have become necessary with Ubuntu 21+. | :return: None """ - cmd1 = f"ls -ld {Path.home()} | cut -d' ' -f1" - homedir_perm = subprocess.run(cmd1, shell=True, stdout=subprocess.PIPE, text=True) + cmd = f"ls -ld {Path.home()} | cut -d' ' -f1" + homedir_perm = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, text=True) homedir_perm = homedir_perm.stdout if homedir_perm.count("x") < 3: @@ -321,7 +324,7 @@ def control_systemd_service( Logger.print_status(f"{action.capitalize()} {name}.service ...") command = ["sudo", "systemctl", action, f"{name}.service"] subprocess.run(command, stderr=subprocess.PIPE, check=True) - Logger.print_ok(f"OK!") + Logger.print_ok("OK!") except subprocess.CalledProcessError as e: log = f"Failed to {action} {name}.service: {e.stderr.decode()}" Logger.print_error(log) From 012b6c4bb7ca4eb319140e0944fa35fb164c31ce Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 15:12:08 +0100 Subject: [PATCH 068/137] refactor(Moonraker): rework remove process Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/base_instance.py | 2 +- .../core/instance_manager/instance_manager.py | 8 +- kiauh/core/menus/install_menu.py | 2 +- kiauh/core/menus/remove_menu.py | 8 +- kiauh/modules/moonraker/menus/__init__.py | 0 .../moonraker/menus/moonraker_remove_menu.py | 119 +++++++++++++ kiauh/modules/moonraker/moonraker.py | 19 +-- kiauh/modules/moonraker/moonraker_remove.py | 156 ++++++++++++++++++ kiauh/modules/moonraker/moonraker_setup.py | 138 +++------------- 9 files changed, 308 insertions(+), 144 deletions(-) create mode 100644 kiauh/modules/moonraker/menus/__init__.py create mode 100644 kiauh/modules/moonraker/menus/moonraker_remove_menu.py create mode 100644 kiauh/modules/moonraker/moonraker_remove.py 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: From 142b4498a35a51186694b9aaa4beec3f7f6afa3c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 15:35:13 +0100 Subject: [PATCH 069/137] refactor(Klipper): rework remove process Signed-off-by: Dominik Willner --- kiauh/core/menus/install_menu.py | 2 +- kiauh/core/menus/remove_menu.py | 7 +- kiauh/modules/klipper/klipper.py | 19 +-- kiauh/modules/klipper/klipper_remove.py | 133 +++++++++++++++++ kiauh/modules/klipper/klipper_setup.py | 138 ++++-------------- kiauh/modules/klipper/menus/__init__.py | 0 .../klipper/menus/klipper_remove_menu.py | 109 ++++++++++++++ 7 files changed, 273 insertions(+), 135 deletions(-) create mode 100644 kiauh/modules/klipper/klipper_remove.py create mode 100644 kiauh/modules/klipper/menus/__init__.py create mode 100644 kiauh/modules/klipper/menus/klipper_remove_menu.py diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index efb1650..45ecccb 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -64,7 +64,7 @@ class InstallMenu(BaseMenu): print(menu, end="") def install_klipper(self): - klipper_setup.run_klipper_setup(install=True) + klipper_setup.install_klipper() def install_moonraker(self): moonraker_setup.install_moonraker() diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 5526406..922c2cf 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -13,7 +13,7 @@ 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.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from kiauh.modules.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu from kiauh.modules.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu from kiauh.utils.constants import COLOR_RED, RESET_FORMAT @@ -25,7 +25,7 @@ class RemoveMenu(BaseMenu): super().__init__( header=True, options={ - 1: self.remove_klipper, + 1: KlipperRemoveMenu, 2: MoonrakerRemoveMenu, 3: MainsailRemoveMenu, 5: self.remove_fluidd, @@ -69,9 +69,6 @@ class RemoveMenu(BaseMenu): )[1:] print(menu, end="") - def remove_klipper(self): - klipper_setup.run_klipper_setup(install=False) - def remove_fluidd(self): print("remove_fluidd") diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index ef064a8..71eb435 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -75,7 +75,7 @@ class Klipper(BaseInstance): Logger.print_error(f"Error creating env file {env_file_target}: {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() @@ -89,9 +89,6 @@ class Klipper(BaseInstance): Logger.print_error(f"Error deleting service file: {e}") raise - if del_remnants: - self._delete_klipper_remnants() - def write_service_file( self, service_template_path: Path, @@ -118,20 +115,6 @@ class Klipper(BaseInstance): env_file.write(env_file_content) Logger.print_ok(f"Env file created: {env_file_target}") - def _delete_klipper_remnants(self) -> None: - try: - Logger.print_status(f"Delete {self.klipper_dir} ...") - shutil.rmtree(Path(self.klipper_dir)) - Logger.print_status(f"Delete {self.env_dir} ...") - shutil.rmtree(Path(self.env_dir)) - except FileNotFoundError: - Logger.print_status("Cannot delete Klipper directories. Not found.") - except PermissionError as e: - Logger.print_error(f"Error deleting Klipper 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/klipper/klipper_remove.py b/kiauh/modules/klipper/klipper_remove.py new file mode 100644 index 0000000..07f2fe9 --- /dev/null +++ b/kiauh/modules/klipper/klipper_remove.py @@ -0,0 +1,133 @@ +#!/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 +from typing import List, Union + +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.klipper.klipper_dialogs import print_instance_overview +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_klipper_removal( + remove_service: bool, + remove_dir: bool, + remove_env: bool, + delete_logs: bool, +) -> None: + im = InstanceManager(Klipper) + + if remove_service: + Logger.print_status("Removing Klipper instances ...") + if im.instances: + instances_to_remove = select_instances_to_remove(im.instances) + remove_instances(im, instances_to_remove) + else: + Logger.print_info("No Klipper Services installed! Skipped ...") + + im.find_instances() + if (remove_dir or remove_env) and im.instances: + Logger.print_warn("There are still other Klipper services installed!") + Logger.print_warn("Therefor the following parts cannot be removed:") + Logger.print_warn( + """ + ● Klipper local repository + ● Klipper Python environment + """, + False, + ) + else: + if remove_dir: + Logger.print_status("Removing Klipper local repository ...") + remove_klipper_dir() + if remove_env: + Logger.print_status("Removing Klipper Python environment ...") + remove_klipper_env() + + # delete klipper logs of all instances + if delete_logs: + Logger.print_status("Removing all Klipper logs ...") + delete_klipper_logs(im.instances) + + +def select_instances_to_remove( + instances: List[Klipper], +) -> Union[List[Klipper], 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 Klipper 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[Klipper], +) -> 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_klipper_dir() -> None: + if not KLIPPER_DIR.exists(): + Logger.print_info(f"'{KLIPPER_DIR}' does not exist. Skipped ...") + return + + try: + shutil.rmtree(KLIPPER_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{KLIPPER_DIR}':\n{e}") + + +def remove_klipper_env() -> None: + if not KLIPPER_ENV_DIR.exists(): + Logger.print_info(f"'{KLIPPER_ENV_DIR}' does not exist. Skipped ...") + return + + try: + shutil.rmtree(KLIPPER_ENV_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{KLIPPER_ENV_DIR}':\n{e}") + + +def delete_klipper_logs(instances: List[Klipper]) -> None: + all_logfiles = [] + for instance in instances: + all_logfiles = list(instance.log_dir.glob("klippy.log*")) + if not all_logfiles: + Logger.print_info("No Klipper logs found. Skipped ...") + return + + for log in all_logfiles: + Logger.print_status(f"Remove '{log}'") + remove_file(log) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 6d6fd66..efa78e5 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import subprocess from pathlib import Path from typing import List, Union @@ -40,11 +39,7 @@ from kiauh.modules.klipper.klipper_utils import ( create_example_printer_cfg, ) from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.utils.input_utils import ( - get_confirm, - get_number_input, - get_selection_input, -) +from kiauh.utils.input_utils import get_confirm, get_number_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( parse_packages_from_file, @@ -55,85 +50,52 @@ from kiauh.utils.system_utils import ( ) -def run_klipper_setup(install: bool) -> None: - instance_manager = InstanceManager(Klipper) - instance_list = instance_manager.instances - instances_installed = len(instance_list) +def install_klipper() -> None: + im = InstanceManager(Klipper) - is_klipper_installed = instances_installed > 0 - if not install and not is_klipper_installed: - Logger.print_warn("Klipper not installed!") + add_additional = handle_existing_instances(im.instances) + if len(im.instances) > 0 and not add_additional: + Logger.print_status(EXIT_KLIPPER_SETUP) return - if install: - add_additional = handle_existing_instances(instance_list) - if is_klipper_installed and not add_additional: - Logger.print_status(EXIT_KLIPPER_SETUP) - return - - install_klipper(instance_manager, instance_list) - - if not install: - if instances_installed == 1: - remove_single_instance(instance_manager, instance_list) - else: - remove_multi_instance(instance_manager, 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_klipper( - instance_manager: InstanceManager, instance_list: List[Klipper] -) -> None: print_select_instance_count_dialog() - question = f"Number of{' additional' if len(instance_list) > 0 else ''} Klipper instances to set up" + question = f"Number of{' additional' if len(im.instances) > 0 else ''} Klipper instances to set up" install_count = get_number_input(question, 1, default=1, allow_go_back=True) if install_count is None: Logger.print_status(EXIT_KLIPPER_SETUP) return - instance_names = set_instance_suffix(instance_list, install_count) + instance_names = set_instance_suffix(im.instances, install_count) if instance_names is None: Logger.print_status(EXIT_KLIPPER_SETUP) return create_example_cfg = get_confirm("Create example printer.cfg?") - if len(instance_list) < 1: + if len(im.instances) < 1: setup_klipper_prerequesites() convert_single_to_multi = ( - len(instance_list) == 1 - and instance_list[0].suffix is None - and install_count >= 1 + len(im.instances) == 1 and im.instances[0].suffix is None and install_count >= 1 ) for name in instance_names: if convert_single_to_multi: - current_instance = handle_single_to_multi_conversion(instance_manager, name) + current_instance = handle_single_to_multi_conversion(im, name) convert_single_to_multi = False else: current_instance = Klipper(suffix=name) - instance_manager.current_instance = current_instance - instance_manager.create_instance() - instance_manager.enable_instance() + im.current_instance = current_instance + im.create_instance() + im.enable_instance() if create_example_cfg: create_example_printer_cfg(current_instance) - instance_manager.start_instance() + im.start_instance() - instance_manager.reload_daemon() + im.reload_daemon() # step 4: check/handle conflicting packages/services handle_disruptive_system_packages() @@ -175,6 +137,17 @@ def install_klipper_packages(klipper_dir: Path) -> None: install_system_packages(packages) +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 set_instance_suffix( instance_list: List[Klipper], install_count: int ) -> List[Union[str, None]]: @@ -199,63 +172,6 @@ def set_instance_suffix( ) -def remove_single_instance( - instance_manager: InstanceManager, instance_list: List[Klipper] -) -> None: - question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?" - del_remnants = get_confirm(question, allow_go_back=True) - if del_remnants is None: - Logger.print_status("Exiting Klipper Uninstaller ...") - return - - try: - instance_manager.current_instance = instance_list[0] - instance_manager.stop_instance() - instance_manager.disable_instance() - instance_manager.delete_instance(del_remnants=del_remnants) - instance_manager.reload_daemon() - except (OSError, subprocess.CalledProcessError): - Logger.print_error("Removing instance failed!") - return - - -def remove_multi_instance( - instance_manager: InstanceManager, instance_list: List[Klipper] -) -> None: - print_instance_overview(instance_list, show_index=True, show_select_all=True) - - options = [str(i) for i in range(len(instance_list))] - options.extend(["a", "A", "b", "B"]) - - selection = get_selection_input("Select Klipper instance to remove", options) - - if selection == "b".lower(): - return - elif selection == "a".lower(): - question = f"Delete {KLIPPER_DIR} and {KLIPPER_ENV_DIR}?" - del_remnants = get_confirm(question, allow_go_back=True) - if del_remnants is None: - Logger.print_status("Exiting Klipper Uninstaller ...") - return - - Logger.print_status("Removing all Klipper instances ...") - 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) - else: - instance = instance_list[int(selection)] - log = f"Removing Klipper instance: {instance.get_service_file_name()}" - Logger.print_status(log) - instance_manager.current_instance = instance - instance_manager.stop_instance() - instance_manager.disable_instance() - instance_manager.delete_instance(del_remnants=False) - - instance_manager.reload_daemon() - - def update_klipper() -> None: print_update_warn_dialog() if not get_confirm("Update Klipper now?"): diff --git a/kiauh/modules/klipper/menus/__init__.py b/kiauh/modules/klipper/menus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/modules/klipper/menus/klipper_remove_menu.py b/kiauh/modules/klipper/menus/klipper_remove_menu.py new file mode 100644 index 0000000..ed9740f --- /dev/null +++ b/kiauh/modules/klipper/menus/klipper_remove_menu.py @@ -0,0 +1,109 @@ +#!/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.klipper import klipper_remove +from kiauh.modules.moonraker import moonraker_remove +from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN + + +class KlipperRemoveMenu(BaseMenu): + def __init__(self): + super().__init__( + header=False, + options={ + 0: self.toggle_all, + 1: self.toggle_remove_klipper_service, + 2: self.toggle_remove_klipper_dir, + 3: self.toggle_remove_klipper_env, + 4: self.toggle_delete_klipper_logs, + 5: self.run_removal_process, + }, + footer_type=BACK_HELP_FOOTER, + ) + self.remove_klipper_service = False + self.remove_klipper_dir = False + self.remove_klipper_env = False + self.delete_klipper_logs = False + + def print_menu(self) -> None: + header = " [ Remove Klipper ] " + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) + checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + unchecked = "[ ]" + o1 = checked if self.remove_klipper_service else unchecked + o2 = checked if self.remove_klipper_dir else unchecked + o3 = checked if self.remove_klipper_env else unchecked + o4 = checked if self.delete_klipper_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} Delete all Log-Files | + |-------------------------------------------------------| + | 5) Continue | + """ + )[1:] + print(menu, end="") + + def toggle_all(self) -> None: + self.remove_klipper_service = True + self.remove_klipper_dir = True + self.remove_klipper_env = True + self.delete_klipper_logs = True + + def toggle_remove_klipper_service(self) -> None: + self.remove_klipper_service = not self.remove_klipper_service + + def toggle_remove_klipper_dir(self) -> None: + self.remove_klipper_dir = not self.remove_klipper_dir + + def toggle_remove_klipper_env(self) -> None: + self.remove_klipper_env = not self.remove_klipper_env + + def toggle_delete_klipper_logs(self) -> None: + self.delete_klipper_logs = not self.delete_klipper_logs + + def run_removal_process(self) -> None: + if ( + not self.remove_klipper_service + and not self.remove_klipper_dir + and not self.remove_klipper_env + and not self.delete_klipper_logs + ): + error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" + print(error) + return + + klipper_remove.run_klipper_removal( + self.remove_klipper_service, + self.remove_klipper_dir, + self.remove_klipper_env, + self.delete_klipper_logs, + ) + + self.remove_klipper_service = False + self.remove_klipper_dir = False + self.remove_klipper_env = False + self.delete_klipper_logs = False From fc9fa39eeec3dfdfc6075d9314b7c3eb2531445a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 19:43:30 +0100 Subject: [PATCH 070/137] refactor(Mainsail): use same wording in MainsailRemoveMenu Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/menus/mainsail_remove_menu.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py index fd56a60..071a818 100644 --- a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py @@ -57,17 +57,17 @@ class MainsailRemoveMenu(BaseMenu): | Enter a number and hit enter to select / deselect | | the specific option for removal. | |-------------------------------------------------------| - | 0) Everything | + | 0) Select everything | |-------------------------------------------------------| - | 1) {o1} Remove Mainsail Web UI | + | 1) {o1} Remove Mainsail | | 2) {o2} Remove mainsail-config | - | 3) {o3} Backup Mainsail config.json | + | 3) {o3} Backup config.json | | | | printer.cfg & moonraker.conf | - | 4) {o4} Remove Moonraker updater section | + | 4) {o4} Remove Moonraker update section | | 5) {o5} Remove printer.cfg include | |-------------------------------------------------------| - | 6) Start removal | + | 6) Continue | """ )[1:] print(menu, end="") From b69ecbc9b5a28283fb5966c995e48735ef98b20d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 19:56:43 +0100 Subject: [PATCH 071/137] fix(KIAUH): wrong logic in status detection Signed-off-by: Dominik Willner --- kiauh/utils/common.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 8cb20c8..13ab2c4 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -111,10 +111,12 @@ def get_install_status_webui( nginx_cfg_exist = check_file_exist(nginx_cfg) upstreams_cfg_exist = check_file_exist(upstreams_cfg) common_cfg_exist = check_file_exist(common_cfg) - status = [dir_exist, nginx_cfg_exist, upstreams_cfg_exist, common_cfg_exist] - if all(status): + status = [dir_exist, nginx_cfg_exist] + general_nginx_status = [upstreams_cfg_exist, common_cfg_exist] + + if all(status) and all(general_nginx_status): return f"{COLOR_GREEN}Installed!{RESET_FORMAT}" - elif not any(status): + elif not all(status): return f"{COLOR_RED}Not installed!{RESET_FORMAT}" else: return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" From b4f5c3c1ace8635c002b09be8fcaf1c4b225159d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 20:16:38 +0100 Subject: [PATCH 072/137] refactor(Mainsail): remove mainsail.zip after extracting content Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_setup.py | 4 +++- kiauh/utils/system_utils.py | 12 ++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 758c39a..cb0e85e 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -147,11 +147,13 @@ def run_mainsail_installation() -> None: def download_mainsail() -> None: try: Logger.print_status("Downloading Mainsail ...") - download_file(MAINSAIL_URL, Path.home(), "mainsail.zip") + target = Path.home().joinpath("mainsail.zip") + download_file(MAINSAIL_URL, target, True) Logger.print_ok("Download complete!") Logger.print_status("Extracting mainsail.zip ...") unzip(Path.home().joinpath("mainsail.zip"), MAINSAIL_DIR) + target.unlink(missing_ok=True) Logger.print_ok("OK!") except Exception: diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index be317ea..5d7292e 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -246,24 +246,20 @@ def get_ipv4_addr() -> str: s.close() -def download_file( - url: str, target_folder: Path, target_name: str, show_progress=True -) -> None: +def download_file(url: str, target: Path, show_progress=True) -> None: """ Helper method for downloading files from a provided URL | :param url: the url to the file - :param target_folder: the target folder to download the file into - :param target_name: the name of the downloaded file + :param target: the target path incl filename :param show_progress: show download progress or not :return: None """ - target_path = target_folder.joinpath(target_name) try: if show_progress: - urllib.request.urlretrieve(url, target_path, download_progress) + urllib.request.urlretrieve(url, target, download_progress) sys.stdout.write("\n") else: - urllib.request.urlretrieve(url, target_path) + urllib.request.urlretrieve(url, target) except urllib.error.HTTPError as e: Logger.print_error(f"Download failed! HTTP error occured: {e}") raise From 6c59d58193503f86526e06dad647dd2d28a419cc Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 20:44:04 +0100 Subject: [PATCH 073/137] refactor(KIAUH): use red dash instead of "Unknown" if repo info not available Signed-off-by: Dominik Willner --- kiauh/utils/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 13ab2c4..aebbaf3 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -68,7 +68,7 @@ def get_repo_name(repo_dir: Path) -> str: result = "/".join(result.decode().strip().split("/")[-2:]) return f"{COLOR_CYAN}{result}{RESET_FORMAT}" except subprocess.CalledProcessError: - return f"{COLOR_YELLOW}Unknown{RESET_FORMAT}" + return f"{COLOR_RED}-{RESET_FORMAT}" def get_install_status_common( From b165d888559daad963d508c5a1660df1563d7a77 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 20:57:22 +0100 Subject: [PATCH 074/137] fix(Moonraker): missing return statement if all requirements met Signed-off-by: Dominik Willner --- kiauh/modules/moonraker/moonraker_setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 3d16a91..430e157 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -69,6 +69,8 @@ def check_moonraker_install_requirements() -> bool: Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") return False + return True + def install_moonraker() -> None: if not check_moonraker_install_requirements(): From a9367cc0640ad1557e9196e8b49b7b422c3af2c5 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 22:01:18 +0100 Subject: [PATCH 075/137] fix(Klipper): remove obsolete method parameter Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index bd929c6..14e6361 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -110,7 +110,7 @@ def handle_single_to_multi_conversion( old_data_dir_name = instance_manager.instances[0].data_dir instance_manager.stop_instance() instance_manager.disable_instance() - instance_manager.delete_instance(del_remnants=False) + instance_manager.delete_instance() instance_manager.current_instance = Klipper(suffix=name) new_data_dir_name = instance_manager.current_instance.data_dir try: From 16a28ffda03af7dcef3ed80ee461884ede2bb95d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 22:03:44 +0100 Subject: [PATCH 076/137] fix(Klipper/Moonraker): config files now always have a Path, are never None anymore Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper.py | 8 +------- kiauh/modules/moonraker/moonraker.py | 10 ++-------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 71eb435..1f11fa2 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -30,7 +30,7 @@ class Klipper(BaseInstance): super().__init__(instance_type=self, suffix=suffix) self.klipper_dir: Path = KLIPPER_DIR self.env_dir: Path = KLIPPER_ENV_DIR - self._cfg_file = self._get_cfg() + self._cfg_file = self.cfg_dir.joinpath("printer.cfg") self._log = self.log_dir.joinpath("klippy.log") self._serial = self.comms_dir.joinpath("klippy.serial") self._uds = self.comms_dir.joinpath("klippy.sock") @@ -153,9 +153,3 @@ class Klipper(BaseInstance): env_file_content = env_file_content.replace("%LOG%", str(self.log)) env_file_content = env_file_content.replace("%UDS%", str(self.uds)) return env_file_content - - def _get_cfg(self) -> Union[Path, None]: - cfg_file_loc = self.cfg_dir.joinpath("printer.cfg") - if cfg_file_loc.is_file(): - return cfg_file_loc - return None diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py index 4a1ae12..78d60c7 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/modules/moonraker/moonraker.py @@ -31,7 +31,7 @@ class Moonraker(BaseInstance): super().__init__(instance_type=self, suffix=suffix) self.moonraker_dir: Path = MOONRAKER_DIR self.env_dir: Path = MOONRAKER_ENV_DIR - self.cfg_file = self._get_cfg() + self.cfg_file = self.cfg_dir.joinpath("moonraker.conf") self.port = self._get_port() self.backup_dir = self.data_dir.joinpath("backup") self.certs_dir = self.data_dir.joinpath("certs") @@ -138,14 +138,8 @@ class Moonraker(BaseInstance): ) return env_file_content - def _get_cfg(self) -> Union[Path, None]: - cfg_file_loc = self.cfg_dir.joinpath("moonraker.conf") - if cfg_file_loc.is_file(): - return cfg_file_loc - return None - def _get_port(self) -> Union[int, None]: - if self.cfg_file is None: + if not self.cfg_file.is_file(): return None cm = ConfigManager(cfg_file=self.cfg_file) From d20d82aeacf3ad2124fde3d0e59a2d6e28fa49fe Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 22:06:40 +0100 Subject: [PATCH 077/137] fix(Mainsail): proper check if config exists Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_remove.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py index 275ce1f..4785f27 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import os # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # @@ -94,16 +95,16 @@ def remove_nginx_logs() -> None: def remove_updater_section(name: str) -> None: Logger.print_status("Remove updater section from moonraker.conf ...") im = InstanceManager(Moonraker) - if not im.instances: - Logger.print_info("Moonraker not installed. Skipping ...") + instances: List[Moonraker] = im.instances + if not instances: + Logger.print_info("Moonraker not installed. Skipped ...") return - for instance in im.instances: - log = f"Remove section '{name}' in '{instance.cfg_file}' ..." - Logger.print_status(log) + for instance in instances: + Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...") - if not Path(instance.cfg_file).exists(): - Logger.print_info("Section not present. Skipping ...") + if not instance.cfg_file.is_file(): + Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...") continue cm = ConfigManager(instance.cfg_file) From 1b4c76d080dcebd9d499d31b241c65d385ed20fc Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 25 Dec 2023 22:31:18 +0100 Subject: [PATCH 078/137] fix(KIAUH): more file path handling improvements Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_utils.py | 6 ++--- kiauh/modules/mainsail/mainsail_remove.py | 30 ++++++++++++---------- kiauh/modules/moonraker/moonraker_utils.py | 6 ++--- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 14e6361..baf1b3a 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -205,12 +205,12 @@ def get_highest_index(instance_list: List[Klipper]) -> int: def create_example_printer_cfg(instance: Klipper) -> None: Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'") - if instance.cfg_file is not None: - Logger.print_info(f"printer.cfg in '{instance.cfg_dir}' already exists.") + if instance.cfg_file.is_file(): + Logger.print_info(f"'{instance.cfg_file}' already exists.") return source = MODULE_PATH.joinpath("res/printer.cfg") - target = instance.cfg_dir.joinpath("printer.cfg") + target = instance.cfg_file try: shutil.copy(source, target) except OSError as e: diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py index 4785f27..162e77f 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import os # ======================================================================= # # Copyright (C) 2020 - 2023 Dominik Willner # @@ -53,7 +52,7 @@ def run_mainsail_removal( def remove_mainsail_dir() -> None: Logger.print_status("Removing Mainsail ...") - if not Path(MAINSAIL_DIR).exists(): + if not MAINSAIL_DIR.exists(): Logger.print_info(f"'{MAINSAIL_DIR}' does not exist. Skipping ...") return @@ -81,12 +80,13 @@ def remove_nginx_logs() -> None: remove_file(Path("/var/log/nginx/mainsail-error.log"), True) im = InstanceManager(Klipper) - if not im.instances: + instances: List[Klipper] = im.instances + if not instances: return - for instance in im.instances: - remove_file(Path(instance.log_dir, "mainsail-access.log")) - remove_file(Path(instance.log_dir, "mainsail-error.log")) + for instance in instances: + remove_file(instance.log_dir.joinpath("mainsail-access.log")) + remove_file(instance.log_dir.joinpath("mainsail-error.log")) except (OSError, subprocess.CalledProcessError) as e: Logger.print_error(f"Unable to NGINX logs:\n{e}") @@ -118,7 +118,7 @@ def remove_updater_section(name: str) -> None: def remove_mainsail_cfg_dir() -> None: Logger.print_status("Removing mainsail-config ...") - if not Path(MAINSAIL_CONFIG_DIR).exists(): + if not MAINSAIL_CONFIG_DIR.exists(): Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...") return @@ -130,11 +130,12 @@ def remove_mainsail_cfg_dir() -> None: def remove_mainsail_cfg_symlink() -> None: Logger.print_status("Removing mainsail.cfg symlinks ...") - im = InstanceManager(Moonraker) - for instance in im.instances: - Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...") + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + for instance in instances: + Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") try: - remove_file(Path(instance.cfg_dir, "mainsail.cfg")) + remove_file(instance.cfg_dir.joinpath("mainsail.cfg")) except subprocess.CalledProcessError: Logger.print_error("Failed to remove symlink!") @@ -142,15 +143,16 @@ def remove_mainsail_cfg_symlink() -> None: def remove_printer_cfg_include() -> None: Logger.print_status("Remove mainsail-config include from printer.cfg ...") im = InstanceManager(Klipper) - if not im.instances: + instances: List[Klipper] = im.instances + if not instances: Logger.print_info("Klipper not installed. Skipping ...") return - for instance in im.instances: + for instance in instances: log = f"Removing include from '{instance.cfg_file}' ..." Logger.print_status(log) - if not Path(instance.cfg_file).exists(): + if not instance.cfg_file.is_file(): continue cm = ConfigManager(instance.cfg_file) diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index 01ee7d7..b9c8981 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -40,12 +40,12 @@ def create_example_moonraker_conf( instance: Moonraker, ports_map: Dict[str, int] ) -> None: Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'") - if instance.cfg_file is not None: - Logger.print_info(f"moonraker.conf in '{instance.cfg_dir}' already exists.") + if instance.cfg_file.is_file(): + Logger.print_info(f"'{instance.cfg_file}' already exists.") return source = MODULE_PATH.joinpath("res/moonraker.conf") - target = instance.cfg_dir.joinpath("moonraker.conf") + target = instance.cfg_file try: shutil.copy(source, target) except OSError as e: From 9dedf38079c6ef45dfe2536334677c0e8edc458f Mon Sep 17 00:00:00 2001 From: dw-0 Date: Tue, 26 Dec 2023 23:37:35 +0100 Subject: [PATCH 079/137] refactor(KIAUH): big refactor of instance handling Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/base_instance.py | 10 +- .../core/instance_manager/instance_manager.py | 6 +- kiauh/core/instance_manager/name_scheme.py | 8 ++ kiauh/modules/klipper/klipper.py | 5 +- kiauh/modules/klipper/klipper_setup.py | 134 +++++++++--------- kiauh/modules/klipper/klipper_utils.py | 127 +++++++---------- kiauh/modules/moonraker/moonraker.py | 3 +- kiauh/modules/moonraker/moonraker_setup.py | 14 +- kiauh/modules/moonraker/moonraker_utils.py | 36 ++++- 9 files changed, 179 insertions(+), 164 deletions(-) create mode 100644 kiauh/core/instance_manager/name_scheme.py diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index bccacee..903c76b 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -11,7 +11,7 @@ from abc import abstractmethod, ABC from pathlib import Path -from typing import List, Union, Optional, Type, TypeVar +from typing import List, Type, TypeVar from kiauh.utils.constants import SYSTEMD, CURRENT_USER @@ -25,7 +25,7 @@ class BaseInstance(ABC): def __init__( self, - suffix: Optional[str], + suffix: str, instance_type: B = B, ): self._instance_type = instance_type @@ -52,7 +52,7 @@ class BaseInstance(ABC): return self._suffix @suffix.setter - def suffix(self, value: Union[str, None]) -> None: + def suffix(self, value: str) -> None: self._suffix = value @property @@ -144,7 +144,7 @@ class BaseInstance(ABC): def get_service_file_name(self, extension: bool = False) -> str: name = f"{self.__class__.__name__.lower()}" - if self.suffix is not None: + if self.suffix != "": name += f"-{self.suffix}" return name if not extension else f"{name}.service" @@ -153,7 +153,7 @@ class BaseInstance(ABC): return SYSTEMD.joinpath(self.get_service_file_name(extension=True)) def get_data_dir_name_from_suffix(self) -> str: - if self._suffix is None: + if self._suffix == "": return "printer" elif self._suffix.isdigit(): return f"printer_{self._suffix}" diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 411a25b..4a45e39 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -207,10 +207,8 @@ class InstanceManager: return instance_list - def _get_instance_suffix(self, file_path: Path) -> Union[str, None]: - full_name = file_path.name.split(".")[0] - - return full_name.split("-")[-1] if "-" in full_name else None + def _get_instance_suffix(self, file_path: Path) -> str: + return file_path.stem.split("-")[-1] if "-" in file_path.stem else "" def _sort_instance_list(self, s: Union[int, str, None]): if s is None: diff --git a/kiauh/core/instance_manager/name_scheme.py b/kiauh/core/instance_manager/name_scheme.py new file mode 100644 index 0000000..bfd9e2c --- /dev/null +++ b/kiauh/core/instance_manager/name_scheme.py @@ -0,0 +1,8 @@ +from enum import unique, Enum + + +@unique +class NameScheme(Enum): + SINGLE = "SINGLE" + INDEX = "INDEX" + CUSTOM = "CUSTOM" diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 1f11fa2..6a43452 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -9,10 +9,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import shutil import subprocess from pathlib import Path -from typing import List, Union +from typing import List from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH @@ -26,7 +25,7 @@ class Klipper(BaseInstance): def blacklist(cls) -> List[str]: return ["None", "mcu"] - def __init__(self, suffix: str = None): + def __init__(self, suffix: str = ""): super().__init__(instance_type=self, suffix=suffix) self.klipper_dir: Path = KLIPPER_DIR self.env_dir: Path = KLIPPER_ENV_DIR diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index efa78e5..a30c27c 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -10,12 +10,13 @@ # ======================================================================= # from pathlib import Path -from typing import List, Union +from typing import List from kiauh import KIAUH_CFG from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.instance_manager.name_scheme import NameScheme from kiauh.modules.klipper import ( EXIT_KLIPPER_SETUP, DEFAULT_KLIPPER_REPO_URL, @@ -25,20 +26,21 @@ from kiauh.modules.klipper import ( ) from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( - print_instance_overview, - print_select_instance_count_dialog, print_update_warn_dialog, + print_select_custom_name_dialog, ) from kiauh.modules.klipper.klipper_utils import ( - handle_convert_single_to_multi_instance_names, - handle_new_multi_instance_names, - handle_existing_multi_instance_names, handle_disruptive_system_packages, check_user_groups, - handle_single_to_multi_conversion, + handle_to_multi_instance_conversion, create_example_printer_cfg, + detect_name_scheme, + add_to_existing, + get_install_count, + assign_custom_name, ) from kiauh.core.repo_manager.repo_manager import RepoManager +from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.utils.input_utils import get_confirm, get_number_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( @@ -50,50 +52,81 @@ from kiauh.utils.system_utils import ( ) +# TODO: this method needs refactoring! (but it works for now) def install_klipper() -> None: im = InstanceManager(Klipper) + kl_instances: List[Klipper] = im.instances - add_additional = handle_existing_instances(im.instances) - if len(im.instances) > 0 and not add_additional: + # ask to add new instances, if there are existing ones + if kl_instances and not add_to_existing(): Logger.print_status(EXIT_KLIPPER_SETUP) return - print_select_instance_count_dialog() - question = f"Number of{' additional' if len(im.instances) > 0 else ''} Klipper instances to set up" - install_count = get_number_input(question, 1, default=1, allow_go_back=True) + install_count = get_install_count() + # install_count = None -> user entered "b" to go back if install_count is None: Logger.print_status(EXIT_KLIPPER_SETUP) return - instance_names = set_instance_suffix(im.instances, install_count) - if instance_names is None: - Logger.print_status(EXIT_KLIPPER_SETUP) - return + # create a dict of the size of the existing instances + install count + name_scheme = NameScheme.SINGLE + single_to_multi = len(kl_instances) == 1 and kl_instances[0].suffix == "" + name_dict = {c: "" for c in range(len(kl_instances) + install_count)} + + if (not kl_instances and install_count > 1) or single_to_multi: + print_select_custom_name_dialog() + if get_confirm("Assign custom names?", False, allow_go_back=True): + name_scheme = NameScheme.CUSTOM + else: + name_scheme = NameScheme.INDEX + + # if there are more moonraker instances installed than klipper, we + # load their names into the name_dict, as we will detect and enforce that naming scheme + mr_instances: List[Moonraker] = InstanceManager(Moonraker).instances + if len(mr_instances) > len(kl_instances): + for k, v in enumerate(mr_instances): + name_dict[k] = v.suffix + name_scheme = detect_name_scheme(mr_instances) + elif len(kl_instances) > 1: + for k, v in enumerate(kl_instances): + name_dict[k] = v.suffix + name_scheme = detect_name_scheme(kl_instances) + + # set instance names if multiple instances will be created + if name_scheme != NameScheme.SINGLE: + for k in name_dict: + if name_dict[k] == "" and name_scheme == NameScheme.INDEX: + name_dict[k] = str(k + 1) + elif name_dict[k] == "" and name_scheme == NameScheme.CUSTOM: + assign_custom_name(k, name_dict) create_example_cfg = get_confirm("Create example printer.cfg?") - if len(im.instances) < 1: + if not kl_instances: setup_klipper_prerequesites() - convert_single_to_multi = ( - len(im.instances) == 1 and im.instances[0].suffix is None and install_count >= 1 - ) - - for name in instance_names: - if convert_single_to_multi: - current_instance = handle_single_to_multi_conversion(im, name) - convert_single_to_multi = False + count = 0 + for name in name_dict: + if name_dict[name] in [n.suffix for n in kl_instances]: + continue else: - current_instance = Klipper(suffix=name) + count += 1 - im.current_instance = current_instance - im.create_instance() - im.enable_instance() + if single_to_multi: + handle_to_multi_instance_conversion(name_dict[name]) + single_to_multi = False + count -= 1 + else: + new_instance = Klipper(suffix=name_dict[name]) + im.current_instance = new_instance + im.create_instance() + im.enable_instance() + if create_example_cfg: + create_example_printer_cfg(new_instance) + im.start_instance() - if create_example_cfg: - create_example_printer_cfg(current_instance) - - im.start_instance() + if count == install_count: + break im.reload_daemon() @@ -137,41 +170,6 @@ def install_klipper_packages(klipper_dir: Path) -> None: install_system_packages(packages) -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 set_instance_suffix( - instance_list: List[Klipper], install_count: int -) -> List[Union[str, None]]: - instance_count = len(instance_list) - - # new single instance install - if instance_count == 0 and install_count == 1: - return [None] - - # convert single instance install to multi install - elif instance_count == 1 and install_count >= 1 and instance_list[0].suffix is None: - return handle_convert_single_to_multi_instance_names(install_count) - - # new multi instance install - elif instance_count == 0 and install_count > 1: - return handle_new_multi_instance_names(instance_count, install_count) - - # existing multi instance install - elif instance_count > 1: - return handle_existing_multi_instance_names( - instance_count, install_count, instance_list - ) - - def update_klipper() -> None: print_update_warn_dialog() if not get_confirm("Update Klipper now?"): diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index baf1b3a..b6980dc 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -20,16 +20,20 @@ from pathlib import Path from typing import List, Union, Literal, Dict from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.instance_manager.name_scheme import NameScheme from kiauh.modules.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, - print_select_custom_name_dialog, + print_instance_overview, + print_select_instance_count_dialog, ) +from kiauh.modules.moonraker.moonraker_utils import moonraker_to_multi_conversion from kiauh.utils.common import get_install_status_common, get_repo_name from kiauh.utils.constants import CURRENT_USER -from kiauh.utils.input_utils import get_confirm, get_string_input +from kiauh.utils.input_utils import get_confirm, get_string_input, get_number_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import mask_system_service @@ -41,84 +45,57 @@ def get_klipper_status() -> Dict[Literal["status", "repo"], str]: } -def assign_custom_names( - instance_count: int, install_count: int, instance_list: List[Klipper] = None -) -> List[str]: - instance_names = [] - exclude = Klipper.blacklist() - - # if an instance_list is provided, exclude all existing instance suffixes - if instance_list is not None: - for instance in instance_list: - exclude.append(instance.suffix) - - for i in range(instance_count + install_count): - question = f"Enter name for instance {i + 1}" - name = get_string_input(question, exclude=exclude) - instance_names.append(name) - exclude.append(name) - - return instance_names +def add_to_existing() -> bool: + kl_instances = InstanceManager(Klipper).instances + print_instance_overview(kl_instances) + return get_confirm("Add new instances?", allow_go_back=True) -def handle_convert_single_to_multi_instance_names( - install_count: int, -) -> Union[List[str], None]: - print_select_custom_name_dialog() - choice = get_confirm("Assign custom names?", False, allow_go_back=True) - if choice is True: - # instance_count = 0 and install_count + 1 as we want to assign a new name to the existing single install - return assign_custom_names(0, install_count + 1) - elif choice is False: - # "install_count + 2" as we need to account for the existing single install - _range = range(1, install_count + 2) - return [str(i) for i in _range] - - return None +def get_install_count() -> Union[int, None]: + kl_instances = InstanceManager(Klipper).instances + print_select_instance_count_dialog() + question = f"Number of{' additional' if len(kl_instances) > 0 else ''} Klipper instances to set up" + return get_number_input(question, 1, default=1, allow_go_back=True) -def handle_new_multi_instance_names( - instance_count: int, install_count: int -) -> Union[List[str], None]: - print_select_custom_name_dialog() - choice = get_confirm("Assign custom names?", False, allow_go_back=True) - if choice is True: - return assign_custom_names(instance_count, install_count) - elif choice is False: - _range = range(1, install_count + 1) - return [str(i) for i in _range] - - return None +def assign_custom_name(key: int, name_dict: Dict[int, str]) -> None: + existing_names = [] + existing_names.extend(Klipper.blacklist()) + existing_names.extend(name_dict[n] for n in name_dict) + question = f"Enter name for instance {key + 1}" + name_dict[key] = get_string_input(question, exclude=existing_names) -def handle_existing_multi_instance_names( - instance_count: int, install_count: int, instance_list: List[Klipper] -) -> List[str]: - if has_custom_names(instance_list): - return assign_custom_names(instance_count, install_count, instance_list) +def handle_to_multi_instance_conversion(new_name: str) -> None: + Logger.print_status("Converting single instance to multi instances ...") + klipper_to_multi_conversion(new_name) + moonraker_to_multi_conversion(new_name) + + +def klipper_to_multi_conversion(new_name: str) -> None: + Logger.print_status("Convert Klipper single to multi instance ...") + im = InstanceManager(Klipper) + im.current_instance = im.instances[0] + # temporarily store the data dir path + old_data_dir = im.instances[0].data_dir + # remove the old single instance + im.stop_instance() + im.disable_instance() + im.delete_instance() + # create a new klipper instance with the new name + im.current_instance = Klipper(suffix=new_name) + new_data_dir: Path = im.current_instance.data_dir + + # rename the old data dir and use it for the new instance + Logger.print_status(f"Rename '{old_data_dir}' to '{new_data_dir}' ...") + if not new_data_dir.is_dir(): + old_data_dir.rename(new_data_dir) else: - start = get_highest_index(instance_list) + 1 - _range = range(start, start + install_count) - return [str(i) for i in _range] + Logger.print_info(f"'{new_data_dir}' already exist. Skipped ...") - -def handle_single_to_multi_conversion( - instance_manager: InstanceManager, name: str -) -> Klipper: - instance_list = instance_manager.instances - instance_manager.current_instance = instance_list[0] - old_data_dir_name = instance_manager.instances[0].data_dir - instance_manager.stop_instance() - instance_manager.disable_instance() - instance_manager.delete_instance() - instance_manager.current_instance = Klipper(suffix=name) - new_data_dir_name = instance_manager.current_instance.data_dir - try: - Path(old_data_dir_name).rename(new_data_dir_name) - return instance_manager.current_instance - except OSError as e: - log = f"Cannot rename {old_data_dir_name} to {new_data_dir_name}:\n{e}" - Logger.print_error(log) + im.create_instance() + im.enable_instance() + im.start_instance() def check_user_groups(): @@ -189,13 +166,13 @@ def handle_disruptive_system_packages() -> None: Logger.print_warn(warn_msg) -def has_custom_names(instance_list: List[Klipper]) -> bool: +def detect_name_scheme(instance_list: List[BaseInstance]) -> NameScheme: pattern = re.compile("^\d+$") for instance in instance_list: if not pattern.match(instance.suffix): - return True + return NameScheme.CUSTOM - return False + return NameScheme.INDEX def get_highest_index(instance_list: List[Klipper]) -> int: diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py index 78d60c7..904c813 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/modules/moonraker/moonraker.py @@ -9,7 +9,6 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import shutil import subprocess from pathlib import Path from typing import List, Union @@ -27,7 +26,7 @@ class Moonraker(BaseInstance): def blacklist(cls) -> List[str]: return ["None", "mcu"] - def __init__(self, suffix: str = None): + def __init__(self, suffix: str = ""): super().__init__(instance_type=self, suffix=suffix) self.moonraker_dir: Path = MOONRAKER_DIR self.env_dir: Path = MOONRAKER_ENV_DIR diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 430e157..8208d16 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -54,21 +54,23 @@ from kiauh.utils.system_utils import ( def check_moonraker_install_requirements() -> bool: - kl_im = InstanceManager(Klipper) - kl_instance_list = kl_im.instances - kl_instance_count = len(kl_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 False - is_klipper_installed = kl_instance_count > 0 - if not is_klipper_installed: + kl_instance_count = len(InstanceManager(Klipper).instances) + if kl_instance_count < 1: Logger.print_warn("Klipper not installed!") Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") return False + mr_instance_count = len(InstanceManager(Moonraker).instances) + if mr_instance_count >= kl_instance_count: + Logger.print_warn("Unable to install more Moonraker instances!") + Logger.print_warn("More Klipper instances required.") + return False + return True diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index b9c8981..9045b42 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -10,9 +10,10 @@ # ======================================================================= # import shutil -from typing import Dict, Literal +from typing import Dict, Literal, List from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.modules.moonraker import ( DEFAULT_MOONRAKER_PORT, MODULE_PATH, @@ -83,3 +84,36 @@ def create_example_moonraker_conf( cm.write_config() Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'") + + +def moonraker_to_multi_conversion(new_name: str) -> None: + """ + Converts the first instance in the List of Moonraker instances to an instance + with a new name. This method will be called when converting from a single Klipper + instance install to a multi instance install when Moonraker is also already + installed with a single instance. + :param new_name: new name the previous single instance is renamed to + :return: None + """ + im = InstanceManager(Moonraker) + instances: List[Moonraker] = im.instances + if not instances: + return + + # in case there are multiple Moonraker instances, we don't want to do anything + if len(instances) > 1: + Logger.print_info("More than a single Moonraker instance found. Skipped ...") + return + + Logger.print_status("Convert Moonraker single to multi instance ...") + # remove the old single instance + im.current_instance = im.instances[0] + im.stop_instance() + im.disable_instance() + im.delete_instance() + # create a new klipper instance with the new name + im.current_instance = Moonraker(suffix=new_name) + # create, enable and start the new moonraker instance + im.create_instance() + im.enable_instance() + im.start_instance() From ad0dbf63b82ef872ad507ac17796a7568deea38b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 27 Dec 2023 00:11:11 +0100 Subject: [PATCH 080/137] refactor(Mainsail): enable remote mode if moonraker multi instance Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_setup.py | 45 ++++++++++++------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index cb0e85e..4851d50 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -59,21 +59,22 @@ from kiauh.utils.system_utils import ( def run_mainsail_installation() -> None: - im_mr = InstanceManager(Moonraker) - is_moonraker_installed = len(im_mr.instances) > 0 + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances enable_remotemode = False - if not is_moonraker_installed: + if not mr_instances: print_moonraker_not_found_dialog() - do_continue = get_confirm("Continue Mainsail installation?", allow_go_back=True) - if do_continue: - enable_remotemode = True - else: + if not get_confirm("Continue Mainsail installation?", allow_go_back=True): return - is_mainsail_installed = Path.home().joinpath("mainsail").exists() + # if moonraker is not installed or multiple instances + # are installed we enable mainsails remote mode + if not mr_instances or len(mr_instances) > 1: + enable_remotemode = True + do_reinstall = False - if is_mainsail_installed: + if Path.home().joinpath("mainsail").exists(): print_mainsail_already_installed_dialog() do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True) if do_reinstall: @@ -81,10 +82,10 @@ def run_mainsail_installation() -> None: else: return - im_kl = InstanceManager(Klipper) - is_klipper_installed = len(im_kl.instances) > 0 + kl_im = InstanceManager(Klipper) + kl_instances = kl_im.instances install_ms_config = False - if is_klipper_installed: + if kl_instances: print_install_mainsail_config_dialog() question = "Download the recommended macros?" install_ms_config = get_confirm(question, allow_go_back=False) @@ -108,31 +109,31 @@ def run_mainsail_installation() -> None: restore_config_json() if enable_remotemode: enable_mainsail_remotemode() - if is_moonraker_installed: + if mr_instances: patch_moonraker_conf( - im_mr.instances, + mr_instances, "Mainsail", "update_manager mainsail", "mainsail-updater.conf", ) - im_mr.restart_all_instance() - if install_ms_config and is_klipper_installed: + mr_im.restart_all_instance() + if install_ms_config and kl_instances: download_mainsail_cfg() - create_mainsail_cfg_symlink(im_kl.instances) + create_mainsail_cfg_symlink(kl_instances) patch_moonraker_conf( - im_mr.instances, + mr_instances, "mainsail-config", "update_manager mainsail-config", "mainsail-config-updater.conf", ) - patch_printer_config(im_kl.instances) - im_kl.restart_all_instance() + patch_printer_config(kl_instances) + kl_im.restart_all_instance() copy_upstream_nginx_cfg() copy_common_vars_nginx_cfg() create_mainsail_nginx_cfg(mainsail_port) - if is_klipper_installed: - symlink_webui_nginx_log(im_kl.instances) + if kl_instances: + symlink_webui_nginx_log(kl_instances) control_systemd_service("nginx", "restart") except Exception as e: From 625a808484d49998031f57082a02ece139f12ef5 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 27 Dec 2023 14:09:51 +0100 Subject: [PATCH 081/137] fix(InstanceManager): return an updated list when getting the instances property Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/instance_manager.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 4a45e39..8bc9e9c 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -82,10 +82,7 @@ class InstanceManager: @property def instances(self) -> List[I]: - if not self._instances: - self._instances = self.find_instances() - - return sorted(self._instances, key=lambda x: self._sort_instance_list(x.suffix)) + return self.find_instances() @instances.setter def instances(self, value: List[I]): @@ -205,7 +202,7 @@ class InstanceManager: for service in service_list ] - return instance_list + return sorted(instance_list, key=lambda x: self._sort_instance_list(x.suffix)) def _get_instance_suffix(self, file_path: Path) -> str: return file_path.stem.split("-")[-1] if "-" in file_path.stem else "" From 8f44187568480162a52d2c6cb2cf9919f31db71b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 27 Dec 2023 15:42:46 +0100 Subject: [PATCH 082/137] feat(Moonraker): enable Mainsail remote mode after multi instance setup Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_utils.py | 3 +++ kiauh/modules/moonraker/moonraker_setup.py | 7 +++++++ kiauh/modules/moonraker/moonraker_utils.py | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index 196e27a..dafc2f7 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -51,10 +51,12 @@ def restore_config_json() -> None: def enable_mainsail_remotemode() -> None: + Logger.print_status("Enable Mainsails remote mode ...") with open(MAINSAIL_CONFIG_JSON, "r") as f: config_data = json.load(f) if config_data["instancesDB"] == "browser": + Logger.print_info("Remote mode already configured. Skipped ...") return Logger.print_status("Setting instance storage location to 'browser' ...") @@ -62,6 +64,7 @@ def enable_mainsail_remotemode() -> None: with open(MAINSAIL_CONFIG_JSON, "w") as f: json.dump(config_data, f, indent=4) + Logger.print_ok("Mainsails remote mode enabled!") def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 8208d16..e7a8506 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -24,6 +24,8 @@ from kiauh.modules.klipper.klipper_dialogs import ( print_update_warn_dialog, ) from kiauh.core.repo_manager.repo_manager import RepoManager +from kiauh.modules.mainsail import MAINSAIL_DIR +from kiauh.modules.mainsail.mainsail_utils import enable_mainsail_remotemode from kiauh.modules.moonraker import ( EXIT_MOONRAKER_SETUP, DEFAULT_MOONRAKER_REPO_URL, @@ -130,6 +132,11 @@ def install_moonraker() -> None: mr_im.reload_daemon() + # if mainsail is installed, and we installed + # multiple moonraker instances, we enable mainsails remote mode + if MAINSAIL_DIR.exists() and len(mr_im.instances) > 1: + enable_mainsail_remotemode() + def setup_moonraker_prerequesites() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index 9045b42..c92721f 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -14,6 +14,8 @@ from typing import Dict, Literal, List from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.mainsail import MAINSAIL_DIR +from kiauh.modules.mainsail.mainsail_utils import enable_mainsail_remotemode from kiauh.modules.moonraker import ( DEFAULT_MOONRAKER_PORT, MODULE_PATH, @@ -117,3 +119,7 @@ def moonraker_to_multi_conversion(new_name: str) -> None: im.create_instance() im.enable_instance() im.start_instance() + + # if mainsail is installed, we enable mainsails remote mode + if MAINSAIL_DIR.exists() and len(im.instances) > 1: + enable_mainsail_remotemode() From 83e5d9c0d5383f55e9c9ed16c045a602f62c51b5 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 27 Dec 2023 15:58:37 +0100 Subject: [PATCH 083/137] refactor(Klipper/Moonraker): remove obsolete method calls Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_remove.py | 1 - kiauh/modules/moonraker/moonraker_remove.py | 1 - 2 files changed, 2 deletions(-) diff --git a/kiauh/modules/klipper/klipper_remove.py b/kiauh/modules/klipper/klipper_remove.py index 07f2fe9..a62712a 100644 --- a/kiauh/modules/klipper/klipper_remove.py +++ b/kiauh/modules/klipper/klipper_remove.py @@ -37,7 +37,6 @@ def run_klipper_removal( else: Logger.print_info("No Klipper Services installed! Skipped ...") - im.find_instances() if (remove_dir or remove_env) and im.instances: Logger.print_warn("There are still other Klipper services installed!") Logger.print_warn("Therefor the following parts cannot be removed:") diff --git a/kiauh/modules/moonraker/moonraker_remove.py b/kiauh/modules/moonraker/moonraker_remove.py index e9631db..ab7f22c 100644 --- a/kiauh/modules/moonraker/moonraker_remove.py +++ b/kiauh/modules/moonraker/moonraker_remove.py @@ -39,7 +39,6 @@ def run_moonraker_removal( 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:") From f3b0e45e39fa40b8542b7b765e0b2b6c111af6d0 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 27 Dec 2023 23:49:34 +0100 Subject: [PATCH 084/137] refactor(Klipper): refactor klipper_setup to reduce cognitive complexity Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 93 ++++++++++---------------- kiauh/modules/klipper/klipper_utils.py | 67 +++++++++++++++++++ 2 files changed, 101 insertions(+), 59 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index a30c27c..05aa038 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -10,13 +10,11 @@ # ======================================================================= # from pathlib import Path -from typing import List from kiauh import KIAUH_CFG from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.instance_manager.name_scheme import NameScheme from kiauh.modules.klipper import ( EXIT_KLIPPER_SETUP, DEFAULT_KLIPPER_REPO_URL, @@ -25,23 +23,22 @@ from kiauh.modules.klipper import ( KLIPPER_REQUIREMENTS_TXT, ) from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_dialogs import ( - print_update_warn_dialog, - print_select_custom_name_dialog, -) +from kiauh.modules.klipper.klipper_dialogs import print_update_warn_dialog from kiauh.modules.klipper.klipper_utils import ( handle_disruptive_system_packages, check_user_groups, handle_to_multi_instance_conversion, create_example_printer_cfg, - detect_name_scheme, add_to_existing, get_install_count, - assign_custom_name, + init_name_scheme, + check_is_single_to_multi_conversion, + update_name_scheme, + handle_instance_naming, ) from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.modules.moonraker.moonraker import Moonraker -from kiauh.utils.input_utils import get_confirm, get_number_input +from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( parse_packages_from_file, @@ -52,83 +49,50 @@ from kiauh.utils.system_utils import ( ) -# TODO: this method needs refactoring! (but it works for now) def install_klipper() -> None: - im = InstanceManager(Klipper) - kl_instances: List[Klipper] = im.instances + kl_im = InstanceManager(Klipper) # ask to add new instances, if there are existing ones - if kl_instances and not add_to_existing(): + if kl_im.instances and not add_to_existing(): Logger.print_status(EXIT_KLIPPER_SETUP) return install_count = get_install_count() - # install_count = None -> user entered "b" to go back if install_count is None: Logger.print_status(EXIT_KLIPPER_SETUP) return # create a dict of the size of the existing instances + install count - name_scheme = NameScheme.SINGLE - single_to_multi = len(kl_instances) == 1 and kl_instances[0].suffix == "" - name_dict = {c: "" for c in range(len(kl_instances) + install_count)} + name_dict = {c: "" for c in range(len(kl_im.instances) + install_count)} + name_scheme = init_name_scheme(kl_im.instances, install_count) + mr_im = InstanceManager(Moonraker) + name_scheme = update_name_scheme( + name_scheme, name_dict, kl_im.instances, mr_im.instances + ) - if (not kl_instances and install_count > 1) or single_to_multi: - print_select_custom_name_dialog() - if get_confirm("Assign custom names?", False, allow_go_back=True): - name_scheme = NameScheme.CUSTOM - else: - name_scheme = NameScheme.INDEX - - # if there are more moonraker instances installed than klipper, we - # load their names into the name_dict, as we will detect and enforce that naming scheme - mr_instances: List[Moonraker] = InstanceManager(Moonraker).instances - if len(mr_instances) > len(kl_instances): - for k, v in enumerate(mr_instances): - name_dict[k] = v.suffix - name_scheme = detect_name_scheme(mr_instances) - elif len(kl_instances) > 1: - for k, v in enumerate(kl_instances): - name_dict[k] = v.suffix - name_scheme = detect_name_scheme(kl_instances) - - # set instance names if multiple instances will be created - if name_scheme != NameScheme.SINGLE: - for k in name_dict: - if name_dict[k] == "" and name_scheme == NameScheme.INDEX: - name_dict[k] = str(k + 1) - elif name_dict[k] == "" and name_scheme == NameScheme.CUSTOM: - assign_custom_name(k, name_dict) + handle_instance_naming(name_dict, name_scheme) create_example_cfg = get_confirm("Create example printer.cfg?") - if not kl_instances: + if not kl_im.instances: setup_klipper_prerequesites() count = 0 for name in name_dict: - if name_dict[name] in [n.suffix for n in kl_instances]: + if name_dict[name] in [n.suffix for n in kl_im.instances]: continue - else: - count += 1 - if single_to_multi: + if check_is_single_to_multi_conversion(kl_im.instances): handle_to_multi_instance_conversion(name_dict[name]) - single_to_multi = False - count -= 1 - else: - new_instance = Klipper(suffix=name_dict[name]) - im.current_instance = new_instance - im.create_instance() - im.enable_instance() - if create_example_cfg: - create_example_printer_cfg(new_instance) - im.start_instance() + continue + + count += 1 + create_klipper_instance(name_dict[name], create_example_cfg) if count == install_count: break - im.reload_daemon() + kl_im.reload_daemon() # step 4: check/handle conflicting packages/services handle_disruptive_system_packages() @@ -194,3 +158,14 @@ def update_klipper() -> None: ) repo_manager.pull_repo() instance_manager.start_all_instance() + + +def create_klipper_instance(name: str, create_example_cfg: bool) -> None: + kl_im = InstanceManager(Klipper) + new_instance = Klipper(suffix=name) + kl_im.current_instance = new_instance + kl_im.create_instance() + kl_im.enable_instance() + if create_example_cfg: + create_example_printer_cfg(new_instance) + kl_im.start_instance() diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index b6980dc..67818dd 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -29,7 +29,9 @@ from kiauh.modules.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, print_instance_overview, print_select_instance_count_dialog, + print_select_custom_name_dialog, ) +from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.modules.moonraker.moonraker_utils import moonraker_to_multi_conversion from kiauh.utils.common import get_install_status_common, get_repo_name from kiauh.utils.constants import CURRENT_USER @@ -45,6 +47,65 @@ def get_klipper_status() -> Dict[Literal["status", "repo"], str]: } +def check_is_multi_install( + existing_instances: List[Klipper], install_count: int +) -> bool: + return not existing_instances and install_count > 1 + + +def check_is_single_to_multi_conversion(existing_instances: List[Klipper]) -> bool: + return len(existing_instances) == 1 and existing_instances[0].suffix == "" + + +def init_name_scheme( + existing_instances: List[Klipper], install_count: int +) -> NameScheme: + if check_is_multi_install( + existing_instances, install_count + ) or check_is_single_to_multi_conversion(existing_instances): + print_select_custom_name_dialog() + if get_confirm("Assign custom names?", False, allow_go_back=True): + return NameScheme.CUSTOM + else: + return NameScheme.INDEX + else: + return NameScheme.SINGLE + + +def update_name_scheme( + name_scheme: NameScheme, + name_dict: Dict[int, str], + klipper_instances: List[Klipper], + moonraker_instances: List[Moonraker], +) -> NameScheme: + # if there are more moonraker instances installed than klipper, we + # load their names into the name_dict, as we will detect and enforce that naming scheme + if len(moonraker_instances) > len(klipper_instances): + update_name_dict(name_dict, moonraker_instances) + return detect_name_scheme(moonraker_instances) + elif len(klipper_instances) > 1: + update_name_dict(name_dict, klipper_instances) + return detect_name_scheme(klipper_instances) + else: + return name_scheme + + +def update_name_dict(name_dict: Dict[int, str], instances: List[BaseInstance]) -> None: + for k, v in enumerate(instances): + name_dict[k] = v.suffix + + +def handle_instance_naming(name_dict: Dict[int, str], name_scheme: NameScheme) -> None: + if name_scheme == NameScheme.SINGLE: + return + + for k in name_dict: + if name_dict[k] == "" and name_scheme == NameScheme.INDEX: + name_dict[k] = str(k + 1) + elif name_dict[k] == "" and name_scheme == NameScheme.CUSTOM: + assign_custom_name(k, name_dict) + + def add_to_existing() -> bool: kl_instances = InstanceManager(Klipper).instances print_instance_overview(kl_instances) @@ -52,6 +113,12 @@ def add_to_existing() -> bool: def get_install_count() -> Union[int, None]: + """ + Print a dialog for selecting the amount of Klipper instances + to set up with an option to navigate back. Returns None if the + user selected to go back, otherwise an integer greater or equal than 1 | + :return: Integer >= 1 or None + """ kl_instances = InstanceManager(Klipper).instances print_select_instance_count_dialog() question = f"Number of{' additional' if len(kl_instances) > 0 else ''} Klipper instances to set up" From dfbce3b489d3c83255f888ccf66e9f9462a36484 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 28 Dec 2023 13:38:24 +0100 Subject: [PATCH 085/137] feat(KIAUH): show commit in UpdateMenu Signed-off-by: Dominik Willner --- kiauh/core/menus/main_menu.py | 57 +++++++++++----- kiauh/core/menus/update_menu.py | 76 +++++++++++++++------- kiauh/core/repo_manager/repo_manager.py | 40 ++++++++++++ kiauh/modules/klipper/klipper_utils.py | 19 ++++-- kiauh/modules/moonraker/moonraker_utils.py | 23 +++++-- kiauh/utils/common.py | 40 +++++------- 6 files changed, 182 insertions(+), 73 deletions(-) diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index b62b312..4f1579f 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -21,7 +21,14 @@ from kiauh.core.menus.update_menu import UpdateMenu from kiauh.modules.klipper.klipper_utils import get_klipper_status from kiauh.modules.mainsail.mainsail_utils import get_mainsail_status from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status -from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT, COLOR_RED +from kiauh.utils.constants import ( + COLOR_MAGENTA, + COLOR_CYAN, + RESET_FORMAT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW, +) class MainMenu(BaseMenu): @@ -39,18 +46,18 @@ class MainMenu(BaseMenu): }, footer_type=QUIT_FOOTER, ) - self.kl_status = None - self.kl_repo = None - self.mr_status = None - self.mr_repo = None - self.ms_status = None - self.fl_status = None - self.ks_status = None - self.mb_status = None - self.cn_status = None - self.tg_status = None - self.ob_status = None - self.oe_status = None + self.kl_status = "" + self.kl_repo = "" + self.mr_status = "" + self.mr_repo = "" + self.ms_status = "" + self.fl_status = "" + self.ks_status = "" + self.mb_status = "" + self.cn_status = "" + self.tg_status = "" + self.ob_status = "" + self.oe_status = "" self.init_status() def init_status(self) -> None: @@ -60,14 +67,30 @@ class MainMenu(BaseMenu): def fetch_status(self) -> None: # klipper - self.kl_status = get_klipper_status().get("status") - self.kl_repo = get_klipper_status().get("repo") + klipper_status = get_klipper_status() + kl_status = klipper_status.get("status") + kl_code = klipper_status.get("status_code") + kl_instances = f" {klipper_status.get('instances')}" if kl_code == 1 else "" + self.kl_status = self.format_status_by_code(kl_code, kl_status, kl_instances) + self.kl_repo = f"{COLOR_CYAN}{klipper_status.get('repo')}{RESET_FORMAT}" # moonraker - self.mr_status = get_moonraker_status().get("status") - self.mr_repo = get_moonraker_status().get("repo") + moonraker_status = get_moonraker_status() + mr_status = moonraker_status.get("status") + mr_code = moonraker_status.get("status_code") + mr_instances = f" {moonraker_status.get('instances')}" if mr_code == 1 else "" + self.mr_status = self.format_status_by_code(mr_code, mr_status, mr_instances) + self.mr_repo = f"{COLOR_CYAN}{moonraker_status.get('repo')}{RESET_FORMAT}" # mainsail self.ms_status = get_mainsail_status() + def format_status_by_code(self, code: int, status: str, count: str) -> str: + if code == 1: + return f"{COLOR_GREEN}{status}{count}{RESET_FORMAT}" + elif code == 2: + return f"{COLOR_RED}{status}{count}{RESET_FORMAT}" + + return f"{COLOR_YELLOW}{status}{count}{RESET_FORMAT}" + def print_menu(self): self.fetch_status() diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 3413616..21da09b 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -14,7 +14,11 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper.klipper_setup import update_klipper -from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT +from kiauh.modules.klipper.klipper_utils import ( + get_klipper_status, +) +from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status +from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE # noinspection PyMethodMayBeStatic @@ -39,8 +43,14 @@ class UpdateMenu(BaseMenu): }, footer_type=BACK_FOOTER, ) + self.kl_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.mr_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.mr_remote = f"{COLOR_WHITE}{RESET_FORMAT}" def print_menu(self): + self.fetch_update_status() + header = " [ Update Menu ] " color = COLOR_GREEN count = 62 - len(color) - len(RESET_FORMAT) @@ -49,28 +59,28 @@ class UpdateMenu(BaseMenu): /=======================================================\\ | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| - | 0) [Update all] | | | - | | Current: | Latest: | - | Klipper & API: |--------------|--------------| - | 1) [Klipper] | | | - | 2) [Moonraker] | | | - | | | | - | Klipper Webinterface: |--------------|--------------| - | 3) [Mainsail] | | | - | 4) [Fluidd] | | | - | | | | - | Touchscreen GUI: |--------------|--------------| - | 5) [KlipperScreen] | | | - | | | | - | Other: |--------------|--------------| - | 6) [PrettyGCode] | | | - | 7) [Telegram Bot] | | | - | 8) [Obico for Klipper] | | | - | 9) [OctoEverywhere] | | | - | 10) [Mobileraker] | | | - | 11) [Crowsnest] | | | - | |-----------------------------| - | 12) [System] | | | + | 0) Update all | | | + | | Current: | Latest: | + | Klipper & API: |---------------|---------------| + | 1) Klipper | {self.kl_local:<22} | {self.kl_remote:<22} | + | 2) Moonraker | {self.mr_local:<22} | {self.mr_remote:<22} | + | | | | + | Klipper Webinterface: |---------------|---------------| + | 3) Mainsail | | | + | 4) Fluidd | | | + | | | | + | Touchscreen GUI: |---------------|---------------| + | 5) KlipperScreen | | | + | | | | + | Other: |---------------|---------------| + | 6) PrettyGCode | | | + | 7) Telegram Bot | | | + | 8) Obico for Klipper | | | + | 9) OctoEverywhere | | | + | 10) Mobileraker | | | + | 11) Crowsnest | | | + | |-------------------------------| + | 12) System | | """ )[1:] print(menu, end="") @@ -113,3 +123,23 @@ class UpdateMenu(BaseMenu): def upgrade_system_packages(self): print("upgrade_system_packages") + + def fetch_update_status(self): + # klipper + kl_status = get_klipper_status() + self.kl_local = kl_status.get("local") + self.kl_remote = kl_status.get("remote") + if self.kl_local == self.kl_remote: + self.kl_local = f"{COLOR_GREEN}{self.kl_local}{RESET_FORMAT}" + else: + self.kl_local = f"{COLOR_YELLOW}{self.kl_local}{RESET_FORMAT}" + self.kl_remote = f"{COLOR_GREEN}{self.kl_remote}{RESET_FORMAT}" + # moonraker + mr_status = get_moonraker_status() + self.mr_local = mr_status.get("local") + self.mr_remote = mr_status.get("remote") + if self.mr_local == self.mr_remote: + self.mr_local = f"{COLOR_GREEN}{self.mr_local}{RESET_FORMAT}" + else: + self.mr_local = f"{COLOR_YELLOW}{self.mr_local}{RESET_FORMAT}" + self.mr_remote = f"{COLOR_GREEN}{self.mr_remote}{RESET_FORMAT}" diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index a3343e8..61250c4 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -62,6 +62,46 @@ class RepoManager: def target_dir(self, value) -> None: self._target_dir = value + @staticmethod + def get_repo_name(repo: Path) -> str: + """ + Helper method to extract the organisation and name of a repository | + :param repo: repository to extract the values from + :return: String in form of "/" + """ + try: + cmd = ["git", "-C", repo, "config", "--get", "remote.origin.url"] + result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) + return "/".join(result.decode().strip().split("/")[-2:]) + except subprocess.CalledProcessError: + return "-" + + @staticmethod + def get_local_commit(repo: Path) -> str: + if not repo.exists() and repo.joinpath(".git").exists(): + return "-" + + try: + cmd = f"cd {repo} && git describe HEAD --always --tags | cut -d '-' -f 1,2" + return subprocess.check_output(cmd, shell=True, text=True).strip() + except subprocess.CalledProcessError: + return "-" + + @staticmethod + def get_remote_commit(repo: Path) -> str: + if not repo.exists() and repo.joinpath(".git").exists(): + return "-" + + try: + # get locally checked out branch + branch_cmd = f"cd {repo} && git branch | grep -E '\*'" + branch = subprocess.check_output(branch_cmd, shell=True, text=True) + branch = branch.split("*")[-1].strip() + cmd = f"cd {repo} && git describe 'origin/{branch}' --always --tags | cut -d '-' -f 1,2" + return subprocess.check_output(cmd, shell=True, text=True).strip() + except subprocess.CalledProcessError: + return "-" + def clone_repo(self): log = f"Cloning repository from '{self.repo}' with method '{self.method}'" Logger.print_status(log) diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 67818dd..3aae6b9 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -23,6 +23,7 @@ from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.instance_manager.name_scheme import NameScheme +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.modules.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper_dialogs import ( @@ -33,17 +34,27 @@ from kiauh.modules.klipper.klipper_dialogs import ( ) from kiauh.modules.moonraker.moonraker import Moonraker from kiauh.modules.moonraker.moonraker_utils import moonraker_to_multi_conversion -from kiauh.utils.common import get_install_status_common, get_repo_name +from kiauh.utils.common import get_install_status_common from kiauh.utils.constants import CURRENT_USER from kiauh.utils.input_utils import get_confirm, get_string_input, get_number_input from kiauh.utils.logger import Logger from kiauh.utils.system_utils import mask_system_service -def get_klipper_status() -> Dict[Literal["status", "repo"], str]: +def get_klipper_status() -> ( + Dict[ + Literal["status", "status_code", "instances", "repo", "local", "remote"], + Union[str, int], + ] +): + status = get_install_status_common(Klipper, KLIPPER_DIR, KLIPPER_ENV_DIR) return { - "status": get_install_status_common(Klipper, KLIPPER_DIR, KLIPPER_ENV_DIR), - "repo": get_repo_name(KLIPPER_DIR), + "status": status.get("status"), + "status_code": status.get("status_code"), + "instances": status.get("instances"), + "repo": RepoManager.get_repo_name(KLIPPER_DIR), + "local": RepoManager.get_local_commit(KLIPPER_DIR), + "remote": RepoManager.get_remote_commit(KLIPPER_DIR), } diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index c92721f..c0e1e81 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -10,10 +10,11 @@ # ======================================================================= # import shutil -from typing import Dict, Literal, List +from typing import Dict, Literal, List, Union from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.modules.mainsail import MAINSAIL_DIR from kiauh.modules.mainsail.mainsail_utils import enable_mainsail_remotemode from kiauh.modules.moonraker import ( @@ -23,19 +24,27 @@ from kiauh.modules.moonraker import ( MOONRAKER_ENV_DIR, ) from kiauh.modules.moonraker.moonraker import Moonraker -from kiauh.utils.common import get_install_status_common, get_repo_name +from kiauh.utils.common import get_install_status_common from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( get_ipv4_addr, ) -def get_moonraker_status() -> Dict[Literal["status", "repo"], str]: +def get_moonraker_status() -> ( + Dict[ + Literal["status", "status_code", "instances", "repo", "local", "remote"], + Union[str, int], + ] +): + status = get_install_status_common(Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR) return { - "status": get_install_status_common( - Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR - ), - "repo": get_repo_name(MOONRAKER_DIR), + "status": status.get("status"), + "status_code": status.get("status_code"), + "instances": status.get("instances"), + "repo": RepoManager.get_repo_name(MOONRAKER_DIR), + "local": RepoManager.get_local_commit(MOONRAKER_DIR), + "remote": RepoManager.get_remote_commit(MOONRAKER_DIR), } diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index aebbaf3..ee8739c 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -9,10 +9,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import subprocess from datetime import datetime from pathlib import Path -from typing import Dict, Literal, List, Type +from typing import Dict, Literal, List, Type, Union from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.core.instance_manager.instance_manager import InstanceManager @@ -56,24 +55,9 @@ def check_install_dependencies(deps: List[str]) -> None: install_system_packages(requirements) -def get_repo_name(repo_dir: Path) -> str: - """ - Helper method to extract the organisation and name of a repository | - :param repo_dir: repository to extract the values from - :return: String in form of "/" - """ - try: - cmd = ["git", "-C", repo_dir, "config", "--get", "remote.origin.url"] - result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) - result = "/".join(result.decode().strip().split("/")[-2:]) - return f"{COLOR_CYAN}{result}{RESET_FORMAT}" - except subprocess.CalledProcessError: - return f"{COLOR_RED}-{RESET_FORMAT}" - - def get_install_status_common( instance_type: Type[BaseInstance], repo_dir: Path, env_dir: Path -) -> str: +) -> Dict[Literal["status", "status_code", "instances"], Union[str, int]]: """ Helper method to get the installation status of software components, which only consist of 3 major parts and if those parts exist, the @@ -82,17 +66,29 @@ def get_install_status_common( :param instance_type: The component type :param repo_dir: the repository directory :param env_dir: the python environment directory - :return: formatted string, containing the status + :return: Dictionary with status string, statuscode and instance count """ im = InstanceManager(instance_type) instances_exist = len(im.instances) > 0 status = [repo_dir.exists(), env_dir.exists(), instances_exist] if all(status): - return f"{COLOR_GREEN}Installed: {len(im.instances)}{RESET_FORMAT}" + return { + "status": "Installed:", + "status_code": 1, + "instances": len(im.instances), + } elif not any(status): - return f"{COLOR_RED}Not installed!{RESET_FORMAT}" + return { + "status": "Not installed!", + "status_code": 2, + "instances": len(im.instances), + } else: - return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" + return { + "status": "Incomplete!", + "status_code": 3, + "instances": len(im.instances), + } def get_install_status_webui( From 85b4b68f16f66e89405c7c574d4d6e736b8f2ad3 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 28 Dec 2023 13:47:24 +0100 Subject: [PATCH 086/137] refactor(Klipper/Moonraker): install new packages during updates Signed-off-by: Dominik Willner --- kiauh/core/menus/update_menu.py | 3 ++- kiauh/modules/klipper/klipper_setup.py | 6 ++++++ kiauh/modules/moonraker/moonraker_setup.py | 12 +++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 21da09b..50aae17 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -17,6 +17,7 @@ from kiauh.modules.klipper.klipper_setup import update_klipper from kiauh.modules.klipper.klipper_utils import ( get_klipper_status, ) +from kiauh.modules.moonraker.moonraker_setup import update_moonraker from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE @@ -92,7 +93,7 @@ class UpdateMenu(BaseMenu): update_klipper() def update_moonraker(self): - print("update_moonraker") + update_moonraker() def update_mainsail(self): print("update_mainsail") diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 05aa038..5b0c6f7 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -157,6 +157,12 @@ def update_klipper() -> None: target_dir=KLIPPER_DIR, ) repo_manager.pull_repo() + + # install possible new system packages + install_klipper_packages(KLIPPER_DIR) + # install possible new python dependencies + install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT) + instance_manager.start_all_instance() diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index e7a8506..0b08a54 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -19,10 +19,7 @@ from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_dialogs import ( - print_instance_overview, - print_update_warn_dialog, -) +from kiauh.modules.klipper.klipper_dialogs import print_instance_overview from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.modules.mainsail import MAINSAIL_DIR from kiauh.modules.mainsail.mainsail_utils import enable_mainsail_remotemode @@ -204,7 +201,6 @@ def handle_existing_instances(instance_list: List[Klipper]) -> bool: def update_moonraker() -> None: - print_update_warn_dialog() if not get_confirm("Update Moonraker now?"): return @@ -228,4 +224,10 @@ def update_moonraker() -> None: target_dir=MOONRAKER_DIR, ) repo_manager.pull_repo() + + # install possible new system packages + install_moonraker_packages(MOONRAKER_DIR) + # install possible new python dependencies + install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQUIREMENTS_TXT) + instance_manager.start_all_instance() From 9a657daffddaef715944034e4aa3c067d9441fac Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 28 Dec 2023 18:05:43 +0100 Subject: [PATCH 087/137] feat(KIAUH): show Mainsail in UpdateMenu Signed-off-by: Dominik Willner --- kiauh/core/menus/update_menu.py | 19 +++++++++++++++++-- kiauh/modules/mainsail/mainsail_setup.py | 7 +++++++ kiauh/modules/mainsail/mainsail_utils.py | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 50aae17..68fcc90 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -17,6 +17,11 @@ from kiauh.modules.klipper.klipper_setup import update_klipper from kiauh.modules.klipper.klipper_utils import ( get_klipper_status, ) +from kiauh.modules.mainsail.mainsail_setup import update_mainsail +from kiauh.modules.mainsail.mainsail_utils import ( + get_mainsail_local_version, + get_mainsail_remote_version, +) from kiauh.modules.moonraker.moonraker_setup import update_moonraker from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE @@ -48,6 +53,8 @@ class UpdateMenu(BaseMenu): self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.mr_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.mr_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.ms_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.ms_remote = f"{COLOR_WHITE}{RESET_FORMAT}" def print_menu(self): self.fetch_update_status() @@ -67,7 +74,7 @@ class UpdateMenu(BaseMenu): | 2) Moonraker | {self.mr_local:<22} | {self.mr_remote:<22} | | | | | | Klipper Webinterface: |---------------|---------------| - | 3) Mainsail | | | + | 3) Mainsail | {self.ms_local:<22} | {self.ms_remote:<22} | | 4) Fluidd | | | | | | | | Touchscreen GUI: |---------------|---------------| @@ -96,7 +103,7 @@ class UpdateMenu(BaseMenu): update_moonraker() def update_mainsail(self): - print("update_mainsail") + update_mainsail() def update_fluidd(self): print("update_fluidd") @@ -144,3 +151,11 @@ class UpdateMenu(BaseMenu): else: self.mr_local = f"{COLOR_YELLOW}{self.mr_local}{RESET_FORMAT}" self.mr_remote = f"{COLOR_GREEN}{self.mr_remote}{RESET_FORMAT}" + # mainsail + self.ms_local = get_mainsail_local_version() + self.ms_remote = get_mainsail_remote_version() + if self.ms_local == self.ms_remote: + self.ms_local = f"{COLOR_GREEN}{self.ms_local}{RESET_FORMAT}" + else: + self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}" + self.ms_remote = f"{COLOR_GREEN}{self.ms_remote}{RESET_FORMAT}" diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 4851d50..70f7dc4 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -162,6 +162,13 @@ def download_mainsail() -> None: raise +def update_mainsail() -> None: + Logger.print_status("Updating Mainsail ...") + backup_config_json(is_temp=True) + download_mainsail() + restore_config_json() + + def download_mainsail_cfg() -> None: try: Logger.print_status("Downloading mainsail-config ...") diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index dafc2f7..329dff6 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -11,6 +11,7 @@ import json import shutil +import requests from pathlib import Path from typing import List @@ -80,3 +81,19 @@ def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: desti_error = instance.log_dir.joinpath("mainsail-error.log") if not desti_error.exists(): desti_error.symlink_to(error_log) + + +def get_mainsail_local_version() -> str: + relinfo_file = MAINSAIL_DIR.joinpath("release_info.json") + if not relinfo_file.is_file(): + return "-" + + with open(relinfo_file, "r") as f: + return json.load(f)["version"] + + +def get_mainsail_remote_version() -> str: + url = "https://api.github.com/repos/mainsail-crew/mainsail/tags" + response = requests.get(url) + data = json.loads(response.text) + return data[0]["name"] From cda6d31a7c2b772c2213c7c36147f96055fe6580 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 29 Dec 2023 18:51:23 +0100 Subject: [PATCH 088/137] fix(RepoManager): check if git dir exists Signed-off-by: Dominik Willner --- kiauh/core/repo_manager/repo_manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index 61250c4..4754a8d 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -69,6 +69,9 @@ class RepoManager: :param repo: repository to extract the values from :return: String in form of "/" """ + if not repo.exists() and not repo.joinpath(".git").exists(): + return "-" + try: cmd = ["git", "-C", repo, "config", "--get", "remote.origin.url"] result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) @@ -78,7 +81,7 @@ class RepoManager: @staticmethod def get_local_commit(repo: Path) -> str: - if not repo.exists() and repo.joinpath(".git").exists(): + if not repo.exists() and not repo.joinpath(".git").exists(): return "-" try: @@ -89,7 +92,7 @@ class RepoManager: @staticmethod def get_remote_commit(repo: Path) -> str: - if not repo.exists() and repo.joinpath(".git").exists(): + if not repo.exists() and not repo.joinpath(".git").exists(): return "-" try: From c28d5c28b9b6a59a306cedf6f190f7c2f230e407 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 29 Dec 2023 19:20:04 +0100 Subject: [PATCH 089/137] refactor(KIAUH): use pythons own venv module to create a venv Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 7 +++---- kiauh/utils/system_utils.py | 11 ++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 5b0c6f7..10ce452 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -114,10 +114,9 @@ def setup_klipper_prerequesites() -> None: repo_manager.clone_repo() # install klipper dependencies and create python virtualenv - install_klipper_packages(Path(KLIPPER_DIR)) - create_python_venv(Path(KLIPPER_ENV_DIR)) - klipper_py_req = Path(KLIPPER_REQUIREMENTS_TXT) - install_python_requirements(Path(KLIPPER_ENV_DIR), klipper_py_req) + install_klipper_packages(KLIPPER_DIR) + create_python_venv(KLIPPER_ENV_DIR) + install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT) def install_klipper_packages(klipper_dir: Path) -> None: diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 5d7292e..5b15a16 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -17,6 +17,7 @@ import sys import time import urllib.error import urllib.request +import venv from pathlib import Path from typing import List, Literal @@ -68,14 +69,10 @@ def create_python_venv(target: Path) -> None: Logger.print_status("Set up Python virtual environment ...") if not target.exists(): try: - command = ["python3", "-m", "venv", f"{target}"] - result = subprocess.run(command, stderr=subprocess.PIPE, text=True) - if result.returncode != 0 or result.stderr: - Logger.print_error(f"{result.stderr}", prefix=False) - Logger.print_error("Setup of virtualenv failed!") - return - + venv.create(target, with_pip=True) Logger.print_ok("Setup of virtualenv successfull!") + except OSError as e: + Logger.print_error(f"Error setting up virtualenv:\n{e}") except subprocess.CalledProcessError as e: Logger.print_error(f"Error setting up virtualenv:\n{e.output.decode()}") else: From 7820155094519879afc3d3175fabf96fe6c8ad35 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 29 Dec 2023 19:47:45 +0100 Subject: [PATCH 090/137] refactor(Klipper): add some exception handling Signed-off-by: Dominik Willner --- kiauh/modules/klipper/klipper_setup.py | 43 ++++++++++++++++---------- kiauh/utils/system_utils.py | 4 +++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 10ce452..4e3382b 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -74,25 +74,30 @@ def install_klipper() -> None: create_example_cfg = get_confirm("Create example printer.cfg?") - if not kl_im.instances: - setup_klipper_prerequesites() + try: + if not kl_im.instances: + setup_klipper_prerequesites() - count = 0 - for name in name_dict: - if name_dict[name] in [n.suffix for n in kl_im.instances]: - continue + count = 0 + for name in name_dict: + if name_dict[name] in [n.suffix for n in kl_im.instances]: + continue - if check_is_single_to_multi_conversion(kl_im.instances): - handle_to_multi_instance_conversion(name_dict[name]) - continue + if check_is_single_to_multi_conversion(kl_im.instances): + handle_to_multi_instance_conversion(name_dict[name]) + continue - count += 1 - create_klipper_instance(name_dict[name], create_example_cfg) + count += 1 + create_klipper_instance(name_dict[name], create_example_cfg) - if count == install_count: - break + if count == install_count: + break - kl_im.reload_daemon() + kl_im.reload_daemon() + + except Exception: + Logger.print_error("Klipper installation failed!") + return # step 4: check/handle conflicting packages/services handle_disruptive_system_packages() @@ -114,9 +119,13 @@ def setup_klipper_prerequesites() -> None: repo_manager.clone_repo() # install klipper dependencies and create python virtualenv - install_klipper_packages(KLIPPER_DIR) - create_python_venv(KLIPPER_ENV_DIR) - install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT) + try: + install_klipper_packages(KLIPPER_DIR) + create_python_venv(KLIPPER_ENV_DIR) + install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT) + except Exception: + Logger.print_error("Error during installation of Klipper requirements!") + raise def install_klipper_packages(klipper_dir: Path) -> None: diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 5b15a16..cb4b70b 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -73,8 +73,10 @@ def create_python_venv(target: Path) -> None: Logger.print_ok("Setup of virtualenv successfull!") except OSError as e: Logger.print_error(f"Error setting up virtualenv:\n{e}") + raise except subprocess.CalledProcessError as e: Logger.print_error(f"Error setting up virtualenv:\n{e.output.decode()}") + raise else: if get_confirm("Virtualenv already exists. Re-create?", default_choice=False): try: @@ -83,6 +85,7 @@ def create_python_venv(target: Path) -> None: except OSError as e: log = f"Error removing existing virtualenv: {e.strerror}" Logger.print_error(log, False) + raise else: Logger.print_info("Skipping re-creation of virtualenv ...") @@ -128,6 +131,7 @@ def install_python_requirements(target: Path, requirements: Path) -> None: except subprocess.CalledProcessError as e: log = f"Error installing Python requirements:\n{e.output.decode()}" Logger.print_error(log) + raise def update_system_package_lists(silent: bool, rls_info_change=False) -> None: From 77f1089041441d85d0a8f0be9a1e32ab21f8e961 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 28 Jan 2024 12:51:23 +0100 Subject: [PATCH 091/137] chore(kiauh): reformat code / rename method Signed-off-by: Dominik Willner --- kiauh/core/menus/install_menu.py | 2 +- kiauh/modules/mainsail/mainsail_setup.py | 2 +- kiauh/modules/moonraker/moonraker_setup.py | 40 ++++++++++------------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 45ecccb..52e6592 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -70,7 +70,7 @@ class InstallMenu(BaseMenu): moonraker_setup.install_moonraker() def install_mainsail(self): - mainsail_setup.run_mainsail_installation() + mainsail_setup.install_mainsail() def install_fluidd(self): print("install_fluidd") diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 70f7dc4..437c19b 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -58,7 +58,7 @@ from kiauh.utils.system_utils import ( ) -def run_mainsail_installation() -> None: +def install_mainsail() -> None: mr_im = InstanceManager(Moonraker) mr_instances: List[Moonraker] = mr_im.instances diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index 0b08a54..e47b7eb 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -52,27 +52,6 @@ from kiauh.utils.system_utils import ( ) -def check_moonraker_install_requirements() -> bool: - 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 False - - kl_instance_count = len(InstanceManager(Klipper).instances) - if kl_instance_count < 1: - Logger.print_warn("Klipper not installed!") - Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") - return False - - mr_instance_count = len(InstanceManager(Moonraker).instances) - if mr_instance_count >= kl_instance_count: - Logger.print_warn("Unable to install more Moonraker instances!") - Logger.print_warn("More Klipper instances required.") - return False - - return True - - def install_moonraker() -> None: if not check_moonraker_install_requirements(): return @@ -134,6 +113,25 @@ def install_moonraker() -> None: if MAINSAIL_DIR.exists() and len(mr_im.instances) > 1: enable_mainsail_remotemode() +def check_moonraker_install_requirements() -> bool: + 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 False + + kl_instance_count = len(InstanceManager(Klipper).instances) + if kl_instance_count < 1: + Logger.print_warn("Klipper not installed!") + Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") + return False + + mr_instance_count = len(InstanceManager(Moonraker).instances) + if mr_instance_count >= kl_instance_count: + Logger.print_warn("Unable to install more Moonraker instances!") + Logger.print_warn("More Klipper instances required.") + return False + + return True def setup_moonraker_prerequesites() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) From 35911604af82a9f201e1740f259cf1b0af5a236f Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 28 Jan 2024 12:54:06 +0100 Subject: [PATCH 092/137] chore(kiauh): update copyright Signed-off-by: Dominik Willner --- kiauh.py | 2 +- kiauh/__init__.py | 2 +- kiauh/core/backup_manager/backup_manager.py | 2 +- kiauh/core/config_manager/config_manager.py | 2 +- kiauh/core/instance_manager/base_instance.py | 2 +- kiauh/core/instance_manager/instance_manager.py | 2 +- kiauh/core/menus/__init__.py | 2 +- kiauh/core/menus/advanced_menu.py | 2 +- kiauh/core/menus/base_menu.py | 2 +- kiauh/core/menus/install_menu.py | 2 +- kiauh/core/menus/main_menu.py | 2 +- kiauh/core/menus/remove_menu.py | 2 +- kiauh/core/menus/settings_menu.py | 2 +- kiauh/core/menus/update_menu.py | 2 +- kiauh/core/repo_manager/repo_manager.py | 2 +- kiauh/main.py | 2 +- kiauh/modules/klipper/__init__.py | 2 +- kiauh/modules/klipper/klipper.py | 2 +- kiauh/modules/klipper/klipper_dialogs.py | 2 +- kiauh/modules/klipper/klipper_remove.py | 2 +- kiauh/modules/klipper/klipper_setup.py | 2 +- kiauh/modules/klipper/klipper_utils.py | 2 +- kiauh/modules/klipper/menus/klipper_remove_menu.py | 2 +- kiauh/modules/mainsail/__init__.py | 2 +- kiauh/modules/mainsail/mainsail_dialogs.py | 2 +- kiauh/modules/mainsail/mainsail_remove.py | 2 +- kiauh/modules/mainsail/mainsail_setup.py | 2 +- kiauh/modules/mainsail/mainsail_utils.py | 2 +- kiauh/modules/mainsail/menus/mainsail_remove_menu.py | 2 +- kiauh/modules/moonraker/__init__.py | 2 +- kiauh/modules/moonraker/menus/moonraker_remove_menu.py | 2 +- kiauh/modules/moonraker/moonraker.py | 2 +- kiauh/modules/moonraker/moonraker_dialogs.py | 2 +- kiauh/modules/moonraker/moonraker_remove.py | 2 +- kiauh/modules/moonraker/moonraker_setup.py | 4 +++- kiauh/modules/moonraker/moonraker_utils.py | 2 +- kiauh/utils/__init__.py | 2 +- kiauh/utils/common.py | 2 +- kiauh/utils/constants.py | 2 +- kiauh/utils/filesystem_utils.py | 2 +- kiauh/utils/input_utils.py | 2 +- kiauh/utils/logger.py | 2 +- kiauh/utils/system_utils.py | 2 +- 43 files changed, 45 insertions(+), 43 deletions(-) diff --git a/kiauh.py b/kiauh.py index 227775f..ff930a4 100644 --- a/kiauh.py +++ b/kiauh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/__init__.py b/kiauh/__init__.py index e5fd81f..3a58dc0 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 68fa338..556346d 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 5e56626..63a4490 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 903c76b..571985e 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 8bc9e9c..d4e2f28 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/__init__.py b/kiauh/core/menus/__init__.py index 23cd836..afca077 100644 --- a/kiauh/core/menus/__init__.py +++ b/kiauh/core/menus/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 038409a..533b5c1 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 1950e4b..434293c 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 52e6592..22bdad2 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 4f1579f..66324bc 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 922c2cf..bb81c31 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index 8cb7723..1305ad1 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 68fcc90..f947d84 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index 4754a8d..9134694 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/main.py b/kiauh/main.py index e064aec..0dd1b31 100644 --- a/kiauh/main.py +++ b/kiauh/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/modules/klipper/__init__.py index e4d32b6..6b87989 100644 --- a/kiauh/modules/klipper/__init__.py +++ b/kiauh/modules/klipper/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/modules/klipper/klipper.py index 6a43452..a42eb53 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/modules/klipper/klipper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/modules/klipper/klipper_dialogs.py index d3b86bd..94e9374 100644 --- a/kiauh/modules/klipper/klipper_dialogs.py +++ b/kiauh/modules/klipper/klipper_dialogs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/klipper_remove.py b/kiauh/modules/klipper/klipper_remove.py index a62712a..4037b49 100644 --- a/kiauh/modules/klipper/klipper_remove.py +++ b/kiauh/modules/klipper/klipper_remove.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/modules/klipper/klipper_setup.py index 4e3382b..b5b4def 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/modules/klipper/klipper_setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/modules/klipper/klipper_utils.py index 3aae6b9..3169fd6 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/modules/klipper/klipper_utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/klipper/menus/klipper_remove_menu.py b/kiauh/modules/klipper/menus/klipper_remove_menu.py index ed9740f..d5cd0d9 100644 --- a/kiauh/modules/klipper/menus/klipper_remove_menu.py +++ b/kiauh/modules/klipper/menus/klipper_remove_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/mainsail/__init__.py b/kiauh/modules/mainsail/__init__.py index 87dc2f5..9c01f87 100644 --- a/kiauh/modules/mainsail/__init__.py +++ b/kiauh/modules/mainsail/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/mainsail/mainsail_dialogs.py b/kiauh/modules/mainsail/mainsail_dialogs.py index 0c60bbf..6c8ea69 100644 --- a/kiauh/modules/mainsail/mainsail_dialogs.py +++ b/kiauh/modules/mainsail/mainsail_dialogs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/modules/mainsail/mainsail_remove.py index 162e77f..7a10041 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/modules/mainsail/mainsail_remove.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index 437c19b..ee15812 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/modules/mainsail/mainsail_utils.py index 329dff6..1fdfa5a 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/modules/mainsail/mainsail_utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py index 071a818..bd0abef 100644 --- a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/moonraker/__init__.py b/kiauh/modules/moonraker/__init__.py index 9e7231e..14cdac0 100644 --- a/kiauh/modules/moonraker/__init__.py +++ b/kiauh/modules/moonraker/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py b/kiauh/modules/moonraker/menus/moonraker_remove_menu.py index c6d0a05..348b34d 100644 --- a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/modules/moonraker/menus/moonraker_remove_menu.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py index 904c813..89f47b3 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/modules/moonraker/moonraker.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/moonraker/moonraker_dialogs.py b/kiauh/modules/moonraker/moonraker_dialogs.py index 6e79b49..f1576cd 100644 --- a/kiauh/modules/moonraker/moonraker_dialogs.py +++ b/kiauh/modules/moonraker/moonraker_dialogs.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/moonraker/moonraker_remove.py b/kiauh/modules/moonraker/moonraker_remove.py index ab7f22c..4ddc0c9 100644 --- a/kiauh/modules/moonraker/moonraker_remove.py +++ b/kiauh/modules/moonraker/moonraker_remove.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py index e47b7eb..faf50ca 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # @@ -113,6 +113,7 @@ def install_moonraker() -> None: if MAINSAIL_DIR.exists() and len(mr_im.instances) > 1: enable_mainsail_remotemode() + def check_moonraker_install_requirements() -> bool: if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7): Logger.print_error("Versioncheck failed!") @@ -133,6 +134,7 @@ def check_moonraker_install_requirements() -> bool: return True + def setup_moonraker_prerequesites() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) repo = str( diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/modules/moonraker/moonraker_utils.py index c0e1e81..35fe973 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/modules/moonraker/moonraker_utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index f44410e..f097b5d 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index ee8739c..fbae241 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index 647b55b..a2dd612 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 505c1f1..360461e 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index 7f9f1f5..e2daf8e 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index 8732878..fcd0c9b 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index cb4b70b..fce0977 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # ======================================================================= # -# Copyright (C) 2020 - 2023 Dominik Willner # +# Copyright (C) 2020 - 2024 Dominik Willner # # # # This file is part of KIAUH - Klipper Installation And Update Helper # # https://github.com/dw-0/kiauh # From ee81ee4c0c62328ad8bd7c61fe2e8f4610db3c75 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 28 Jan 2024 15:35:00 +0100 Subject: [PATCH 093/137] fix(Mainsail): correctly handle invalid config value for default_port Signed-off-by: Dominik Willner --- kiauh/modules/mainsail/mainsail_setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/modules/mainsail/mainsail_setup.py index ee15812..294db45 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/modules/mainsail/mainsail_setup.py @@ -90,10 +90,13 @@ def install_mainsail() -> None: question = "Download the recommended macros?" install_ms_config = get_confirm(question, allow_go_back=False) + # if a default port is configured in the kiauh.cfg, we use that for the port + # otherwise we default to port 80, but show the user a dialog to confirm/change that port cm = ConfigManager(cfg_file=KIAUH_CFG) default_port = cm.get_value("mainsail", "default_port") - mainsail_port = default_port if default_port else "80" - if not default_port: + is_valid_port = default_port and default_port.isdigit() + mainsail_port = default_port if is_valid_port else "80" + if not is_valid_port: print_mainsail_port_select_dialog(mainsail_port) mainsail_port = get_number_input( "Configure Mainsail for port", From bc30cf418bde8d717164e63410d62d59f56de8a5 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 28 Jan 2024 22:25:07 +0100 Subject: [PATCH 094/137] refactor(kiauh): add option index parameter to method calls from menus Signed-off-by: Dominik Willner --- kiauh/core/menus/base_menu.py | 2 +- kiauh/core/menus/install_menu.py | 23 ++++++++-------- kiauh/core/menus/remove_menu.py | 23 ++++++++-------- kiauh/core/menus/update_menu.py | 27 ++++++++++--------- .../klipper/menus/klipper_remove_menu.py | 13 ++++----- .../mainsail/menus/mainsail_remove_menu.py | 15 ++++++----- .../moonraker/menus/moonraker_remove_menu.py | 15 ++++++----- 7 files changed, 62 insertions(+), 56 deletions(-) diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 434293c..59b5d91 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -175,7 +175,7 @@ class BaseMenu(ABC): if isinstance(option, type) and issubclass(option, BaseMenu): self.navigate_to_submenu(option) elif callable(option): - option() + option(opt_index=choice) elif option is None: raise NotImplementedError(f"No implementation for option {choice}") else: diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 22bdad2..83344ab 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -19,6 +19,7 @@ from kiauh.modules.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT +# noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class InstallMenu(BaseMenu): def __init__(self): @@ -63,35 +64,35 @@ class InstallMenu(BaseMenu): )[1:] print(menu, end="") - def install_klipper(self): + def install_klipper(self, **kwargs): klipper_setup.install_klipper() - def install_moonraker(self): + def install_moonraker(self, **kwargs): moonraker_setup.install_moonraker() - def install_mainsail(self): + def install_mainsail(self, **kwargs): mainsail_setup.install_mainsail() - def install_fluidd(self): + def install_fluidd(self, **kwargs): print("install_fluidd") - def install_klipperscreen(self): + def install_klipperscreen(self, **kwargs): print("install_klipperscreen") - def install_pretty_gcode(self): + def install_pretty_gcode(self, **kwargs): print("install_pretty_gcode") - def install_telegram_bot(self): + def install_telegram_bot(self, **kwargs): print("install_telegram_bot") - def install_obico(self): + def install_obico(self, **kwargs): print("install_obico") - def install_octoeverywhere(self): + def install_octoeverywhere(self, **kwargs): print("install_octoeverywhere") - def install_mobileraker(self): + def install_mobileraker(self, **kwargs): print("install_mobileraker") - def install_crowsnest(self): + def install_crowsnest(self, **kwargs): print("install_crowsnest") diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index bb81c31..e8ddd79 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -19,6 +19,7 @@ from kiauh.modules.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveM from kiauh.utils.constants import COLOR_RED, RESET_FORMAT +# noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class RemoveMenu(BaseMenu): def __init__(self): @@ -69,35 +70,35 @@ class RemoveMenu(BaseMenu): )[1:] print(menu, end="") - def remove_fluidd(self): + def remove_fluidd(self, **kwargs): print("remove_fluidd") - def remove_fluidd_config(self): + def remove_fluidd_config(self, **kwargs): print("remove_fluidd_config") - def remove_klipperscreen(self): + def remove_klipperscreen(self, **kwargs): print("remove_klipperscreen") - def remove_crowsnest(self): + def remove_crowsnest(self, **kwargs): print("remove_crowsnest") - def remove_mjpgstreamer(self): + def remove_mjpgstreamer(self, **kwargs): print("remove_mjpgstreamer") - def remove_pretty_gcode(self): + def remove_pretty_gcode(self, **kwargs): print("remove_pretty_gcode") - def remove_telegram_bot(self): + def remove_telegram_bot(self, **kwargs): print("remove_telegram_bot") - def remove_obico(self): + def remove_obico(self, **kwargs): print("remove_obico") - def remove_octoeverywhere(self): + def remove_octoeverywhere(self, **kwargs): print("remove_octoeverywhere") - def remove_mobileraker(self): + def remove_mobileraker(self, **kwargs): print("remove_mobileraker") - def remove_nginx(self): + def remove_nginx(self, **kwargs): print("remove_nginx") diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index f947d84..5b7c6ef 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -27,6 +27,7 @@ from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE +# noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class UpdateMenu(BaseMenu): def __init__(self): @@ -93,43 +94,43 @@ class UpdateMenu(BaseMenu): )[1:] print(menu, end="") - def update_all(self): + def update_all(self, **kwargs): print("update_all") - def update_klipper(self): + def update_klipper(self, **kwargs): update_klipper() - def update_moonraker(self): + def update_moonraker(self, **kwargs): update_moonraker() - def update_mainsail(self): + def update_mainsail(self, **kwargs): update_mainsail() - def update_fluidd(self): + def update_fluidd(self, **kwargs): print("update_fluidd") - def update_klipperscreen(self): + def update_klipperscreen(self, **kwargs): print("update_klipperscreen") - def update_pgc_for_klipper(self): + def update_pgc_for_klipper(self, **kwargs): print("update_pgc_for_klipper") - def update_telegram_bot(self): + def update_telegram_bot(self, **kwargs): print("update_telegram_bot") - def update_moonraker_obico(self): + def update_moonraker_obico(self, **kwargs): print("update_moonraker_obico") - def update_octoeverywhere(self): + def update_octoeverywhere(self, **kwargs): print("update_octoeverywhere") - def update_mobileraker(self): + def update_mobileraker(self, **kwargs): print("update_mobileraker") - def update_crowsnest(self): + def update_crowsnest(self, **kwargs): print("update_crowsnest") - def upgrade_system_packages(self): + def upgrade_system_packages(self, **kwargs): print("upgrade_system_packages") def fetch_update_status(self): diff --git a/kiauh/modules/klipper/menus/klipper_remove_menu.py b/kiauh/modules/klipper/menus/klipper_remove_menu.py index d5cd0d9..61fdc94 100644 --- a/kiauh/modules/klipper/menus/klipper_remove_menu.py +++ b/kiauh/modules/klipper/menus/klipper_remove_menu.py @@ -18,6 +18,7 @@ from kiauh.modules.moonraker import moonraker_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN +# noinspection PyUnusedLocal class KlipperRemoveMenu(BaseMenu): def __init__(self): super().__init__( @@ -67,25 +68,25 @@ class KlipperRemoveMenu(BaseMenu): )[1:] print(menu, end="") - def toggle_all(self) -> None: + def toggle_all(self, **kwargs) -> None: self.remove_klipper_service = True self.remove_klipper_dir = True self.remove_klipper_env = True self.delete_klipper_logs = True - def toggle_remove_klipper_service(self) -> None: + def toggle_remove_klipper_service(self, **kwargs) -> None: self.remove_klipper_service = not self.remove_klipper_service - def toggle_remove_klipper_dir(self) -> None: + def toggle_remove_klipper_dir(self, **kwargs) -> None: self.remove_klipper_dir = not self.remove_klipper_dir - def toggle_remove_klipper_env(self) -> None: + def toggle_remove_klipper_env(self, **kwargs) -> None: self.remove_klipper_env = not self.remove_klipper_env - def toggle_delete_klipper_logs(self) -> None: + def toggle_delete_klipper_logs(self, **kwargs) -> None: self.delete_klipper_logs = not self.delete_klipper_logs - def run_removal_process(self) -> None: + def run_removal_process(self, **kwargs) -> None: if ( not self.remove_klipper_service and not self.remove_klipper_dir diff --git a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py index bd0abef..e2ec352 100644 --- a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/modules/mainsail/menus/mainsail_remove_menu.py @@ -17,6 +17,7 @@ from kiauh.modules.mainsail import mainsail_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN +# noinspection PyUnusedLocal class MainsailRemoveMenu(BaseMenu): def __init__(self): super().__init__( @@ -72,29 +73,29 @@ class MainsailRemoveMenu(BaseMenu): )[1:] print(menu, end="") - def toggle_all(self) -> None: + def toggle_all(self, **kwargs) -> None: self.remove_mainsail = True self.remove_ms_config = True self.backup_config_json = True self.remove_updater_section = True self.remove_printer_cfg_include = True - def toggle_remove_mainsail(self) -> None: + def toggle_remove_mainsail(self, **kwargs) -> None: self.remove_mainsail = not self.remove_mainsail - def toggle_remove_ms_config(self) -> None: + def toggle_remove_ms_config(self, **kwargs) -> None: self.remove_ms_config = not self.remove_ms_config - def toggle_backup_config_json(self) -> None: + def toggle_backup_config_json(self, **kwargs) -> None: self.backup_config_json = not self.backup_config_json - def toggle_remove_updater_section(self) -> None: + def toggle_remove_updater_section(self, **kwargs) -> None: self.remove_updater_section = not self.remove_updater_section - def toggle_remove_printer_cfg_include(self) -> None: + def toggle_remove_printer_cfg_include(self, **kwargs) -> None: self.remove_printer_cfg_include = not self.remove_printer_cfg_include - def run_removal_process(self) -> None: + def run_removal_process(self, **kwargs) -> None: if ( not self.remove_mainsail and not self.remove_ms_config diff --git a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py b/kiauh/modules/moonraker/menus/moonraker_remove_menu.py index 348b34d..c193f85 100644 --- a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/modules/moonraker/menus/moonraker_remove_menu.py @@ -17,6 +17,7 @@ from kiauh.modules.moonraker import moonraker_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN +# noinspection PyUnusedLocal class MoonrakerRemoveMenu(BaseMenu): def __init__(self): super().__init__( @@ -70,29 +71,29 @@ class MoonrakerRemoveMenu(BaseMenu): )[1:] print(menu, end="") - def toggle_all(self) -> None: + def toggle_all(self, **kwargs) -> 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: + def toggle_remove_moonraker_service(self, **kwargs) -> None: self.remove_moonraker_service = not self.remove_moonraker_service - def toggle_remove_moonraker_dir(self) -> None: + def toggle_remove_moonraker_dir(self, **kwargs) -> None: self.remove_moonraker_dir = not self.remove_moonraker_dir - def toggle_remove_moonraker_env(self) -> None: + def toggle_remove_moonraker_env(self, **kwargs) -> None: self.remove_moonraker_env = not self.remove_moonraker_env - def toggle_remove_moonraker_polkit(self) -> None: + def toggle_remove_moonraker_polkit(self, **kwargs) -> None: self.remove_moonraker_polkit = not self.remove_moonraker_polkit - def toggle_delete_moonraker_logs(self) -> None: + def toggle_delete_moonraker_logs(self, **kwargs) -> None: self.delete_moonraker_logs = not self.delete_moonraker_logs - def run_removal_process(self) -> None: + def run_removal_process(self, **kwargs) -> None: if ( not self.remove_moonraker_service and not self.remove_moonraker_dir From c6999f1990785b9b5ba76804d4f9e19baa9f0fc5 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 28 Jan 2024 22:26:54 +0100 Subject: [PATCH 095/137] refactor(kiauh): if self.options is an empty dict, return invalid input error message. Signed-off-by: Dominik Willner --- kiauh/core/menus/base_menu.py | 36 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 59b5d91..35d87fb 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -23,6 +23,7 @@ from kiauh.utils.constants import ( COLOR_CYAN, RESET_FORMAT, ) +from kiauh.utils.logger import Logger def clear(): @@ -92,6 +93,8 @@ def print_back_help_footer(): class BaseMenu(ABC): + NAVI_OPTIONS = {"quit": ["q"], "back": ["b"], "back_help": ["b", "h"]} + def __init__( self, options: Dict[int, Any], @@ -130,29 +133,20 @@ class BaseMenu(ABC): while True: choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}") - error_msg = ( - f"{COLOR_RED}Invalid input.{RESET_FORMAT}" - if choice.isalpha() - else f"{COLOR_RED}Invalid input. Select a number between {min(self.options)} and {max(self.options)}.{RESET_FORMAT}" - ) - if choice.isdigit() and 0 <= int(choice) < len(self.options): return choice - elif choice.isalpha(): - allowed_input = { - "quit": ["q"], - "back": ["b"], - "back_help": ["b", "h"], - } - if ( - self.footer_type in allowed_input - and choice.lower() in allowed_input[self.footer_type] - ): - return choice - else: - print(error_msg) + elif choice.isalpha() and ( + self.footer_type in self.NAVI_OPTIONS + and choice.lower() in self.NAVI_OPTIONS[self.footer_type] + ): + return choice else: - print(error_msg) + error_msg = ( + "Invalid input!" + if choice.isalpha() or (not self.options and len(self.options) < 1) + else f"Invalid input! Select a number between {min(self.options)} and {max(self.options)}." + ) + Logger.print_error(error_msg, False) def start(self): while True: @@ -160,7 +154,7 @@ class BaseMenu(ABC): choice = self.handle_user_input() if choice == "q": - print(f"{COLOR_GREEN}###### Happy printing!{RESET_FORMAT}") + Logger.print_ok("###### Happy printing!", False) sys.exit(0) elif choice == "b": return From ad56b51e707dfc4bffbb642ad20654a995026e18 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 28 Jan 2024 22:27:38 +0100 Subject: [PATCH 096/137] feat(LogUpload): implement log upload feature Signed-off-by: Dominik Willner --- kiauh/core/menus/main_menu.py | 3 +- kiauh/modules/log_uploads/__init__.py | 16 ++++++ kiauh/modules/log_uploads/log_upload_utils.py | 57 +++++++++++++++++++ .../log_uploads/menus/log_upload_menu.py | 54 ++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 kiauh/modules/log_uploads/__init__.py create mode 100644 kiauh/modules/log_uploads/log_upload_utils.py create mode 100644 kiauh/modules/log_uploads/menus/log_upload_menu.py diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 66324bc..68e964e 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -19,6 +19,7 @@ from kiauh.core.menus.remove_menu import RemoveMenu from kiauh.core.menus.settings_menu import SettingsMenu from kiauh.core.menus.update_menu import UpdateMenu from kiauh.modules.klipper.klipper_utils import get_klipper_status +from kiauh.modules.log_uploads.menus.log_upload_menu import LogUploadMenu from kiauh.modules.mainsail.mainsail_utils import get_mainsail_status from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import ( @@ -36,7 +37,7 @@ class MainMenu(BaseMenu): super().__init__( header=True, options={ - 0: None, + 0: LogUploadMenu, 1: InstallMenu, 2: UpdateMenu, 3: RemoveMenu, diff --git a/kiauh/modules/log_uploads/__init__.py b/kiauh/modules/log_uploads/__init__.py new file mode 100644 index 0000000..7672138 --- /dev/null +++ b/kiauh/modules/log_uploads/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 typing import Dict, Union, Literal + +FileKey = Literal["filepath", "display_name"] +LogFile = Dict[FileKey, Union[str, Path]] diff --git a/kiauh/modules/log_uploads/log_upload_utils.py b/kiauh/modules/log_uploads/log_upload_utils.py new file mode 100644 index 0000000..c61dbc6 --- /dev/null +++ b/kiauh/modules/log_uploads/log_upload_utils.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 typing import List +from pathlib import Path + +import urllib.request + +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.log_uploads import LogFile +from kiauh.utils.logger import Logger + + +def get_logfile_list() -> List[LogFile]: + cm = InstanceManager(Klipper) + log_dirs: List[Path] = [instance.log_dir for instance in cm.instances] + + logfiles: List[LogFile] = [] + for _dir in log_dirs: + for f in _dir.iterdir(): + logfiles.append({"filepath": f, "display_name": get_display_name(f)}) + + return logfiles + + +def get_display_name(filepath: Path) -> str: + printer = " ".join(filepath.parts[-3].split("_")[:-1]) + name = filepath.name + + return f"{printer}: {name}" + + +def upload_logfile(logfile: LogFile) -> None: + file = logfile.get("filepath") + name = logfile.get("display_name") + Logger.print_status(f"Uploading the following logfile from {name} ...") + + with open(file, "rb") as f: + headers = {"x-random": ""} + req = urllib.request.Request("http://paste.c-net.org/", headers=headers, data=f) + try: + response = urllib.request.urlopen(req) + link = response.read().decode("utf-8") + Logger.print_ok("Upload successfull! Access it via the following link:") + Logger.print_ok(f">>>> {link}", False) + except Exception as e: + Logger.print_error(f"Uploading logfile failed!") + Logger.print_error(str(e)) diff --git a/kiauh/modules/log_uploads/menus/log_upload_menu.py b/kiauh/modules/log_uploads/menus/log_upload_menu.py new file mode 100644 index 0000000..9c96c5c --- /dev/null +++ b/kiauh/modules/log_uploads/menus/log_upload_menu.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 textwrap + +from kiauh.core.menus import BACK_FOOTER +from kiauh.core.menus.base_menu import BaseMenu +from kiauh.modules.log_uploads.log_upload_utils import upload_logfile +from kiauh.modules.log_uploads.log_upload_utils import get_logfile_list +from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW + + +# noinspection PyMethodMayBeStatic +class LogUploadMenu(BaseMenu): + def __init__(self): + self.logfile_list = get_logfile_list() + options = {index: self.upload for index in range(len(self.logfile_list))} + super().__init__( + header=True, + options=options, + footer_type=BACK_FOOTER, + ) + + def print_menu(self): + header = " [ Log Upload ] " + color = COLOR_YELLOW + count = 62 - len(color) - len(RESET_FORMAT) + menu = textwrap.dedent( + f""" + /=======================================================\\ + | {color}{header:~^{count}}{RESET_FORMAT} | + |-------------------------------------------------------| + | You can select the following logfiles for uploading: | + | | + """ + )[1:] + + logfile_list = get_logfile_list() + for logfile in enumerate(logfile_list): + line = f"{logfile[0]}) {logfile[1].get('display_name')}" + menu += f"| {line:<54}|\n" + + print(menu, end="") + + def upload(self, **kwargs): + upload_logfile(self.logfile_list[kwargs.get("opt_index")]) From 5a3d21c40b8d114ebe9f6d232f001af9ce6574c4 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 29 Jan 2024 21:10:14 +0100 Subject: [PATCH 097/137] chore(kiauh): rename "modules" to "components" Signed-off-by: Dominik Willner --- kiauh/{modules => components}/__init__.py | 0 .../{modules => components}/klipper/__init__.py | 0 kiauh/{modules => components}/klipper/klipper.py | 2 +- .../klipper/klipper_dialogs.py | 0 .../klipper/klipper_remove.py | 6 +++--- .../klipper/klipper_setup.py | 10 +++++----- .../klipper/klipper_utils.py | 10 +++++----- .../klipper/menus/__init__.py | 0 .../klipper/menus/klipper_remove_menu.py | 3 +-- .../klipper/res/klipper.env | 0 .../klipper/res/klipper.service | 0 .../klipper/res/printer.cfg | 0 .../log_uploads/__init__.py | 0 .../log_uploads/log_upload_utils.py | 4 ++-- .../log_uploads/menus/log_upload_menu.py | 4 ++-- .../{modules => components}/mainsail/__init__.py | 0 .../mainsail/mainsail_dialogs.py | 0 .../mainsail/mainsail_remove.py | 8 ++++---- .../mainsail/mainsail_setup.py | 10 +++++----- .../mainsail/mainsail_utils.py | 4 ++-- .../mainsail/menus/__init__.py | 0 .../mainsail/menus/mainsail_remove_menu.py | 2 +- .../mainsail/res/mainsail-config-updater.conf | 0 .../mainsail/res/mainsail-updater.conf | 0 .../moonraker/__init__.py | 0 .../moonraker/menus/__init__.py | 0 .../moonraker/menus/moonraker_remove_menu.py | 2 +- .../moonraker/moonraker.py | 2 +- .../moonraker/moonraker_dialogs.py | 4 ++-- .../moonraker/moonraker_remove.py | 6 +++--- .../moonraker/moonraker_setup.py | 16 ++++++++-------- .../moonraker/moonraker_utils.py | 8 ++++---- .../moonraker/res/moonraker.conf | 0 .../moonraker/res/moonraker.env | 0 .../moonraker/res/moonraker.service | 0 kiauh/core/menus/install_menu.py | 6 +++--- kiauh/core/menus/main_menu.py | 8 ++++---- kiauh/core/menus/remove_menu.py | 6 +++--- kiauh/core/menus/update_menu.py | 12 ++++++------ 39 files changed, 66 insertions(+), 67 deletions(-) rename kiauh/{modules => components}/__init__.py (100%) rename kiauh/{modules => components}/klipper/__init__.py (100%) rename kiauh/{modules => components}/klipper/klipper.py (98%) rename kiauh/{modules => components}/klipper/klipper_dialogs.py (100%) rename kiauh/{modules => components}/klipper/klipper_remove.py (95%) rename kiauh/{modules => components}/klipper/klipper_setup.py (95%) rename kiauh/{modules => components}/klipper/klipper_utils.py (96%) rename kiauh/{modules => components}/klipper/menus/__init__.py (100%) rename kiauh/{modules => components}/klipper/menus/klipper_remove_menu.py (97%) rename kiauh/{modules => components}/klipper/res/klipper.env (100%) rename kiauh/{modules => components}/klipper/res/klipper.service (100%) rename kiauh/{modules => components}/klipper/res/printer.cfg (100%) rename kiauh/{modules => components}/log_uploads/__init__.py (100%) rename kiauh/{modules => components}/log_uploads/log_upload_utils.py (95%) rename kiauh/{modules => components}/log_uploads/menus/log_upload_menu.py (93%) rename kiauh/{modules => components}/mainsail/__init__.py (100%) rename kiauh/{modules => components}/mainsail/mainsail_dialogs.py (100%) rename kiauh/{modules => components}/mainsail/mainsail_remove.py (95%) rename kiauh/{modules => components}/mainsail/mainsail_setup.py (97%) rename kiauh/{modules => components}/mainsail/mainsail_utils.py (96%) rename kiauh/{modules => components}/mainsail/menus/__init__.py (100%) rename kiauh/{modules => components}/mainsail/menus/mainsail_remove_menu.py (98%) rename kiauh/{modules => components}/mainsail/res/mainsail-config-updater.conf (100%) rename kiauh/{modules => components}/mainsail/res/mainsail-updater.conf (100%) rename kiauh/{modules => components}/moonraker/__init__.py (100%) rename kiauh/{modules => components}/moonraker/menus/__init__.py (100%) rename kiauh/{modules => components}/moonraker/menus/moonraker_remove_menu.py (98%) rename kiauh/{modules => components}/moonraker/moonraker.py (98%) rename kiauh/{modules => components}/moonraker/moonraker_dialogs.py (95%) rename kiauh/{modules => components}/moonraker/moonraker_remove.py (96%) rename kiauh/{modules => components}/moonraker/moonraker_setup.py (93%) rename kiauh/{modules => components}/moonraker/moonraker_utils.py (95%) rename kiauh/{modules => components}/moonraker/res/moonraker.conf (100%) rename kiauh/{modules => components}/moonraker/res/moonraker.env (100%) rename kiauh/{modules => components}/moonraker/res/moonraker.service (100%) diff --git a/kiauh/modules/__init__.py b/kiauh/components/__init__.py similarity index 100% rename from kiauh/modules/__init__.py rename to kiauh/components/__init__.py diff --git a/kiauh/modules/klipper/__init__.py b/kiauh/components/klipper/__init__.py similarity index 100% rename from kiauh/modules/klipper/__init__.py rename to kiauh/components/klipper/__init__.py diff --git a/kiauh/modules/klipper/klipper.py b/kiauh/components/klipper/klipper.py similarity index 98% rename from kiauh/modules/klipper/klipper.py rename to kiauh/components/klipper/klipper.py index a42eb53..78dc8f4 100644 --- a/kiauh/modules/klipper/klipper.py +++ b/kiauh/components/klipper/klipper.py @@ -14,7 +14,7 @@ from pathlib import Path from typing import List from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH +from kiauh.components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger diff --git a/kiauh/modules/klipper/klipper_dialogs.py b/kiauh/components/klipper/klipper_dialogs.py similarity index 100% rename from kiauh/modules/klipper/klipper_dialogs.py rename to kiauh/components/klipper/klipper_dialogs.py diff --git a/kiauh/modules/klipper/klipper_remove.py b/kiauh/components/klipper/klipper_remove.py similarity index 95% rename from kiauh/modules/klipper/klipper_remove.py rename to kiauh/components/klipper/klipper_remove.py index 4037b49..3534094 100644 --- a/kiauh/modules/klipper/klipper_remove.py +++ b/kiauh/components/klipper/klipper_remove.py @@ -13,9 +13,9 @@ import shutil from typing import List, Union from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_dialogs import print_instance_overview +from kiauh.components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.klipper.klipper_dialogs import print_instance_overview from kiauh.utils.filesystem_utils import remove_file from kiauh.utils.input_utils import get_selection_input from kiauh.utils.logger import Logger diff --git a/kiauh/modules/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py similarity index 95% rename from kiauh/modules/klipper/klipper_setup.py rename to kiauh/components/klipper/klipper_setup.py index b5b4def..bed720d 100644 --- a/kiauh/modules/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -15,16 +15,16 @@ from kiauh import KIAUH_CFG from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.modules.klipper import ( +from kiauh.components.klipper import ( EXIT_KLIPPER_SETUP, DEFAULT_KLIPPER_REPO_URL, KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT, ) -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_dialogs import print_update_warn_dialog -from kiauh.modules.klipper.klipper_utils import ( +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.klipper.klipper_dialogs import print_update_warn_dialog +from kiauh.components.klipper.klipper_utils import ( handle_disruptive_system_packages, check_user_groups, handle_to_multi_instance_conversion, @@ -37,7 +37,7 @@ from kiauh.modules.klipper.klipper_utils import ( handle_instance_naming, ) from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.components.moonraker.moonraker import Moonraker from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( diff --git a/kiauh/modules/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py similarity index 96% rename from kiauh/modules/klipper/klipper_utils.py rename to kiauh/components/klipper/klipper_utils.py index 3169fd6..c5de7bf 100644 --- a/kiauh/modules/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -24,16 +24,16 @@ from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.instance_manager.name_scheme import NameScheme from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.modules.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_dialogs import ( +from kiauh.components.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, print_instance_overview, print_select_instance_count_dialog, print_select_custom_name_dialog, ) -from kiauh.modules.moonraker.moonraker import Moonraker -from kiauh.modules.moonraker.moonraker_utils import moonraker_to_multi_conversion +from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.components.moonraker.moonraker_utils import moonraker_to_multi_conversion from kiauh.utils.common import get_install_status_common from kiauh.utils.constants import CURRENT_USER from kiauh.utils.input_utils import get_confirm, get_string_input, get_number_input diff --git a/kiauh/modules/klipper/menus/__init__.py b/kiauh/components/klipper/menus/__init__.py similarity index 100% rename from kiauh/modules/klipper/menus/__init__.py rename to kiauh/components/klipper/menus/__init__.py diff --git a/kiauh/modules/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py similarity index 97% rename from kiauh/modules/klipper/menus/klipper_remove_menu.py rename to kiauh/components/klipper/menus/klipper_remove_menu.py index 61fdc94..a75d58d 100644 --- a/kiauh/modules/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -13,8 +13,7 @@ import textwrap from kiauh.core.menus import BACK_HELP_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.modules.klipper import klipper_remove -from kiauh.modules.moonraker import moonraker_remove +from kiauh.components.klipper import klipper_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN diff --git a/kiauh/modules/klipper/res/klipper.env b/kiauh/components/klipper/res/klipper.env similarity index 100% rename from kiauh/modules/klipper/res/klipper.env rename to kiauh/components/klipper/res/klipper.env diff --git a/kiauh/modules/klipper/res/klipper.service b/kiauh/components/klipper/res/klipper.service similarity index 100% rename from kiauh/modules/klipper/res/klipper.service rename to kiauh/components/klipper/res/klipper.service diff --git a/kiauh/modules/klipper/res/printer.cfg b/kiauh/components/klipper/res/printer.cfg similarity index 100% rename from kiauh/modules/klipper/res/printer.cfg rename to kiauh/components/klipper/res/printer.cfg diff --git a/kiauh/modules/log_uploads/__init__.py b/kiauh/components/log_uploads/__init__.py similarity index 100% rename from kiauh/modules/log_uploads/__init__.py rename to kiauh/components/log_uploads/__init__.py diff --git a/kiauh/modules/log_uploads/log_upload_utils.py b/kiauh/components/log_uploads/log_upload_utils.py similarity index 95% rename from kiauh/modules/log_uploads/log_upload_utils.py rename to kiauh/components/log_uploads/log_upload_utils.py index c61dbc6..9ce95c8 100644 --- a/kiauh/modules/log_uploads/log_upload_utils.py +++ b/kiauh/components/log_uploads/log_upload_utils.py @@ -15,8 +15,8 @@ from pathlib import Path import urllib.request from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.log_uploads import LogFile +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.log_uploads import LogFile from kiauh.utils.logger import Logger diff --git a/kiauh/modules/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py similarity index 93% rename from kiauh/modules/log_uploads/menus/log_upload_menu.py rename to kiauh/components/log_uploads/menus/log_upload_menu.py index 9c96c5c..c140b2e 100644 --- a/kiauh/modules/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -13,8 +13,8 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.modules.log_uploads.log_upload_utils import upload_logfile -from kiauh.modules.log_uploads.log_upload_utils import get_logfile_list +from kiauh.components.log_uploads.log_upload_utils import upload_logfile +from kiauh.components.log_uploads.log_upload_utils import get_logfile_list from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW diff --git a/kiauh/modules/mainsail/__init__.py b/kiauh/components/mainsail/__init__.py similarity index 100% rename from kiauh/modules/mainsail/__init__.py rename to kiauh/components/mainsail/__init__.py diff --git a/kiauh/modules/mainsail/mainsail_dialogs.py b/kiauh/components/mainsail/mainsail_dialogs.py similarity index 100% rename from kiauh/modules/mainsail/mainsail_dialogs.py rename to kiauh/components/mainsail/mainsail_dialogs.py diff --git a/kiauh/modules/mainsail/mainsail_remove.py b/kiauh/components/mainsail/mainsail_remove.py similarity index 95% rename from kiauh/modules/mainsail/mainsail_remove.py rename to kiauh/components/mainsail/mainsail_remove.py index 7a10041..fc396ba 100644 --- a/kiauh/modules/mainsail/mainsail_remove.py +++ b/kiauh/components/mainsail/mainsail_remove.py @@ -17,10 +17,10 @@ from typing import List from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR -from kiauh.modules.mainsail.mainsail_utils import backup_config_json -from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR +from kiauh.components.mainsail.mainsail_utils import backup_config_json +from kiauh.components.moonraker.moonraker import Moonraker from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from kiauh.utils.filesystem_utils import remove_file from kiauh.utils.logger import Logger diff --git a/kiauh/modules/mainsail/mainsail_setup.py b/kiauh/components/mainsail/mainsail_setup.py similarity index 97% rename from kiauh/modules/mainsail/mainsail_setup.py rename to kiauh/components/mainsail/mainsail_setup.py index 294db45..0149794 100644 --- a/kiauh/modules/mainsail/mainsail_setup.py +++ b/kiauh/components/mainsail/mainsail_setup.py @@ -17,27 +17,27 @@ from kiauh import KIAUH_CFG from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.mainsail import ( +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.mainsail import ( MAINSAIL_URL, MAINSAIL_DIR, MAINSAIL_CONFIG_DIR, MAINSAIL_CONFIG_REPO_URL, MODULE_PATH, ) -from kiauh.modules.mainsail.mainsail_dialogs import ( +from kiauh.components.mainsail.mainsail_dialogs import ( print_moonraker_not_found_dialog, print_mainsail_already_installed_dialog, print_install_mainsail_config_dialog, print_mainsail_port_select_dialog, ) -from kiauh.modules.mainsail.mainsail_utils import ( +from kiauh.components.mainsail.mainsail_utils import ( restore_config_json, enable_mainsail_remotemode, backup_config_json, symlink_webui_nginx_log, ) -from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.components.moonraker.moonraker import Moonraker from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from kiauh.utils.common import check_install_dependencies from kiauh.utils.filesystem_utils import ( diff --git a/kiauh/modules/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py similarity index 96% rename from kiauh/modules/mainsail/mainsail_utils.py rename to kiauh/components/mainsail/mainsail_utils.py index 1fdfa5a..5f08765 100644 --- a/kiauh/modules/mainsail/mainsail_utils.py +++ b/kiauh/components/mainsail/mainsail_utils.py @@ -16,8 +16,8 @@ from pathlib import Path from typing import List from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.mainsail import MAINSAIL_CONFIG_JSON, MAINSAIL_DIR +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.mainsail import MAINSAIL_CONFIG_JSON, MAINSAIL_DIR from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_CONFD from kiauh.utils.common import get_install_status_webui from kiauh.utils.logger import Logger diff --git a/kiauh/modules/mainsail/menus/__init__.py b/kiauh/components/mainsail/menus/__init__.py similarity index 100% rename from kiauh/modules/mainsail/menus/__init__.py rename to kiauh/components/mainsail/menus/__init__.py diff --git a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py b/kiauh/components/mainsail/menus/mainsail_remove_menu.py similarity index 98% rename from kiauh/modules/mainsail/menus/mainsail_remove_menu.py rename to kiauh/components/mainsail/menus/mainsail_remove_menu.py index e2ec352..0beca2a 100644 --- a/kiauh/modules/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/components/mainsail/menus/mainsail_remove_menu.py @@ -13,7 +13,7 @@ import textwrap from kiauh.core.menus import BACK_HELP_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.modules.mainsail import mainsail_remove +from kiauh.components.mainsail import mainsail_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN diff --git a/kiauh/modules/mainsail/res/mainsail-config-updater.conf b/kiauh/components/mainsail/res/mainsail-config-updater.conf similarity index 100% rename from kiauh/modules/mainsail/res/mainsail-config-updater.conf rename to kiauh/components/mainsail/res/mainsail-config-updater.conf diff --git a/kiauh/modules/mainsail/res/mainsail-updater.conf b/kiauh/components/mainsail/res/mainsail-updater.conf similarity index 100% rename from kiauh/modules/mainsail/res/mainsail-updater.conf rename to kiauh/components/mainsail/res/mainsail-updater.conf diff --git a/kiauh/modules/moonraker/__init__.py b/kiauh/components/moonraker/__init__.py similarity index 100% rename from kiauh/modules/moonraker/__init__.py rename to kiauh/components/moonraker/__init__.py diff --git a/kiauh/modules/moonraker/menus/__init__.py b/kiauh/components/moonraker/menus/__init__.py similarity index 100% rename from kiauh/modules/moonraker/menus/__init__.py rename to kiauh/components/moonraker/menus/__init__.py diff --git a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py similarity index 98% rename from kiauh/modules/moonraker/menus/moonraker_remove_menu.py rename to kiauh/components/moonraker/menus/moonraker_remove_menu.py index c193f85..f8a9ae4 100644 --- a/kiauh/modules/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -13,7 +13,7 @@ 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.components.moonraker import moonraker_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/components/moonraker/moonraker.py similarity index 98% rename from kiauh/modules/moonraker/moonraker.py rename to kiauh/components/moonraker/moonraker.py index 89f47b3..688b825 100644 --- a/kiauh/modules/moonraker/moonraker.py +++ b/kiauh/components/moonraker/moonraker.py @@ -15,7 +15,7 @@ from typing import List, Union from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH +from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger diff --git a/kiauh/modules/moonraker/moonraker_dialogs.py b/kiauh/components/moonraker/moonraker_dialogs.py similarity index 95% rename from kiauh/modules/moonraker/moonraker_dialogs.py rename to kiauh/components/moonraker/moonraker_dialogs.py index f1576cd..62867bd 100644 --- a/kiauh/modules/moonraker/moonraker_dialogs.py +++ b/kiauh/components/moonraker/moonraker_dialogs.py @@ -13,8 +13,8 @@ import textwrap from typing import List from kiauh.core.menus.base_menu import print_back_footer -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.moonraker.moonraker import Moonraker from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN diff --git a/kiauh/modules/moonraker/moonraker_remove.py b/kiauh/components/moonraker/moonraker_remove.py similarity index 96% rename from kiauh/modules/moonraker/moonraker_remove.py rename to kiauh/components/moonraker/moonraker_remove.py index 4ddc0c9..cc76be8 100644 --- a/kiauh/modules/moonraker/moonraker_remove.py +++ b/kiauh/components/moonraker/moonraker_remove.py @@ -14,9 +14,9 @@ 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.components.klipper.klipper_dialogs import print_instance_overview +from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR +from kiauh.components.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 diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py similarity index 93% rename from kiauh/modules/moonraker/moonraker_setup.py rename to kiauh/components/moonraker/moonraker_setup.py index faf50ca..066d627 100644 --- a/kiauh/modules/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -18,12 +18,12 @@ from kiauh import KIAUH_CFG from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.modules.klipper.klipper import Klipper -from kiauh.modules.klipper.klipper_dialogs import print_instance_overview +from kiauh.components.klipper.klipper import Klipper +from kiauh.components.klipper.klipper_dialogs import print_instance_overview from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.modules.mainsail import MAINSAIL_DIR -from kiauh.modules.mainsail.mainsail_utils import enable_mainsail_remotemode -from kiauh.modules.moonraker import ( +from kiauh.components.mainsail import MAINSAIL_DIR +from kiauh.components.mainsail.mainsail_utils import enable_mainsail_remotemode +from kiauh.components.moonraker import ( EXIT_MOONRAKER_SETUP, DEFAULT_MOONRAKER_REPO_URL, MOONRAKER_DIR, @@ -34,9 +34,9 @@ from kiauh.modules.moonraker import ( POLKIT_USR_FILE, POLKIT_SCRIPT, ) -from kiauh.modules.moonraker.moonraker import Moonraker -from kiauh.modules.moonraker.moonraker_dialogs import print_moonraker_overview -from kiauh.modules.moonraker.moonraker_utils import create_example_moonraker_conf +from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.components.moonraker.moonraker_dialogs import print_moonraker_overview +from kiauh.components.moonraker.moonraker_utils import create_example_moonraker_conf from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import ( get_confirm, diff --git a/kiauh/modules/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py similarity index 95% rename from kiauh/modules/moonraker/moonraker_utils.py rename to kiauh/components/moonraker/moonraker_utils.py index 35fe973..2533b4d 100644 --- a/kiauh/modules/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -15,15 +15,15 @@ from typing import Dict, Literal, List, Union from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.modules.mainsail import MAINSAIL_DIR -from kiauh.modules.mainsail.mainsail_utils import enable_mainsail_remotemode -from kiauh.modules.moonraker import ( +from kiauh.components.mainsail import MAINSAIL_DIR +from kiauh.components.mainsail.mainsail_utils import enable_mainsail_remotemode +from kiauh.components.moonraker import ( DEFAULT_MOONRAKER_PORT, MODULE_PATH, MOONRAKER_DIR, MOONRAKER_ENV_DIR, ) -from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.components.moonraker.moonraker import Moonraker from kiauh.utils.common import get_install_status_common from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( diff --git a/kiauh/modules/moonraker/res/moonraker.conf b/kiauh/components/moonraker/res/moonraker.conf similarity index 100% rename from kiauh/modules/moonraker/res/moonraker.conf rename to kiauh/components/moonraker/res/moonraker.conf diff --git a/kiauh/modules/moonraker/res/moonraker.env b/kiauh/components/moonraker/res/moonraker.env similarity index 100% rename from kiauh/modules/moonraker/res/moonraker.env rename to kiauh/components/moonraker/res/moonraker.env diff --git a/kiauh/modules/moonraker/res/moonraker.service b/kiauh/components/moonraker/res/moonraker.service similarity index 100% rename from kiauh/modules/moonraker/res/moonraker.service rename to kiauh/components/moonraker/res/moonraker.service diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 83344ab..29be6d6 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -13,9 +13,9 @@ 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.moonraker import moonraker_setup +from kiauh.components.klipper import klipper_setup +from kiauh.components.mainsail import mainsail_setup +from kiauh.components.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 68e964e..0464412 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -18,10 +18,10 @@ from kiauh.core.menus.install_menu import InstallMenu from kiauh.core.menus.remove_menu import RemoveMenu from kiauh.core.menus.settings_menu import SettingsMenu from kiauh.core.menus.update_menu import UpdateMenu -from kiauh.modules.klipper.klipper_utils import get_klipper_status -from kiauh.modules.log_uploads.menus.log_upload_menu import LogUploadMenu -from kiauh.modules.mainsail.mainsail_utils import get_mainsail_status -from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status +from kiauh.components.klipper.klipper_utils import get_klipper_status +from kiauh.components.log_uploads.menus.log_upload_menu import LogUploadMenu +from kiauh.components.mainsail.mainsail_utils import get_mainsail_status +from kiauh.components.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import ( COLOR_MAGENTA, COLOR_CYAN, diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index e8ddd79..ce9c3a2 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -13,9 +13,9 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.modules.klipper.menus.klipper_remove_menu import KlipperRemoveMenu -from kiauh.modules.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu -from kiauh.modules.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu +from kiauh.components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu +from kiauh.components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu +from kiauh.components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu from kiauh.utils.constants import COLOR_RED, RESET_FORMAT diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 5b7c6ef..fef8983 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -13,17 +13,17 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.modules.klipper.klipper_setup import update_klipper -from kiauh.modules.klipper.klipper_utils import ( +from kiauh.components.klipper.klipper_setup import update_klipper +from kiauh.components.klipper.klipper_utils import ( get_klipper_status, ) -from kiauh.modules.mainsail.mainsail_setup import update_mainsail -from kiauh.modules.mainsail.mainsail_utils import ( +from kiauh.components.mainsail.mainsail_setup import update_mainsail +from kiauh.components.mainsail.mainsail_utils import ( get_mainsail_local_version, get_mainsail_remote_version, ) -from kiauh.modules.moonraker.moonraker_setup import update_moonraker -from kiauh.modules.moonraker.moonraker_utils import get_moonraker_status +from kiauh.components.moonraker.moonraker_setup import update_moonraker +from kiauh.components.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE From 7cb2231584ead920a4914b3177c79239217fec4b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 29 Jan 2024 21:28:01 +0100 Subject: [PATCH 098/137] chore(kiauh): rename "res" to "assets" Signed-off-by: Dominik Willner --- kiauh/components/klipper/{res => assets}/klipper.env | 0 kiauh/components/klipper/{res => assets}/klipper.service | 0 kiauh/components/klipper/{res => assets}/printer.cfg | 0 kiauh/components/klipper/klipper.py | 4 ++-- kiauh/components/klipper/klipper_utils.py | 2 +- .../mainsail/{res => assets}/mainsail-config-updater.conf | 0 .../mainsail/{res => assets}/mainsail-updater.conf | 0 kiauh/components/mainsail/mainsail_setup.py | 2 +- kiauh/components/moonraker/{res => assets}/moonraker.conf | 0 kiauh/components/moonraker/{res => assets}/moonraker.env | 0 .../components/moonraker/{res => assets}/moonraker.service | 0 kiauh/components/moonraker/moonraker.py | 4 ++-- kiauh/components/moonraker/moonraker_utils.py | 2 +- kiauh/utils/{res => assets}/common_vars.conf | 0 kiauh/utils/{res => assets}/nginx_cfg | 0 kiauh/utils/{res => assets}/upstreams.conf | 0 kiauh/utils/filesystem_utils.py | 6 +++--- 17 files changed, 10 insertions(+), 10 deletions(-) rename kiauh/components/klipper/{res => assets}/klipper.env (100%) rename kiauh/components/klipper/{res => assets}/klipper.service (100%) rename kiauh/components/klipper/{res => assets}/printer.cfg (100%) rename kiauh/components/mainsail/{res => assets}/mainsail-config-updater.conf (100%) rename kiauh/components/mainsail/{res => assets}/mainsail-updater.conf (100%) rename kiauh/components/moonraker/{res => assets}/moonraker.conf (100%) rename kiauh/components/moonraker/{res => assets}/moonraker.env (100%) rename kiauh/components/moonraker/{res => assets}/moonraker.service (100%) rename kiauh/utils/{res => assets}/common_vars.conf (100%) rename kiauh/utils/{res => assets}/nginx_cfg (100%) rename kiauh/utils/{res => assets}/upstreams.conf (100%) diff --git a/kiauh/components/klipper/res/klipper.env b/kiauh/components/klipper/assets/klipper.env similarity index 100% rename from kiauh/components/klipper/res/klipper.env rename to kiauh/components/klipper/assets/klipper.env diff --git a/kiauh/components/klipper/res/klipper.service b/kiauh/components/klipper/assets/klipper.service similarity index 100% rename from kiauh/components/klipper/res/klipper.service rename to kiauh/components/klipper/assets/klipper.service diff --git a/kiauh/components/klipper/res/printer.cfg b/kiauh/components/klipper/assets/printer.cfg similarity index 100% rename from kiauh/components/klipper/res/printer.cfg rename to kiauh/components/klipper/assets/printer.cfg diff --git a/kiauh/components/klipper/klipper.py b/kiauh/components/klipper/klipper.py index 78dc8f4..1f8e6e5 100644 --- a/kiauh/components/klipper/klipper.py +++ b/kiauh/components/klipper/klipper.py @@ -52,10 +52,10 @@ class Klipper(BaseInstance): def create(self) -> None: Logger.print_status("Creating new Klipper Instance ...") - service_template_path = MODULE_PATH.joinpath("res/klipper.service") + service_template_path = MODULE_PATH.joinpath("assets/klipper.service") service_file_name = self.get_service_file_name(extension=True) service_file_target = SYSTEMD.joinpath(service_file_name) - env_template_file_path = MODULE_PATH.joinpath("res/klipper.env") + env_template_file_path = MODULE_PATH.joinpath("assets/klipper.env") env_file_target = self.sysd_dir.joinpath("klipper.env") try: diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index c5de7bf..f49cdec 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -264,7 +264,7 @@ def create_example_printer_cfg(instance: Klipper) -> None: Logger.print_info(f"'{instance.cfg_file}' already exists.") return - source = MODULE_PATH.joinpath("res/printer.cfg") + source = MODULE_PATH.joinpath("assets/printer.cfg") target = instance.cfg_file try: shutil.copy(source, target) diff --git a/kiauh/components/mainsail/res/mainsail-config-updater.conf b/kiauh/components/mainsail/assets/mainsail-config-updater.conf similarity index 100% rename from kiauh/components/mainsail/res/mainsail-config-updater.conf rename to kiauh/components/mainsail/assets/mainsail-config-updater.conf diff --git a/kiauh/components/mainsail/res/mainsail-updater.conf b/kiauh/components/mainsail/assets/mainsail-updater.conf similarity index 100% rename from kiauh/components/mainsail/res/mainsail-updater.conf rename to kiauh/components/mainsail/assets/mainsail-updater.conf diff --git a/kiauh/components/mainsail/mainsail_setup.py b/kiauh/components/mainsail/mainsail_setup.py index 0149794..33b764a 100644 --- a/kiauh/components/mainsail/mainsail_setup.py +++ b/kiauh/components/mainsail/mainsail_setup.py @@ -229,7 +229,7 @@ def patch_moonraker_conf( Logger.print_info("Section already exist. Skipped ...") return - template = MODULE_PATH.joinpath("res", template_file) + template = MODULE_PATH.joinpath("assets", template_file) with open(template, "r") as t: template_content = "\n" template_content += t.read() diff --git a/kiauh/components/moonraker/res/moonraker.conf b/kiauh/components/moonraker/assets/moonraker.conf similarity index 100% rename from kiauh/components/moonraker/res/moonraker.conf rename to kiauh/components/moonraker/assets/moonraker.conf diff --git a/kiauh/components/moonraker/res/moonraker.env b/kiauh/components/moonraker/assets/moonraker.env similarity index 100% rename from kiauh/components/moonraker/res/moonraker.env rename to kiauh/components/moonraker/assets/moonraker.env diff --git a/kiauh/components/moonraker/res/moonraker.service b/kiauh/components/moonraker/assets/moonraker.service similarity index 100% rename from kiauh/components/moonraker/res/moonraker.service rename to kiauh/components/moonraker/assets/moonraker.service diff --git a/kiauh/components/moonraker/moonraker.py b/kiauh/components/moonraker/moonraker.py index 688b825..f33d039 100644 --- a/kiauh/components/moonraker/moonraker.py +++ b/kiauh/components/moonraker/moonraker.py @@ -39,8 +39,8 @@ class Moonraker(BaseInstance): def create(self, create_example_cfg: bool = False) -> None: Logger.print_status("Creating new Moonraker Instance ...") - service_template_path = MODULE_PATH.joinpath("res/moonraker.service") - env_template_file_path = MODULE_PATH.joinpath("res/moonraker.env") + service_template_path = MODULE_PATH.joinpath("assets/moonraker.service") + env_template_file_path = MODULE_PATH.joinpath("assets/moonraker.env") service_file_name = self.get_service_file_name(extension=True) service_file_target = SYSTEMD.joinpath(service_file_name) env_file_target = self.sysd_dir.joinpath("moonraker.env") diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index 2533b4d..017e00a 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -56,7 +56,7 @@ def create_example_moonraker_conf( Logger.print_info(f"'{instance.cfg_file}' already exists.") return - source = MODULE_PATH.joinpath("res/moonraker.conf") + source = MODULE_PATH.joinpath("assets/moonraker.conf") target = instance.cfg_file try: shutil.copy(source, target) diff --git a/kiauh/utils/res/common_vars.conf b/kiauh/utils/assets/common_vars.conf similarity index 100% rename from kiauh/utils/res/common_vars.conf rename to kiauh/utils/assets/common_vars.conf diff --git a/kiauh/utils/res/nginx_cfg b/kiauh/utils/assets/nginx_cfg similarity index 100% rename from kiauh/utils/res/nginx_cfg rename to kiauh/utils/assets/nginx_cfg diff --git a/kiauh/utils/res/upstreams.conf b/kiauh/utils/assets/upstreams.conf similarity index 100% rename from kiauh/utils/res/upstreams.conf rename to kiauh/utils/assets/upstreams.conf diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 360461e..68ce432 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -79,7 +79,7 @@ def copy_upstream_nginx_cfg() -> None: Creates an upstream.conf in /etc/nginx/conf.d :return: None """ - source = MODULE_PATH.joinpath("res/upstreams.conf") + source = MODULE_PATH.joinpath("assets/upstreams.conf") target = NGINX_CONFD.joinpath("upstreams.conf") try: command = ["sudo", "cp", source, target] @@ -95,7 +95,7 @@ def copy_common_vars_nginx_cfg() -> None: Creates a common_vars.conf in /etc/nginx/conf.d :return: None """ - source = MODULE_PATH.joinpath("res/common_vars.conf") + source = MODULE_PATH.joinpath("assets/common_vars.conf") target = NGINX_CONFD.joinpath("common_vars.conf") try: command = ["sudo", "cp", source, target] @@ -115,7 +115,7 @@ def create_nginx_cfg(name: str, port: int, root_dir: Path) -> None: :return: None """ tmp = Path.home().joinpath(f"{name}.tmp") - shutil.copy(MODULE_PATH.joinpath("res/nginx_cfg"), tmp) + shutil.copy(MODULE_PATH.joinpath("assets/nginx_cfg"), tmp) with open(tmp, "r+") as f: content = f.read() content = content.replace("%NAME%", name) From 0447bc4405aa8cb765634cf5c34c8b5a25ec0453 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 5 Feb 2024 21:57:19 +0100 Subject: [PATCH 099/137] refactor(kiauh): allow menus to link options to letters Signed-off-by: Dominik Willner --- .../klipper/menus/klipper_remove_menu.py | 12 +++--- .../mainsail/menus/mainsail_remove_menu.py | 14 +++---- .../moonraker/menus/moonraker_remove_menu.py | 14 +++---- kiauh/core/menus/base_menu.py | 40 +++++++++---------- kiauh/core/menus/install_menu.py | 22 +++++----- kiauh/core/menus/main_menu.py | 19 ++++----- kiauh/core/menus/remove_menu.py | 26 ++++++------ kiauh/core/menus/update_menu.py | 26 ++++++------ 8 files changed, 85 insertions(+), 88 deletions(-) diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index a75d58d..7ee5461 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -23,12 +23,12 @@ class KlipperRemoveMenu(BaseMenu): super().__init__( header=False, options={ - 0: self.toggle_all, - 1: self.toggle_remove_klipper_service, - 2: self.toggle_remove_klipper_dir, - 3: self.toggle_remove_klipper_env, - 4: self.toggle_delete_klipper_logs, - 5: self.run_removal_process, + "0": self.toggle_all, + "1": self.toggle_remove_klipper_service, + "2": self.toggle_remove_klipper_dir, + "3": self.toggle_remove_klipper_env, + "4": self.toggle_delete_klipper_logs, + "5": self.run_removal_process, }, footer_type=BACK_HELP_FOOTER, ) diff --git a/kiauh/components/mainsail/menus/mainsail_remove_menu.py b/kiauh/components/mainsail/menus/mainsail_remove_menu.py index 0beca2a..47488fc 100644 --- a/kiauh/components/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/components/mainsail/menus/mainsail_remove_menu.py @@ -23,13 +23,13 @@ class MainsailRemoveMenu(BaseMenu): super().__init__( header=False, options={ - 0: self.toggle_all, - 1: self.toggle_remove_mainsail, - 2: self.toggle_remove_ms_config, - 3: self.toggle_backup_config_json, - 4: self.toggle_remove_updater_section, - 5: self.toggle_remove_printer_cfg_include, - 6: self.run_removal_process, + "0": self.toggle_all, + "1": self.toggle_remove_mainsail, + "2": self.toggle_remove_ms_config, + "3": self.toggle_backup_config_json, + "4": self.toggle_remove_updater_section, + "5": self.toggle_remove_printer_cfg_include, + "6": self.run_removal_process, }, footer_type=BACK_HELP_FOOTER, ) diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index f8a9ae4..f8bc307 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -23,13 +23,13 @@ class MoonrakerRemoveMenu(BaseMenu): 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, + "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, ) diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 35d87fb..38a9873 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -13,7 +13,7 @@ import subprocess import sys import textwrap from abc import abstractmethod, ABC -from typing import Dict, Any, Literal +from typing import Dict, Any, Literal, Union, Callable, Type from kiauh.core.menus import QUIT_FOOTER, BACK_FOOTER, BACK_HELP_FOOTER from kiauh.utils.constants import ( @@ -97,7 +97,7 @@ class BaseMenu(ABC): def __init__( self, - options: Dict[int, Any], + options: Dict[str, Union[Callable, Type["BaseMenu"]]], options_offset: int = 0, header: bool = True, footer_type: Literal[ @@ -110,10 +110,10 @@ class BaseMenu(ABC): self.footer_type = footer_type @abstractmethod - def print_menu(self): + def print_menu(self) -> None: raise NotImplementedError("Subclasses must implement the print_menu method") - def print_footer(self): + def print_footer(self) -> None: footer_type_map = { QUIT_FOOTER: print_quit_footer, BACK_FOOTER: print_back_footer, @@ -122,33 +122,29 @@ class BaseMenu(ABC): footer_function = footer_type_map.get(self.footer_type, print_quit_footer) footer_function() - def display(self): + def display(self) -> None: # clear() if self.header: print_header() self.print_menu() self.print_footer() - def handle_user_input(self): + def handle_user_input(self) -> str: while True: choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}") + option = self.options.get(choice, None) - if choice.isdigit() and 0 <= int(choice) < len(self.options): + has_navi_option = self.footer_type in self.NAVI_OPTIONS + user_navigated = choice.lower() in self.NAVI_OPTIONS[self.footer_type] + if has_navi_option and user_navigated: return choice - elif choice.isalpha() and ( - self.footer_type in self.NAVI_OPTIONS - and choice.lower() in self.NAVI_OPTIONS[self.footer_type] - ): + + if option is not None: return choice else: - error_msg = ( - "Invalid input!" - if choice.isalpha() or (not self.options and len(self.options) < 1) - else f"Invalid input! Select a number between {min(self.options)} and {max(self.options)}." - ) - Logger.print_error(error_msg, False) + Logger.print_error("Invalid input!", False) - def start(self): + def start(self) -> None: while True: self.display() choice = self.handle_user_input() @@ -158,12 +154,12 @@ class BaseMenu(ABC): sys.exit(0) elif choice == "b": return - elif choice == "p": + elif choice == "h": print("help!") else: - self.execute_option(int(choice)) + self.execute_option(choice) - def execute_option(self, choice): + def execute_option(self, choice: str) -> None: option = self.options.get(choice, None) if isinstance(option, type) and issubclass(option, BaseMenu): @@ -177,7 +173,7 @@ class BaseMenu(ABC): f"Type {type(option)} of option {choice} not of type BaseMenu or Method" ) - def navigate_to_submenu(self, submenu_class): + def navigate_to_submenu(self, submenu_class) -> None: submenu = submenu_class() submenu.previous_menu = self submenu.start() diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 29be6d6..239dfaf 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -26,17 +26,17 @@ class InstallMenu(BaseMenu): super().__init__( header=True, options={ - 1: self.install_klipper, - 2: self.install_moonraker, - 3: self.install_mainsail, - 4: self.install_fluidd, - 5: self.install_klipperscreen, - 6: self.install_pretty_gcode, - 7: self.install_telegram_bot, - 8: self.install_obico, - 9: self.install_octoeverywhere, - 10: self.install_mobileraker, - 11: self.install_crowsnest, + "1": self.install_klipper, + "2": self.install_moonraker, + "3": self.install_mainsail, + "4": self.install_fluidd, + "5": self.install_klipperscreen, + "6": self.install_pretty_gcode, + "7": self.install_telegram_bot, + "8": self.install_obico, + "9": self.install_octoeverywhere, + "10": self.install_mobileraker, + "11": self.install_crowsnest, }, footer_type=BACK_FOOTER, ) diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 0464412..aa59d79 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -37,13 +37,14 @@ class MainMenu(BaseMenu): super().__init__( header=True, options={ - 0: LogUploadMenu, - 1: InstallMenu, - 2: UpdateMenu, - 3: RemoveMenu, - 4: AdvancedMenu, - 5: None, - 6: SettingsMenu, + "0": LogUploadMenu, + "1": InstallMenu, + "2": UpdateMenu, + "3": RemoveMenu, + "4": AdvancedMenu, + "5": None, + "e": None, + "s": SettingsMenu, }, footer_type=QUIT_FOOTER, ) @@ -113,13 +114,13 @@ class MainMenu(BaseMenu): | 4) [Advanced] |------------------------------------| | 5) [Backup] | Mainsail: {self.ms_status:<26} | | | Fluidd: {self.fl_status:<26} | - | 6) [Settings] | KlipperScreen: {self.ks_status:<26} | + | E) [Extensions] | KlipperScreen: {self.ks_status:<26} | | | Mobileraker: {self.mb_status:<26} | | | | | | Crowsnest: {self.cn_status:<26} | | | Telegram Bot: {self.tg_status:<26} | | | Obico: {self.ob_status:<26} | - | | OctoEverywhere: {self.oe_status:<26} | + | S) [Settings] | OctoEverywhere: {self.oe_status:<26} | |-------------------------------------------------------| | {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} | """ diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index ce9c3a2..060927e 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -26,19 +26,19 @@ class RemoveMenu(BaseMenu): super().__init__( header=True, options={ - 1: KlipperRemoveMenu, - 2: MoonrakerRemoveMenu, - 3: MainsailRemoveMenu, - 5: self.remove_fluidd, - 6: self.remove_klipperscreen, - 7: self.remove_crowsnest, - 8: self.remove_mjpgstreamer, - 9: self.remove_pretty_gcode, - 10: self.remove_telegram_bot, - 11: self.remove_obico, - 12: self.remove_octoeverywhere, - 13: self.remove_mobileraker, - 14: self.remove_nginx, + "1": KlipperRemoveMenu, + "2": MoonrakerRemoveMenu, + "3": MainsailRemoveMenu, + "5": self.remove_fluidd, + "6": self.remove_klipperscreen, + "7": self.remove_crowsnest, + "8": self.remove_mjpgstreamer, + "9": self.remove_pretty_gcode, + "10": self.remove_telegram_bot, + "11": self.remove_obico, + "12": self.remove_octoeverywhere, + "13": self.remove_mobileraker, + "14": self.remove_nginx, }, footer_type=BACK_FOOTER, ) diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index fef8983..c90de1c 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -34,19 +34,19 @@ class UpdateMenu(BaseMenu): super().__init__( header=True, options={ - 0: self.update_all, - 1: self.update_klipper, - 2: self.update_moonraker, - 3: self.update_mainsail, - 4: self.update_fluidd, - 5: self.update_klipperscreen, - 6: self.update_pgc_for_klipper, - 7: self.update_telegram_bot, - 8: self.update_moonraker_obico, - 9: self.update_octoeverywhere, - 10: self.update_mobileraker, - 11: self.update_crowsnest, - 12: self.upgrade_system_packages, + "0": self.update_all, + "1": self.update_klipper, + "2": self.update_moonraker, + "3": self.update_mainsail, + "4": self.update_fluidd, + "5": self.update_klipperscreen, + "6": self.update_pgc_for_klipper, + "7": self.update_telegram_bot, + "8": self.update_moonraker_obico, + "9": self.update_octoeverywhere, + "10": self.update_mobileraker, + "11": self.update_crowsnest, + "12": self.upgrade_system_packages, }, footer_type=BACK_FOOTER, ) From 2f34253badcfde34330d9e441fcc58975fb92bee Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 8 Feb 2024 22:41:52 +0100 Subject: [PATCH 100/137] refactor(kiauh): handle menus based on if they need instantiation or not Signed-off-by: Dominik Willner --- kiauh/core/menus/base_menu.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 38a9873..2cdf379 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -97,13 +97,14 @@ class BaseMenu(ABC): def __init__( self, - options: Dict[str, Union[Callable, Type["BaseMenu"]]], + options: Dict[str, Union[Callable, Any]], options_offset: int = 0, header: bool = True, footer_type: Literal[ "QUIT_FOOTER", "BACK_FOOTER", "BACK_HELP_FOOTER" ] = QUIT_FOOTER, ): + self.previous_menu = None self.options = options self.options_offset = options_offset self.header = header @@ -131,11 +132,11 @@ class BaseMenu(ABC): def handle_user_input(self) -> str: while True: - choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}") + choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}").lower() option = self.options.get(choice, None) has_navi_option = self.footer_type in self.NAVI_OPTIONS - user_navigated = choice.lower() in self.NAVI_OPTIONS[self.footer_type] + user_navigated = choice in self.NAVI_OPTIONS[self.footer_type] if has_navi_option and user_navigated: return choice @@ -163,7 +164,9 @@ class BaseMenu(ABC): option = self.options.get(choice, None) if isinstance(option, type) and issubclass(option, BaseMenu): - self.navigate_to_submenu(option) + self.navigate_to_menu(option, True) + elif isinstance(option, BaseMenu): + self.navigate_to_menu(option, False) elif callable(option): option(opt_index=choice) elif option is None: @@ -173,7 +176,14 @@ class BaseMenu(ABC): f"Type {type(option)} of option {choice} not of type BaseMenu or Method" ) - def navigate_to_submenu(self, submenu_class) -> None: - submenu = submenu_class() - submenu.previous_menu = self - submenu.start() + def navigate_to_menu(self, menu, instantiate: bool) -> None: + """ + Method for handling the actual menu switch. Can either take in a menu type or an already + instantiated menu class. Use instantiated menu classes only if the menu requires specific input parameters + :param menu: A menu type or menu instance + :param instantiate: Specify if the menu requires instantiation + :return: None + """ + menu = menu() if instantiate else menu + menu.previous_menu = self + menu.start() From 5ace920d3edc635de5e1982c9e4738df9155a43c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 9 Feb 2024 15:47:13 +0100 Subject: [PATCH 101/137] feat(extensions): implement initial extension feature and first extension Signed-off-by: Dominik Willner --- kiauh/core/base_extension.py | 32 +++++ kiauh/core/menus/extensions_menu.py | 135 ++++++++++++++++++ kiauh/core/menus/main_menu.py | 3 +- kiauh/extensions/__init__.py | 0 kiauh/extensions/gcode_shell_cmd/__init__.py | 22 +++ .../assets/gcode_shell_command.py | 87 +++++++++++ .../gcode_shell_cmd/assets/shell_command.cfg | 7 + .../gcode_shell_cmd_extension.py | 127 ++++++++++++++++ .../extensions/gcode_shell_cmd/metadata.json | 9 ++ 9 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 kiauh/core/base_extension.py create mode 100644 kiauh/core/menus/extensions_menu.py create mode 100644 kiauh/extensions/__init__.py create mode 100644 kiauh/extensions/gcode_shell_cmd/__init__.py create mode 100644 kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py create mode 100644 kiauh/extensions/gcode_shell_cmd/assets/shell_command.cfg create mode 100644 kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py create mode 100644 kiauh/extensions/gcode_shell_cmd/metadata.json diff --git a/kiauh/core/base_extension.py b/kiauh/core/base_extension.py new file mode 100644 index 0000000..09245ee --- /dev/null +++ b/kiauh/core/base_extension.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 abc import abstractmethod, ABC +from typing import Dict + + +# noinspection PyUnusedLocal +# noinspection PyMethodMayBeStatic +class BaseExtension(ABC): + def __init__(self, metadata: Dict[str, str]): + self.metadata = metadata + + @abstractmethod + def install_extension(self, **kwargs) -> None: + raise NotImplementedError( + "Subclasses must implement the install_extension method" + ) + + @abstractmethod + def remove_extension(self, **kwargs) -> None: + raise NotImplementedError( + "Subclasses must implement the remove_extension method" + ) diff --git a/kiauh/core/menus/extensions_menu.py b/kiauh/core/menus/extensions_menu.py new file mode 100644 index 0000000..ebbc4c5 --- /dev/null +++ b/kiauh/core/menus/extensions_menu.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 json +import textwrap +import importlib +import inspect +from pathlib import Path +from typing import List, Dict + +from kiauh.core.base_extension import BaseExtension +from kiauh.core.menus import BACK_FOOTER +from kiauh.core.menus.base_menu import BaseMenu +from kiauh.utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW + + +# noinspection PyUnusedLocal +# noinspection PyMethodMayBeStatic +class ExtensionsMenu(BaseMenu): + def __init__(self): + self.extensions = self.discover_extensions() + super().__init__( + header=True, + options=self.get_options(), + footer_type=BACK_FOOTER, + ) + + def discover_extensions(self) -> List[BaseExtension]: + extensions = [] + extensions_dir = Path(__file__).resolve().parents[2].joinpath("extensions") + + for extension in extensions_dir.iterdir(): + metadata_json = Path(extension).joinpath("metadata.json") + if not metadata_json.exists(): + continue + + try: + with open(metadata_json, "r") as m: + metadata = json.load(m).get("metadata") + module_name = ( + f"kiauh.extensions.{extension.name}.{metadata.get('module')}" + ) + name, extension = inspect.getmembers( + importlib.import_module(module_name), + predicate=lambda o: inspect.isclass(o) + and issubclass(o, BaseExtension) + and o != BaseExtension, + )[0] + extensions.append(extension(metadata)) + except (IOError, json.JSONDecodeError, ImportError) as e: + print(f"Failed loading extension {extension}: {e}") + + return sorted(extensions, key=lambda ex: ex.metadata.get("index")) + + def get_options(self) -> Dict[str, BaseMenu]: + options = {} + for extension in self.extensions: + index = extension.metadata.get("index") + options[f"{index}"] = ExtensionSubmenu(extension) + + return options + + def print_menu(self): + header = " [ Extensions Menu ] " + color = COLOR_CYAN + line1 = f"{COLOR_YELLOW}Available Extensions:{RESET_FORMAT}" + count = 62 - len(color) - len(RESET_FORMAT) + menu = textwrap.dedent( + f""" + /=======================================================\\ + | {color}{header:~^{count}}{RESET_FORMAT} | + |-------------------------------------------------------| + | {line1:<62} | + | | + """ + )[1:] + print(menu, end="") + + for extension in self.extensions: + index = extension.metadata.get("index") + name = extension.metadata.get("display_name") + row = f"{index}) {name}" + print(f"| {row:<53} |") + + +# noinspection PyUnusedLocal +# noinspection PyMethodMayBeStatic +class ExtensionSubmenu(BaseMenu): + def __init__(self, extension: BaseExtension): + self.extension = extension + self.extension_name = extension.metadata.get("display_name") + self.extension_desc = extension.metadata.get("description") + super().__init__( + header=False, + options={ + "1": extension.install_extension, + "2": extension.remove_extension, + }, + footer_type=BACK_FOOTER, + ) + + def print_menu(self) -> None: + header = f" [ {self.extension_name} ] " + color = COLOR_YELLOW + count = 62 - len(color) - len(RESET_FORMAT) + + wrapper = textwrap.TextWrapper(55, initial_indent="| ", subsequent_indent="| ") + lines = wrapper.wrap(self.extension_desc) + formatted_lines = [f"{line:<55} |" for line in lines] + description_text = "\n".join(formatted_lines) + + menu = textwrap.dedent( + f""" + /=======================================================\\ + | {color}{header:~^{count}}{RESET_FORMAT} | + |-------------------------------------------------------| + """ + )[1:] + menu += f"{description_text}\n" + menu += textwrap.dedent( + """ + |-------------------------------------------------------| + | 1) Install | + | 2) Remove | + """ + )[1:] + print(menu, end="") diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index aa59d79..366b99a 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -14,6 +14,7 @@ import textwrap from kiauh.core.menus import QUIT_FOOTER from kiauh.core.menus.advanced_menu import AdvancedMenu from kiauh.core.menus.base_menu import BaseMenu +from kiauh.core.menus.extensions_menu import ExtensionsMenu from kiauh.core.menus.install_menu import InstallMenu from kiauh.core.menus.remove_menu import RemoveMenu from kiauh.core.menus.settings_menu import SettingsMenu @@ -43,7 +44,7 @@ class MainMenu(BaseMenu): "3": RemoveMenu, "4": AdvancedMenu, "5": None, - "e": None, + "e": ExtensionsMenu, "s": SettingsMenu, }, footer_type=QUIT_FOOTER, diff --git a/kiauh/extensions/__init__.py b/kiauh/extensions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/extensions/gcode_shell_cmd/__init__.py b/kiauh/extensions/gcode_shell_cmd/__init__.py new file mode 100644 index 0000000..c0d921b --- /dev/null +++ b/kiauh/extensions/gcode_shell_cmd/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 + + +EXT_MODULE_NAME = "gcode_shell_command.py" +MODULE_PATH = Path(__file__).resolve().parent +MODULE_ASSETS = MODULE_PATH.joinpath("assets") +KLIPPER_DIR = Path.home().joinpath("klipper") +KLIPPER_EXTRAS = KLIPPER_DIR.joinpath("klippy/extras") +EXTENSION_SRC = MODULE_ASSETS.joinpath(EXT_MODULE_NAME) +EXTENSION_TARGET_PATH = KLIPPER_EXTRAS.joinpath(EXT_MODULE_NAME) +EXAMPLE_CFG_SRC = MODULE_ASSETS.joinpath("shell_command.cfg") diff --git a/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py b/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py new file mode 100644 index 0000000..bb38ae5 --- /dev/null +++ b/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py @@ -0,0 +1,87 @@ +# Run a shell command via gcode +# +# Copyright (C) 2019 Eric Callahan +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import os +import shlex +import subprocess +import logging + +class ShellCommand: + def __init__(self, config): + self.name = config.get_name().split()[-1] + self.printer = config.get_printer() + self.gcode = self.printer.lookup_object('gcode') + cmd = config.get('command') + cmd = os.path.expanduser(cmd) + self.command = shlex.split(cmd) + self.timeout = config.getfloat('timeout', 2., above=0.) + self.verbose = config.getboolean('verbose', True) + self.proc_fd = None + self.partial_output = "" + self.gcode.register_mux_command( + "RUN_SHELL_COMMAND", "CMD", self.name, + self.cmd_RUN_SHELL_COMMAND, + desc=self.cmd_RUN_SHELL_COMMAND_help) + + def _process_output(self, eventime): + if self.proc_fd is None: + return + try: + data = os.read(self.proc_fd, 4096) + except Exception: + pass + data = self.partial_output + data.decode() + if '\n' not in data: + self.partial_output = data + return + elif data[-1] != '\n': + split = data.rfind('\n') + 1 + self.partial_output = data[split:] + data = data[:split] + else: + self.partial_output = "" + self.gcode.respond_info(data) + + cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command" + def cmd_RUN_SHELL_COMMAND(self, params): + gcode_params = params.get('PARAMS','') + gcode_params = shlex.split(gcode_params) + reactor = self.printer.get_reactor() + try: + proc = subprocess.Popen( + self.command + gcode_params, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except Exception: + logging.exception( + "shell_command: Command {%s} failed" % (self.name)) + raise self.gcode.error("Error running command {%s}" % (self.name)) + if self.verbose: + self.proc_fd = proc.stdout.fileno() + self.gcode.respond_info("Running Command {%s}...:" % (self.name)) + hdl = reactor.register_fd(self.proc_fd, self._process_output) + eventtime = reactor.monotonic() + endtime = eventtime + self.timeout + complete = False + while eventtime < endtime: + eventtime = reactor.pause(eventtime + .05) + if proc.poll() is not None: + complete = True + break + if not complete: + proc.terminate() + if self.verbose: + if self.partial_output: + self.gcode.respond_info(self.partial_output) + self.partial_output = "" + if complete: + msg = "Command {%s} finished\n" % (self.name) + else: + msg = "Command {%s} timed out" % (self.name) + self.gcode.respond_info(msg) + reactor.unregister_fd(hdl) + self.proc_fd = None + + +def load_config_prefix(config): + return ShellCommand(config) diff --git a/kiauh/extensions/gcode_shell_cmd/assets/shell_command.cfg b/kiauh/extensions/gcode_shell_cmd/assets/shell_command.cfg new file mode 100644 index 0000000..34e7581 --- /dev/null +++ b/kiauh/extensions/gcode_shell_cmd/assets/shell_command.cfg @@ -0,0 +1,7 @@ +[gcode_shell_command hello_world] +command: echo hello world +timeout: 2. +verbose: True +[gcode_macro HELLO_WORLD] +gcode: + RUN_SHELL_COMMAND CMD=hello_world \ No newline at end of file diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py new file mode 100644 index 0000000..d8ed49b --- /dev/null +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 os +import shutil +from typing import List + +from kiauh.components.klipper.klipper import Klipper +from kiauh.core.backup_manager.backup_manager import BackupManager +from kiauh.core.base_extension import BaseExtension +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.extensions.gcode_shell_cmd import ( + EXTENSION_TARGET_PATH, + EXTENSION_SRC, + KLIPPER_DIR, + EXAMPLE_CFG_SRC, + KLIPPER_EXTRAS, +) +from kiauh.utils.filesystem_utils import check_file_exist +from kiauh.utils.input_utils import get_confirm +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class GcodeShellCmdExtension(BaseExtension): + def install_extension(self, **kwargs) -> None: + install_example = get_confirm("Create an example shell command?", False, False) + + klipper_dir_exists = check_file_exist(KLIPPER_DIR) + if not klipper_dir_exists: + Logger.print_warn( + "No Klipper directory found! Unable to install extension." + ) + return + + extension_installed = check_file_exist(EXTENSION_TARGET_PATH) + overwrite = True + if extension_installed: + overwrite = get_confirm( + "Extension seems to be installed already. Overwrite?", True, False + ) + + if not overwrite: + Logger.print_warn("Installation aborted due to user request.") + return + + im = InstanceManager(Klipper) + im.stop_all_instance() + + try: + Logger.print_status(f"Copy extension to '{KLIPPER_EXTRAS}' ...") + shutil.copy(EXTENSION_SRC, EXTENSION_TARGET_PATH) + except OSError as e: + Logger.print_error(f"Unable to install extension: {e}") + return + + if install_example: + self.install_example_cfg(im.instances) + + im.start_all_instance() + + Logger.print_ok("Installing G-Code Shell Command extension successfull!") + + def remove_extension(self, **kwargs) -> None: + extension_installed = check_file_exist(EXTENSION_TARGET_PATH) + if not extension_installed: + Logger.print_info("Extension does not seem to be installed! Skipping ...") + return + + question = "Do you really want to remove the extension?" + if get_confirm(question, True, False): + try: + Logger.print_status(f"Removing '{EXTENSION_TARGET_PATH}' ...") + os.remove(EXTENSION_TARGET_PATH) + Logger.print_ok("Extension successfully removed!") + except OSError as e: + Logger.print_error(f"Unable to remove extension: {e}") + + Logger.print_warn("PLEASE NOTE:") + Logger.print_warn( + "Remaining gcode shell command will cause Klipper to throw an error." + ) + Logger.print_warn("Make sure to remove them from the printer.cfg!") + + def install_example_cfg(self, instances: List[Klipper]): + cfg_dirs = [instance.cfg_dir for instance in instances] + # copy extension to klippy/extras + for cfg_dir in cfg_dirs: + Logger.print_status(f"Create shell_command.cfg in '{cfg_dir}' ...") + if check_file_exist(cfg_dir.joinpath("shell_command.cfg")): + Logger.print_info("File already exists! Skipping ...") + continue + try: + shutil.copy(EXAMPLE_CFG_SRC, cfg_dir) + Logger.print_ok("Done!") + except OSError as e: + Logger.warn(f"Unable to create example config: {e}") + + # backup each printer.cfg before modification + bm = BackupManager() + for instance in instances: + bm.backup_file( + [instance.cfg_file], + custom_filename=f"{instance.suffix}.printer.cfg", + ) + + # add section to printer.cfg if not already defined + section = "include shell_command.cfg" + cfg_files = [instance.cfg_file for instance in instances] + for cfg_file in cfg_files: + Logger.print_status(f"Include shell_command.cfg in '{cfg_file}' ...") + cm = ConfigManager(cfg_file) + if cm.config.has_section(section): + Logger.print_info("Section already defined! Skipping ...") + continue + cm.config.add_section(section) + cm.write_config() + Logger.print_ok("Done!") diff --git a/kiauh/extensions/gcode_shell_cmd/metadata.json b/kiauh/extensions/gcode_shell_cmd/metadata.json new file mode 100644 index 0000000..cfb38b4 --- /dev/null +++ b/kiauh/extensions/gcode_shell_cmd/metadata.json @@ -0,0 +1,9 @@ +{ + "metadata": { + "index": 1, + "module": "gcode_shell_cmd_extension", + "maintained_by": "dw-0", + "display_name": "G-Code Shell Command", + "description": "Allows to run a shell command from gcode." + } +} From 3bef6ecb85cd8f706402a4ce0c942069ddeecf53 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 10 Feb 2024 00:50:45 +0100 Subject: [PATCH 102/137] feat(BackupManager): allow to ignore folders Signed-off-by: Dominik Willner --- kiauh/core/backup_manager/backup_manager.py | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 556346d..d5cfca5 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -13,15 +13,17 @@ import shutil from pathlib import Path from typing import List -from kiauh import KIAUH_BACKUP_DIR +from kiauh.core.backup_manager import BACKUP_ROOT_DIR from kiauh.utils.common import get_current_date from kiauh.utils.logger import Logger +# noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class BackupManager: - def __init__(self, backup_root_dir: Path = KIAUH_BACKUP_DIR): + def __init__(self, backup_root_dir: Path = BACKUP_ROOT_DIR): self._backup_root_dir = backup_root_dir + self._ignore_folders = None @property def backup_root_dir(self) -> Path: @@ -31,6 +33,14 @@ class BackupManager: def backup_root_dir(self, value: Path): self._backup_root_dir = value + @property + def ignore_folders(self) -> List[str]: + return self._ignore_folders + + @ignore_folders.setter + def ignore_folders(self, value: List[str]): + self._ignore_folders = value + def backup_file( self, files: List[Path] = None, target: Path = None, custom_filename=None ): @@ -56,7 +66,7 @@ class BackupManager: def backup_directory(self, name: str, source: Path, target: Path = None) -> None: if source is None or not Path(source).exists(): - raise OSError + raise OSError("Parameter 'source' is None or Path does not exist!") target = self.backup_root_dir if target is None else target try: @@ -64,9 +74,20 @@ class BackupManager: Logger.print_status(log) date = get_current_date().get("date") time = get_current_date().get("time") - shutil.copytree(source, target.joinpath(f"{name}-{date}-{time}")) + shutil.copytree( + source, + target.joinpath(f"{name.lower()}-{date}-{time}"), + ignore=self.ignore_folders_func, + ) except OSError as e: Logger.print_error(f"Unable to backup directory '{source}':\n{e}") return Logger.print_ok("Backup successfull!") + + def ignore_folders_func(self, dirpath, filenames): + return ( + [f for f in filenames if f in self._ignore_folders] + if self._ignore_folders is not None + else [] + ) From 34ebe5d15e6dbac169f8e3f53a2ef518be3d4ff9 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 10 Feb 2024 11:38:23 +0100 Subject: [PATCH 103/137] refactor(BackupManager): backup_file method only takes in single files now Signed-off-by: Dominik Willner --- kiauh/components/mainsail/mainsail_utils.py | 4 +- kiauh/core/backup_manager/backup_manager.py | 38 +++++++++---------- .../gcode_shell_cmd_extension.py | 4 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/kiauh/components/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py index 5f08765..de54941 100644 --- a/kiauh/components/mainsail/mainsail_utils.py +++ b/kiauh/components/mainsail/mainsail_utils.py @@ -37,9 +37,9 @@ def backup_config_json(is_temp=False) -> None: bm = BackupManager() if is_temp: fn = Path.home().joinpath("config.json.kiauh.bak") - bm.backup_file([MAINSAIL_CONFIG_JSON], custom_filename=fn) + bm.backup_file(MAINSAIL_CONFIG_JSON, custom_filename=fn) else: - bm.backup_file([MAINSAIL_CONFIG_JSON]) + bm.backup_file(MAINSAIL_CONFIG_JSON) def restore_config_json() -> None: diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index d5cfca5..7f5a887 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -42,27 +42,27 @@ class BackupManager: self._ignore_folders = value def backup_file( - self, files: List[Path] = None, target: Path = None, custom_filename=None + self, file: Path = None, target: Path = None, custom_filename=None ): - if not files: - raise ValueError("Parameter 'files' cannot be None or an empty List!") + if not file: + raise ValueError("Parameter 'file' cannot be None!") target = self.backup_root_dir if target is None else target - for file in files: - Logger.print_status(f"Creating backup of {file} ...") - if Path(file).is_file(): - date = get_current_date().get("date") - time = get_current_date().get("time") - filename = f"{file.stem}-{date}-{time}{file.suffix}" - filename = custom_filename if custom_filename is not None else filename - try: - Path(target).mkdir(exist_ok=True) - shutil.copyfile(file, target.joinpath(filename)) - except OSError as e: - Logger.print_error(f"Unable to backup '{file}':\n{e}") - continue - else: - Logger.print_info(f"File '{file}' not found ...") + + Logger.print_status(f"Creating backup of {file} ...") + if Path(file).is_file(): + date = get_current_date().get("date") + time = get_current_date().get("time") + filename = f"{file.stem}-{date}-{time}{file.suffix}" + filename = custom_filename if custom_filename is not None else filename + try: + Path(target).mkdir(exist_ok=True) + shutil.copyfile(file, target.joinpath(filename)) + Logger.print_ok("Backup successfull!") + except OSError as e: + Logger.print_error(f"Unable to backup '{file}':\n{e}") + else: + Logger.print_info(f"File '{file}' not found ...") def backup_directory(self, name: str, source: Path, target: Path = None) -> None: if source is None or not Path(source).exists(): @@ -79,11 +79,11 @@ class BackupManager: target.joinpath(f"{name.lower()}-{date}-{time}"), ignore=self.ignore_folders_func, ) + Logger.print_ok("Backup successfull!") except OSError as e: Logger.print_error(f"Unable to backup directory '{source}':\n{e}") return - Logger.print_ok("Backup successfull!") def ignore_folders_func(self, dirpath, filenames): return ( diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py index d8ed49b..c0c28c1 100644 --- a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -24,7 +24,7 @@ from kiauh.extensions.gcode_shell_cmd import ( KLIPPER_DIR, EXAMPLE_CFG_SRC, KLIPPER_EXTRAS, -) + ) from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger @@ -109,7 +109,7 @@ class GcodeShellCmdExtension(BaseExtension): bm = BackupManager() for instance in instances: bm.backup_file( - [instance.cfg_file], + instance.cfg_file, custom_filename=f"{instance.suffix}.printer.cfg", ) From 948927cfd3c9f0a7d010ec6034b4471d29c68269 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 10 Feb 2024 11:47:27 +0100 Subject: [PATCH 104/137] feat: implement backup menu and backup methods for existing components Signed-off-by: Dominik Willner --- kiauh/__init__.py | 1 - kiauh/components/klipper/__init__.py | 3 + kiauh/components/klipper/klipper_setup.py | 18 ++-- kiauh/components/klipper/klipper_utils.py | 29 ++++-- kiauh/components/mainsail/__init__.py | 9 +- kiauh/components/mainsail/mainsail_utils.py | 20 ++++- kiauh/components/moonraker/__init__.py | 4 + kiauh/components/moonraker/moonraker.py | 10 ++- kiauh/components/moonraker/moonraker_setup.py | 22 ++--- kiauh/components/moonraker/moonraker_utils.py | 33 +++++-- kiauh/core/backup_manager/__init__.py | 14 +++ kiauh/core/menus/backup_menu.py | 89 +++++++++++++++++++ kiauh/core/menus/main_menu.py | 14 +-- kiauh/utils/__init__.py | 3 + kiauh/utils/common.py | 21 ++++- 15 files changed, 238 insertions(+), 52 deletions(-) create mode 100644 kiauh/core/menus/backup_menu.py diff --git a/kiauh/__init__.py b/kiauh/__init__.py index 3a58dc0..91fd073 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -13,4 +13,3 @@ from pathlib import Path APPLICATION_ROOT = Path(__file__).resolve().parent.parent KIAUH_CFG = APPLICATION_ROOT.joinpath("kiauh.cfg") -KIAUH_BACKUP_DIR = Path.home().joinpath("kiauh-backups") diff --git a/kiauh/components/klipper/__init__.py b/kiauh/components/klipper/__init__.py index 6b87989..ff3ae20 100644 --- a/kiauh/components/klipper/__init__.py +++ b/kiauh/components/klipper/__init__.py @@ -11,10 +11,13 @@ from pathlib import Path +from kiauh.core.backup_manager import BACKUP_ROOT_DIR + MODULE_PATH = Path(__file__).resolve().parent KLIPPER_DIR = Path.home().joinpath("klipper") KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") +KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") KLIPPER_REQUIREMENTS_TXT = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt") DEFAULT_KLIPPER_REPO_URL = "https://github.com/Klipper3D/klipper" diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index bed720d..05cfb1c 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -12,16 +12,13 @@ from pathlib import Path from kiauh import KIAUH_CFG -from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.components.klipper import ( EXIT_KLIPPER_SETUP, DEFAULT_KLIPPER_REPO_URL, KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT, -) + ) from kiauh.components.klipper.klipper import Klipper from kiauh.components.klipper.klipper_dialogs import print_update_warn_dialog from kiauh.components.klipper.klipper_utils import ( @@ -35,9 +32,12 @@ from kiauh.components.klipper.klipper_utils import ( check_is_single_to_multi_conversion, update_name_scheme, handle_instance_naming, -) -from kiauh.core.repo_manager.repo_manager import RepoManager + backup_klipper_dir, + ) from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( @@ -46,7 +46,7 @@ from kiauh.utils.system_utils import ( install_python_requirements, update_system_package_lists, install_system_packages, -) + ) def install_klipper() -> None: @@ -149,9 +149,7 @@ def update_klipper() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) if cm.get_value("kiauh", "backup_before_update"): - bm = BackupManager() - bm.backup_directory("klipper", KLIPPER_DIR) - bm.backup_directory("klippy-env", KLIPPER_ENV_DIR) + backup_klipper_dir() instance_manager = InstanceManager(Klipper) instance_manager.stop_all_instance() diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index f49cdec..373afc3 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -9,31 +9,36 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import grp import os import re -import grp import shutil import subprocess import textwrap from pathlib import Path - from typing import List, Union, Literal, Dict -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.instance_manager.name_scheme import NameScheme -from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.components.klipper import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR +from kiauh.components.klipper import ( + MODULE_PATH, + KLIPPER_DIR, + KLIPPER_ENV_DIR, + KLIPPER_BACKUP_DIR, + ) from kiauh.components.klipper.klipper import Klipper from kiauh.components.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, print_instance_overview, print_select_instance_count_dialog, print_select_custom_name_dialog, -) + ) from kiauh.components.moonraker.moonraker import Moonraker from kiauh.components.moonraker.moonraker_utils import moonraker_to_multi_conversion +from kiauh.core.backup_manager.backup_manager import BackupManager +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.base_instance import BaseInstance +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.instance_manager.name_scheme import NameScheme +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils.common import get_install_status_common from kiauh.utils.constants import CURRENT_USER from kiauh.utils.input_utils import get_confirm, get_string_input, get_number_input @@ -276,3 +281,9 @@ def create_example_printer_cfg(instance: Klipper) -> None: cm.set_value("virtual_sdcard", "path", str(instance.gcodes_dir)) cm.write_config() Logger.print_ok(f"Example printer.cfg created in '{instance.cfg_dir}'") + + +def backup_klipper_dir() -> None: + bm = BackupManager() + bm.backup_directory("klipper", source=KLIPPER_DIR, target=KLIPPER_BACKUP_DIR) + bm.backup_directory("klippy-env", source=KLIPPER_ENV_DIR, target=KLIPPER_BACKUP_DIR) diff --git a/kiauh/components/mainsail/__init__.py b/kiauh/components/mainsail/__init__.py index 9c01f87..0b2d2af 100644 --- a/kiauh/components/mainsail/__init__.py +++ b/kiauh/components/mainsail/__init__.py @@ -11,10 +11,13 @@ from pathlib import Path +from kiauh.core.backup_manager import BACKUP_ROOT_DIR + MODULE_PATH = Path(__file__).resolve().parent -MAINSAIL_DIR = Path(Path.home(), "mainsail") -MAINSAIL_CONFIG_DIR = Path(Path.home(), "mainsail-config") -MAINSAIL_CONFIG_JSON = Path(MAINSAIL_DIR, "config.json") +MAINSAIL_DIR = Path.home().joinpath("mainsail") +MAINSAIL_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-backups") +MAINSAIL_CONFIG_DIR = Path.home().joinpath("mainsail-config") +MAINSAIL_CONFIG_JSON = MAINSAIL_DIR.joinpath("config.json") MAINSAIL_URL = ( "https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip" ) diff --git a/kiauh/components/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py index de54941..9a01e9d 100644 --- a/kiauh/components/mainsail/mainsail_utils.py +++ b/kiauh/components/mainsail/mainsail_utils.py @@ -11,13 +11,18 @@ import json import shutil -import requests from pathlib import Path from typing import List -from kiauh.core.backup_manager.backup_manager import BackupManager +import requests + from kiauh.components.klipper.klipper import Klipper -from kiauh.components.mainsail import MAINSAIL_CONFIG_JSON, MAINSAIL_DIR +from kiauh.components.mainsail import ( + MAINSAIL_CONFIG_JSON, + MAINSAIL_DIR, + MAINSAIL_BACKUP_DIR, + ) +from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_CONFD from kiauh.utils.common import get_install_status_webui from kiauh.utils.logger import Logger @@ -97,3 +102,12 @@ def get_mainsail_remote_version() -> str: response = requests.get(url) data = json.loads(response.text) return data[0]["name"] + + +def backup_mainsail_data() -> None: + with open(MAINSAIL_DIR.joinpath(".version"), "r") as v: + version = v.readlines()[0] + bm = BackupManager() + bm.backup_directory(f"mainsail-{version}", MAINSAIL_DIR, MAINSAIL_BACKUP_DIR) + bm.backup_file(MAINSAIL_CONFIG_JSON, MAINSAIL_BACKUP_DIR) + bm.backup_file(NGINX_SITES_AVAILABLE.joinpath("mainsail"), MAINSAIL_BACKUP_DIR) diff --git a/kiauh/components/moonraker/__init__.py b/kiauh/components/moonraker/__init__.py index 14cdac0..e42f3e9 100644 --- a/kiauh/components/moonraker/__init__.py +++ b/kiauh/components/moonraker/__init__.py @@ -11,10 +11,14 @@ from pathlib import Path +from kiauh.core.backup_manager import BACKUP_ROOT_DIR + MODULE_PATH = Path(__file__).resolve().parent MOONRAKER_DIR = Path.home().joinpath("moonraker") MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env") +MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups") +MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups") MOONRAKER_REQUIREMENTS_TXT = MOONRAKER_DIR.joinpath( "scripts/moonraker-requirements.txt" ) diff --git a/kiauh/components/moonraker/moonraker.py b/kiauh/components/moonraker/moonraker.py index f33d039..c2a9d29 100644 --- a/kiauh/components/moonraker/moonraker.py +++ b/kiauh/components/moonraker/moonraker.py @@ -13,9 +13,9 @@ import subprocess from pathlib import Path from typing import List, Union +from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger @@ -34,9 +34,13 @@ class Moonraker(BaseInstance): self.port = self._get_port() self.backup_dir = self.data_dir.joinpath("backup") self.certs_dir = self.data_dir.joinpath("certs") - self.db_dir = self.data_dir.joinpath("database") + self._db_dir = self.data_dir.joinpath("database") self.log = self.log_dir.joinpath("moonraker.log") + @property + def db_dir(self) -> Path: + return self._db_dir + def create(self, create_example_cfg: bool = False) -> None: Logger.print_status("Creating new Moonraker Instance ...") service_template_path = MODULE_PATH.joinpath("assets/moonraker.service") @@ -46,7 +50,7 @@ class Moonraker(BaseInstance): env_file_target = self.sysd_dir.joinpath("moonraker.env") try: - self.create_folders([self.backup_dir, self.certs_dir, self.db_dir]) + self.create_folders([self.backup_dir, self.certs_dir, self._db_dir]) self.write_service_file( service_template_path, service_file_target, env_file_target ) diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index 066d627..9ed8fa0 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -15,12 +15,8 @@ from pathlib import Path from typing import List from kiauh import KIAUH_CFG -from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.components.klipper.klipper import Klipper from kiauh.components.klipper.klipper_dialogs import print_instance_overview -from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.components.mainsail import MAINSAIL_DIR from kiauh.components.mainsail.mainsail_utils import enable_mainsail_remotemode from kiauh.components.moonraker import ( @@ -33,15 +29,21 @@ from kiauh.components.moonraker import ( POLKIT_FILE, POLKIT_USR_FILE, POLKIT_SCRIPT, -) + ) from kiauh.components.moonraker.moonraker import Moonraker from kiauh.components.moonraker.moonraker_dialogs import print_moonraker_overview -from kiauh.components.moonraker.moonraker_utils import create_example_moonraker_conf +from kiauh.components.moonraker.moonraker_utils import ( + create_example_moonraker_conf, + backup_moonraker_dir, + ) +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import ( get_confirm, get_selection_input, -) + ) from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( parse_packages_from_file, @@ -49,7 +51,7 @@ from kiauh.utils.system_utils import ( install_python_requirements, update_system_package_lists, install_system_packages, -) + ) def install_moonraker() -> None: @@ -206,9 +208,7 @@ def update_moonraker() -> None: cm = ConfigManager(cfg_file=KIAUH_CFG) if cm.get_value("kiauh", "backup_before_update"): - bm = BackupManager() - bm.backup_directory("moonraker", MOONRAKER_DIR) - bm.backup_directory("moonraker-env", MOONRAKER_ENV_DIR) + backup_moonraker_dir() instance_manager = InstanceManager(Moonraker) instance_manager.stop_all_instance() diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index 017e00a..e3db3c6 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -12,9 +12,6 @@ import shutil from typing import Dict, Literal, List, Union -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.components.mainsail import MAINSAIL_DIR from kiauh.components.mainsail.mainsail_utils import enable_mainsail_remotemode from kiauh.components.moonraker import ( @@ -22,13 +19,19 @@ from kiauh.components.moonraker import ( MODULE_PATH, MOONRAKER_DIR, MOONRAKER_ENV_DIR, -) + MOONRAKER_BACKUP_DIR, + MOONRAKER_DB_BACKUP_DIR, + ) from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.core.backup_manager.backup_manager import BackupManager +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils.common import get_install_status_common from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( get_ipv4_addr, -) + ) def get_moonraker_status() -> ( @@ -132,3 +135,23 @@ def moonraker_to_multi_conversion(new_name: str) -> None: # if mainsail is installed, we enable mainsails remote mode if MAINSAIL_DIR.exists() and len(im.instances) > 1: enable_mainsail_remotemode() + + +def backup_moonraker_dir(): + bm = BackupManager() + bm.backup_directory("moonraker", source=MOONRAKER_DIR, target=MOONRAKER_BACKUP_DIR) + bm.backup_directory( + "moonraker-env", source=MOONRAKER_ENV_DIR, target=MOONRAKER_BACKUP_DIR + ) + + +def backup_moonraker_db_dir() -> None: + im = InstanceManager(Moonraker) + instances: List[Moonraker] = im.instances + bm = BackupManager() + + for instance in instances: + name = f"database-{instance.data_dir_name}" + bm.backup_directory( + name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR + ) diff --git a/kiauh/core/backup_manager/__init__.py b/kiauh/core/backup_manager/__init__.py index e69de29..7bbbe1e 100644 --- a/kiauh/core/backup_manager/__init__.py +++ b/kiauh/core/backup_manager/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 + +BACKUP_ROOT_DIR = Path.home().joinpath("kiauh-backups") diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py new file mode 100644 index 0000000..ffc246b --- /dev/null +++ b/kiauh/core/menus/backup_menu.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 textwrap + +from kiauh.components.klipper.klipper_utils import backup_klipper_dir +from kiauh.components.mainsail.mainsail_utils import backup_mainsail_data +from kiauh.components.moonraker.moonraker_utils import ( + backup_moonraker_dir, + backup_moonraker_db_dir, + ) +from kiauh.core.menus import BACK_FOOTER +from kiauh.core.menus.base_menu import BaseMenu +from kiauh.utils.common import backup_printer_config_dir +from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW + + +# noinspection PyUnusedLocal +# noinspection PyMethodMayBeStatic +class BackupMenu(BaseMenu): + def __init__(self): + super().__init__( + header=True, + options={ + "1": self.backup_klipper, + "2": self.backup_moonraker, + "3": self.backup_printer_config, + "4": self.backup_moonraker_db, + "5": self.backup_mainsail + }, + footer_type=BACK_FOOTER, + ) + + def print_menu(self): + header = " [ Backup Menu ] " + line1 = f"{COLOR_YELLOW}INFO: Backups are located in '~/kiauh-backups'{RESET_FORMAT}" + color = COLOR_CYAN + count = 62 - len(color) - len(RESET_FORMAT) + menu = textwrap.dedent( + f""" + /=======================================================\\ + | {color}{header:~^{count}}{RESET_FORMAT} | + |-------------------------------------------------------| + | {line1:^62} | + |-------------------------------------------------------| + | Klipper & Moonraker API: | Touchscreen GUI: | + | 1) [Klipper] | 7) [KlipperScreen] | + | 2) [Moonraker] | | + | 3) [Config Folder] | Other: | + | 4) [Moonraker Database] | 9) [Telegram Bot] | + | | | + | Klipper Webinterface: | | + | 5) [Mainsail] | | + | 6) [Fluidd] | | + """ + )[1:] + print(menu, end="") + + def backup_klipper(self, **kwargs): + backup_klipper_dir() + + def backup_moonraker(self, **kwargs): + backup_moonraker_dir() + + def backup_printer_config(self, **kwargs): + backup_printer_config_dir() + + def backup_moonraker_db(self, **kwargs): + backup_moonraker_db_dir() + + def backup_mainsail(self, **kwargs): + backup_mainsail_data() + + def backup_fluidd(self, **kwargs): + pass + + def backup_klipperscreen(self, **kwargs): + pass + + def backup_telegram_bot(self, **kwargs): + pass diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 366b99a..14c80cc 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -11,18 +11,19 @@ import textwrap +from kiauh.components.klipper.klipper_utils import get_klipper_status +from kiauh.components.log_uploads.menus.log_upload_menu import LogUploadMenu +from kiauh.components.mainsail.mainsail_utils import get_mainsail_status +from kiauh.components.moonraker.moonraker_utils import get_moonraker_status from kiauh.core.menus import QUIT_FOOTER from kiauh.core.menus.advanced_menu import AdvancedMenu +from kiauh.core.menus.backup_menu import BackupMenu from kiauh.core.menus.base_menu import BaseMenu from kiauh.core.menus.extensions_menu import ExtensionsMenu from kiauh.core.menus.install_menu import InstallMenu from kiauh.core.menus.remove_menu import RemoveMenu from kiauh.core.menus.settings_menu import SettingsMenu from kiauh.core.menus.update_menu import UpdateMenu -from kiauh.components.klipper.klipper_utils import get_klipper_status -from kiauh.components.log_uploads.menus.log_upload_menu import LogUploadMenu -from kiauh.components.mainsail.mainsail_utils import get_mainsail_status -from kiauh.components.moonraker.moonraker_utils import get_moonraker_status from kiauh.utils.constants import ( COLOR_MAGENTA, COLOR_CYAN, @@ -30,9 +31,10 @@ from kiauh.utils.constants import ( COLOR_RED, COLOR_GREEN, COLOR_YELLOW, -) + ) +# noinspection PyMethodMayBeStatic class MainMenu(BaseMenu): def __init__(self): super().__init__( @@ -43,7 +45,7 @@ class MainMenu(BaseMenu): "2": UpdateMenu, "3": RemoveMenu, "4": AdvancedMenu, - "5": None, + "5": BackupMenu, "e": ExtensionsMenu, "s": SettingsMenu, }, diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index f097b5d..319fc15 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -11,8 +11,11 @@ from pathlib import Path +from kiauh.core.backup_manager import BACKUP_ROOT_DIR + MODULE_PATH = Path(__file__).resolve().parent INVALID_CHOICE = "Invalid choice. Please select a valid value." +PRINTER_CFG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-cfg-backups") # ================== NGINX =====================# NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available") diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index fbae241..222b58d 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -13,15 +13,17 @@ from datetime import datetime from pathlib import Path from typing import Dict, Literal, List, Type, Union +from kiauh.components.klipper.klipper import Klipper from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.utils import PRINTER_CFG_BACKUP_DIR from kiauh.utils.constants import ( COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW, COLOR_GREEN, COLOR_RED, -) + ) from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.logger import Logger from kiauh.utils.system_utils import check_package_install, install_system_packages @@ -116,3 +118,20 @@ def get_install_status_webui( return f"{COLOR_RED}Not installed!{RESET_FORMAT}" else: return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" + + +def backup_printer_config_dir(): + # local import to prevent circular import + from kiauh.core.backup_manager.backup_manager import BackupManager + + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + bm = BackupManager() + + for instance in instances: + name = f"config-{instance.data_dir_name}" + bm.backup_directory( + name, + source=instance.cfg_dir, + target=PRINTER_CFG_BACKUP_DIR, + ) From be5f345a7c8ad0c7d724167acd02da40f1840dfe Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 10 Feb 2024 11:51:43 +0100 Subject: [PATCH 105/137] style: reformat code Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper.py | 2 +- kiauh/components/klipper/klipper_remove.py | 2 +- kiauh/components/klipper/klipper_setup.py | 6 +-- kiauh/components/klipper/klipper_utils.py | 14 +++---- .../klipper/menus/klipper_remove_menu.py | 2 +- .../log_uploads/log_upload_utils.py | 7 ++-- .../log_uploads/menus/log_upload_menu.py | 4 +- kiauh/components/mainsail/mainsail_remove.py | 4 +- kiauh/components/mainsail/mainsail_setup.py | 6 +-- kiauh/components/mainsail/mainsail_utils.py | 2 +- .../mainsail/menus/mainsail_remove_menu.py | 2 +- .../moonraker/menus/moonraker_remove_menu.py | 2 +- .../components/moonraker/moonraker_dialogs.py | 10 ++--- .../components/moonraker/moonraker_remove.py | 2 +- kiauh/components/moonraker/moonraker_setup.py | 8 ++-- kiauh/components/moonraker/moonraker_utils.py | 14 +++---- kiauh/core/backup_manager/backup_manager.py | 5 +-- .../core/instance_manager/instance_manager.py | 1 - kiauh/core/menus/backup_menu.py | 4 +- kiauh/core/menus/base_menu.py | 2 +- kiauh/core/menus/extensions_menu.py | 4 +- kiauh/core/menus/install_menu.py | 4 +- kiauh/core/menus/main_menu.py | 2 +- kiauh/core/menus/remove_menu.py | 4 +- kiauh/core/menus/update_menu.py | 4 +- kiauh/extensions/gcode_shell_cmd/__init__.py | 1 - .../assets/gcode_shell_command.py | 37 +++++++++++-------- .../gcode_shell_cmd_extension.py | 2 +- kiauh/utils/common.py | 2 +- kiauh/utils/system_utils.py | 2 +- 30 files changed, 79 insertions(+), 82 deletions(-) diff --git a/kiauh/components/klipper/klipper.py b/kiauh/components/klipper/klipper.py index 1f8e6e5..1b442c6 100644 --- a/kiauh/components/klipper/klipper.py +++ b/kiauh/components/klipper/klipper.py @@ -13,8 +13,8 @@ import subprocess from pathlib import Path from typing import List -from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH +from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger diff --git a/kiauh/components/klipper/klipper_remove.py b/kiauh/components/klipper/klipper_remove.py index 3534094..61b007c 100644 --- a/kiauh/components/klipper/klipper_remove.py +++ b/kiauh/components/klipper/klipper_remove.py @@ -12,10 +12,10 @@ import shutil from typing import List, Union -from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR from kiauh.components.klipper.klipper import Klipper from kiauh.components.klipper.klipper_dialogs import print_instance_overview +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.utils.filesystem_utils import remove_file from kiauh.utils.input_utils import get_selection_input from kiauh.utils.logger import Logger diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index 05cfb1c..1e553f2 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -18,7 +18,7 @@ from kiauh.components.klipper import ( KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT, - ) +) from kiauh.components.klipper.klipper import Klipper from kiauh.components.klipper.klipper_dialogs import print_update_warn_dialog from kiauh.components.klipper.klipper_utils import ( @@ -33,7 +33,7 @@ from kiauh.components.klipper.klipper_utils import ( update_name_scheme, handle_instance_naming, backup_klipper_dir, - ) +) from kiauh.components.moonraker.moonraker import Moonraker from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager @@ -46,7 +46,7 @@ from kiauh.utils.system_utils import ( install_python_requirements, update_system_package_lists, install_system_packages, - ) +) def install_klipper() -> None: diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 373afc3..564d7f7 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -23,14 +23,14 @@ from kiauh.components.klipper import ( KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_BACKUP_DIR, - ) +) from kiauh.components.klipper.klipper import Klipper from kiauh.components.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, print_instance_overview, print_select_instance_count_dialog, print_select_custom_name_dialog, - ) +) from kiauh.components.moonraker.moonraker import Moonraker from kiauh.components.moonraker.moonraker_utils import moonraker_to_multi_conversion from kiauh.core.backup_manager.backup_manager import BackupManager @@ -46,12 +46,10 @@ from kiauh.utils.logger import Logger from kiauh.utils.system_utils import mask_system_service -def get_klipper_status() -> ( - Dict[ - Literal["status", "status_code", "instances", "repo", "local", "remote"], - Union[str, int], - ] -): +def get_klipper_status() -> Dict[ + Literal["status", "status_code", "instances", "repo", "local", "remote"], + Union[str, int], +]: status = get_install_status_common(Klipper, KLIPPER_DIR, KLIPPER_ENV_DIR) return { "status": status.get("status"), diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index 7ee5461..a36d51b 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -11,9 +11,9 @@ import textwrap +from kiauh.components.klipper import klipper_remove from kiauh.core.menus import BACK_HELP_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.components.klipper import klipper_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN diff --git a/kiauh/components/log_uploads/log_upload_utils.py b/kiauh/components/log_uploads/log_upload_utils.py index 9ce95c8..4247a0e 100644 --- a/kiauh/components/log_uploads/log_upload_utils.py +++ b/kiauh/components/log_uploads/log_upload_utils.py @@ -9,14 +9,13 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from typing import List -from pathlib import Path - import urllib.request +from pathlib import Path +from typing import List -from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.components.klipper.klipper import Klipper from kiauh.components.log_uploads import LogFile +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.utils.logger import Logger diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index c140b2e..11bb65e 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -11,10 +11,10 @@ import textwrap +from kiauh.components.log_uploads.log_upload_utils import get_logfile_list +from kiauh.components.log_uploads.log_upload_utils import upload_logfile from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.components.log_uploads.log_upload_utils import upload_logfile -from kiauh.components.log_uploads.log_upload_utils import get_logfile_list from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW diff --git a/kiauh/components/mainsail/mainsail_remove.py b/kiauh/components/mainsail/mainsail_remove.py index fc396ba..3a4f797 100644 --- a/kiauh/components/mainsail/mainsail_remove.py +++ b/kiauh/components/mainsail/mainsail_remove.py @@ -15,12 +15,12 @@ import subprocess from pathlib import Path from typing import List -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.components.klipper.klipper import Klipper from kiauh.components.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR from kiauh.components.mainsail.mainsail_utils import backup_config_json from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from kiauh.utils.filesystem_utils import remove_file from kiauh.utils.logger import Logger diff --git a/kiauh/components/mainsail/mainsail_setup.py b/kiauh/components/mainsail/mainsail_setup.py index 33b764a..e370847 100644 --- a/kiauh/components/mainsail/mainsail_setup.py +++ b/kiauh/components/mainsail/mainsail_setup.py @@ -14,9 +14,6 @@ from pathlib import Path from typing import List from kiauh import KIAUH_CFG -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.components.klipper.klipper import Klipper from kiauh.components.mainsail import ( MAINSAIL_URL, @@ -38,6 +35,9 @@ from kiauh.components.mainsail.mainsail_utils import ( symlink_webui_nginx_log, ) from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.core.repo_manager.repo_manager import RepoManager from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from kiauh.utils.common import check_install_dependencies from kiauh.utils.filesystem_utils import ( diff --git a/kiauh/components/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py index 9a01e9d..3cb7102 100644 --- a/kiauh/components/mainsail/mainsail_utils.py +++ b/kiauh/components/mainsail/mainsail_utils.py @@ -21,7 +21,7 @@ from kiauh.components.mainsail import ( MAINSAIL_CONFIG_JSON, MAINSAIL_DIR, MAINSAIL_BACKUP_DIR, - ) +) from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_CONFD from kiauh.utils.common import get_install_status_webui diff --git a/kiauh/components/mainsail/menus/mainsail_remove_menu.py b/kiauh/components/mainsail/menus/mainsail_remove_menu.py index 47488fc..187d974 100644 --- a/kiauh/components/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/components/mainsail/menus/mainsail_remove_menu.py @@ -11,9 +11,9 @@ import textwrap +from kiauh.components.mainsail import mainsail_remove from kiauh.core.menus import BACK_HELP_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.components.mainsail import mainsail_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index f8bc307..962e07d 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -11,9 +11,9 @@ import textwrap +from kiauh.components.moonraker import moonraker_remove from kiauh.core.menus import BACK_HELP_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.components.moonraker import moonraker_remove from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN diff --git a/kiauh/components/moonraker/moonraker_dialogs.py b/kiauh/components/moonraker/moonraker_dialogs.py index 62867bd..2bc18b2 100644 --- a/kiauh/components/moonraker/moonraker_dialogs.py +++ b/kiauh/components/moonraker/moonraker_dialogs.py @@ -12,9 +12,9 @@ import textwrap from typing import List -from kiauh.core.menus.base_menu import print_back_footer from kiauh.components.klipper.klipper import Klipper from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.core.menus.base_menu import print_back_footer from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN @@ -39,11 +39,11 @@ def print_moonraker_overview( dialog += "| |\n" instance_map = { - k.get_service_file_name(): k.get_service_file_name().replace( - "klipper", "moonraker" + k.get_service_file_name(): ( + k.get_service_file_name().replace("klipper", "moonraker") + if k.suffix in [m.suffix for m in moonraker_instances] + else "" ) - if k.suffix in [m.suffix for m in moonraker_instances] - else "" for k in klipper_instances } diff --git a/kiauh/components/moonraker/moonraker_remove.py b/kiauh/components/moonraker/moonraker_remove.py index cc76be8..02af9dd 100644 --- a/kiauh/components/moonraker/moonraker_remove.py +++ b/kiauh/components/moonraker/moonraker_remove.py @@ -13,10 +13,10 @@ import shutil import subprocess from typing import List, Union -from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.components.klipper.klipper_dialogs import print_instance_overview from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR from kiauh.components.moonraker.moonraker import Moonraker +from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.utils.filesystem_utils import remove_file from kiauh.utils.input_utils import get_selection_input from kiauh.utils.logger import Logger diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index 9ed8fa0..f041a0e 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -29,13 +29,13 @@ from kiauh.components.moonraker import ( POLKIT_FILE, POLKIT_USR_FILE, POLKIT_SCRIPT, - ) +) from kiauh.components.moonraker.moonraker import Moonraker from kiauh.components.moonraker.moonraker_dialogs import print_moonraker_overview from kiauh.components.moonraker.moonraker_utils import ( create_example_moonraker_conf, backup_moonraker_dir, - ) +) from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.repo_manager.repo_manager import RepoManager @@ -43,7 +43,7 @@ from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import ( get_confirm, get_selection_input, - ) +) from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( parse_packages_from_file, @@ -51,7 +51,7 @@ from kiauh.utils.system_utils import ( install_python_requirements, update_system_package_lists, install_system_packages, - ) +) def install_moonraker() -> None: diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index e3db3c6..0a9ae11 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -21,7 +21,7 @@ from kiauh.components.moonraker import ( MOONRAKER_ENV_DIR, MOONRAKER_BACKUP_DIR, MOONRAKER_DB_BACKUP_DIR, - ) +) from kiauh.components.moonraker.moonraker import Moonraker from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.config_manager.config_manager import ConfigManager @@ -31,15 +31,13 @@ from kiauh.utils.common import get_install_status_common from kiauh.utils.logger import Logger from kiauh.utils.system_utils import ( get_ipv4_addr, - ) +) -def get_moonraker_status() -> ( - Dict[ - Literal["status", "status_code", "instances", "repo", "local", "remote"], - Union[str, int], - ] -): +def get_moonraker_status() -> Dict[ + Literal["status", "status_code", "instances", "repo", "local", "remote"], + Union[str, int], +]: status = get_install_status_common(Moonraker, MOONRAKER_DIR, MOONRAKER_ENV_DIR) return { "status": status.get("status"), diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 7f5a887..8f2dbcf 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -41,9 +41,7 @@ class BackupManager: def ignore_folders(self, value: List[str]): self._ignore_folders = value - def backup_file( - self, file: Path = None, target: Path = None, custom_filename=None - ): + def backup_file(self, file: Path = None, target: Path = None, custom_filename=None): if not file: raise ValueError("Parameter 'file' cannot be None!") @@ -84,7 +82,6 @@ class BackupManager: Logger.print_error(f"Unable to backup directory '{source}':\n{e}") return - def ignore_folders_func(self, dirpath, filenames): return ( [f for f in filenames if f in self._ignore_folders] diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index d4e2f28..0a920d6 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -18,7 +18,6 @@ from kiauh.core.instance_manager.base_instance import BaseInstance from kiauh.utils.constants import SYSTEMD from kiauh.utils.logger import Logger - I = TypeVar(name="I", bound=BaseInstance, covariant=True) diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index ffc246b..184e26c 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -16,7 +16,7 @@ from kiauh.components.mainsail.mainsail_utils import backup_mainsail_data from kiauh.components.moonraker.moonraker_utils import ( backup_moonraker_dir, backup_moonraker_db_dir, - ) +) from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.utils.common import backup_printer_config_dir @@ -34,7 +34,7 @@ class BackupMenu(BaseMenu): "2": self.backup_moonraker, "3": self.backup_printer_config, "4": self.backup_moonraker_db, - "5": self.backup_mainsail + "5": self.backup_mainsail, }, footer_type=BACK_FOOTER, ) diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 2cdf379..89ed09b 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -13,7 +13,7 @@ import subprocess import sys import textwrap from abc import abstractmethod, ABC -from typing import Dict, Any, Literal, Union, Callable, Type +from typing import Dict, Any, Literal, Union, Callable from kiauh.core.menus import QUIT_FOOTER, BACK_FOOTER, BACK_HELP_FOOTER from kiauh.utils.constants import ( diff --git a/kiauh/core/menus/extensions_menu.py b/kiauh/core/menus/extensions_menu.py index ebbc4c5..50fd53b 100644 --- a/kiauh/core/menus/extensions_menu.py +++ b/kiauh/core/menus/extensions_menu.py @@ -9,10 +9,10 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -import json -import textwrap import importlib import inspect +import json +import textwrap from pathlib import Path from typing import List, Dict diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 239dfaf..85e1ef3 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -11,11 +11,11 @@ import textwrap -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu from kiauh.components.klipper import klipper_setup from kiauh.components.mainsail import mainsail_setup from kiauh.components.moonraker import moonraker_setup +from kiauh.core.menus import BACK_FOOTER +from kiauh.core.menus.base_menu import BaseMenu from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 14c80cc..60dc9f8 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -31,7 +31,7 @@ from kiauh.utils.constants import ( COLOR_RED, COLOR_GREEN, COLOR_YELLOW, - ) +) # noinspection PyMethodMayBeStatic diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 060927e..1bd2eba 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -11,11 +11,11 @@ import textwrap -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu from kiauh.components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from kiauh.components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu from kiauh.components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu +from kiauh.core.menus import BACK_FOOTER +from kiauh.core.menus.base_menu import BaseMenu from kiauh.utils.constants import COLOR_RED, RESET_FORMAT diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index c90de1c..f2951dd 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -11,8 +11,6 @@ import textwrap -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu from kiauh.components.klipper.klipper_setup import update_klipper from kiauh.components.klipper.klipper_utils import ( get_klipper_status, @@ -24,6 +22,8 @@ from kiauh.components.mainsail.mainsail_utils import ( ) from kiauh.components.moonraker.moonraker_setup import update_moonraker from kiauh.components.moonraker.moonraker_utils import get_moonraker_status +from kiauh.core.menus import BACK_FOOTER +from kiauh.core.menus.base_menu import BaseMenu from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE diff --git a/kiauh/extensions/gcode_shell_cmd/__init__.py b/kiauh/extensions/gcode_shell_cmd/__init__.py index c0d921b..27ffecb 100644 --- a/kiauh/extensions/gcode_shell_cmd/__init__.py +++ b/kiauh/extensions/gcode_shell_cmd/__init__.py @@ -11,7 +11,6 @@ from pathlib import Path - EXT_MODULE_NAME = "gcode_shell_command.py" MODULE_PATH = Path(__file__).resolve().parent MODULE_ASSETS = MODULE_PATH.joinpath("assets") diff --git a/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py b/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py index bb38ae5..85b664b 100644 --- a/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py +++ b/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py @@ -3,27 +3,31 @@ # Copyright (C) 2019 Eric Callahan # # This file may be distributed under the terms of the GNU GPLv3 license. +import logging import os import shlex import subprocess -import logging + class ShellCommand: def __init__(self, config): self.name = config.get_name().split()[-1] self.printer = config.get_printer() - self.gcode = self.printer.lookup_object('gcode') - cmd = config.get('command') + self.gcode = self.printer.lookup_object("gcode") + cmd = config.get("command") cmd = os.path.expanduser(cmd) self.command = shlex.split(cmd) - self.timeout = config.getfloat('timeout', 2., above=0.) - self.verbose = config.getboolean('verbose', True) + self.timeout = config.getfloat("timeout", 2.0, above=0.0) + self.verbose = config.getboolean("verbose", True) self.proc_fd = None self.partial_output = "" self.gcode.register_mux_command( - "RUN_SHELL_COMMAND", "CMD", self.name, + "RUN_SHELL_COMMAND", + "CMD", + self.name, self.cmd_RUN_SHELL_COMMAND, - desc=self.cmd_RUN_SHELL_COMMAND_help) + desc=self.cmd_RUN_SHELL_COMMAND_help, + ) def _process_output(self, eventime): if self.proc_fd is None: @@ -33,11 +37,11 @@ class ShellCommand: except Exception: pass data = self.partial_output + data.decode() - if '\n' not in data: + if "\n" not in data: self.partial_output = data return - elif data[-1] != '\n': - split = data.rfind('\n') + 1 + elif data[-1] != "\n": + split = data.rfind("\n") + 1 self.partial_output = data[split:] data = data[:split] else: @@ -45,16 +49,19 @@ class ShellCommand: self.gcode.respond_info(data) cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command" + def cmd_RUN_SHELL_COMMAND(self, params): - gcode_params = params.get('PARAMS','') + gcode_params = params.get("PARAMS", "") gcode_params = shlex.split(gcode_params) reactor = self.printer.get_reactor() try: proc = subprocess.Popen( - self.command + gcode_params, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.command + gcode_params, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) except Exception: - logging.exception( - "shell_command: Command {%s} failed" % (self.name)) + logging.exception("shell_command: Command {%s} failed" % (self.name)) raise self.gcode.error("Error running command {%s}" % (self.name)) if self.verbose: self.proc_fd = proc.stdout.fileno() @@ -64,7 +71,7 @@ class ShellCommand: endtime = eventtime + self.timeout complete = False while eventtime < endtime: - eventtime = reactor.pause(eventtime + .05) + eventtime = reactor.pause(eventtime + 0.05) if proc.poll() is not None: complete = True break diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py index c0c28c1..a9798e2 100644 --- a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -24,7 +24,7 @@ from kiauh.extensions.gcode_shell_cmd import ( KLIPPER_DIR, EXAMPLE_CFG_SRC, KLIPPER_EXTRAS, - ) +) from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 222b58d..6557cd0 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -23,7 +23,7 @@ from kiauh.utils.constants import ( COLOR_YELLOW, COLOR_GREEN, COLOR_RED, - ) +) from kiauh.utils.filesystem_utils import check_file_exist from kiauh.utils.logger import Logger from kiauh.utils.system_utils import check_package_install, install_system_packages diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index fce0977..1f3b018 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -285,7 +285,7 @@ def download_progress(block_num, block_size, total_size) -> None: mb = 1024 * 1024 progress = int(percent / 5) remaining = "-" * (20 - progress) - dl = f"\rDownloading: [{'#' * progress}{remaining}]{percent:.2f}% ({downloaded/mb:.2f}/{total_size/mb:.2f}MB)" + dl = f"\rDownloading: [{'#' * progress}{remaining}]{percent:.2f}% ({downloaded / mb:.2f}/{total_size / mb:.2f}MB)" sys.stdout.write(dl) sys.stdout.flush() From 863c62511c80686e5b761aa405487f7890c7b627 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 11 Feb 2024 20:15:35 +0100 Subject: [PATCH 106/137] fix(klipper): add python3-venv dependency for creating venv Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper_setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index 1e553f2..2cf5690 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -132,6 +132,7 @@ def install_klipper_packages(klipper_dir: Path) -> None: script = klipper_dir.joinpath("scripts/install-debian.sh") packages = parse_packages_from_file(script) packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages] + packages.append("python3-venv") # Add dfu-util for octopi-images packages.append("dfu-util") # Add dbus requirement for DietPi distro From 05b4ef2d18152dd6c03cdef359ed1bb5be328bf1 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 11 Feb 2024 20:16:21 +0100 Subject: [PATCH 107/137] refactor(utils): raise exception if pip not found in venv Signed-off-by: Dominik Willner --- kiauh/utils/system_utils.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 1f3b018..352d575 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -23,6 +23,7 @@ from typing import List, Literal from kiauh.utils.input_utils import get_confirm from kiauh.utils.logger import Logger +from kiauh.utils.filesystem_utils import check_file_exist def kill(opt_err_msg: str = "") -> None: @@ -98,7 +99,12 @@ def update_python_pip(target: Path) -> None: """ Logger.print_status("Updating pip ...") try: - command = [target.joinpath("bin/pip"), "install", "-U", "pip"] + pip_location = target.joinpath("bin/pip") + pip_exists = check_file_exist(pip_location) + if not pip_exists: + raise FileNotFoundError("Error updating pip! Not found.") + + command = [pip_location, "install", "-U", "pip"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) if result.returncode != 0 or result.stderr: Logger.print_error(f"{result.stderr}", False) @@ -106,8 +112,12 @@ def update_python_pip(target: Path) -> None: return Logger.print_ok("Updating pip successfull!") + except FileNotFoundError as e: + Logger.print_error(e) + raise except subprocess.CalledProcessError as e: Logger.print_error(f"Error updating pip:\n{e.output.decode()}") + raise def install_python_requirements(target: Path, requirements: Path) -> None: @@ -117,9 +127,9 @@ def install_python_requirements(target: Path, requirements: Path) -> None: :param requirements: Path to the requirements.txt file :return: None """ - update_python_pip(target) Logger.print_status("Installing Python requirements ...") try: + update_python_pip(target) command = [target.joinpath("bin/pip"), "install", "-r", f"{requirements}"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) if result.returncode != 0 or result.stderr: From 573dc7c3c970a4f395e293001b486817f8599794 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 18 Feb 2024 22:08:21 +0100 Subject: [PATCH 108/137] refactor(Mainsail): use urllib.request instead of requests module requests is actually not part of the python 3.8 standard library, hence we use urllib.request now, which is. Signed-off-by: Dominik Willner --- kiauh/components/mainsail/mainsail_utils.py | 12 ++++++++---- kiauh/core/menus/update_menu.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/kiauh/components/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py index 3cb7102..b2aa32b 100644 --- a/kiauh/components/mainsail/mainsail_utils.py +++ b/kiauh/components/mainsail/mainsail_utils.py @@ -11,10 +11,11 @@ import json import shutil +from json import JSONDecodeError from pathlib import Path from typing import List -import requests +import urllib.request from kiauh.components.klipper.klipper import Klipper from kiauh.components.mainsail import ( @@ -99,9 +100,12 @@ def get_mainsail_local_version() -> str: def get_mainsail_remote_version() -> str: url = "https://api.github.com/repos/mainsail-crew/mainsail/tags" - response = requests.get(url) - data = json.loads(response.text) - return data[0]["name"] + try: + with urllib.request.urlopen(url) as response: + data = json.loads(response.read()) + return data[0]["name"] + except (JSONDecodeError, TypeError): + return "ERROR" def backup_mainsail_data() -> None: diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index f2951dd..ba25e07 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -24,7 +24,13 @@ from kiauh.components.moonraker.moonraker_setup import update_moonraker from kiauh.components.moonraker.moonraker_utils import get_moonraker_status from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_WHITE +from kiauh.utils.constants import ( + COLOR_GREEN, + RESET_FORMAT, + COLOR_YELLOW, + COLOR_WHITE, + COLOR_RED, +) # noinspection PyUnusedLocal @@ -159,4 +165,4 @@ class UpdateMenu(BaseMenu): self.ms_local = f"{COLOR_GREEN}{self.ms_local}{RESET_FORMAT}" else: self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}" - self.ms_remote = f"{COLOR_GREEN}{self.ms_remote}{RESET_FORMAT}" + self.ms_remote = f"{COLOR_GREEN if self.ms_remote != 'ERROR' else COLOR_RED}{self.ms_remote}{RESET_FORMAT}" From 2a4fcf3a3a821fbe2078c2ef312d5221f4eefb55 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 18 Feb 2024 22:30:28 +0100 Subject: [PATCH 109/137] refactor(KIAUH): add application root to sys path shortens imports and helps with auto imports from IDE Signed-off-by: Dominik Willner --- kiauh/__init__.py | 8 +++-- kiauh/components/klipper/__init__.py | 2 +- kiauh/components/klipper/klipper.py | 8 ++--- kiauh/components/klipper/klipper_dialogs.py | 6 ++-- kiauh/components/klipper/klipper_remove.py | 14 ++++---- kiauh/components/klipper/klipper_setup.py | 22 ++++++------- kiauh/components/klipper/klipper_utils.py | 32 +++++++++---------- .../klipper/menus/klipper_remove_menu.py | 8 ++--- .../log_uploads/log_upload_utils.py | 8 ++--- .../log_uploads/menus/log_upload_menu.py | 10 +++--- kiauh/components/mainsail/__init__.py | 2 +- kiauh/components/mainsail/mainsail_dialogs.py | 4 +-- kiauh/components/mainsail/mainsail_remove.py | 18 +++++------ kiauh/components/mainsail/mainsail_setup.py | 28 ++++++++-------- kiauh/components/mainsail/mainsail_utils.py | 12 +++---- .../mainsail/menus/mainsail_remove_menu.py | 8 ++--- kiauh/components/moonraker/__init__.py | 2 +- .../moonraker/menus/moonraker_remove_menu.py | 8 ++--- kiauh/components/moonraker/moonraker.py | 10 +++--- .../components/moonraker/moonraker_dialogs.py | 8 ++--- .../components/moonraker/moonraker_remove.py | 14 ++++---- kiauh/components/moonraker/moonraker_setup.py | 30 ++++++++--------- kiauh/components/moonraker/moonraker_utils.py | 22 ++++++------- kiauh/core/backup_manager/backup_manager.py | 6 ++-- kiauh/core/config_manager/config_manager.py | 2 +- kiauh/core/instance_manager/base_instance.py | 2 +- .../core/instance_manager/instance_manager.py | 6 ++-- kiauh/core/menus/advanced_menu.py | 6 ++-- kiauh/core/menus/backup_menu.py | 14 ++++---- kiauh/core/menus/base_menu.py | 6 ++-- kiauh/core/menus/extensions_menu.py | 8 ++--- kiauh/core/menus/install_menu.py | 13 ++++---- kiauh/core/menus/remove_menu.py | 12 +++---- kiauh/core/menus/settings_menu.py | 2 +- kiauh/core/repo_manager/repo_manager.py | 4 +-- .../gcode_shell_cmd_extension.py | 18 +++++------ kiauh/main.py | 4 +-- kiauh/utils/__init__.py | 2 +- kiauh/utils/common.py | 18 +++++------ kiauh/utils/filesystem_utils.py | 4 +-- kiauh/utils/input_utils.py | 6 ++-- kiauh/utils/logger.py | 2 +- kiauh/utils/system_utils.py | 6 ++-- 43 files changed, 215 insertions(+), 210 deletions(-) diff --git a/kiauh/__init__.py b/kiauh/__init__.py index 91fd073..f93f7de 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -9,7 +9,11 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +import sys from pathlib import Path -APPLICATION_ROOT = Path(__file__).resolve().parent.parent -KIAUH_CFG = APPLICATION_ROOT.joinpath("kiauh.cfg") +PROJECT_ROOT = Path(__file__).resolve().parent.parent +KIAUH_CFG = PROJECT_ROOT.joinpath("kiauh.cfg") + +APPLICATION_ROOT = Path(__file__).resolve().parent +sys.path.append(str(APPLICATION_ROOT)) diff --git a/kiauh/components/klipper/__init__.py b/kiauh/components/klipper/__init__.py index ff3ae20..9f46783 100644 --- a/kiauh/components/klipper/__init__.py +++ b/kiauh/components/klipper/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path -from kiauh.core.backup_manager import BACKUP_ROOT_DIR +from core.backup_manager import BACKUP_ROOT_DIR MODULE_PATH = Path(__file__).resolve().parent diff --git a/kiauh/components/klipper/klipper.py b/kiauh/components/klipper/klipper.py index 1b442c6..05eef56 100644 --- a/kiauh/components/klipper/klipper.py +++ b/kiauh/components/klipper/klipper.py @@ -13,10 +13,10 @@ import subprocess from pathlib import Path from typing import List -from kiauh.components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.utils.constants import SYSTEMD -from kiauh.utils.logger import Logger +from components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH +from core.instance_manager.base_instance import BaseInstance +from utils.constants import SYSTEMD +from utils.logger import Logger # noinspection PyMethodMayBeStatic diff --git a/kiauh/components/klipper/klipper_dialogs.py b/kiauh/components/klipper/klipper_dialogs.py index 94e9374..30892c5 100644 --- a/kiauh/components/klipper/klipper_dialogs.py +++ b/kiauh/components/klipper/klipper_dialogs.py @@ -12,9 +12,9 @@ import textwrap from typing import List -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.core.menus.base_menu import print_back_footer -from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN +from core.instance_manager.base_instance import BaseInstance +from core.menus.base_menu import print_back_footer +from utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN def print_instance_overview( diff --git a/kiauh/components/klipper/klipper_remove.py b/kiauh/components/klipper/klipper_remove.py index 61b007c..d802d4b 100644 --- a/kiauh/components/klipper/klipper_remove.py +++ b/kiauh/components/klipper/klipper_remove.py @@ -12,13 +12,13 @@ import shutil from typing import List, Union -from kiauh.components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.klipper.klipper_dialogs import print_instance_overview -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.utils.filesystem_utils import remove_file -from kiauh.utils.input_utils import get_selection_input -from kiauh.utils.logger import Logger +from components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR +from components.klipper.klipper import Klipper +from components.klipper.klipper_dialogs import print_instance_overview +from core.instance_manager.instance_manager import InstanceManager +from utils.filesystem_utils import remove_file +from utils.input_utils import get_selection_input +from utils.logger import Logger def run_klipper_removal( diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index 2cf5690..e9761e9 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -12,16 +12,16 @@ from pathlib import Path from kiauh import KIAUH_CFG -from kiauh.components.klipper import ( +from components.klipper import ( EXIT_KLIPPER_SETUP, DEFAULT_KLIPPER_REPO_URL, KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT, ) -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.klipper.klipper_dialogs import print_update_warn_dialog -from kiauh.components.klipper.klipper_utils import ( +from components.klipper.klipper import Klipper +from components.klipper.klipper_dialogs import print_update_warn_dialog +from components.klipper.klipper_utils import ( handle_disruptive_system_packages, check_user_groups, handle_to_multi_instance_conversion, @@ -34,13 +34,13 @@ from kiauh.components.klipper.klipper_utils import ( handle_instance_naming, backup_klipper_dir, ) -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.utils.input_utils import get_confirm -from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import ( +from components.moonraker.moonraker import Moonraker +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from core.repo_manager.repo_manager import RepoManager +from utils.input_utils import get_confirm +from utils.logger import Logger +from utils.system_utils import ( parse_packages_from_file, create_python_venv, install_python_requirements, diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 564d7f7..e2ff73a 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -18,32 +18,32 @@ import textwrap from pathlib import Path from typing import List, Union, Literal, Dict -from kiauh.components.klipper import ( +from components.klipper import ( MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_BACKUP_DIR, ) -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.klipper.klipper_dialogs import ( +from components.klipper.klipper import Klipper +from components.klipper.klipper_dialogs import ( print_missing_usergroup_dialog, print_instance_overview, print_select_instance_count_dialog, print_select_custom_name_dialog, ) -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.components.moonraker.moonraker_utils import moonraker_to_multi_conversion -from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.instance_manager.name_scheme import NameScheme -from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.utils.common import get_install_status_common -from kiauh.utils.constants import CURRENT_USER -from kiauh.utils.input_utils import get_confirm, get_string_input, get_number_input -from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import mask_system_service +from components.moonraker.moonraker import Moonraker +from components.moonraker.moonraker_utils import moonraker_to_multi_conversion +from core.backup_manager.backup_manager import BackupManager +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.base_instance import BaseInstance +from core.instance_manager.instance_manager import InstanceManager +from core.instance_manager.name_scheme import NameScheme +from core.repo_manager.repo_manager import RepoManager +from utils.common import get_install_status_common +from utils.constants import CURRENT_USER +from utils.input_utils import get_confirm, get_string_input, get_number_input +from utils.logger import Logger +from utils.system_utils import mask_system_service def get_klipper_status() -> Dict[ diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index a36d51b..fc953ab 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -11,10 +11,10 @@ import textwrap -from kiauh.components.klipper import klipper_remove -from kiauh.core.menus import BACK_HELP_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN +from components.klipper import klipper_remove +from core.menus import BACK_HELP_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal diff --git a/kiauh/components/log_uploads/log_upload_utils.py b/kiauh/components/log_uploads/log_upload_utils.py index 4247a0e..daa67e4 100644 --- a/kiauh/components/log_uploads/log_upload_utils.py +++ b/kiauh/components/log_uploads/log_upload_utils.py @@ -13,10 +13,10 @@ import urllib.request from pathlib import Path from typing import List -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.log_uploads import LogFile -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.utils.logger import Logger +from components.klipper.klipper import Klipper +from components.log_uploads import LogFile +from core.instance_manager.instance_manager import InstanceManager +from utils.logger import Logger def get_logfile_list() -> List[LogFile]: diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index 11bb65e..baccce5 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -11,11 +11,11 @@ import textwrap -from kiauh.components.log_uploads.log_upload_utils import get_logfile_list -from kiauh.components.log_uploads.log_upload_utils import upload_logfile -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW +from components.log_uploads.log_upload_utils import get_logfile_list +from components.log_uploads.log_upload_utils import upload_logfile +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_YELLOW # noinspection PyMethodMayBeStatic diff --git a/kiauh/components/mainsail/__init__.py b/kiauh/components/mainsail/__init__.py index 0b2d2af..53a9924 100644 --- a/kiauh/components/mainsail/__init__.py +++ b/kiauh/components/mainsail/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path -from kiauh.core.backup_manager import BACKUP_ROOT_DIR +from core.backup_manager import BACKUP_ROOT_DIR MODULE_PATH = Path(__file__).resolve().parent MAINSAIL_DIR = Path.home().joinpath("mainsail") diff --git a/kiauh/components/mainsail/mainsail_dialogs.py b/kiauh/components/mainsail/mainsail_dialogs.py index 6c8ea69..36453ff 100644 --- a/kiauh/components/mainsail/mainsail_dialogs.py +++ b/kiauh/components/mainsail/mainsail_dialogs.py @@ -11,8 +11,8 @@ import textwrap -from kiauh.core.menus.base_menu import print_back_footer -from kiauh.utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN +from core.menus.base_menu import print_back_footer +from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN def print_moonraker_not_found_dialog(): diff --git a/kiauh/components/mainsail/mainsail_remove.py b/kiauh/components/mainsail/mainsail_remove.py index 3a4f797..204e8a6 100644 --- a/kiauh/components/mainsail/mainsail_remove.py +++ b/kiauh/components/mainsail/mainsail_remove.py @@ -15,15 +15,15 @@ import subprocess from pathlib import Path from typing import List -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR -from kiauh.components.mainsail.mainsail_utils import backup_config_json -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED -from kiauh.utils.filesystem_utils import remove_file -from kiauh.utils.logger import Logger +from components.klipper.klipper import Klipper +from components.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR +from components.mainsail.mainsail_utils import backup_config_json +from components.moonraker.moonraker import Moonraker +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED +from utils.filesystem_utils import remove_file +from utils.logger import Logger def run_mainsail_removal( diff --git a/kiauh/components/mainsail/mainsail_setup.py b/kiauh/components/mainsail/mainsail_setup.py index e370847..210dc9e 100644 --- a/kiauh/components/mainsail/mainsail_setup.py +++ b/kiauh/components/mainsail/mainsail_setup.py @@ -14,33 +14,33 @@ from pathlib import Path from typing import List from kiauh import KIAUH_CFG -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.mainsail import ( +from components.klipper.klipper import Klipper +from components.mainsail import ( MAINSAIL_URL, MAINSAIL_DIR, MAINSAIL_CONFIG_DIR, MAINSAIL_CONFIG_REPO_URL, MODULE_PATH, ) -from kiauh.components.mainsail.mainsail_dialogs import ( +from components.mainsail.mainsail_dialogs import ( print_moonraker_not_found_dialog, print_mainsail_already_installed_dialog, print_install_mainsail_config_dialog, print_mainsail_port_select_dialog, ) -from kiauh.components.mainsail.mainsail_utils import ( +from components.mainsail.mainsail_utils import ( restore_config_json, enable_mainsail_remotemode, backup_config_json, symlink_webui_nginx_log, ) -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED -from kiauh.utils.common import check_install_dependencies -from kiauh.utils.filesystem_utils import ( +from components.moonraker.moonraker import Moonraker +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from core.repo_manager.repo_manager import RepoManager +from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED +from utils.common import check_install_dependencies +from utils.filesystem_utils import ( unzip, copy_upstream_nginx_cfg, copy_common_vars_nginx_cfg, @@ -48,9 +48,9 @@ from kiauh.utils.filesystem_utils import ( create_symlink, remove_file, ) -from kiauh.utils.input_utils import get_confirm, get_number_input -from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import ( +from utils.input_utils import get_confirm, get_number_input +from utils.logger import Logger +from utils.system_utils import ( download_file, set_nginx_permissions, get_ipv4_addr, diff --git a/kiauh/components/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py index b2aa32b..321f326 100644 --- a/kiauh/components/mainsail/mainsail_utils.py +++ b/kiauh/components/mainsail/mainsail_utils.py @@ -17,16 +17,16 @@ from typing import List import urllib.request -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.mainsail import ( +from components.klipper.klipper import Klipper +from components.mainsail import ( MAINSAIL_CONFIG_JSON, MAINSAIL_DIR, MAINSAIL_BACKUP_DIR, ) -from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.utils import NGINX_SITES_AVAILABLE, NGINX_CONFD -from kiauh.utils.common import get_install_status_webui -from kiauh.utils.logger import Logger +from core.backup_manager.backup_manager import BackupManager +from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD +from utils.common import get_install_status_webui +from utils.logger import Logger def get_mainsail_status() -> str: diff --git a/kiauh/components/mainsail/menus/mainsail_remove_menu.py b/kiauh/components/mainsail/menus/mainsail_remove_menu.py index 187d974..48cfb4b 100644 --- a/kiauh/components/mainsail/menus/mainsail_remove_menu.py +++ b/kiauh/components/mainsail/menus/mainsail_remove_menu.py @@ -11,10 +11,10 @@ import textwrap -from kiauh.components.mainsail import mainsail_remove -from kiauh.core.menus import BACK_HELP_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN +from components.mainsail import mainsail_remove +from core.menus import BACK_HELP_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal diff --git a/kiauh/components/moonraker/__init__.py b/kiauh/components/moonraker/__init__.py index e42f3e9..9341992 100644 --- a/kiauh/components/moonraker/__init__.py +++ b/kiauh/components/moonraker/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path -from kiauh.core.backup_manager import BACKUP_ROOT_DIR +from core.backup_manager import BACKUP_ROOT_DIR MODULE_PATH = Path(__file__).resolve().parent diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index 962e07d..652d028 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -11,10 +11,10 @@ import textwrap -from kiauh.components.moonraker import moonraker_remove -from kiauh.core.menus import BACK_HELP_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN +from components.moonraker import moonraker_remove +from core.menus import BACK_HELP_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal diff --git a/kiauh/components/moonraker/moonraker.py b/kiauh/components/moonraker/moonraker.py index c2a9d29..aa0e03b 100644 --- a/kiauh/components/moonraker/moonraker.py +++ b/kiauh/components/moonraker/moonraker.py @@ -13,11 +13,11 @@ import subprocess from pathlib import Path from typing import List, Union -from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.utils.constants import SYSTEMD -from kiauh.utils.logger import Logger +from components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.base_instance import BaseInstance +from utils.constants import SYSTEMD +from utils.logger import Logger # noinspection PyMethodMayBeStatic diff --git a/kiauh/components/moonraker/moonraker_dialogs.py b/kiauh/components/moonraker/moonraker_dialogs.py index 2bc18b2..4f13d10 100644 --- a/kiauh/components/moonraker/moonraker_dialogs.py +++ b/kiauh/components/moonraker/moonraker_dialogs.py @@ -12,10 +12,10 @@ import textwrap from typing import List -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.core.menus.base_menu import print_back_footer -from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN +from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker +from core.menus.base_menu import print_back_footer +from utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN def print_moonraker_overview( diff --git a/kiauh/components/moonraker/moonraker_remove.py b/kiauh/components/moonraker/moonraker_remove.py index 02af9dd..db10bf4 100644 --- a/kiauh/components/moonraker/moonraker_remove.py +++ b/kiauh/components/moonraker/moonraker_remove.py @@ -13,13 +13,13 @@ import shutil import subprocess from typing import List, Union -from kiauh.components.klipper.klipper_dialogs import print_instance_overview -from kiauh.components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.utils.filesystem_utils import remove_file -from kiauh.utils.input_utils import get_selection_input -from kiauh.utils.logger import Logger +from components.klipper.klipper_dialogs import print_instance_overview +from components.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR +from components.moonraker.moonraker import Moonraker +from core.instance_manager.instance_manager import InstanceManager +from utils.filesystem_utils import remove_file +from utils.input_utils import get_selection_input +from utils.logger import Logger def run_moonraker_removal( diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index f041a0e..fe635f8 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -15,11 +15,11 @@ from pathlib import Path from typing import List from kiauh import KIAUH_CFG -from kiauh.components.klipper.klipper import Klipper -from kiauh.components.klipper.klipper_dialogs import print_instance_overview -from kiauh.components.mainsail import MAINSAIL_DIR -from kiauh.components.mainsail.mainsail_utils import enable_mainsail_remotemode -from kiauh.components.moonraker import ( +from components.klipper.klipper import Klipper +from components.klipper.klipper_dialogs import print_instance_overview +from components.mainsail import MAINSAIL_DIR +from components.mainsail.mainsail_utils import enable_mainsail_remotemode +from components.moonraker import ( EXIT_MOONRAKER_SETUP, DEFAULT_MOONRAKER_REPO_URL, MOONRAKER_DIR, @@ -30,22 +30,22 @@ from kiauh.components.moonraker import ( POLKIT_USR_FILE, POLKIT_SCRIPT, ) -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.components.moonraker.moonraker_dialogs import print_moonraker_overview -from kiauh.components.moonraker.moonraker_utils import ( +from components.moonraker.moonraker import Moonraker +from components.moonraker.moonraker_dialogs import print_moonraker_overview +from components.moonraker.moonraker_utils import ( create_example_moonraker_conf, backup_moonraker_dir, ) -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.utils.filesystem_utils import check_file_exist -from kiauh.utils.input_utils import ( +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from core.repo_manager.repo_manager import RepoManager +from utils.filesystem_utils import check_file_exist +from utils.input_utils import ( get_confirm, get_selection_input, ) -from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import ( +from utils.logger import Logger +from utils.system_utils import ( parse_packages_from_file, create_python_venv, install_python_requirements, diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index 0a9ae11..3321781 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -12,9 +12,9 @@ import shutil from typing import Dict, Literal, List, Union -from kiauh.components.mainsail import MAINSAIL_DIR -from kiauh.components.mainsail.mainsail_utils import enable_mainsail_remotemode -from kiauh.components.moonraker import ( +from components.mainsail import MAINSAIL_DIR +from components.mainsail.mainsail_utils import enable_mainsail_remotemode +from components.moonraker import ( DEFAULT_MOONRAKER_PORT, MODULE_PATH, MOONRAKER_DIR, @@ -22,14 +22,14 @@ from kiauh.components.moonraker import ( MOONRAKER_BACKUP_DIR, MOONRAKER_DB_BACKUP_DIR, ) -from kiauh.components.moonraker.moonraker import Moonraker -from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.core.repo_manager.repo_manager import RepoManager -from kiauh.utils.common import get_install_status_common -from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import ( +from components.moonraker.moonraker import Moonraker +from core.backup_manager.backup_manager import BackupManager +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from core.repo_manager.repo_manager import RepoManager +from utils.common import get_install_status_common +from utils.logger import Logger +from utils.system_utils import ( get_ipv4_addr, ) diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 8f2dbcf..1c27266 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -13,9 +13,9 @@ import shutil from pathlib import Path from typing import List -from kiauh.core.backup_manager import BACKUP_ROOT_DIR -from kiauh.utils.common import get_current_date -from kiauh.utils.logger import Logger +from core.backup_manager import BACKUP_ROOT_DIR +from utils.common import get_current_date +from utils.logger import Logger # noinspection PyUnusedLocal diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 63a4490..12d4eb0 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -13,7 +13,7 @@ import configparser from pathlib import Path from typing import Union -from kiauh.utils.logger import Logger +from utils.logger import Logger # noinspection PyMethodMayBeStatic diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 571985e..95e24dc 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -13,7 +13,7 @@ from abc import abstractmethod, ABC from pathlib import Path from typing import List, Type, TypeVar -from kiauh.utils.constants import SYSTEMD, CURRENT_USER +from utils.constants import SYSTEMD, CURRENT_USER B = TypeVar(name="B", bound="BaseInstance", covariant=True) diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 0a920d6..9750086 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -14,9 +14,9 @@ import subprocess from pathlib import Path from typing import List, Optional, Union, TypeVar -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.utils.constants import SYSTEMD -from kiauh.utils.logger import Logger +from core.instance_manager.base_instance import BaseInstance +from utils.constants import SYSTEMD +from utils.logger import Logger I = TypeVar(name="I", bound=BaseInstance, covariant=True) diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 533b5c1..27eaacd 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -11,9 +11,9 @@ import textwrap -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import COLOR_YELLOW, RESET_FORMAT class AdvancedMenu(BaseMenu): diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index 184e26c..8b92978 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -11,16 +11,16 @@ import textwrap -from kiauh.components.klipper.klipper_utils import backup_klipper_dir -from kiauh.components.mainsail.mainsail_utils import backup_mainsail_data -from kiauh.components.moonraker.moonraker_utils import ( +from components.klipper.klipper_utils import backup_klipper_dir +from components.mainsail.mainsail_utils import backup_mainsail_data +from components.moonraker.moonraker_utils import ( backup_moonraker_dir, backup_moonraker_db_dir, ) -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.common import backup_printer_config_dir -from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.common import backup_printer_config_dir +from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW # noinspection PyUnusedLocal diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 89ed09b..06ad576 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -15,15 +15,15 @@ import textwrap from abc import abstractmethod, ABC from typing import Dict, Any, Literal, Union, Callable -from kiauh.core.menus import QUIT_FOOTER, BACK_FOOTER, BACK_HELP_FOOTER -from kiauh.utils.constants import ( +from core.menus import QUIT_FOOTER, BACK_FOOTER, BACK_HELP_FOOTER +from utils.constants import ( COLOR_GREEN, COLOR_YELLOW, COLOR_RED, COLOR_CYAN, RESET_FORMAT, ) -from kiauh.utils.logger import Logger +from utils.logger import Logger def clear(): diff --git a/kiauh/core/menus/extensions_menu.py b/kiauh/core/menus/extensions_menu.py index 50fd53b..a46b00c 100644 --- a/kiauh/core/menus/extensions_menu.py +++ b/kiauh/core/menus/extensions_menu.py @@ -16,10 +16,10 @@ import textwrap from pathlib import Path from typing import List, Dict -from kiauh.core.base_extension import BaseExtension -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW +from core.base_extension import BaseExtension +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW # noinspection PyUnusedLocal diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 85e1ef3..8ab2730 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -11,12 +11,13 @@ import textwrap -from kiauh.components.klipper import klipper_setup -from kiauh.components.mainsail import mainsail_setup -from kiauh.components.moonraker import moonraker_setup -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT +from components.fluidd import fluidd_setup +from components.klipper import klipper_setup +from components.mainsail import mainsail_setup +from components.moonraker import moonraker_setup +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import COLOR_GREEN, RESET_FORMAT # noinspection PyUnusedLocal diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 1bd2eba..3fd94b9 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -11,12 +11,12 @@ import textwrap -from kiauh.components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu -from kiauh.components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu -from kiauh.components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import COLOR_RED, RESET_FORMAT +from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu +from components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu +from components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import COLOR_RED, RESET_FORMAT # noinspection PyUnusedLocal diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index 1305ad1..66f352c 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -9,7 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from kiauh.core.menus.base_menu import BaseMenu +from core.menus.base_menu import BaseMenu # noinspection PyMethodMayBeStatic diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index 9134694..a8f9eec 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -13,8 +13,8 @@ import shutil import subprocess from pathlib import Path -from kiauh.utils.input_utils import get_confirm -from kiauh.utils.logger import Logger +from utils.input_utils import get_confirm +from utils.logger import Logger # noinspection PyMethodMayBeStatic diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py index a9798e2..bf0a977 100644 --- a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -13,21 +13,21 @@ import os import shutil from typing import List -from kiauh.components.klipper.klipper import Klipper -from kiauh.core.backup_manager.backup_manager import BackupManager -from kiauh.core.base_extension import BaseExtension -from kiauh.core.config_manager.config_manager import ConfigManager -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.extensions.gcode_shell_cmd import ( +from components.klipper.klipper import Klipper +from core.backup_manager.backup_manager import BackupManager +from core.base_extension import BaseExtension +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from extensions.gcode_shell_cmd import ( EXTENSION_TARGET_PATH, EXTENSION_SRC, KLIPPER_DIR, EXAMPLE_CFG_SRC, KLIPPER_EXTRAS, ) -from kiauh.utils.filesystem_utils import check_file_exist -from kiauh.utils.input_utils import get_confirm -from kiauh.utils.logger import Logger +from utils.filesystem_utils import check_file_exist +from utils.input_utils import get_confirm +from utils.logger import Logger # noinspection PyMethodMayBeStatic diff --git a/kiauh/main.py b/kiauh/main.py index 0dd1b31..0ca361c 100644 --- a/kiauh/main.py +++ b/kiauh/main.py @@ -9,8 +9,8 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from kiauh.core.menus.main_menu import MainMenu -from kiauh.utils.logger import Logger +from core.menus.main_menu import MainMenu +from utils.logger import Logger def main(): diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index 319fc15..afc69aa 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path -from kiauh.core.backup_manager import BACKUP_ROOT_DIR +from core.backup_manager import BACKUP_ROOT_DIR MODULE_PATH = Path(__file__).resolve().parent INVALID_CHOICE = "Invalid choice. Please select a valid value." diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 6557cd0..4e5bbee 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -13,20 +13,20 @@ from datetime import datetime from pathlib import Path from typing import Dict, Literal, List, Type, Union -from kiauh.components.klipper.klipper import Klipper -from kiauh.core.instance_manager.base_instance import BaseInstance -from kiauh.core.instance_manager.instance_manager import InstanceManager -from kiauh.utils import PRINTER_CFG_BACKUP_DIR -from kiauh.utils.constants import ( +from components.klipper.klipper import Klipper +from core.instance_manager.base_instance import BaseInstance +from core.instance_manager.instance_manager import InstanceManager +from utils import PRINTER_CFG_BACKUP_DIR +from utils.constants import ( COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW, COLOR_GREEN, COLOR_RED, ) -from kiauh.utils.filesystem_utils import check_file_exist -from kiauh.utils.logger import Logger -from kiauh.utils.system_utils import check_package_install, install_system_packages +from utils.filesystem_utils import check_file_exist +from utils.logger import Logger +from utils.system_utils import check_package_install, install_system_packages def get_current_date() -> Dict[Literal["date", "time"], str]: @@ -122,7 +122,7 @@ def get_install_status_webui( def backup_printer_config_dir(): # local import to prevent circular import - from kiauh.core.backup_manager.backup_manager import BackupManager + from core.backup_manager.backup_manager import BackupManager im = InstanceManager(Klipper) instances: List[Klipper] = im.instances diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 68ce432..57b414f 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -13,12 +13,12 @@ import subprocess from pathlib import Path from zipfile import ZipFile -from kiauh.utils import ( +from utils import ( NGINX_SITES_AVAILABLE, MODULE_PATH, NGINX_CONFD, ) -from kiauh.utils.logger import Logger +from utils.logger import Logger def check_file_exist(file_path: Path, sudo=False) -> bool: diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index e2daf8e..d0a3b48 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -11,9 +11,9 @@ from typing import Optional, List, Union -from kiauh.utils import INVALID_CHOICE -from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT -from kiauh.utils.logger import Logger +from utils import INVALID_CHOICE +from utils.constants import COLOR_CYAN, RESET_FORMAT +from utils.logger import Logger def get_confirm( diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index fcd0c9b..66f9326 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -9,7 +9,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # -from kiauh.utils.constants import ( +from utils.constants import ( COLOR_WHITE, COLOR_GREEN, COLOR_YELLOW, diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 352d575..718607a 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -21,9 +21,9 @@ import venv from pathlib import Path from typing import List, Literal -from kiauh.utils.input_utils import get_confirm -from kiauh.utils.logger import Logger -from kiauh.utils.filesystem_utils import check_file_exist +from utils.input_utils import get_confirm +from utils.logger import Logger +from utils.filesystem_utils import check_file_exist def kill(opt_err_msg: str = "") -> None: From 384503c4f58246e3c7189da21aa5715be6b427f9 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 24 Feb 2024 15:26:32 +0100 Subject: [PATCH 110/137] feat(Fluidd): add Fluidd Signed-off-by: Dominik Willner --- kiauh.cfg.example | 6 +- kiauh/components/fluidd/__init__.py | 26 ++ .../fluidd/assets/fluidd-config-updater.conf | 6 + .../fluidd/assets/fluidd-updater.conf | 5 + kiauh/components/fluidd/fluidd_dialogs.py | 104 ++++++++ kiauh/components/fluidd/fluidd_remove.py | 160 ++++++++++++ kiauh/components/fluidd/fluidd_setup.py | 242 ++++++++++++++++++ kiauh/components/fluidd/fluidd_utils.py | 79 ++++++ kiauh/components/fluidd/menus/__init__.py | 0 .../fluidd/menus/fluidd_remove_menu.py | 111 ++++++++ kiauh/core/menus/install_menu.py | 2 +- kiauh/core/menus/main_menu.py | 31 ++- kiauh/core/menus/remove_menu.py | 54 +--- kiauh/core/menus/update_menu.py | 37 ++- kiauh/utils/filesystem_utils.py | 35 +++ 15 files changed, 828 insertions(+), 70 deletions(-) create mode 100644 kiauh/components/fluidd/__init__.py create mode 100644 kiauh/components/fluidd/assets/fluidd-config-updater.conf create mode 100644 kiauh/components/fluidd/assets/fluidd-updater.conf create mode 100644 kiauh/components/fluidd/fluidd_dialogs.py create mode 100644 kiauh/components/fluidd/fluidd_remove.py create mode 100644 kiauh/components/fluidd/fluidd_setup.py create mode 100644 kiauh/components/fluidd/fluidd_utils.py create mode 100644 kiauh/components/fluidd/menus/__init__.py create mode 100644 kiauh/components/fluidd/menus/fluidd_remove_menu.py diff --git a/kiauh.cfg.example b/kiauh.cfg.example index e1724d2..0d61335 100644 --- a/kiauh.cfg.example +++ b/kiauh.cfg.example @@ -12,5 +12,9 @@ branch: master method: https [mainsail] -default_port: 80 +port: 80 +unstable_releases: False + +[fluidd] +port: 80 unstable_releases: False diff --git a/kiauh/components/fluidd/__init__.py b/kiauh/components/fluidd/__init__.py new file mode 100644 index 0000000..70bc91f --- /dev/null +++ b/kiauh/components/fluidd/__init__.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 + +MODULE_PATH = Path(__file__).resolve().parent +FLUIDD_DIR = Path.home().joinpath("fluidd") +FLUIDD_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-backups") +FLUIDD_CONFIG_DIR = Path.home().joinpath("fluidd-config") +FLUIDD_NGINX_CFG = Path("/etc/nginx/sites-enabled/fluidd") +FLUIDD_URL = "https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip" +FLUIDD_UNSTABLE_URL = ( + "https://github.com/fluidd-core/fluidd/releases/download/%TAG%/fluidd.zip" +) +FLUIDD_CONFIG_REPO_URL = "https://github.com/fluidd-core/fluidd-config.git" + diff --git a/kiauh/components/fluidd/assets/fluidd-config-updater.conf b/kiauh/components/fluidd/assets/fluidd-config-updater.conf new file mode 100644 index 0000000..bc4a5cc --- /dev/null +++ b/kiauh/components/fluidd/assets/fluidd-config-updater.conf @@ -0,0 +1,6 @@ +[update_manager fluidd-config] +type: git_repo +primary_branch: master +path: ~/fluidd-config +origin: https://github.com/fluidd-core/fluidd-config.git +managed_services: klipper diff --git a/kiauh/components/fluidd/assets/fluidd-updater.conf b/kiauh/components/fluidd/assets/fluidd-updater.conf new file mode 100644 index 0000000..d9d1e7d --- /dev/null +++ b/kiauh/components/fluidd/assets/fluidd-updater.conf @@ -0,0 +1,5 @@ +[update_manager fluidd] +type: web +channel: stable +repo: fluidd-core/fluidd +path: ~/fluidd diff --git a/kiauh/components/fluidd/fluidd_dialogs.py b/kiauh/components/fluidd/fluidd_dialogs.py new file mode 100644 index 0000000..deec016 --- /dev/null +++ b/kiauh/components/fluidd/fluidd_dialogs.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 textwrap +from typing import List + +from core.menus.base_menu import print_back_footer +from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN + + +def print_moonraker_not_found_dialog(): + line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}No local Moonraker installation was found!{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | {line1:<63}| + | {line2:<63}| + |-------------------------------------------------------| + | It is possible to install Fluidd without a local | + | Moonraker installation. If you continue, you need to | + | make sure, that Moonraker is installed on another | + | machine in your network. Otherwise Fluidd will NOT | + | work correctly. | + """ + )[1:] + + print(dialog, end="") + print_back_footer() + + +def print_fluidd_already_installed_dialog(): + line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}Fluidd seems to be already installed!{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | {line1:<63}| + | {line2:<63}| + |-------------------------------------------------------| + | If you continue, your current Fluidd installation | + | will be overwritten. You will not loose any printer | + | configurations and the Moonraker database will remain | + | untouched. | + """ + )[1:] + + print(dialog, end="") + print_back_footer() + + +def print_install_fluidd_config_dialog(): + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | It is recommended to use special macros in order to | + | have Fluidd fully functional and working. | + | | + | The recommended macros for Fluidd can be seen here: | + | https://github.com/fluidd-core/fluidd-config | + | | + | If you already use these macros skip this step. | + | Otherwise you should consider to answer with 'Y' to | + | download the recommended macros. | + \\=======================================================/ + """ + )[1:] + + print(dialog, end="") + + +def print_fluidd_port_select_dialog(port: str, ports_in_use: List[str]): + port = f"{COLOR_CYAN}{port}{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | Please select the port, Fluidd should be served on. | + | If you are unsure what to select, hit Enter to apply | + | the suggested value of: {port:38} | + | | + | In case you need Fluidd to be served on a specific | + | port, you can set it now. Make sure the port is not | + | used by any other application on your system! | + """ + )[1:] + + if len(ports_in_use) > 0: + dialog += "|-------------------------------------------------------|\n" + dialog += "| The following ports were found to be in use already: |\n" + for port in ports_in_use: + port = f"{COLOR_CYAN}● {port}{RESET_FORMAT}" + dialog += f"| {port:60} |\n" + + dialog += "\\=======================================================/\n" + + print(dialog, end="") diff --git a/kiauh/components/fluidd/fluidd_remove.py b/kiauh/components/fluidd/fluidd_remove.py new file mode 100644 index 0000000..b617cec --- /dev/null +++ b/kiauh/components/fluidd/fluidd_remove.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 +import subprocess +from pathlib import Path +from typing import List + +from components.klipper.klipper import Klipper +from components.fluidd import FLUIDD_DIR, FLUIDD_CONFIG_DIR +from components.moonraker.moonraker import Moonraker +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED +from utils.filesystem_utils import remove_file +from utils.logger import Logger + + +def run_fluidd_removal( + remove_fluidd: bool, + remove_fl_config: bool, + remove_mr_updater_section: bool, + remove_flc_printer_cfg_include: bool, +) -> None: + if remove_fluidd: + remove_fluidd_dir() + remove_nginx_config() + remove_nginx_logs() + if remove_mr_updater_section: + remove_updater_section("update_manager fluidd") + if remove_fl_config: + remove_fluidd_cfg_dir() + remove_fluidd_cfg_symlink() + if remove_mr_updater_section: + remove_updater_section("update_manager fluidd-config") + if remove_flc_printer_cfg_include: + remove_printer_cfg_include() + + +def remove_fluidd_dir() -> None: + Logger.print_status("Removing Fluidd ...") + if not FLUIDD_DIR.exists(): + Logger.print_info(f"'{FLUIDD_DIR}' does not exist. Skipping ...") + return + + try: + shutil.rmtree(FLUIDD_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{FLUIDD_DIR}':\n{e}") + + +def remove_nginx_config() -> None: + Logger.print_status("Removing Fluidd NGINX config ...") + try: + remove_file(NGINX_SITES_AVAILABLE.joinpath("fluidd"), True) + remove_file(NGINX_SITES_ENABLED.joinpath("fluidd"), True) + + except subprocess.CalledProcessError as e: + log = f"Unable to remove Fluidd NGINX config:\n{e.stderr.decode()}" + Logger.print_error(log) + + +def remove_nginx_logs() -> None: + Logger.print_status("Removing Fluidd NGINX logs ...") + try: + remove_file(Path("/var/log/nginx/fluidd-access.log"), True) + remove_file(Path("/var/log/nginx/fluidd-error.log"), True) + + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + if not instances: + return + + for instance in instances: + remove_file(instance.log_dir.joinpath("fluidd-access.log")) + remove_file(instance.log_dir.joinpath("fluidd-error.log")) + + except (OSError, subprocess.CalledProcessError) as e: + Logger.print_error(f"Unable to NGINX logs:\n{e}") + + +def remove_updater_section(name: str) -> None: + Logger.print_status("Remove updater section from moonraker.conf ...") + im = InstanceManager(Moonraker) + instances: List[Moonraker] = im.instances + if not instances: + Logger.print_info("Moonraker not installed. Skipped ...") + return + + for instance in instances: + Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...") + + if not instance.cfg_file.is_file(): + Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...") + continue + + cm = ConfigManager(instance.cfg_file) + if not cm.config.has_section(name): + Logger.print_info("Section not present. Skipped ...") + continue + + cm.config.remove_section(name) + cm.write_config() + + +def remove_fluidd_cfg_dir() -> None: + Logger.print_status("Removing fluidd-config ...") + if not FLUIDD_CONFIG_DIR.exists(): + Logger.print_info(f"'{FLUIDD_CONFIG_DIR}' does not exist. Skipping ...") + return + + try: + shutil.rmtree(FLUIDD_CONFIG_DIR) + except OSError as e: + Logger.print_error(f"Unable to delete '{FLUIDD_CONFIG_DIR}':\n{e}") + + +def remove_fluidd_cfg_symlink() -> None: + Logger.print_status("Removing fluidd.cfg symlinks ...") + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + for instance in instances: + Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") + try: + remove_file(instance.cfg_dir.joinpath("fluidd.cfg")) + except subprocess.CalledProcessError: + Logger.print_error("Failed to remove symlink!") + + +def remove_printer_cfg_include() -> None: + Logger.print_status("Remove fluidd-config include from printer.cfg ...") + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + if not instances: + Logger.print_info("Klipper not installed. Skipping ...") + return + + for instance in instances: + log = f"Removing include from '{instance.cfg_file}' ..." + Logger.print_status(log) + + if not instance.cfg_file.is_file(): + continue + + cm = ConfigManager(instance.cfg_file) + if not cm.config.has_section("include fluidd.cfg"): + Logger.print_info("Section not present. Skipped ...") + continue + + cm.config.remove_section("include fluidd.cfg") + cm.write_config() diff --git a/kiauh/components/fluidd/fluidd_setup.py b/kiauh/components/fluidd/fluidd_setup.py new file mode 100644 index 0000000..7c23bb5 --- /dev/null +++ b/kiauh/components/fluidd/fluidd_setup.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 subprocess +from pathlib import Path +from typing import List + +from components.fluidd import ( + FLUIDD_URL, + FLUIDD_CONFIG_REPO_URL, + FLUIDD_CONFIG_DIR, + FLUIDD_DIR, + MODULE_PATH, +) +from components.fluidd.fluidd_dialogs import ( + print_fluidd_already_installed_dialog, + print_install_fluidd_config_dialog, + print_fluidd_port_select_dialog, + print_moonraker_not_found_dialog, +) +from components.fluidd.fluidd_utils import symlink_webui_nginx_log +from kiauh import KIAUH_CFG +from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from core.repo_manager.repo_manager import RepoManager +from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED +from utils.common import check_install_dependencies +from utils.filesystem_utils import ( + unzip, + copy_upstream_nginx_cfg, + copy_common_vars_nginx_cfg, + create_nginx_cfg, + create_symlink, + remove_file, + read_ports_from_nginx_configs, + get_next_free_port, is_valid_port, + ) +from utils.input_utils import get_confirm, get_number_input +from utils.logger import Logger +from utils.system_utils import ( + download_file, + set_nginx_permissions, + get_ipv4_addr, + control_systemd_service, +) + + +def install_fluidd() -> None: + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances + + if not mr_instances: + print_moonraker_not_found_dialog() + if not get_confirm("Continue Fluidd installation?", allow_go_back=True): + return + + if Path.home().joinpath("fluidd").exists(): + print_fluidd_already_installed_dialog() + do_reinstall = get_confirm("Re-install Fluidd?", allow_go_back=True) + if not do_reinstall: + return + + kl_im = InstanceManager(Klipper) + kl_instances = kl_im.instances + install_fl_config = False + if kl_instances: + print_install_fluidd_config_dialog() + question = "Download the recommended macros?" + install_fl_config = get_confirm(question, allow_go_back=False) + + cm = ConfigManager(cfg_file=KIAUH_CFG) + fluidd_port = cm.get_value("fluidd", "port") + ports_in_use = read_ports_from_nginx_configs() + + # check if configured port is a valid number and not in use already + valid_port = is_valid_port(fluidd_port, ports_in_use) + while not valid_port: + next_port = get_next_free_port(ports_in_use) + print_fluidd_port_select_dialog(next_port, ports_in_use) + fluidd_port = str(get_number_input( + "Configure Fluidd for port", + min_count=int(next_port), + default=next_port, + )) + valid_port = is_valid_port(fluidd_port, ports_in_use) + + check_install_dependencies(["nginx"]) + + try: + download_fluidd() + if mr_instances: + patch_moonraker_conf( + mr_instances, + "Fluidd", + "update_manager fluidd", + "fluidd-updater.conf", + ) + mr_im.restart_all_instance() + if install_fl_config and kl_instances: + download_fluidd_cfg() + create_fluidd_cfg_symlink(kl_instances) + patch_moonraker_conf( + mr_instances, + "fluidd-config", + "update_manager fluidd-config", + "fluidd-config-updater.conf", + ) + patch_printer_config(kl_instances) + kl_im.restart_all_instance() + + copy_upstream_nginx_cfg() + copy_common_vars_nginx_cfg() + create_fluidd_nginx_cfg(fluidd_port) + if kl_instances: + symlink_webui_nginx_log(kl_instances) + control_systemd_service("nginx", "restart") + + except Exception as e: + Logger.print_error(f"Fluidd installation failed!\n{e}") + return + + log = f"Open Fluidd now on: http://{get_ipv4_addr()}:{fluidd_port}" + Logger.print_ok("Fluidd installation complete!", start="\n") + Logger.print_ok(log, prefix=False, end="\n\n") + + +def download_fluidd() -> None: + try: + Logger.print_status("Downloading Fluidd ...") + target = Path.home().joinpath("fluidd.zip") + download_file(FLUIDD_URL, target, True) + Logger.print_ok("Download complete!") + + Logger.print_status("Extracting fluidd.zip ...") + unzip(Path.home().joinpath("fluidd.zip"), FLUIDD_DIR) + target.unlink(missing_ok=True) + Logger.print_ok("OK!") + + except Exception: + Logger.print_error("Downloading Fluidd failed!") + raise + + +def update_fluidd() -> None: + Logger.print_status("Updating Fluidd ...") + download_fluidd() + + +def download_fluidd_cfg() -> None: + try: + Logger.print_status("Downloading fluidd-config ...") + rm = RepoManager(FLUIDD_CONFIG_REPO_URL, target_dir=FLUIDD_CONFIG_DIR) + rm.clone_repo() + except Exception: + Logger.print_error("Downloading fluidd-config failed!") + raise + + +def create_fluidd_cfg_symlink(klipper_instances: List[Klipper]) -> None: + Logger.print_status("Create symlink of fluidd.cfg ...") + source = Path(FLUIDD_CONFIG_DIR, "fluidd.cfg") + for instance in klipper_instances: + target = instance.cfg_dir + Logger.print_status(f"Linking {source} to {target}") + try: + create_symlink(source, target) + except subprocess.CalledProcessError: + Logger.print_error("Creating symlink failed!") + + +def create_fluidd_nginx_cfg(port: int) -> None: + root_dir = FLUIDD_DIR + source = NGINX_SITES_AVAILABLE.joinpath("fluidd") + target = NGINX_SITES_ENABLED.joinpath("fluidd") + try: + Logger.print_status("Creating NGINX config for Fluidd ...") + remove_file(Path("/etc/nginx/sites-enabled/default"), True) + create_nginx_cfg("fluidd", port, root_dir) + create_symlink(source, target, True) + set_nginx_permissions() + Logger.print_ok("NGINX config for Fluidd successfully created.") + except Exception: + Logger.print_error("Creating NGINX config for Fluidd failed!") + raise + + +# TODO: could be fully extracted, its webui agnostic, and used for mainsail and fluidd +def patch_moonraker_conf( + moonraker_instances: List[Moonraker], + name: str, + section_name: str, + template_file: str, +) -> None: + for instance in moonraker_instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Add {name} update section to '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + return + + cm = ConfigManager(cfg_file) + if cm.config.has_section(section_name): + Logger.print_info("Section already exist. Skipped ...") + return + + template = MODULE_PATH.joinpath("assets", template_file) + with open(template, "r") as t: + template_content = "\n" + template_content += t.read() + + with open(cfg_file, "a") as f: + f.write(template_content) + + +# TODO: could be made fully webui agnostic and extracted, and used for mainsail and fluidd +def patch_printer_config(klipper_instances: List[Klipper]) -> None: + for instance in klipper_instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Including fluidd-config in '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + return + + cm = ConfigManager(cfg_file) + if cm.config.has_section("include fluidd.cfg"): + Logger.print_info("Section already exist. Skipped ...") + return + + with open(cfg_file, "a") as f: + f.write("\n[include fluidd.cfg]") diff --git a/kiauh/components/fluidd/fluidd_utils.py b/kiauh/components/fluidd/fluidd_utils.py new file mode 100644 index 0000000..c9c672b --- /dev/null +++ b/kiauh/components/fluidd/fluidd_utils.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 json +import urllib.request +from json import JSONDecodeError +from pathlib import Path +from typing import List + +from components.fluidd import FLUIDD_DIR, FLUIDD_BACKUP_DIR +from components.klipper.klipper import Klipper +from core.backup_manager.backup_manager import BackupManager +from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD +from utils.common import get_install_status_webui +from utils.logger import Logger + + +# TODO: could be extracted and made generic +def get_fluidd_status() -> str: + return get_install_status_webui( + FLUIDD_DIR, + NGINX_SITES_AVAILABLE.joinpath("fluidd"), + NGINX_CONFD.joinpath("upstreams.conf"), + NGINX_CONFD.joinpath("common_vars.conf"), + ) + + +# TODO: could be extracted and made generic +def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: + Logger.print_status("Link NGINX logs into log directory ...") + access_log = Path("/var/log/nginx/fluidd-access.log") + error_log = Path("/var/log/nginx/fluidd-error.log") + + for instance in klipper_instances: + desti_access = instance.log_dir.joinpath("fluidd-access.log") + if not desti_access.exists(): + desti_access.symlink_to(access_log) + + desti_error = instance.log_dir.joinpath("fluidd-error.log") + if not desti_error.exists(): + desti_error.symlink_to(error_log) + + +# TODO: could be extracted and made generic +def get_fluidd_local_version() -> str: + relinfo_file = FLUIDD_DIR.joinpath("release_info.json") + if not relinfo_file.is_file(): + return "-" + + with open(relinfo_file, "r") as f: + return json.load(f)["version"] + + +# TODO: could be extracted and made generic +def get_fluidd_remote_version() -> str: + url = "https://api.github.com/repos/fluidd-core/fluidd/tags" + try: + with urllib.request.urlopen(url) as response: + data = json.loads(response.read()) + return data[0]["name"] + except (JSONDecodeError, TypeError): + return "ERROR" + + +# TODO: could be extracted and made generic +def backup_fluidd_data() -> None: + with open(FLUIDD_DIR.joinpath(".version"), "r") as v: + version = v.readlines()[0] + bm = BackupManager() + bm.backup_directory(f"fluidd-{version}", FLUIDD_DIR, FLUIDD_BACKUP_DIR) + bm.backup_file(NGINX_SITES_AVAILABLE.joinpath("fluidd"), FLUIDD_BACKUP_DIR) diff --git a/kiauh/components/fluidd/menus/__init__.py b/kiauh/components/fluidd/menus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/components/fluidd/menus/fluidd_remove_menu.py b/kiauh/components/fluidd/menus/fluidd_remove_menu.py new file mode 100644 index 0000000..4671799 --- /dev/null +++ b/kiauh/components/fluidd/menus/fluidd_remove_menu.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 textwrap + +from components.fluidd import fluidd_remove +from core.menus import BACK_HELP_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN + + +# noinspection PyUnusedLocal +class FluiddRemoveMenu(BaseMenu): + def __init__(self): + super().__init__( + header=False, + options={ + "0": self.toggle_all, + "1": self.toggle_remove_fluidd, + "2": self.toggle_remove_fl_config, + "3": self.toggle_remove_updater_section, + "4": self.toggle_remove_printer_cfg_include, + "5": self.run_removal_process, + }, + footer_type=BACK_HELP_FOOTER, + ) + self.remove_fluidd = False + self.remove_fl_config = False + self.remove_updater_section = False + self.remove_printer_cfg_include = False + + def print_menu(self) -> None: + header = " [ Remove Fluidd ] " + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) + checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + unchecked = "[ ]" + o1 = checked if self.remove_fluidd else unchecked + o2 = checked if self.remove_fl_config else unchecked + o3 = checked if self.remove_updater_section else unchecked + o4 = checked if self.remove_printer_cfg_include 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 Fluidd | + | 2) {o2} Remove fluidd-config | + | | + | printer.cfg & moonraker.conf | + | 3) {o3} Remove Moonraker update section | + | 4) {o4} Remove printer.cfg include | + |-------------------------------------------------------| + | 5) Continue | + """ + )[1:] + print(menu, end="") + + def toggle_all(self, **kwargs) -> None: + self.remove_fluidd = True + self.remove_fl_config = True + self.remove_updater_section = True + self.remove_printer_cfg_include = True + + def toggle_remove_fluidd(self, **kwargs) -> None: + self.remove_fluidd = not self.remove_fluidd + + def toggle_remove_fl_config(self, **kwargs) -> None: + self.remove_fl_config = not self.remove_fl_config + + def toggle_remove_updater_section(self, **kwargs) -> None: + self.remove_updater_section = not self.remove_updater_section + + def toggle_remove_printer_cfg_include(self, **kwargs) -> None: + self.remove_printer_cfg_include = not self.remove_printer_cfg_include + + def run_removal_process(self, **kwargs) -> None: + if ( + not self.remove_fluidd + and not self.remove_fl_config + and not self.remove_updater_section + and not self.remove_printer_cfg_include + ): + error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" + print(error) + return + + fluidd_remove.run_fluidd_removal( + remove_fluidd=self.remove_fluidd, + remove_fl_config=self.remove_fl_config, + remove_mr_updater_section=self.remove_updater_section, + remove_flc_printer_cfg_include=self.remove_printer_cfg_include, + ) + + self.remove_fluidd = False + self.remove_fl_config = False + self.remove_updater_section = False + self.remove_printer_cfg_include = False diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 8ab2730..c466fdf 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -75,7 +75,7 @@ class InstallMenu(BaseMenu): mainsail_setup.install_mainsail() def install_fluidd(self, **kwargs): - print("install_fluidd") + fluidd_setup.install_fluidd() def install_klipperscreen(self, **kwargs): print("install_klipperscreen") diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 60dc9f8..716e046 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -11,20 +11,21 @@ import textwrap -from kiauh.components.klipper.klipper_utils import get_klipper_status -from kiauh.components.log_uploads.menus.log_upload_menu import LogUploadMenu -from kiauh.components.mainsail.mainsail_utils import get_mainsail_status -from kiauh.components.moonraker.moonraker_utils import get_moonraker_status -from kiauh.core.menus import QUIT_FOOTER -from kiauh.core.menus.advanced_menu import AdvancedMenu -from kiauh.core.menus.backup_menu import BackupMenu -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.core.menus.extensions_menu import ExtensionsMenu -from kiauh.core.menus.install_menu import InstallMenu -from kiauh.core.menus.remove_menu import RemoveMenu -from kiauh.core.menus.settings_menu import SettingsMenu -from kiauh.core.menus.update_menu import UpdateMenu -from kiauh.utils.constants import ( +from components.fluidd.fluidd_utils import get_fluidd_status +from components.klipper.klipper_utils import get_klipper_status +from components.log_uploads.menus.log_upload_menu import LogUploadMenu +from components.mainsail.mainsail_utils import get_mainsail_status +from components.moonraker.moonraker_utils import get_moonraker_status +from core.menus import QUIT_FOOTER +from core.menus.advanced_menu import AdvancedMenu +from core.menus.backup_menu import BackupMenu +from core.menus.base_menu import BaseMenu +from core.menus.extensions_menu import ExtensionsMenu +from core.menus.install_menu import InstallMenu +from core.menus.remove_menu import RemoveMenu +from core.menus.settings_menu import SettingsMenu +from core.menus.update_menu import UpdateMenu +from utils.constants import ( COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT, @@ -87,6 +88,8 @@ class MainMenu(BaseMenu): self.mr_repo = f"{COLOR_CYAN}{moonraker_status.get('repo')}{RESET_FORMAT}" # mainsail self.ms_status = get_mainsail_status() + # fluidd + self.fl_status = get_fluidd_status() def format_status_by_code(self, code: int, status: str, count: str) -> str: if code == 1: diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 3fd94b9..8df2380 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -11,6 +11,7 @@ import textwrap +from components.fluidd.menus.fluidd_remove_menu import FluiddRemoveMenu from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu from components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu @@ -29,16 +30,16 @@ class RemoveMenu(BaseMenu): "1": KlipperRemoveMenu, "2": MoonrakerRemoveMenu, "3": MainsailRemoveMenu, - "5": self.remove_fluidd, - "6": self.remove_klipperscreen, - "7": self.remove_crowsnest, - "8": self.remove_mjpgstreamer, - "9": self.remove_pretty_gcode, - "10": self.remove_telegram_bot, - "11": self.remove_obico, - "12": self.remove_octoeverywhere, - "13": self.remove_mobileraker, - "14": self.remove_nginx, + "4": FluiddRemoveMenu, + "5": None, + "6": None, + "7": None, + "8": None, + "9": None, + "10": None, + "11": None, + "12": None, + "13": None, }, footer_type=BACK_FOOTER, ) @@ -69,36 +70,3 @@ class RemoveMenu(BaseMenu): """ )[1:] print(menu, end="") - - def remove_fluidd(self, **kwargs): - print("remove_fluidd") - - def remove_fluidd_config(self, **kwargs): - print("remove_fluidd_config") - - def remove_klipperscreen(self, **kwargs): - print("remove_klipperscreen") - - def remove_crowsnest(self, **kwargs): - print("remove_crowsnest") - - def remove_mjpgstreamer(self, **kwargs): - print("remove_mjpgstreamer") - - def remove_pretty_gcode(self, **kwargs): - print("remove_pretty_gcode") - - def remove_telegram_bot(self, **kwargs): - print("remove_telegram_bot") - - def remove_obico(self, **kwargs): - print("remove_obico") - - def remove_octoeverywhere(self, **kwargs): - print("remove_octoeverywhere") - - def remove_mobileraker(self, **kwargs): - print("remove_mobileraker") - - def remove_nginx(self, **kwargs): - print("remove_nginx") diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index ba25e07..04d6de5 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -11,20 +11,25 @@ import textwrap -from kiauh.components.klipper.klipper_setup import update_klipper -from kiauh.components.klipper.klipper_utils import ( +from components.fluidd.fluidd_setup import update_fluidd +from components.fluidd.fluidd_utils import ( + get_fluidd_local_version, + get_fluidd_remote_version, +) +from components.klipper.klipper_setup import update_klipper +from components.klipper.klipper_utils import ( get_klipper_status, ) -from kiauh.components.mainsail.mainsail_setup import update_mainsail -from kiauh.components.mainsail.mainsail_utils import ( +from components.mainsail.mainsail_setup import update_mainsail +from components.mainsail.mainsail_utils import ( get_mainsail_local_version, get_mainsail_remote_version, ) -from kiauh.components.moonraker.moonraker_setup import update_moonraker -from kiauh.components.moonraker.moonraker_utils import get_moonraker_status -from kiauh.core.menus import BACK_FOOTER -from kiauh.core.menus.base_menu import BaseMenu -from kiauh.utils.constants import ( +from components.moonraker.moonraker_setup import update_moonraker +from components.moonraker.moonraker_utils import get_moonraker_status +from core.menus import BACK_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import ( COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, @@ -62,6 +67,8 @@ class UpdateMenu(BaseMenu): self.mr_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.ms_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.ms_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.fl_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.fl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" def print_menu(self): self.fetch_update_status() @@ -82,7 +89,7 @@ class UpdateMenu(BaseMenu): | | | | | Klipper Webinterface: |---------------|---------------| | 3) Mainsail | {self.ms_local:<22} | {self.ms_remote:<22} | - | 4) Fluidd | | | + | 4) Fluidd | {self.fl_local:<22} | {self.fl_remote:<22} | | | | | | Touchscreen GUI: |---------------|---------------| | 5) KlipperScreen | | | @@ -113,7 +120,7 @@ class UpdateMenu(BaseMenu): update_mainsail() def update_fluidd(self, **kwargs): - print("update_fluidd") + update_fluidd() def update_klipperscreen(self, **kwargs): print("update_klipperscreen") @@ -166,3 +173,11 @@ class UpdateMenu(BaseMenu): else: self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}" self.ms_remote = f"{COLOR_GREEN if self.ms_remote != 'ERROR' else COLOR_RED}{self.ms_remote}{RESET_FORMAT}" + # fluidd + self.fl_local = get_fluidd_local_version() + self.fl_remote = get_fluidd_remote_version() + if self.fl_local == self.fl_remote: + self.fl_local = f"{COLOR_GREEN}{self.fl_local}{RESET_FORMAT}" + else: + self.fl_local = f"{COLOR_YELLOW}{self.fl_local}{RESET_FORMAT}" + self.fl_remote = f"{COLOR_GREEN if self.fl_remote != 'ERROR' else COLOR_RED}{self.fl_remote}{RESET_FORMAT}" diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 57b414f..52603d6 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -13,10 +13,13 @@ import subprocess from pathlib import Path from zipfile import ZipFile +from typing import List + from utils import ( NGINX_SITES_AVAILABLE, MODULE_PATH, NGINX_CONFD, + NGINX_SITES_ENABLED, ) from utils.logger import Logger @@ -133,3 +136,35 @@ def create_nginx_cfg(name: str, port: int, root_dir: Path) -> None: log = f"Unable to create '{target}': {e.stderr.decode()}" Logger.print_error(log) raise + + +def read_ports_from_nginx_configs() -> List[str]: + """ + Helper function to iterate over all NGINX configs and read all ports defined for listen + :return: A sorted list of listen ports + """ + if not NGINX_SITES_ENABLED.exists(): + return [] + + port_list = [] + for config in NGINX_SITES_ENABLED.iterdir(): + with open(config, "r") as cfg: + lines = cfg.readlines() + + for line in lines: + line = line.strip().replace(";", "") + if line.startswith("listen"): + port_list.append(line.split()[-1]) + + return sorted(port_list, key=lambda x: int(x)) + + +def is_valid_port(port: str, ports_in_use: List[str]) -> bool: + return port.isdigit() and port not in ports_in_use + + +def get_next_free_port(ports_in_use: List[str]) -> str: + valid_ports = set(range(80, 7125)) + used_ports = set(map(int, ports_in_use)) + + return str(min(valid_ports - used_ports)) From 750cb7b3078bef2c109abf5c596252359307a54a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 24 Feb 2024 15:44:19 +0100 Subject: [PATCH 111/137] refactor(KIAUH): update NGINX config to match mainsails structure Signed-off-by: Dominik Willner --- kiauh/utils/assets/nginx_cfg | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kiauh/utils/assets/nginx_cfg b/kiauh/utils/assets/nginx_cfg index 6303674..d7aabf4 100644 --- a/kiauh/utils/assets/nginx_cfg +++ b/kiauh/utils/assets/nginx_cfg @@ -1,7 +1,7 @@ -# /etc/nginx/sites-available/%NAME% - server { listen %PORT%; + # uncomment the next line to activate IPv6 + # listen [::]:%PORT%; access_log /var/log/nginx/%NAME%-access.log; error_log /var/log/nginx/%NAME%-error.log; @@ -55,7 +55,6 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Scheme $scheme; - proxy_read_timeout 600; } location /webcam/ { From 7fd91e6cef7df4c166f1fd5858581fe4cae07fc1 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 24 Feb 2024 15:46:02 +0100 Subject: [PATCH 112/137] refactor(KIAUH): allow reading ipv6 configured ports with possible default_server suffixes Signed-off-by: Dominik Willner --- kiauh/utils/filesystem_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 52603d6..14ad26f 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -7,7 +7,7 @@ # # # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # - +import re import shutil import subprocess from pathlib import Path @@ -152,8 +152,9 @@ def read_ports_from_nginx_configs() -> List[str]: lines = cfg.readlines() for line in lines: - line = line.strip().replace(";", "") - if line.startswith("listen"): + line = line.replace("default_server", "") + line = re.sub(r"[;:\[\]]", "", line.strip()) + if line.startswith("listen") and line.split()[-1] not in port_list: port_list.append(line.split()[-1]) return sorted(port_list, key=lambda x: int(x)) From 1620efe56c79cb0fa965ea10be8f172646e0e798 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 2 Mar 2024 17:22:37 +0100 Subject: [PATCH 113/137] refactor(KIAUH): full refactor of client and client-config installation Signed-off-by: Dominik Willner --- kiauh/components/fluidd/__init__.py | 26 -- .../fluidd/assets/fluidd-config-updater.conf | 6 - .../fluidd/assets/fluidd-updater.conf | 5 - kiauh/components/fluidd/fluidd_remove.py | 160 ----------- kiauh/components/fluidd/fluidd_setup.py | 242 ----------------- kiauh/components/fluidd/fluidd_utils.py | 79 ------ .../fluidd/menus/fluidd_remove_menu.py | 111 -------- .../klipper/menus/klipper_remove_menu.py | 4 +- kiauh/components/mainsail/__init__.py | 27 -- .../assets/mainsail-config-updater.conf | 6 - .../mainsail/assets/mainsail-updater.conf | 5 - kiauh/components/mainsail/mainsail_dialogs.py | 95 ------- kiauh/components/mainsail/mainsail_remove.py | 164 ----------- kiauh/components/mainsail/mainsail_setup.py | 256 ------------------ kiauh/components/mainsail/mainsail_utils.py | 117 -------- .../mainsail/menus/mainsail_remove_menu.py | 122 --------- .../moonraker/menus/moonraker_remove_menu.py | 4 +- kiauh/components/moonraker/moonraker_setup.py | 4 +- kiauh/components/moonraker/moonraker_utils.py | 4 +- kiauh/components/webui_client/__init__.py | 77 ++++++ .../client_config}/__init__.py | 0 .../client_config/client_config_remove.py | 64 +++++ .../client_config/client_config_setup.py | 145 ++++++++++ .../client_dialogs.py} | 69 ++--- .../components/webui_client/client_remove.py | 75 +++++ kiauh/components/webui_client/client_setup.py | 207 ++++++++++++++ kiauh/components/webui_client/client_utils.py | 237 ++++++++++++++++ .../menus/__init__.py | 0 .../webui_client/menus/client_remove_menu.py | 149 ++++++++++ kiauh/core/menus/backup_menu.py | 41 ++- kiauh/core/menus/install_menu.py | 67 ++--- kiauh/core/menus/main_menu.py | 31 ++- kiauh/core/menus/remove_menu.py | 8 +- kiauh/core/menus/update_menu.py | 116 ++++---- kiauh/utils/filesystem_utils.py | 112 +++++++- 35 files changed, 1248 insertions(+), 1587 deletions(-) delete mode 100644 kiauh/components/fluidd/__init__.py delete mode 100644 kiauh/components/fluidd/assets/fluidd-config-updater.conf delete mode 100644 kiauh/components/fluidd/assets/fluidd-updater.conf delete mode 100644 kiauh/components/fluidd/fluidd_remove.py delete mode 100644 kiauh/components/fluidd/fluidd_setup.py delete mode 100644 kiauh/components/fluidd/fluidd_utils.py delete mode 100644 kiauh/components/fluidd/menus/fluidd_remove_menu.py delete mode 100644 kiauh/components/mainsail/__init__.py delete mode 100644 kiauh/components/mainsail/assets/mainsail-config-updater.conf delete mode 100644 kiauh/components/mainsail/assets/mainsail-updater.conf delete mode 100644 kiauh/components/mainsail/mainsail_dialogs.py delete mode 100644 kiauh/components/mainsail/mainsail_remove.py delete mode 100644 kiauh/components/mainsail/mainsail_setup.py delete mode 100644 kiauh/components/mainsail/mainsail_utils.py delete mode 100644 kiauh/components/mainsail/menus/mainsail_remove_menu.py create mode 100644 kiauh/components/webui_client/__init__.py rename kiauh/components/{fluidd/menus => webui_client/client_config}/__init__.py (100%) create mode 100644 kiauh/components/webui_client/client_config/client_config_remove.py create mode 100644 kiauh/components/webui_client/client_config/client_config_setup.py rename kiauh/components/{fluidd/fluidd_dialogs.py => webui_client/client_dialogs.py} (75%) create mode 100644 kiauh/components/webui_client/client_remove.py create mode 100644 kiauh/components/webui_client/client_setup.py create mode 100644 kiauh/components/webui_client/client_utils.py rename kiauh/components/{mainsail => webui_client}/menus/__init__.py (100%) create mode 100644 kiauh/components/webui_client/menus/client_remove_menu.py diff --git a/kiauh/components/fluidd/__init__.py b/kiauh/components/fluidd/__init__.py deleted file mode 100644 index 70bc91f..0000000 --- a/kiauh/components/fluidd/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 - -MODULE_PATH = Path(__file__).resolve().parent -FLUIDD_DIR = Path.home().joinpath("fluidd") -FLUIDD_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-backups") -FLUIDD_CONFIG_DIR = Path.home().joinpath("fluidd-config") -FLUIDD_NGINX_CFG = Path("/etc/nginx/sites-enabled/fluidd") -FLUIDD_URL = "https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip" -FLUIDD_UNSTABLE_URL = ( - "https://github.com/fluidd-core/fluidd/releases/download/%TAG%/fluidd.zip" -) -FLUIDD_CONFIG_REPO_URL = "https://github.com/fluidd-core/fluidd-config.git" - diff --git a/kiauh/components/fluidd/assets/fluidd-config-updater.conf b/kiauh/components/fluidd/assets/fluidd-config-updater.conf deleted file mode 100644 index bc4a5cc..0000000 --- a/kiauh/components/fluidd/assets/fluidd-config-updater.conf +++ /dev/null @@ -1,6 +0,0 @@ -[update_manager fluidd-config] -type: git_repo -primary_branch: master -path: ~/fluidd-config -origin: https://github.com/fluidd-core/fluidd-config.git -managed_services: klipper diff --git a/kiauh/components/fluidd/assets/fluidd-updater.conf b/kiauh/components/fluidd/assets/fluidd-updater.conf deleted file mode 100644 index d9d1e7d..0000000 --- a/kiauh/components/fluidd/assets/fluidd-updater.conf +++ /dev/null @@ -1,5 +0,0 @@ -[update_manager fluidd] -type: web -channel: stable -repo: fluidd-core/fluidd -path: ~/fluidd diff --git a/kiauh/components/fluidd/fluidd_remove.py b/kiauh/components/fluidd/fluidd_remove.py deleted file mode 100644 index b617cec..0000000 --- a/kiauh/components/fluidd/fluidd_remove.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 -import subprocess -from pathlib import Path -from typing import List - -from components.klipper.klipper import Klipper -from components.fluidd import FLUIDD_DIR, FLUIDD_CONFIG_DIR -from components.moonraker.moonraker import Moonraker -from core.config_manager.config_manager import ConfigManager -from core.instance_manager.instance_manager import InstanceManager -from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED -from utils.filesystem_utils import remove_file -from utils.logger import Logger - - -def run_fluidd_removal( - remove_fluidd: bool, - remove_fl_config: bool, - remove_mr_updater_section: bool, - remove_flc_printer_cfg_include: bool, -) -> None: - if remove_fluidd: - remove_fluidd_dir() - remove_nginx_config() - remove_nginx_logs() - if remove_mr_updater_section: - remove_updater_section("update_manager fluidd") - if remove_fl_config: - remove_fluidd_cfg_dir() - remove_fluidd_cfg_symlink() - if remove_mr_updater_section: - remove_updater_section("update_manager fluidd-config") - if remove_flc_printer_cfg_include: - remove_printer_cfg_include() - - -def remove_fluidd_dir() -> None: - Logger.print_status("Removing Fluidd ...") - if not FLUIDD_DIR.exists(): - Logger.print_info(f"'{FLUIDD_DIR}' does not exist. Skipping ...") - return - - try: - shutil.rmtree(FLUIDD_DIR) - except OSError as e: - Logger.print_error(f"Unable to delete '{FLUIDD_DIR}':\n{e}") - - -def remove_nginx_config() -> None: - Logger.print_status("Removing Fluidd NGINX config ...") - try: - remove_file(NGINX_SITES_AVAILABLE.joinpath("fluidd"), True) - remove_file(NGINX_SITES_ENABLED.joinpath("fluidd"), True) - - except subprocess.CalledProcessError as e: - log = f"Unable to remove Fluidd NGINX config:\n{e.stderr.decode()}" - Logger.print_error(log) - - -def remove_nginx_logs() -> None: - Logger.print_status("Removing Fluidd NGINX logs ...") - try: - remove_file(Path("/var/log/nginx/fluidd-access.log"), True) - remove_file(Path("/var/log/nginx/fluidd-error.log"), True) - - im = InstanceManager(Klipper) - instances: List[Klipper] = im.instances - if not instances: - return - - for instance in instances: - remove_file(instance.log_dir.joinpath("fluidd-access.log")) - remove_file(instance.log_dir.joinpath("fluidd-error.log")) - - except (OSError, subprocess.CalledProcessError) as e: - Logger.print_error(f"Unable to NGINX logs:\n{e}") - - -def remove_updater_section(name: str) -> None: - Logger.print_status("Remove updater section from moonraker.conf ...") - im = InstanceManager(Moonraker) - instances: List[Moonraker] = im.instances - if not instances: - Logger.print_info("Moonraker not installed. Skipped ...") - return - - for instance in instances: - Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...") - - if not instance.cfg_file.is_file(): - Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...") - continue - - cm = ConfigManager(instance.cfg_file) - if not cm.config.has_section(name): - Logger.print_info("Section not present. Skipped ...") - continue - - cm.config.remove_section(name) - cm.write_config() - - -def remove_fluidd_cfg_dir() -> None: - Logger.print_status("Removing fluidd-config ...") - if not FLUIDD_CONFIG_DIR.exists(): - Logger.print_info(f"'{FLUIDD_CONFIG_DIR}' does not exist. Skipping ...") - return - - try: - shutil.rmtree(FLUIDD_CONFIG_DIR) - except OSError as e: - Logger.print_error(f"Unable to delete '{FLUIDD_CONFIG_DIR}':\n{e}") - - -def remove_fluidd_cfg_symlink() -> None: - Logger.print_status("Removing fluidd.cfg symlinks ...") - im = InstanceManager(Klipper) - instances: List[Klipper] = im.instances - for instance in instances: - Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") - try: - remove_file(instance.cfg_dir.joinpath("fluidd.cfg")) - except subprocess.CalledProcessError: - Logger.print_error("Failed to remove symlink!") - - -def remove_printer_cfg_include() -> None: - Logger.print_status("Remove fluidd-config include from printer.cfg ...") - im = InstanceManager(Klipper) - instances: List[Klipper] = im.instances - if not instances: - Logger.print_info("Klipper not installed. Skipping ...") - return - - for instance in instances: - log = f"Removing include from '{instance.cfg_file}' ..." - Logger.print_status(log) - - if not instance.cfg_file.is_file(): - continue - - cm = ConfigManager(instance.cfg_file) - if not cm.config.has_section("include fluidd.cfg"): - Logger.print_info("Section not present. Skipped ...") - continue - - cm.config.remove_section("include fluidd.cfg") - cm.write_config() diff --git a/kiauh/components/fluidd/fluidd_setup.py b/kiauh/components/fluidd/fluidd_setup.py deleted file mode 100644 index 7c23bb5..0000000 --- a/kiauh/components/fluidd/fluidd_setup.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 subprocess -from pathlib import Path -from typing import List - -from components.fluidd import ( - FLUIDD_URL, - FLUIDD_CONFIG_REPO_URL, - FLUIDD_CONFIG_DIR, - FLUIDD_DIR, - MODULE_PATH, -) -from components.fluidd.fluidd_dialogs import ( - print_fluidd_already_installed_dialog, - print_install_fluidd_config_dialog, - print_fluidd_port_select_dialog, - print_moonraker_not_found_dialog, -) -from components.fluidd.fluidd_utils import symlink_webui_nginx_log -from kiauh import KIAUH_CFG -from components.klipper.klipper import Klipper -from components.moonraker.moonraker import Moonraker -from core.config_manager.config_manager import ConfigManager -from core.instance_manager.instance_manager import InstanceManager -from core.repo_manager.repo_manager import RepoManager -from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED -from utils.common import check_install_dependencies -from utils.filesystem_utils import ( - unzip, - copy_upstream_nginx_cfg, - copy_common_vars_nginx_cfg, - create_nginx_cfg, - create_symlink, - remove_file, - read_ports_from_nginx_configs, - get_next_free_port, is_valid_port, - ) -from utils.input_utils import get_confirm, get_number_input -from utils.logger import Logger -from utils.system_utils import ( - download_file, - set_nginx_permissions, - get_ipv4_addr, - control_systemd_service, -) - - -def install_fluidd() -> None: - mr_im = InstanceManager(Moonraker) - mr_instances: List[Moonraker] = mr_im.instances - - if not mr_instances: - print_moonraker_not_found_dialog() - if not get_confirm("Continue Fluidd installation?", allow_go_back=True): - return - - if Path.home().joinpath("fluidd").exists(): - print_fluidd_already_installed_dialog() - do_reinstall = get_confirm("Re-install Fluidd?", allow_go_back=True) - if not do_reinstall: - return - - kl_im = InstanceManager(Klipper) - kl_instances = kl_im.instances - install_fl_config = False - if kl_instances: - print_install_fluidd_config_dialog() - question = "Download the recommended macros?" - install_fl_config = get_confirm(question, allow_go_back=False) - - cm = ConfigManager(cfg_file=KIAUH_CFG) - fluidd_port = cm.get_value("fluidd", "port") - ports_in_use = read_ports_from_nginx_configs() - - # check if configured port is a valid number and not in use already - valid_port = is_valid_port(fluidd_port, ports_in_use) - while not valid_port: - next_port = get_next_free_port(ports_in_use) - print_fluidd_port_select_dialog(next_port, ports_in_use) - fluidd_port = str(get_number_input( - "Configure Fluidd for port", - min_count=int(next_port), - default=next_port, - )) - valid_port = is_valid_port(fluidd_port, ports_in_use) - - check_install_dependencies(["nginx"]) - - try: - download_fluidd() - if mr_instances: - patch_moonraker_conf( - mr_instances, - "Fluidd", - "update_manager fluidd", - "fluidd-updater.conf", - ) - mr_im.restart_all_instance() - if install_fl_config and kl_instances: - download_fluidd_cfg() - create_fluidd_cfg_symlink(kl_instances) - patch_moonraker_conf( - mr_instances, - "fluidd-config", - "update_manager fluidd-config", - "fluidd-config-updater.conf", - ) - patch_printer_config(kl_instances) - kl_im.restart_all_instance() - - copy_upstream_nginx_cfg() - copy_common_vars_nginx_cfg() - create_fluidd_nginx_cfg(fluidd_port) - if kl_instances: - symlink_webui_nginx_log(kl_instances) - control_systemd_service("nginx", "restart") - - except Exception as e: - Logger.print_error(f"Fluidd installation failed!\n{e}") - return - - log = f"Open Fluidd now on: http://{get_ipv4_addr()}:{fluidd_port}" - Logger.print_ok("Fluidd installation complete!", start="\n") - Logger.print_ok(log, prefix=False, end="\n\n") - - -def download_fluidd() -> None: - try: - Logger.print_status("Downloading Fluidd ...") - target = Path.home().joinpath("fluidd.zip") - download_file(FLUIDD_URL, target, True) - Logger.print_ok("Download complete!") - - Logger.print_status("Extracting fluidd.zip ...") - unzip(Path.home().joinpath("fluidd.zip"), FLUIDD_DIR) - target.unlink(missing_ok=True) - Logger.print_ok("OK!") - - except Exception: - Logger.print_error("Downloading Fluidd failed!") - raise - - -def update_fluidd() -> None: - Logger.print_status("Updating Fluidd ...") - download_fluidd() - - -def download_fluidd_cfg() -> None: - try: - Logger.print_status("Downloading fluidd-config ...") - rm = RepoManager(FLUIDD_CONFIG_REPO_URL, target_dir=FLUIDD_CONFIG_DIR) - rm.clone_repo() - except Exception: - Logger.print_error("Downloading fluidd-config failed!") - raise - - -def create_fluidd_cfg_symlink(klipper_instances: List[Klipper]) -> None: - Logger.print_status("Create symlink of fluidd.cfg ...") - source = Path(FLUIDD_CONFIG_DIR, "fluidd.cfg") - for instance in klipper_instances: - target = instance.cfg_dir - Logger.print_status(f"Linking {source} to {target}") - try: - create_symlink(source, target) - except subprocess.CalledProcessError: - Logger.print_error("Creating symlink failed!") - - -def create_fluidd_nginx_cfg(port: int) -> None: - root_dir = FLUIDD_DIR - source = NGINX_SITES_AVAILABLE.joinpath("fluidd") - target = NGINX_SITES_ENABLED.joinpath("fluidd") - try: - Logger.print_status("Creating NGINX config for Fluidd ...") - remove_file(Path("/etc/nginx/sites-enabled/default"), True) - create_nginx_cfg("fluidd", port, root_dir) - create_symlink(source, target, True) - set_nginx_permissions() - Logger.print_ok("NGINX config for Fluidd successfully created.") - except Exception: - Logger.print_error("Creating NGINX config for Fluidd failed!") - raise - - -# TODO: could be fully extracted, its webui agnostic, and used for mainsail and fluidd -def patch_moonraker_conf( - moonraker_instances: List[Moonraker], - name: str, - section_name: str, - template_file: str, -) -> None: - for instance in moonraker_instances: - cfg_file = instance.cfg_file - Logger.print_status(f"Add {name} update section to '{cfg_file}' ...") - - if not Path(cfg_file).exists(): - Logger.print_warn(f"'{cfg_file}' not found!") - return - - cm = ConfigManager(cfg_file) - if cm.config.has_section(section_name): - Logger.print_info("Section already exist. Skipped ...") - return - - template = MODULE_PATH.joinpath("assets", template_file) - with open(template, "r") as t: - template_content = "\n" - template_content += t.read() - - with open(cfg_file, "a") as f: - f.write(template_content) - - -# TODO: could be made fully webui agnostic and extracted, and used for mainsail and fluidd -def patch_printer_config(klipper_instances: List[Klipper]) -> None: - for instance in klipper_instances: - cfg_file = instance.cfg_file - Logger.print_status(f"Including fluidd-config in '{cfg_file}' ...") - - if not Path(cfg_file).exists(): - Logger.print_warn(f"'{cfg_file}' not found!") - return - - cm = ConfigManager(cfg_file) - if cm.config.has_section("include fluidd.cfg"): - Logger.print_info("Section already exist. Skipped ...") - return - - with open(cfg_file, "a") as f: - f.write("\n[include fluidd.cfg]") diff --git a/kiauh/components/fluidd/fluidd_utils.py b/kiauh/components/fluidd/fluidd_utils.py deleted file mode 100644 index c9c672b..0000000 --- a/kiauh/components/fluidd/fluidd_utils.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 json -import urllib.request -from json import JSONDecodeError -from pathlib import Path -from typing import List - -from components.fluidd import FLUIDD_DIR, FLUIDD_BACKUP_DIR -from components.klipper.klipper import Klipper -from core.backup_manager.backup_manager import BackupManager -from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD -from utils.common import get_install_status_webui -from utils.logger import Logger - - -# TODO: could be extracted and made generic -def get_fluidd_status() -> str: - return get_install_status_webui( - FLUIDD_DIR, - NGINX_SITES_AVAILABLE.joinpath("fluidd"), - NGINX_CONFD.joinpath("upstreams.conf"), - NGINX_CONFD.joinpath("common_vars.conf"), - ) - - -# TODO: could be extracted and made generic -def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: - Logger.print_status("Link NGINX logs into log directory ...") - access_log = Path("/var/log/nginx/fluidd-access.log") - error_log = Path("/var/log/nginx/fluidd-error.log") - - for instance in klipper_instances: - desti_access = instance.log_dir.joinpath("fluidd-access.log") - if not desti_access.exists(): - desti_access.symlink_to(access_log) - - desti_error = instance.log_dir.joinpath("fluidd-error.log") - if not desti_error.exists(): - desti_error.symlink_to(error_log) - - -# TODO: could be extracted and made generic -def get_fluidd_local_version() -> str: - relinfo_file = FLUIDD_DIR.joinpath("release_info.json") - if not relinfo_file.is_file(): - return "-" - - with open(relinfo_file, "r") as f: - return json.load(f)["version"] - - -# TODO: could be extracted and made generic -def get_fluidd_remote_version() -> str: - url = "https://api.github.com/repos/fluidd-core/fluidd/tags" - try: - with urllib.request.urlopen(url) as response: - data = json.loads(response.read()) - return data[0]["name"] - except (JSONDecodeError, TypeError): - return "ERROR" - - -# TODO: could be extracted and made generic -def backup_fluidd_data() -> None: - with open(FLUIDD_DIR.joinpath(".version"), "r") as v: - version = v.readlines()[0] - bm = BackupManager() - bm.backup_directory(f"fluidd-{version}", FLUIDD_DIR, FLUIDD_BACKUP_DIR) - bm.backup_file(NGINX_SITES_AVAILABLE.joinpath("fluidd"), FLUIDD_BACKUP_DIR) diff --git a/kiauh/components/fluidd/menus/fluidd_remove_menu.py b/kiauh/components/fluidd/menus/fluidd_remove_menu.py deleted file mode 100644 index 4671799..0000000 --- a/kiauh/components/fluidd/menus/fluidd_remove_menu.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 textwrap - -from components.fluidd import fluidd_remove -from core.menus import BACK_HELP_FOOTER -from core.menus.base_menu import BaseMenu -from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN - - -# noinspection PyUnusedLocal -class FluiddRemoveMenu(BaseMenu): - def __init__(self): - super().__init__( - header=False, - options={ - "0": self.toggle_all, - "1": self.toggle_remove_fluidd, - "2": self.toggle_remove_fl_config, - "3": self.toggle_remove_updater_section, - "4": self.toggle_remove_printer_cfg_include, - "5": self.run_removal_process, - }, - footer_type=BACK_HELP_FOOTER, - ) - self.remove_fluidd = False - self.remove_fl_config = False - self.remove_updater_section = False - self.remove_printer_cfg_include = False - - def print_menu(self) -> None: - header = " [ Remove Fluidd ] " - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" - unchecked = "[ ]" - o1 = checked if self.remove_fluidd else unchecked - o2 = checked if self.remove_fl_config else unchecked - o3 = checked if self.remove_updater_section else unchecked - o4 = checked if self.remove_printer_cfg_include 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 Fluidd | - | 2) {o2} Remove fluidd-config | - | | - | printer.cfg & moonraker.conf | - | 3) {o3} Remove Moonraker update section | - | 4) {o4} Remove printer.cfg include | - |-------------------------------------------------------| - | 5) Continue | - """ - )[1:] - print(menu, end="") - - def toggle_all(self, **kwargs) -> None: - self.remove_fluidd = True - self.remove_fl_config = True - self.remove_updater_section = True - self.remove_printer_cfg_include = True - - def toggle_remove_fluidd(self, **kwargs) -> None: - self.remove_fluidd = not self.remove_fluidd - - def toggle_remove_fl_config(self, **kwargs) -> None: - self.remove_fl_config = not self.remove_fl_config - - def toggle_remove_updater_section(self, **kwargs) -> None: - self.remove_updater_section = not self.remove_updater_section - - def toggle_remove_printer_cfg_include(self, **kwargs) -> None: - self.remove_printer_cfg_include = not self.remove_printer_cfg_include - - def run_removal_process(self, **kwargs) -> None: - if ( - not self.remove_fluidd - and not self.remove_fl_config - and not self.remove_updater_section - and not self.remove_printer_cfg_include - ): - error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" - print(error) - return - - fluidd_remove.run_fluidd_removal( - remove_fluidd=self.remove_fluidd, - remove_fl_config=self.remove_fl_config, - remove_mr_updater_section=self.remove_updater_section, - remove_flc_printer_cfg_include=self.remove_printer_cfg_include, - ) - - self.remove_fluidd = False - self.remove_fl_config = False - self.remove_updater_section = False - self.remove_printer_cfg_include = False diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index fc953ab..dc92b33 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -28,7 +28,7 @@ class KlipperRemoveMenu(BaseMenu): "2": self.toggle_remove_klipper_dir, "3": self.toggle_remove_klipper_env, "4": self.toggle_delete_klipper_logs, - "5": self.run_removal_process, + "c": self.run_removal_process, }, footer_type=BACK_HELP_FOOTER, ) @@ -62,7 +62,7 @@ class KlipperRemoveMenu(BaseMenu): | 3) {o3} Remove Python Environment | | 4) {o4} Delete all Log-Files | |-------------------------------------------------------| - | 5) Continue | + | C) Continue | """ )[1:] print(menu, end="") diff --git a/kiauh/components/mainsail/__init__.py b/kiauh/components/mainsail/__init__.py deleted file mode 100644 index 53a9924..0000000 --- a/kiauh/components/mainsail/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 - -MODULE_PATH = Path(__file__).resolve().parent -MAINSAIL_DIR = Path.home().joinpath("mainsail") -MAINSAIL_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-backups") -MAINSAIL_CONFIG_DIR = Path.home().joinpath("mainsail-config") -MAINSAIL_CONFIG_JSON = MAINSAIL_DIR.joinpath("config.json") -MAINSAIL_URL = ( - "https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip" -) -MAINSAIL_UNSTABLE_URL = ( - "https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip" -) -MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git" diff --git a/kiauh/components/mainsail/assets/mainsail-config-updater.conf b/kiauh/components/mainsail/assets/mainsail-config-updater.conf deleted file mode 100644 index 02bb789..0000000 --- a/kiauh/components/mainsail/assets/mainsail-config-updater.conf +++ /dev/null @@ -1,6 +0,0 @@ -[update_manager mainsail-config] -type: git_repo -primary_branch: master -path: ~/mainsail-config -origin: https://github.com/mainsail-crew/mainsail-config.git -managed_services: klipper diff --git a/kiauh/components/mainsail/assets/mainsail-updater.conf b/kiauh/components/mainsail/assets/mainsail-updater.conf deleted file mode 100644 index f668332..0000000 --- a/kiauh/components/mainsail/assets/mainsail-updater.conf +++ /dev/null @@ -1,5 +0,0 @@ -[update_manager mainsail] -type: web -channel: stable -repo: mainsail-crew/mainsail -path: ~/mainsail diff --git a/kiauh/components/mainsail/mainsail_dialogs.py b/kiauh/components/mainsail/mainsail_dialogs.py deleted file mode 100644 index 36453ff..0000000 --- a/kiauh/components/mainsail/mainsail_dialogs.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 textwrap - -from core.menus.base_menu import print_back_footer -from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN - - -def print_moonraker_not_found_dialog(): - line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" - line2 = f"{COLOR_YELLOW}No local Moonraker installation was found!{RESET_FORMAT}" - dialog = textwrap.dedent( - f""" - /=======================================================\\ - | {line1:<63}| - | {line2:<63}| - |-------------------------------------------------------| - | It is possible to install Mainsail without a local | - | Moonraker installation. If you continue, you need to | - | make sure, that Moonraker is installed on another | - | machine in your network. Otherwise Mainsail will NOT | - | work correctly. | - """ - )[1:] - - print(dialog, end="") - print_back_footer() - - -def print_mainsail_already_installed_dialog(): - line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" - line2 = f"{COLOR_YELLOW}Mainsail seems to be already installed!{RESET_FORMAT}" - dialog = textwrap.dedent( - f""" - /=======================================================\\ - | {line1:<63}| - | {line2:<63}| - |-------------------------------------------------------| - | If you continue, your current Mainsail installation | - | will be overwritten. You will not loose any printer | - | configurations and the Moonraker database will remain | - | untouched. | - """ - )[1:] - - print(dialog, end="") - print_back_footer() - - -def print_install_mainsail_config_dialog(): - dialog = textwrap.dedent( - f""" - /=======================================================\\ - | It is recommended to use special macros in order to | - | have Mainsail fully functional and working. | - | | - | The recommended macros for Mainsail can be seen here: | - | https://github.com/mainsail-crew/mainsail-config | - | | - | If you already use these macros skip this step. | - | Otherwise you should consider to answer with 'Y' to | - | download the recommended macros. | - \\=======================================================/ - """ - )[1:] - - print(dialog, end="") - - -def print_mainsail_port_select_dialog(port: str): - port = f"{COLOR_CYAN}{port}{RESET_FORMAT}" - dialog = textwrap.dedent( - f""" - /=======================================================\\ - | Please select the port, Mainsail should be served on. | - | If you are unsure what to select, hit Enter to apply | - | the suggested value of: {port:38} | - | | - | In case you need Mainsail to be served on a specific | - | port, you can set it now. Make sure the port is not | - | used by any other application on your system! | - \\=======================================================/ - """ - )[1:] - - print(dialog, end="") diff --git a/kiauh/components/mainsail/mainsail_remove.py b/kiauh/components/mainsail/mainsail_remove.py deleted file mode 100644 index 204e8a6..0000000 --- a/kiauh/components/mainsail/mainsail_remove.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 -import subprocess -from pathlib import Path -from typing import List - -from components.klipper.klipper import Klipper -from components.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR -from components.mainsail.mainsail_utils import backup_config_json -from components.moonraker.moonraker import Moonraker -from core.config_manager.config_manager import ConfigManager -from core.instance_manager.instance_manager import InstanceManager -from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED -from utils.filesystem_utils import remove_file -from utils.logger import Logger - - -def run_mainsail_removal( - remove_mainsail: bool, - remove_ms_config: bool, - backup_ms_config_json: bool, - remove_mr_updater_section: bool, - remove_msc_printer_cfg_include: bool, -) -> None: - if backup_ms_config_json: - backup_config_json() - if remove_mainsail: - remove_mainsail_dir() - remove_nginx_config() - remove_nginx_logs() - if remove_mr_updater_section: - remove_updater_section("update_manager mainsail") - if remove_ms_config: - remove_mainsail_cfg_dir() - remove_mainsail_cfg_symlink() - if remove_mr_updater_section: - remove_updater_section("update_manager mainsail-config") - if remove_msc_printer_cfg_include: - remove_printer_cfg_include() - - -def remove_mainsail_dir() -> None: - Logger.print_status("Removing Mainsail ...") - if not MAINSAIL_DIR.exists(): - Logger.print_info(f"'{MAINSAIL_DIR}' does not exist. Skipping ...") - return - - try: - shutil.rmtree(MAINSAIL_DIR) - except OSError as e: - Logger.print_error(f"Unable to delete '{MAINSAIL_DIR}':\n{e}") - - -def remove_nginx_config() -> None: - Logger.print_status("Removing Mainsails NGINX config ...") - try: - remove_file(NGINX_SITES_AVAILABLE.joinpath("mainsail"), True) - remove_file(NGINX_SITES_ENABLED.joinpath("mainsail"), True) - - except subprocess.CalledProcessError as e: - log = f"Unable to remove Mainsail NGINX config:\n{e.stderr.decode()}" - Logger.print_error(log) - - -def remove_nginx_logs() -> None: - Logger.print_status("Removing Mainsails NGINX logs ...") - try: - remove_file(Path("/var/log/nginx/mainsail-access.log"), True) - remove_file(Path("/var/log/nginx/mainsail-error.log"), True) - - im = InstanceManager(Klipper) - instances: List[Klipper] = im.instances - if not instances: - return - - for instance in instances: - remove_file(instance.log_dir.joinpath("mainsail-access.log")) - remove_file(instance.log_dir.joinpath("mainsail-error.log")) - - except (OSError, subprocess.CalledProcessError) as e: - Logger.print_error(f"Unable to NGINX logs:\n{e}") - - -def remove_updater_section(name: str) -> None: - Logger.print_status("Remove updater section from moonraker.conf ...") - im = InstanceManager(Moonraker) - instances: List[Moonraker] = im.instances - if not instances: - Logger.print_info("Moonraker not installed. Skipped ...") - return - - for instance in instances: - Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...") - - if not instance.cfg_file.is_file(): - Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...") - continue - - cm = ConfigManager(instance.cfg_file) - if not cm.config.has_section(name): - Logger.print_info("Section not present. Skipped ...") - continue - - cm.config.remove_section(name) - cm.write_config() - - -def remove_mainsail_cfg_dir() -> None: - Logger.print_status("Removing mainsail-config ...") - if not MAINSAIL_CONFIG_DIR.exists(): - Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...") - return - - try: - shutil.rmtree(MAINSAIL_CONFIG_DIR) - except OSError as e: - Logger.print_error(f"Unable to delete '{MAINSAIL_CONFIG_DIR}':\n{e}") - - -def remove_mainsail_cfg_symlink() -> None: - Logger.print_status("Removing mainsail.cfg symlinks ...") - im = InstanceManager(Klipper) - instances: List[Klipper] = im.instances - for instance in instances: - Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") - try: - remove_file(instance.cfg_dir.joinpath("mainsail.cfg")) - except subprocess.CalledProcessError: - Logger.print_error("Failed to remove symlink!") - - -def remove_printer_cfg_include() -> None: - Logger.print_status("Remove mainsail-config include from printer.cfg ...") - im = InstanceManager(Klipper) - instances: List[Klipper] = im.instances - if not instances: - Logger.print_info("Klipper not installed. Skipping ...") - return - - for instance in instances: - log = f"Removing include from '{instance.cfg_file}' ..." - Logger.print_status(log) - - if not instance.cfg_file.is_file(): - continue - - cm = ConfigManager(instance.cfg_file) - if not cm.config.has_section("include mainsail.cfg"): - Logger.print_info("Section not present. Skipped ...") - continue - - cm.config.remove_section("include mainsail.cfg") - cm.write_config() diff --git a/kiauh/components/mainsail/mainsail_setup.py b/kiauh/components/mainsail/mainsail_setup.py deleted file mode 100644 index 210dc9e..0000000 --- a/kiauh/components/mainsail/mainsail_setup.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 subprocess -from pathlib import Path -from typing import List - -from kiauh import KIAUH_CFG -from components.klipper.klipper import Klipper -from components.mainsail import ( - MAINSAIL_URL, - MAINSAIL_DIR, - MAINSAIL_CONFIG_DIR, - MAINSAIL_CONFIG_REPO_URL, - MODULE_PATH, -) -from components.mainsail.mainsail_dialogs import ( - print_moonraker_not_found_dialog, - print_mainsail_already_installed_dialog, - print_install_mainsail_config_dialog, - print_mainsail_port_select_dialog, -) -from components.mainsail.mainsail_utils import ( - restore_config_json, - enable_mainsail_remotemode, - backup_config_json, - symlink_webui_nginx_log, -) -from components.moonraker.moonraker import Moonraker -from core.config_manager.config_manager import ConfigManager -from core.instance_manager.instance_manager import InstanceManager -from core.repo_manager.repo_manager import RepoManager -from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED -from utils.common import check_install_dependencies -from utils.filesystem_utils import ( - unzip, - copy_upstream_nginx_cfg, - copy_common_vars_nginx_cfg, - create_nginx_cfg, - create_symlink, - remove_file, -) -from utils.input_utils import get_confirm, get_number_input -from utils.logger import Logger -from utils.system_utils import ( - download_file, - set_nginx_permissions, - get_ipv4_addr, - control_systemd_service, -) - - -def install_mainsail() -> None: - mr_im = InstanceManager(Moonraker) - mr_instances: List[Moonraker] = mr_im.instances - - enable_remotemode = False - if not mr_instances: - print_moonraker_not_found_dialog() - if not get_confirm("Continue Mainsail installation?", allow_go_back=True): - return - - # if moonraker is not installed or multiple instances - # are installed we enable mainsails remote mode - if not mr_instances or len(mr_instances) > 1: - enable_remotemode = True - - do_reinstall = False - if Path.home().joinpath("mainsail").exists(): - print_mainsail_already_installed_dialog() - do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True) - if do_reinstall: - backup_config_json(is_temp=True) - else: - return - - kl_im = InstanceManager(Klipper) - kl_instances = kl_im.instances - install_ms_config = False - if kl_instances: - print_install_mainsail_config_dialog() - question = "Download the recommended macros?" - install_ms_config = get_confirm(question, allow_go_back=False) - - # if a default port is configured in the kiauh.cfg, we use that for the port - # otherwise we default to port 80, but show the user a dialog to confirm/change that port - cm = ConfigManager(cfg_file=KIAUH_CFG) - default_port = cm.get_value("mainsail", "default_port") - is_valid_port = default_port and default_port.isdigit() - mainsail_port = default_port if is_valid_port else "80" - if not is_valid_port: - print_mainsail_port_select_dialog(mainsail_port) - mainsail_port = get_number_input( - "Configure Mainsail for port", - min_count=mainsail_port, - default=mainsail_port, - ) - - check_install_dependencies(["nginx"]) - - try: - download_mainsail() - if do_reinstall: - restore_config_json() - if enable_remotemode: - enable_mainsail_remotemode() - if mr_instances: - patch_moonraker_conf( - mr_instances, - "Mainsail", - "update_manager mainsail", - "mainsail-updater.conf", - ) - mr_im.restart_all_instance() - if install_ms_config and kl_instances: - download_mainsail_cfg() - create_mainsail_cfg_symlink(kl_instances) - patch_moonraker_conf( - mr_instances, - "mainsail-config", - "update_manager mainsail-config", - "mainsail-config-updater.conf", - ) - patch_printer_config(kl_instances) - kl_im.restart_all_instance() - - copy_upstream_nginx_cfg() - copy_common_vars_nginx_cfg() - create_mainsail_nginx_cfg(mainsail_port) - if kl_instances: - symlink_webui_nginx_log(kl_instances) - control_systemd_service("nginx", "restart") - - except Exception as e: - Logger.print_error(f"Mainsail installation failed!\n{e}") - return - - log = f"Open Mainsail now on: http://{get_ipv4_addr()}:{mainsail_port}" - Logger.print_ok("Mainsail installation complete!", start="\n") - Logger.print_ok(log, prefix=False, end="\n\n") - - -def download_mainsail() -> None: - try: - Logger.print_status("Downloading Mainsail ...") - target = Path.home().joinpath("mainsail.zip") - download_file(MAINSAIL_URL, target, True) - Logger.print_ok("Download complete!") - - Logger.print_status("Extracting mainsail.zip ...") - unzip(Path.home().joinpath("mainsail.zip"), MAINSAIL_DIR) - target.unlink(missing_ok=True) - Logger.print_ok("OK!") - - except Exception: - Logger.print_error("Downloading Mainsail failed!") - raise - - -def update_mainsail() -> None: - Logger.print_status("Updating Mainsail ...") - backup_config_json(is_temp=True) - download_mainsail() - restore_config_json() - - -def download_mainsail_cfg() -> None: - try: - Logger.print_status("Downloading mainsail-config ...") - rm = RepoManager(MAINSAIL_CONFIG_REPO_URL, target_dir=MAINSAIL_CONFIG_DIR) - rm.clone_repo() - except Exception: - Logger.print_error("Downloading mainsail-config failed!") - raise - - -def create_mainsail_cfg_symlink(klipper_instances: List[Klipper]) -> None: - Logger.print_status("Create symlink of mainsail.cfg ...") - source = Path(MAINSAIL_CONFIG_DIR, "mainsail.cfg") - for instance in klipper_instances: - target = instance.cfg_dir - Logger.print_status(f"Linking {source} to {target}") - try: - create_symlink(source, target) - except subprocess.CalledProcessError: - Logger.print_error("Creating symlink failed!") - - -def create_mainsail_nginx_cfg(port: int) -> None: - root_dir = MAINSAIL_DIR - source = NGINX_SITES_AVAILABLE.joinpath("mainsail") - target = NGINX_SITES_ENABLED.joinpath("mainsail") - try: - Logger.print_status("Creating NGINX config for Mainsail ...") - remove_file(Path("/etc/nginx/sites-enabled/default"), True) - create_nginx_cfg("mainsail", port, root_dir) - create_symlink(source, target, True) - set_nginx_permissions() - Logger.print_ok("NGINX config for Mainsail successfully created.") - except Exception: - Logger.print_error("Creating NGINX config for Mainsail failed!") - raise - - -def patch_moonraker_conf( - moonraker_instances: List[Moonraker], - name: str, - section_name: str, - template_file: str, -) -> None: - for instance in moonraker_instances: - cfg_file = instance.cfg_file - Logger.print_status(f"Add {name} update section to '{cfg_file}' ...") - - if not Path(cfg_file).exists(): - Logger.print_warn(f"'{cfg_file}' not found!") - return - - cm = ConfigManager(cfg_file) - if cm.config.has_section(section_name): - Logger.print_info("Section already exist. Skipped ...") - return - - template = MODULE_PATH.joinpath("assets", template_file) - with open(template, "r") as t: - template_content = "\n" - template_content += t.read() - - with open(cfg_file, "a") as f: - f.write(template_content) - - -def patch_printer_config(klipper_instances: List[Klipper]) -> None: - for instance in klipper_instances: - cfg_file = instance.cfg_file - Logger.print_status(f"Including mainsail-config in '{cfg_file}' ...") - - if not Path(cfg_file).exists(): - Logger.print_warn(f"'{cfg_file}' not found!") - return - - cm = ConfigManager(cfg_file) - if cm.config.has_section("include mainsail.cfg"): - Logger.print_info("Section already exist. Skipped ...") - return - - with open(cfg_file, "a") as f: - f.write("\n[include mainsail.cfg]") diff --git a/kiauh/components/mainsail/mainsail_utils.py b/kiauh/components/mainsail/mainsail_utils.py deleted file mode 100644 index 321f326..0000000 --- a/kiauh/components/mainsail/mainsail_utils.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 json -import shutil -from json import JSONDecodeError -from pathlib import Path -from typing import List - -import urllib.request - -from components.klipper.klipper import Klipper -from components.mainsail import ( - MAINSAIL_CONFIG_JSON, - MAINSAIL_DIR, - MAINSAIL_BACKUP_DIR, -) -from core.backup_manager.backup_manager import BackupManager -from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD -from utils.common import get_install_status_webui -from utils.logger import Logger - - -def get_mainsail_status() -> str: - return get_install_status_webui( - MAINSAIL_DIR, - NGINX_SITES_AVAILABLE.joinpath("mainsail"), - NGINX_CONFD.joinpath("upstreams.conf"), - NGINX_CONFD.joinpath("common_vars.conf"), - ) - - -def backup_config_json(is_temp=False) -> None: - Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") - bm = BackupManager() - if is_temp: - fn = Path.home().joinpath("config.json.kiauh.bak") - bm.backup_file(MAINSAIL_CONFIG_JSON, custom_filename=fn) - else: - bm.backup_file(MAINSAIL_CONFIG_JSON) - - -def restore_config_json() -> None: - try: - Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") - source = Path.home().joinpath("config.json.kiauh.bak") - shutil.copy(source, MAINSAIL_CONFIG_JSON) - except OSError: - Logger.print_info("Unable to restore config.json. Skipped ...") - - -def enable_mainsail_remotemode() -> None: - Logger.print_status("Enable Mainsails remote mode ...") - with open(MAINSAIL_CONFIG_JSON, "r") as f: - config_data = json.load(f) - - if config_data["instancesDB"] == "browser": - Logger.print_info("Remote mode already configured. Skipped ...") - return - - Logger.print_status("Setting instance storage location to 'browser' ...") - config_data["instancesDB"] = "browser" - - with open(MAINSAIL_CONFIG_JSON, "w") as f: - json.dump(config_data, f, indent=4) - Logger.print_ok("Mainsails remote mode enabled!") - - -def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: - Logger.print_status("Link NGINX logs into log directory ...") - access_log = Path("/var/log/nginx/mainsail-access.log") - error_log = Path("/var/log/nginx/mainsail-error.log") - - for instance in klipper_instances: - desti_access = instance.log_dir.joinpath("mainsail-access.log") - if not desti_access.exists(): - desti_access.symlink_to(access_log) - - desti_error = instance.log_dir.joinpath("mainsail-error.log") - if not desti_error.exists(): - desti_error.symlink_to(error_log) - - -def get_mainsail_local_version() -> str: - relinfo_file = MAINSAIL_DIR.joinpath("release_info.json") - if not relinfo_file.is_file(): - return "-" - - with open(relinfo_file, "r") as f: - return json.load(f)["version"] - - -def get_mainsail_remote_version() -> str: - url = "https://api.github.com/repos/mainsail-crew/mainsail/tags" - try: - with urllib.request.urlopen(url) as response: - data = json.loads(response.read()) - return data[0]["name"] - except (JSONDecodeError, TypeError): - return "ERROR" - - -def backup_mainsail_data() -> None: - with open(MAINSAIL_DIR.joinpath(".version"), "r") as v: - version = v.readlines()[0] - bm = BackupManager() - bm.backup_directory(f"mainsail-{version}", MAINSAIL_DIR, MAINSAIL_BACKUP_DIR) - bm.backup_file(MAINSAIL_CONFIG_JSON, MAINSAIL_BACKUP_DIR) - bm.backup_file(NGINX_SITES_AVAILABLE.joinpath("mainsail"), MAINSAIL_BACKUP_DIR) diff --git a/kiauh/components/mainsail/menus/mainsail_remove_menu.py b/kiauh/components/mainsail/menus/mainsail_remove_menu.py deleted file mode 100644 index 48cfb4b..0000000 --- a/kiauh/components/mainsail/menus/mainsail_remove_menu.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 - -# ======================================================================= # -# 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 textwrap - -from components.mainsail import mainsail_remove -from core.menus import BACK_HELP_FOOTER -from core.menus.base_menu import BaseMenu -from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN - - -# noinspection PyUnusedLocal -class MainsailRemoveMenu(BaseMenu): - def __init__(self): - super().__init__( - header=False, - options={ - "0": self.toggle_all, - "1": self.toggle_remove_mainsail, - "2": self.toggle_remove_ms_config, - "3": self.toggle_backup_config_json, - "4": self.toggle_remove_updater_section, - "5": self.toggle_remove_printer_cfg_include, - "6": self.run_removal_process, - }, - footer_type=BACK_HELP_FOOTER, - ) - self.remove_mainsail = False - self.remove_ms_config = False - self.backup_config_json = False - self.remove_updater_section = False - self.remove_printer_cfg_include = False - - def print_menu(self) -> None: - header = " [ Remove Mainsail ] " - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" - unchecked = "[ ]" - o1 = checked if self.remove_mainsail else unchecked - o2 = checked if self.remove_ms_config else unchecked - o3 = checked if self.backup_config_json else unchecked - o4 = checked if self.remove_updater_section else unchecked - o5 = checked if self.remove_printer_cfg_include 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 Mainsail | - | 2) {o2} Remove mainsail-config | - | 3) {o3} Backup config.json | - | | - | printer.cfg & moonraker.conf | - | 4) {o4} Remove Moonraker update section | - | 5) {o5} Remove printer.cfg include | - |-------------------------------------------------------| - | 6) Continue | - """ - )[1:] - print(menu, end="") - - def toggle_all(self, **kwargs) -> None: - self.remove_mainsail = True - self.remove_ms_config = True - self.backup_config_json = True - self.remove_updater_section = True - self.remove_printer_cfg_include = True - - def toggle_remove_mainsail(self, **kwargs) -> None: - self.remove_mainsail = not self.remove_mainsail - - def toggle_remove_ms_config(self, **kwargs) -> None: - self.remove_ms_config = not self.remove_ms_config - - def toggle_backup_config_json(self, **kwargs) -> None: - self.backup_config_json = not self.backup_config_json - - def toggle_remove_updater_section(self, **kwargs) -> None: - self.remove_updater_section = not self.remove_updater_section - - def toggle_remove_printer_cfg_include(self, **kwargs) -> None: - self.remove_printer_cfg_include = not self.remove_printer_cfg_include - - def run_removal_process(self, **kwargs) -> None: - if ( - not self.remove_mainsail - and not self.remove_ms_config - and not self.backup_config_json - and not self.remove_updater_section - and not self.remove_printer_cfg_include - ): - error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" - print(error) - return - - mainsail_remove.run_mainsail_removal( - remove_mainsail=self.remove_mainsail, - remove_ms_config=self.remove_ms_config, - backup_ms_config_json=self.backup_config_json, - remove_mr_updater_section=self.remove_updater_section, - remove_msc_printer_cfg_include=self.remove_printer_cfg_include, - ) - - self.remove_mainsail = False - self.remove_ms_config = False - self.backup_config_json = False - self.remove_updater_section = False - self.remove_printer_cfg_include = False diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index 652d028..f740b45 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -29,7 +29,7 @@ class MoonrakerRemoveMenu(BaseMenu): "3": self.toggle_remove_moonraker_env, "4": self.toggle_remove_moonraker_polkit, "5": self.toggle_delete_moonraker_logs, - "6": self.run_removal_process, + "c": self.run_removal_process, }, footer_type=BACK_HELP_FOOTER, ) @@ -66,7 +66,7 @@ class MoonrakerRemoveMenu(BaseMenu): | 4) {o4} Remove Policy Kit Rules | | 5) {o5} Delete all Log-Files | |-------------------------------------------------------| - | 6) Continue | + | C) Continue | """ )[1:] print(menu, end="") diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index fe635f8..a4c6cf2 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -14,11 +14,11 @@ import sys from pathlib import Path from typing import List +from components.webui_client import MAINSAIL_DIR +from components.webui_client.client_utils import enable_mainsail_remotemode from kiauh import KIAUH_CFG from components.klipper.klipper import Klipper from components.klipper.klipper_dialogs import print_instance_overview -from components.mainsail import MAINSAIL_DIR -from components.mainsail.mainsail_utils import enable_mainsail_remotemode from components.moonraker import ( EXIT_MOONRAKER_SETUP, DEFAULT_MOONRAKER_REPO_URL, diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index 3321781..8f89364 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -12,8 +12,6 @@ import shutil from typing import Dict, Literal, List, Union -from components.mainsail import MAINSAIL_DIR -from components.mainsail.mainsail_utils import enable_mainsail_remotemode from components.moonraker import ( DEFAULT_MOONRAKER_PORT, MODULE_PATH, @@ -23,6 +21,8 @@ from components.moonraker import ( MOONRAKER_DB_BACKUP_DIR, ) from components.moonraker.moonraker import Moonraker +from components.webui_client import MAINSAIL_DIR +from components.webui_client.client_utils import enable_mainsail_remotemode from core.backup_manager.backup_manager import BackupManager from core.config_manager.config_manager import ConfigManager from core.instance_manager.instance_manager import InstanceManager diff --git a/kiauh/components/webui_client/__init__.py b/kiauh/components/webui_client/__init__.py new file mode 100644 index 0000000..b0d44f3 --- /dev/null +++ b/kiauh/components/webui_client/__init__.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 typing import Literal, TypedDict, Set + +from core.backup_manager import BACKUP_ROOT_DIR + +MODULE_PATH = Path(__file__).resolve().parent + +########### +# MAINSAIL +########### +MAINSAIL_DIR = Path.home().joinpath("mainsail") +MAINSAIL_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-backups") +MAINSAIL_CONFIG_DIR = Path.home().joinpath("mainsail-config") +MAINSAIL_CONFIG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups") +MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git" +MAINSAIL_CONFIG_JSON = MAINSAIL_DIR.joinpath("config.json") +MAINSAIL_URL = ( + "https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip" +) +MAINSAIL_PRE_RLS_URL = ( + "https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip" +) +MAINSAIL_TAGS_URL = "https://api.github.com/repos/mainsail-crew/mainsail/tags" + +######### +# FLUIDD +######### +FLUIDD_DIR = Path.home().joinpath("fluidd") +FLUIDD_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-backups") +FLUIDD_CONFIG_DIR = Path.home().joinpath("fluidd-config") +FLUIDD_CONFIG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups") +FLUIDD_CONFIG_REPO_URL = "https://github.com/fluidd-core/fluidd-config.git" +FLUIDD_URL = "https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip" +FLUIDD_PRE_RLS_URL = ( + "https://github.com/fluidd-core/fluidd/releases/download/%TAG%/fluidd.zip" +) +FLUIDD_TAGS_URL = "https://api.github.com/repos/fluidd-core/fluidd/tags" + +ClientName = Literal["mainsail", "fluidd"] +ClientConfigName = Literal["mainsail-config", "fluidd-config"] + + +class ClientData(TypedDict): + name: ClientName + display_name: str + dir: Path + backup_dir: Path + url: str + pre_release_url: str + tags_url: str + remote_mode: bool # required only for Mainsail + mr_conf_repo: str + mr_conf_path: str + client_config: "ClientConfigData" + + +class ClientConfigData(TypedDict): + name: ClientConfigName + display_name: str + cfg_filename: str + dir: Path + backup_dir: Path + url: str + printer_cfg_section: str + mr_conf_path: str + mr_conf_origin: str diff --git a/kiauh/components/fluidd/menus/__init__.py b/kiauh/components/webui_client/client_config/__init__.py similarity index 100% rename from kiauh/components/fluidd/menus/__init__.py rename to kiauh/components/webui_client/client_config/__init__.py diff --git a/kiauh/components/webui_client/client_config/client_config_remove.py b/kiauh/components/webui_client/client_config/client_config_remove.py new file mode 100644 index 0000000..41203a3 --- /dev/null +++ b/kiauh/components/webui_client/client_config/client_config_remove.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 +import subprocess +from typing import List + +from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker +from components.webui_client import ClientConfigData +from core.instance_manager.instance_manager import InstanceManager +from utils.filesystem_utils import remove_file, remove_config_section +from utils.logger import Logger + + +def run_client_config_removal( + client_config: ClientConfigData, + remove_moonraker_conf_section: bool, + remove_printer_cfg_include: bool, + kl_instances: List[Klipper], + mr_instances: List[Moonraker], +) -> None: + remove_client_config_dir(client_config) + remove_client_config_symlink(client_config) + if remove_moonraker_conf_section: + remove_config_section( + f"update_manager {client_config.get('name')}", mr_instances + ) + if remove_printer_cfg_include: + remove_config_section(client_config.get("printer_cfg_section"), kl_instances) + + +def remove_client_config_dir(client_config: ClientConfigData) -> None: + Logger.print_status(f"Removing {client_config.get('name')} ...") + client_config_dir = client_config.get("dir") + if not client_config_dir.exists(): + Logger.print_info(f"'{client_config_dir}' does not exist. Skipping ...") + return + + try: + shutil.rmtree(client_config_dir) + except OSError as e: + Logger.print_error(f"Unable to delete '{client_config_dir}':\n{e}") + + +def remove_client_config_symlink(client_config: ClientConfigData) -> None: + Logger.print_status(f"Removing {client_config.get('cfg_filename')} symlinks ...") + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + for instance in instances: + Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") + try: + remove_file(instance.cfg_dir.joinpath(client_config.get("cfg_filename"))) + except subprocess.CalledProcessError: + Logger.print_error("Failed to remove symlink!") diff --git a/kiauh/components/webui_client/client_config/client_config_setup.py b/kiauh/components/webui_client/client_config/client_config_setup.py new file mode 100644 index 0000000..d893198 --- /dev/null +++ b/kiauh/components/webui_client/client_config/client_config_setup.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 +import subprocess +from pathlib import Path +from typing import List, get_args + +from kiauh import KIAUH_CFG +from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker +from components.webui_client import ClientConfigData, ClientName, ClientData +from components.webui_client.client_dialogs import print_client_already_installed_dialog +from components.webui_client.client_utils import ( + load_client_data, + backup_client_config_data, +) +from core.config_manager.config_manager import ConfigManager + +from core.instance_manager.instance_manager import InstanceManager +from core.repo_manager.repo_manager import RepoManager +from utils.filesystem_utils import ( + create_symlink, + add_config_section, +) +from utils.input_utils import get_confirm +from utils.logger import Logger + + +def install_client_config(client_name: ClientName) -> None: + client: ClientData = load_client_data(client_name) + client_config: ClientConfigData = client.get("client_config") + d_name = client_config.get("display_name") + + if check_existing_client_config_install(client_name): + Logger.print_info("Another Client-Config is already installed! Skipped ...") + return + + if client_config.get("dir").exists(): + print_client_already_installed_dialog(d_name) + if get_confirm(f"Re-install {d_name}?", allow_go_back=True): + shutil.rmtree(client_config.get("dir")) + else: + return + + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances + kl_im = InstanceManager(Klipper) + kl_instances = kl_im.instances + + try: + download_client_config(client_config) + create_client_config_symlink(client_config, kl_instances) + add_config_section( + section=f"update_manager {client_config.get('name')}", + instances=mr_instances, + options=[ + ("type", "git_repo"), + ("primary_branch", "master"), + ("path", client_config.get("mr_conf_path")), + ("origin", client_config.get("mr_conf_origin")), + ("managed_services", "klipper"), + ], + ) + add_config_section(client_config.get("printer_cfg_section"), kl_instances) + kl_im.restart_all_instance() + + except Exception as e: + Logger.print_error(f"{d_name} installation failed!\n{e}") + return + + Logger.print_ok(f"{d_name} installation complete!", start="\n") + + +def check_existing_client_config_install(client_name: ClientName) -> bool: + # check if any other client-configs are present + # as they can conflict each other, or are at least + # redundant to have, so we skip the installation + client_list = list(get_args(ClientName)) + client_list.remove(client_name) + for c in client_list: + c_data: ClientData = load_client_data(c) + c_config_data: ClientConfigData = c_data.get("client_config") + if c_config_data.get("dir").exists(): + return True + + return False + + +def download_client_config(client_config: ClientConfigData) -> None: + try: + Logger.print_status(f"Downloading {client_config.get('display_name')} ...") + rm = RepoManager( + client_config.get("url"), target_dir=str(client_config.get("dir")) + ) + rm.clone_repo() + except Exception: + Logger.print_error(f"Downloading {client_config.get('display_name')} failed!") + raise + + +def update_client_config(client: ClientData) -> None: + client_config: ClientConfigData = client.get("client_config") + + Logger.print_status(f"Updating {client_config.get('display_name')} ...") + + cm = ConfigManager(cfg_file=KIAUH_CFG) + if cm.get_value("kiauh", "backup_before_update"): + backup_client_config_data(client) + + repo_manager = RepoManager( + repo=client_config.get("url"), + branch="master", + target_dir=str(client_config.get("dir")), + ) + repo_manager.pull_repo() + + Logger.print_ok(f"Successfully updated {client_config.get('display_name')}.") + Logger.print_warn("Remember to restart Klipper to reload the configurations!") + + +def create_client_config_symlink( + client_config: ClientConfigData, klipper_instances: List[Klipper] = None +) -> None: + if klipper_instances is None: + kl_im = InstanceManager(Klipper) + klipper_instances = kl_im.instances + + Logger.print_status(f"Create symlink for {client_config.get('cfg_filename')} ...") + source = Path(client_config.get("dir"), client_config.get("cfg_filename")) + for instance in klipper_instances: + target = instance.cfg_dir + Logger.print_status(f"Linking {source} to {target}") + try: + create_symlink(source, target) + except subprocess.CalledProcessError: + Logger.print_error("Creating symlink failed!") diff --git a/kiauh/components/fluidd/fluidd_dialogs.py b/kiauh/components/webui_client/client_dialogs.py similarity index 75% rename from kiauh/components/fluidd/fluidd_dialogs.py rename to kiauh/components/webui_client/client_dialogs.py index deec016..9fb98e9 100644 --- a/kiauh/components/fluidd/fluidd_dialogs.py +++ b/kiauh/components/webui_client/client_dialogs.py @@ -12,6 +12,7 @@ import textwrap from typing import List +from components.webui_client import ClientData from core.menus.base_menu import print_back_footer from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN @@ -25,10 +26,10 @@ def print_moonraker_not_found_dialog(): | {line1:<63}| | {line2:<63}| |-------------------------------------------------------| - | It is possible to install Fluidd without a local | + | It is possible to install Mainsail without a local | | Moonraker installation. If you continue, you need to | | make sure, that Moonraker is installed on another | - | machine in your network. Otherwise Fluidd will NOT | + | machine in your network. Otherwise Mainsail will NOT | | work correctly. | """ )[1:] @@ -37,19 +38,18 @@ def print_moonraker_not_found_dialog(): print_back_footer() -def print_fluidd_already_installed_dialog(): +def print_client_already_installed_dialog(name: str): line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" - line2 = f"{COLOR_YELLOW}Fluidd seems to be already installed!{RESET_FORMAT}" + line2 = f"{COLOR_YELLOW}{name} seems to be already installed!{RESET_FORMAT}" + line3 = f"If you continue, your current {name}" dialog = textwrap.dedent( f""" /=======================================================\\ | {line1:<63}| | {line2:<63}| |-------------------------------------------------------| - | If you continue, your current Fluidd installation | - | will be overwritten. You will not loose any printer | - | configurations and the Moonraker database will remain | - | untouched. | + | {line3:<54}| + | installation will be overwritten. | """ )[1:] @@ -57,38 +57,21 @@ def print_fluidd_already_installed_dialog(): print_back_footer() -def print_install_fluidd_config_dialog(): - dialog = textwrap.dedent( - f""" - /=======================================================\\ - | It is recommended to use special macros in order to | - | have Fluidd fully functional and working. | - | | - | The recommended macros for Fluidd can be seen here: | - | https://github.com/fluidd-core/fluidd-config | - | | - | If you already use these macros skip this step. | - | Otherwise you should consider to answer with 'Y' to | - | download the recommended macros. | - \\=======================================================/ - """ - )[1:] - - print(dialog, end="") - - -def print_fluidd_port_select_dialog(port: str, ports_in_use: List[str]): +def print_client_port_select_dialog(name: str, port: str, ports_in_use: List[str]): port = f"{COLOR_CYAN}{port}{RESET_FORMAT}" + line1 = f"Please select the port, {name} should be served on." + line2 = f"In case you need {name} to be served on a specific" dialog = textwrap.dedent( f""" /=======================================================\\ - | Please select the port, Fluidd should be served on. | + | {line1:<54}| | If you are unsure what to select, hit Enter to apply | | the suggested value of: {port:38} | | | - | In case you need Fluidd to be served on a specific | + | {line2:<54}| | port, you can set it now. Make sure the port is not | | used by any other application on your system! | + \\=======================================================/ """ )[1:] @@ -102,3 +85,27 @@ def print_fluidd_port_select_dialog(port: str, ports_in_use: List[str]): dialog += "\\=======================================================/\n" print(dialog, end="") + + +def print_install_client_config_dialog(client: ClientData): + name = client.get("display_name") + url = client.get("client_config").get("url").replace(".git", "") + line1 = f"have {name} fully functional and working." + line2 = f"The recommended macros for {name} can be seen here:" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + | It is recommended to use special macros in order to | + | {line1:<54}| + | | + | {line2:<54}| + | {url:<54}| + | | + | If you already use these macros skip this step. | + | Otherwise you should consider to answer with 'Y' to | + | download the recommended macros. | + \\=======================================================/ + """ + )[1:] + + print(dialog, end="") diff --git a/kiauh/components/webui_client/client_remove.py b/kiauh/components/webui_client/client_remove.py new file mode 100644 index 0000000..49bc694 --- /dev/null +++ b/kiauh/components/webui_client/client_remove.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 typing import List + +from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker +from components.webui_client import ClientData +from components.webui_client.client_config.client_config_remove import ( + run_client_config_removal, +) +from components.webui_client.client_utils import backup_mainsail_config_json + +from core.instance_manager.instance_manager import InstanceManager +from utils.filesystem_utils import ( + remove_nginx_config, + remove_nginx_logs, + remove_config_section, +) +from utils.logger import Logger + + +def run_client_removal( + client: ClientData, + rm_client: bool, + rm_client_config: bool, + backup_ms_config_json: bool, + rm_moonraker_conf_section: bool, + rm_printer_cfg_section: bool, +) -> None: + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances + kl_im = InstanceManager(Klipper) + kl_instances: List[Klipper] = kl_im.instances + if backup_ms_config_json and client.get("name") == "mainsail": + backup_mainsail_config_json() + if rm_client: + client_name = client.get("name") + remove_client_dir(client) + remove_nginx_config(client_name) + remove_nginx_logs(client_name) + if rm_moonraker_conf_section: + section = f"update_manager {client_name}" + remove_config_section(section, mr_instances) + if rm_client_config: + run_client_config_removal( + client.get("client_config"), + rm_moonraker_conf_section, + rm_printer_cfg_section, + kl_instances, + mr_instances, + ) + + +def remove_client_dir(client: ClientData) -> None: + Logger.print_status(f"Removing {client.get('display_name')} ...") + client_dir = client.get("dir") + if not client.get("dir").exists(): + Logger.print_info(f"'{client_dir}' does not exist. Skipping ...") + return + + try: + shutil.rmtree(client_dir) + except OSError as e: + Logger.print_error(f"Unable to delete '{client_dir}':\n{e}") diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py new file mode 100644 index 0000000..a783507 --- /dev/null +++ b/kiauh/components/webui_client/client_setup.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 typing import List + +from components.klipper.klipper import Klipper +from components.webui_client import ( + ClientName, + ClientData, +) + +from components.moonraker.moonraker import Moonraker +from components.webui_client.client_config.client_config_setup import ( + install_client_config, + check_existing_client_config_install, +) +from components.webui_client.client_dialogs import ( + print_moonraker_not_found_dialog, + print_client_port_select_dialog, + print_install_client_config_dialog, +) +from components.webui_client.client_utils import ( + backup_mainsail_config_json, + restore_mainsail_config_json, + enable_mainsail_remotemode, + symlink_webui_nginx_log, + load_client_data, +) +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.instance_manager import InstanceManager +from kiauh import KIAUH_CFG +from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED +from utils.common import check_install_dependencies +from utils.filesystem_utils import ( + unzip, + copy_upstream_nginx_cfg, + copy_common_vars_nginx_cfg, + create_nginx_cfg, + create_symlink, + remove_file, + add_config_section, + read_ports_from_nginx_configs, + is_valid_port, + get_next_free_port, +) +from utils.input_utils import get_confirm, get_number_input +from utils.logger import Logger +from utils.system_utils import ( + download_file, + set_nginx_permissions, + get_ipv4_addr, + control_systemd_service, +) + + +def install_client(client_name: ClientName) -> None: + client: ClientData = load_client_data(client_name) + d_name = client.get("display_name") + + if client is None: + Logger.print_error("Missing parameter client_name!") + return + + if client.get("dir").exists(): + Logger.print_info( + f"{client.get('display_name')} seems to be already installed! Skipped ..." + ) + return + + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances + + enable_remotemode = False + if not mr_instances: + print_moonraker_not_found_dialog() + if not get_confirm( + f"Continue {d_name} installation?", + allow_go_back=True, + ): + return + + # if moonraker is not installed or multiple instances + # are installed we enable mainsails remote mode + if client.get("remote_mode") and not mr_instances or len(mr_instances) > 1: + enable_remotemode = True + + kl_im = InstanceManager(Klipper) + kl_instances = kl_im.instances + install_client_cfg = False + client_config = client.get("client_config") + if ( + kl_instances + and not client_config.get("dir").exists() + and not check_existing_client_config_install(client.get("name")) + ): + print_install_client_config_dialog(client) + question = f"Download the recommended {client_config.get('display_name')}?" + install_client_cfg = get_confirm(question, allow_go_back=False) + + cm = ConfigManager(cfg_file=KIAUH_CFG) + client_port = cm.get_value(client.get("name"), "port") + ports_in_use = read_ports_from_nginx_configs() + + # check if configured port is a valid number and not in use already + valid_port = is_valid_port(client_port, ports_in_use) + while not valid_port: + next_port = get_next_free_port(ports_in_use) + print_client_port_select_dialog(d_name, next_port, ports_in_use) + client_port = str( + get_number_input( + f"Configure {d_name} for port", + min_count=int(next_port), + default=next_port, + ) + ) + valid_port = is_valid_port(client_port, ports_in_use) + + check_install_dependencies(["nginx"]) + + try: + download_client(client) + if enable_remotemode and client.get("name") == "mainsail": + enable_mainsail_remotemode() + if mr_instances: + add_config_section( + section=f"update_manager {client.get('name')}", + instances=mr_instances, + options=[ + ("type", "web"), + ("channel", "stable"), + ("repo", client.get("mr_conf_repo")), + ("path", client.get("mr_conf_path")), + ], + ) + mr_im.restart_all_instance() + if install_client_cfg and kl_instances: + install_client_config(client.get("name")) + + copy_upstream_nginx_cfg() + copy_common_vars_nginx_cfg() + create_client_nginx_cfg(client, client_port) + if kl_instances: + symlink_webui_nginx_log(kl_instances) + control_systemd_service("nginx", "restart") + + except Exception as e: + Logger.print_error(f"{d_name} installation failed!\n{e}") + return + + log = f"Open {d_name} now on: http://{get_ipv4_addr()}:{client_port}" + Logger.print_ok(f"{d_name} installation complete!", start="\n") + Logger.print_ok(log, prefix=False, end="\n\n") + + +def download_client(client: ClientData) -> None: + zipfile = f"{client.get('name').lower()}.zip" + target = Path().home().joinpath(zipfile) + try: + Logger.print_status(f"Downloading {zipfile} ...") + download_file(client.get("url"), target, True) + Logger.print_ok("Download complete!") + + Logger.print_status(f"Extracting {zipfile} ...") + unzip(target, client.get("dir")) + target.unlink(missing_ok=True) + Logger.print_ok("OK!") + + except Exception: + Logger.print_error(f"Downloading {zipfile} failed!") + raise + + +def update_client(client: ClientData) -> None: + Logger.print_status(f"Updating {client.get('display_name')} ...") + if client.get("name") == "mainsail": + backup_mainsail_config_json(is_temp=True) + + download_client(client) + + if client.get("name") == "mainsail": + restore_mainsail_config_json() + + +def create_client_nginx_cfg(client: ClientData, port: int) -> None: + d_name = client.get("display_name") + root_dir = client.get("dir") + source = NGINX_SITES_AVAILABLE.joinpath(client.get("name")) + target = NGINX_SITES_ENABLED.joinpath(client.get("name")) + try: + Logger.print_status(f"Creating NGINX config for {d_name} ...") + remove_file(Path("/etc/nginx/sites-enabled/default"), True) + create_nginx_cfg(client.get("name"), port, root_dir) + create_symlink(source, target, True) + set_nginx_permissions() + Logger.print_ok(f"NGINX config for {d_name} successfully created.") + except Exception: + Logger.print_error(f"Creating NGINX config for {d_name} failed!") + raise diff --git a/kiauh/components/webui_client/client_utils.py b/kiauh/components/webui_client/client_utils.py new file mode 100644 index 0000000..40207e5 --- /dev/null +++ b/kiauh/components/webui_client/client_utils.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 json +import shutil +from json import JSONDecodeError +from pathlib import Path +from typing import List, Optional, Dict, Literal, Union + +import urllib.request + +from components.klipper.klipper import Klipper +from components.webui_client import ( + MAINSAIL_CONFIG_JSON, + MAINSAIL_DIR, + MAINSAIL_BACKUP_DIR, + FLUIDD_PRE_RLS_URL, + FLUIDD_BACKUP_DIR, + FLUIDD_URL, + FLUIDD_DIR, + ClientData, + FLUIDD_CONFIG_REPO_URL, + FLUIDD_CONFIG_DIR, + ClientConfigData, + MAINSAIL_PRE_RLS_URL, + MAINSAIL_URL, + MAINSAIL_CONFIG_REPO_URL, + MAINSAIL_CONFIG_DIR, + ClientName, + MAINSAIL_TAGS_URL, + FLUIDD_TAGS_URL, + FLUIDD_CONFIG_BACKUP_DIR, + MAINSAIL_CONFIG_BACKUP_DIR, +) +from core.backup_manager.backup_manager import BackupManager +from core.repo_manager.repo_manager import RepoManager +from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD +from utils.common import get_install_status_webui, get_install_status_common +from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW +from utils.logger import Logger + + +def load_client_data(client_name: ClientName) -> Optional[ClientData]: + client_data = None + + if client_name == "mainsail": + client_config_data = ClientConfigData( + name="mainsail-config", + display_name="Mainsail-Config", + cfg_filename="mainsail.cfg", + dir=MAINSAIL_CONFIG_DIR, + backup_dir=MAINSAIL_CONFIG_BACKUP_DIR, + url=MAINSAIL_CONFIG_REPO_URL, + printer_cfg_section="include mainsail.cfg", + mr_conf_path="~/mainsail-config", + mr_conf_origin=MAINSAIL_CONFIG_REPO_URL, + ) + client_data = ClientData( + name=client_name, + display_name=client_name.capitalize(), + dir=MAINSAIL_DIR, + backup_dir=MAINSAIL_BACKUP_DIR, + url=MAINSAIL_URL, + pre_release_url=MAINSAIL_PRE_RLS_URL, + tags_url=MAINSAIL_TAGS_URL, + remote_mode=True, + mr_conf_repo="mainsail-crew/mainsail", + mr_conf_path="~/mainsail", + client_config=client_config_data, + ) + elif client_name == "fluidd": + client_config_data = ClientConfigData( + name="fluidd-config", + display_name="Fluidd-Config", + cfg_filename="fluidd.cfg", + dir=FLUIDD_CONFIG_DIR, + backup_dir=FLUIDD_CONFIG_BACKUP_DIR, + url=FLUIDD_CONFIG_REPO_URL, + printer_cfg_section="include fluidd.cfg", + mr_conf_path="~/fluidd-config", + mr_conf_origin=FLUIDD_CONFIG_REPO_URL, + ) + client_data = ClientData( + name=client_name, + display_name=client_name.capitalize(), + dir=FLUIDD_DIR, + backup_dir=FLUIDD_BACKUP_DIR, + url=FLUIDD_URL, + pre_release_url=FLUIDD_PRE_RLS_URL, + tags_url=FLUIDD_TAGS_URL, + remote_mode=False, + mr_conf_repo="fluidd-core/fluidd", + mr_conf_path="~/fluidd", + client_config=client_config_data, + ) + + return client_data + + +def get_client_status(client: ClientData) -> str: + return get_install_status_webui( + client.get("dir"), + NGINX_SITES_AVAILABLE.joinpath(client.get("name")), + NGINX_CONFD.joinpath("upstreams.conf"), + NGINX_CONFD.joinpath("common_vars.conf"), + ) + + +def get_client_config_status(client: ClientData) -> Dict[ + Literal["repo", "local", "remote"], + Union[str, int], +]: + client_config = client.get("client_config") + client_config = client_config.get("dir") + + return { + "repo": RepoManager.get_repo_name(client_config), + "local": RepoManager.get_local_commit(client_config), + "remote": RepoManager.get_remote_commit(client_config), + } + + +def get_current_client_config(clients: List[ClientData]) -> str: + installed = [] + for client in clients: + client_config = client.get("client_config") + if client_config.get("dir").exists(): + installed.append(client) + + if len(installed) > 1: + return f"{COLOR_YELLOW}Conflict!{RESET_FORMAT}" + elif len(installed) == 1: + cfg = installed[0].get("client_config") + return f"{COLOR_CYAN}{cfg.get('display_name')}{RESET_FORMAT}" + + return f"{COLOR_CYAN}-{RESET_FORMAT}" + + +def backup_mainsail_config_json(is_temp=False) -> None: + Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...") + bm = BackupManager() + if is_temp: + fn = Path.home().joinpath("config.json.kiauh.bak") + bm.backup_file(MAINSAIL_CONFIG_JSON, custom_filename=fn) + else: + bm.backup_file(MAINSAIL_CONFIG_JSON) + + +def restore_mainsail_config_json() -> None: + try: + Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...") + source = Path.home().joinpath("config.json.kiauh.bak") + shutil.copy(source, MAINSAIL_CONFIG_JSON) + except OSError: + Logger.print_info("Unable to restore config.json. Skipped ...") + + +def enable_mainsail_remotemode() -> None: + Logger.print_status("Enable Mainsails remote mode ...") + with open(MAINSAIL_CONFIG_JSON, "r") as f: + config_data = json.load(f) + + if config_data["instancesDB"] == "browser": + Logger.print_info("Remote mode already configured. Skipped ...") + return + + Logger.print_status("Setting instance storage location to 'browser' ...") + config_data["instancesDB"] = "browser" + + with open(MAINSAIL_CONFIG_JSON, "w") as f: + json.dump(config_data, f, indent=4) + Logger.print_ok("Mainsails remote mode enabled!") + + +def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None: + Logger.print_status("Link NGINX logs into log directory ...") + access_log = Path("/var/log/nginx/mainsail-access.log") + error_log = Path("/var/log/nginx/mainsail-error.log") + + for instance in klipper_instances: + desti_access = instance.log_dir.joinpath("mainsail-access.log") + if not desti_access.exists(): + desti_access.symlink_to(access_log) + + desti_error = instance.log_dir.joinpath("mainsail-error.log") + if not desti_error.exists(): + desti_error.symlink_to(error_log) + + +def get_local_client_version(client: ClientData) -> str: + relinfo_file = client.get("dir").joinpath("release_info.json") + if not relinfo_file.is_file(): + return "-" + + with open(relinfo_file, "r") as f: + return json.load(f)["version"] + + +def get_remote_client_version(client: ClientData) -> str: + try: + with urllib.request.urlopen(client.get("tags_url")) as response: + data = json.loads(response.read()) + return data[0]["name"] + except (JSONDecodeError, TypeError): + return "ERROR" + + +def backup_client_data(client: ClientData) -> None: + name = client.get("name") + src = client.get("dir") + dest = client.get("backup_dir") + + with open(src.joinpath(".version"), "r") as v: + version = v.readlines()[0] + + bm = BackupManager() + bm.backup_directory(f"{name}-{version}", src, dest) + if name == "mainsail": + bm.backup_file(MAINSAIL_CONFIG_JSON, dest) + bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest) + + +def backup_client_config_data(client: ClientData) -> None: + client_config = client.get("client_config") + name = client_config.get("name") + source = client_config.get("dir") + target = client_config.get("backup_dir") + bm = BackupManager() + bm.backup_directory(name, source, target) diff --git a/kiauh/components/mainsail/menus/__init__.py b/kiauh/components/webui_client/menus/__init__.py similarity index 100% rename from kiauh/components/mainsail/menus/__init__.py rename to kiauh/components/webui_client/menus/__init__.py diff --git a/kiauh/components/webui_client/menus/client_remove_menu.py b/kiauh/components/webui_client/menus/client_remove_menu.py new file mode 100644 index 0000000..b4648d6 --- /dev/null +++ b/kiauh/components/webui_client/menus/client_remove_menu.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# 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 textwrap +from typing import Callable, Dict + +from components.webui_client import client_remove, ClientData +from core.menus import BACK_HELP_FOOTER +from core.menus.base_menu import BaseMenu +from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN + + +# noinspection PyUnusedLocal +class ClientRemoveMenu(BaseMenu): + def __init__(self, client: ClientData): + self.client = client + self.rm_client = False + self.rm_client_config = False + self.backup_mainsail_config_json = False + self.rm_moonraker_conf_section = False + self.rm_printer_cfg_section = False + + super().__init__( + header=False, + options=self.get_options(), + footer_type=BACK_HELP_FOOTER, + ) + + def get_options(self) -> Dict[str, Callable]: + options = { + "0": self.toggle_all, + "1": self.toggle_rm_client, + "2": self.toggle_rm_client_config, + "3": self.toggle_rm_printer_cfg_section, + "4": self.toggle_rm_moonraker_conf_section, + "c": self.run_removal_process, + } + if self.client.get("name") == "mainsail": + options["5"] = self.toggle_backup_mainsail_config_json + + return options + + def print_menu(self) -> None: + client_name = self.client.get("display_name") + client_config = self.client.get("client_config") + client_config_name = client_config.get("display_name") + + header = f" [ Remove {client_name} ] " + color = COLOR_RED + count = 62 - len(color) - len(RESET_FORMAT) + checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + unchecked = "[ ]" + o1 = checked if self.rm_client else unchecked + o2 = checked if self.rm_client_config else unchecked + o3 = checked if self.rm_printer_cfg_section else unchecked + o4 = checked if self.rm_moonraker_conf_section 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 {client_name:16} | + | 2) {o2} Remove {client_config_name:24} | + | | + | printer.cfg & moonraker.conf | + | 3) {o3} Remove printer.cfg include | + | 4) {o4} Remove Moonraker update section | + """ + )[1:] + + if self.client.get("name") == "mainsail": + o5 = checked if self.backup_mainsail_config_json else unchecked + menu += textwrap.dedent( + f""" + | | + | Mainsail config.json | + | 5) {o5} Backup config.json | + """ + )[1:] + + menu += textwrap.dedent( + """ + |-------------------------------------------------------| + | C) Continue | + """ + )[1:] + print(menu, end="") + + def toggle_all(self, **kwargs) -> None: + self.rm_client = True + self.rm_client_config = True + self.backup_mainsail_config_json = True + self.rm_moonraker_conf_section = True + self.rm_printer_cfg_section = True + + def toggle_rm_client(self, **kwargs) -> None: + self.rm_client = not self.rm_client + + def toggle_rm_client_config(self, **kwargs) -> None: + self.rm_client_config = not self.rm_client_config + + def toggle_backup_mainsail_config_json(self, **kwargs) -> None: + self.backup_mainsail_config_json = not self.backup_mainsail_config_json + + def toggle_rm_moonraker_conf_section(self, **kwargs) -> None: + self.rm_moonraker_conf_section = not self.rm_moonraker_conf_section + + def toggle_rm_printer_cfg_section(self, **kwargs) -> None: + self.rm_printer_cfg_section = not self.rm_printer_cfg_section + + def run_removal_process(self, **kwargs) -> None: + if ( + not self.rm_client + and not self.rm_client_config + and not self.backup_mainsail_config_json + and not self.rm_moonraker_conf_section + and not self.rm_printer_cfg_section + ): + error = f"{COLOR_RED}Nothing selected ...{RESET_FORMAT}" + print(error) + return + + client_remove.run_client_removal( + client=self.client, + rm_client=self.rm_client, + rm_client_config=self.rm_client_config, + backup_ms_config_json=self.backup_mainsail_config_json, + rm_moonraker_conf_section=self.rm_moonraker_conf_section, + rm_printer_cfg_section=self.rm_printer_cfg_section, + ) + + self.rm_client = False + self.rm_client_config = False + self.backup_mainsail_config_json = False + self.rm_moonraker_conf_section = False + self.rm_printer_cfg_section = False diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index 8b92978..459461a 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -12,11 +12,15 @@ import textwrap from components.klipper.klipper_utils import backup_klipper_dir -from components.mainsail.mainsail_utils import backup_mainsail_data from components.moonraker.moonraker_utils import ( backup_moonraker_dir, backup_moonraker_db_dir, ) +from components.webui_client.client_utils import ( + backup_client_data, + load_client_data, + backup_client_config_data, +) from core.menus import BACK_FOOTER from core.menus.base_menu import BaseMenu from utils.common import backup_printer_config_dir @@ -35,6 +39,10 @@ class BackupMenu(BaseMenu): "3": self.backup_printer_config, "4": self.backup_moonraker_db, "5": self.backup_mainsail, + "6": self.backup_fluidd, + "7": self.backup_mainsail_config, + "8": self.backup_fluidd_config, + "9": self.backup_klipperscreen, }, footer_type=BACK_FOOTER, ) @@ -51,15 +59,15 @@ class BackupMenu(BaseMenu): |-------------------------------------------------------| | {line1:^62} | |-------------------------------------------------------| - | Klipper & Moonraker API: | Touchscreen GUI: | - | 1) [Klipper] | 7) [KlipperScreen] | - | 2) [Moonraker] | | - | 3) [Config Folder] | Other: | - | 4) [Moonraker Database] | 9) [Telegram Bot] | - | | | - | Klipper Webinterface: | | - | 5) [Mainsail] | | - | 6) [Fluidd] | | + | Klipper & Moonraker API: | Client-Config: | + | 1) [Klipper] | 7) [Mainsail-Config] | + | 2) [Moonraker] | 8) [Fluidd-Config] | + | 3) [Config Folder] | | + | 4) [Moonraker Database] | Touchscreen GUI: | + | | 9) [KlipperScreen] | + | Webinterface: | | + | 5) [Mainsail] | | + | 6) [Fluidd] | | """ )[1:] print(menu, end="") @@ -77,13 +85,16 @@ class BackupMenu(BaseMenu): backup_moonraker_db_dir() def backup_mainsail(self, **kwargs): - backup_mainsail_data() + backup_client_data(load_client_data("mainsail")) def backup_fluidd(self, **kwargs): - pass + backup_client_data(load_client_data("fluidd")) + + def backup_mainsail_config(self, **kwargs): + backup_client_config_data(load_client_data("mainsail")) + + def backup_fluidd_config(self, **kwargs): + backup_client_config_data(load_client_data("fluidd")) def backup_klipperscreen(self, **kwargs): pass - - def backup_telegram_bot(self, **kwargs): - pass diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index c466fdf..a90e235 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -11,10 +11,10 @@ import textwrap -from components.fluidd import fluidd_setup from components.klipper import klipper_setup -from components.mainsail import mainsail_setup from components.moonraker import moonraker_setup +from components.webui_client import client_setup +from components.webui_client.client_config import client_config_setup from core.menus import BACK_FOOTER from core.menus.base_menu import BaseMenu from utils.constants import COLOR_GREEN, RESET_FORMAT @@ -31,13 +31,11 @@ class InstallMenu(BaseMenu): "2": self.install_moonraker, "3": self.install_mainsail, "4": self.install_fluidd, - "5": self.install_klipperscreen, - "6": self.install_pretty_gcode, - "7": self.install_telegram_bot, - "8": self.install_obico, - "9": self.install_octoeverywhere, - "10": self.install_mobileraker, - "11": self.install_crowsnest, + "5": self.install_mainsail_config, + "6": self.install_fluidd_config, + "7": None, + "8": None, + "9": None, }, footer_type=BACK_FOOTER, ) @@ -51,16 +49,18 @@ class InstallMenu(BaseMenu): /=======================================================\\ | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| - | Firmware & API: | Other: | - | 1) [Klipper] | 6) [PrettyGCode] | - | 2) [Moonraker] | 7) [Telegram Bot] | - | | 8) $(obico_install_title) | - | Klipper Webinterface: | 9) [OctoEverywhere] | - | 3) [Mainsail] | 10) [Mobileraker] | - | 4) [Fluidd] | | - | | Webcam Streamer: | - | Touchscreen GUI: | 11) [Crowsnest] | - | 5) [KlipperScreen] | | + | Firmware & API: | Touchscreen GUI: | + | 1) [Klipper] | 7) [KlipperScreen] | + | 2) [Moonraker] | | + | | Android / iOS: | + | Webinterface: | 8) [Mobileraker] | + | 3) [Mainsail] | | + | 4) [Fluidd] | Webcam Streamer: | + | | 9) [Crowsnest] | + | Client-Config: | | + | 5) [Mainsail-Config] | | + | 6) [Fluidd-Config] | | + | | | """ )[1:] print(menu, end="") @@ -72,28 +72,13 @@ class InstallMenu(BaseMenu): moonraker_setup.install_moonraker() def install_mainsail(self, **kwargs): - mainsail_setup.install_mainsail() + client_setup.install_client(client_name="mainsail") + + def install_mainsail_config(self, **kwargs): + client_config_setup.install_client_config(client_name="mainsail") def install_fluidd(self, **kwargs): - fluidd_setup.install_fluidd() + client_setup.install_client(client_name="fluidd") - def install_klipperscreen(self, **kwargs): - print("install_klipperscreen") - - def install_pretty_gcode(self, **kwargs): - print("install_pretty_gcode") - - def install_telegram_bot(self, **kwargs): - print("install_telegram_bot") - - def install_obico(self, **kwargs): - print("install_obico") - - def install_octoeverywhere(self, **kwargs): - print("install_octoeverywhere") - - def install_mobileraker(self, **kwargs): - print("install_mobileraker") - - def install_crowsnest(self, **kwargs): - print("install_crowsnest") + def install_fluidd_config(self, **kwargs): + client_config_setup.install_client_config(client_name="fluidd") diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 716e046..4ab3b31 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -11,11 +11,14 @@ import textwrap -from components.fluidd.fluidd_utils import get_fluidd_status from components.klipper.klipper_utils import get_klipper_status from components.log_uploads.menus.log_upload_menu import LogUploadMenu -from components.mainsail.mainsail_utils import get_mainsail_status from components.moonraker.moonraker_utils import get_moonraker_status +from components.webui_client.client_utils import ( + get_client_status, + load_client_data, + get_current_client_config, +) from core.menus import QUIT_FOOTER from core.menus.advanced_menu import AdvancedMenu from core.menus.backup_menu import BackupMenu @@ -61,13 +64,11 @@ class MainMenu(BaseMenu): self.ks_status = "" self.mb_status = "" self.cn_status = "" - self.tg_status = "" - self.ob_status = "" - self.oe_status = "" + self.cc_status = "" self.init_status() def init_status(self) -> None: - status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn", "tg", "ob", "oe"] + status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"] for var in status_vars: setattr(self, f"{var}_status", f"{COLOR_RED}Not installed!{RESET_FORMAT}") @@ -87,9 +88,15 @@ class MainMenu(BaseMenu): self.mr_status = self.format_status_by_code(mr_code, mr_status, mr_instances) self.mr_repo = f"{COLOR_CYAN}{moonraker_status.get('repo')}{RESET_FORMAT}" # mainsail - self.ms_status = get_mainsail_status() + mainsail_client_data = load_client_data("mainsail") + self.ms_status = get_client_status(mainsail_client_data) # fluidd - self.fl_status = get_fluidd_status() + fluidd_client_data = load_client_data("fluidd") + self.fl_status = get_client_status(fluidd_client_data) + # client-config + self.cc_status = get_current_client_config( + [mainsail_client_data, fluidd_client_data] + ) def format_status_by_code(self, code: int, status: str, count: str) -> str: if code == 1: @@ -120,13 +127,11 @@ class MainMenu(BaseMenu): | 4) [Advanced] |------------------------------------| | 5) [Backup] | Mainsail: {self.ms_status:<26} | | | Fluidd: {self.fl_status:<26} | - | E) [Extensions] | KlipperScreen: {self.ks_status:<26} | - | | Mobileraker: {self.mb_status:<26} | + | S) [Settings] | Client-Config: {self.cc_status:<26} | | | | + | Community: | KlipperScreen: {self.ks_status:<26} | + | E) [Extensions] | Mobileraker: {self.mb_status:<26} | | | Crowsnest: {self.cn_status:<26} | - | | Telegram Bot: {self.tg_status:<26} | - | | Obico: {self.ob_status:<26} | - | S) [Settings] | OctoEverywhere: {self.oe_status:<26} | |-------------------------------------------------------| | {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} | """ diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 8df2380..ad53c94 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -11,10 +11,10 @@ import textwrap -from components.fluidd.menus.fluidd_remove_menu import FluiddRemoveMenu from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu -from components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu from components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu +from components.webui_client.client_utils import load_client_data +from components.webui_client.menus.client_remove_menu import ClientRemoveMenu from core.menus import BACK_FOOTER from core.menus.base_menu import BaseMenu from utils.constants import COLOR_RED, RESET_FORMAT @@ -29,8 +29,8 @@ class RemoveMenu(BaseMenu): options={ "1": KlipperRemoveMenu, "2": MoonrakerRemoveMenu, - "3": MainsailRemoveMenu, - "4": FluiddRemoveMenu, + "3": ClientRemoveMenu(client=load_client_data("mainsail")), + "4": ClientRemoveMenu(client=load_client_data("fluidd")), "5": None, "6": None, "7": None, diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 04d6de5..ffa74ba 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -11,22 +11,22 @@ import textwrap -from components.fluidd.fluidd_setup import update_fluidd -from components.fluidd.fluidd_utils import ( - get_fluidd_local_version, - get_fluidd_remote_version, -) from components.klipper.klipper_setup import update_klipper from components.klipper.klipper_utils import ( get_klipper_status, ) -from components.mainsail.mainsail_setup import update_mainsail -from components.mainsail.mainsail_utils import ( - get_mainsail_local_version, - get_mainsail_remote_version, -) 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 ( + update_client_config, +) +from components.webui_client.client_setup import update_client +from components.webui_client.client_utils import ( + get_local_client_version, + get_remote_client_version, + load_client_data, + get_client_config_status, +) from core.menus import BACK_FOOTER from core.menus.base_menu import BaseMenu from utils.constants import ( @@ -50,14 +50,12 @@ class UpdateMenu(BaseMenu): "2": self.update_moonraker, "3": self.update_mainsail, "4": self.update_fluidd, - "5": self.update_klipperscreen, - "6": self.update_pgc_for_klipper, - "7": self.update_telegram_bot, - "8": self.update_moonraker_obico, - "9": self.update_octoeverywhere, - "10": self.update_mobileraker, - "11": self.update_crowsnest, - "12": self.upgrade_system_packages, + "5": self.update_mainsail_config, + "6": self.update_fluidd_config, + "7": self.update_klipperscreen, + "8": self.update_mobileraker, + "9": self.update_crowsnest, + "10": self.upgrade_system_packages, }, footer_type=BACK_FOOTER, ) @@ -69,6 +67,10 @@ class UpdateMenu(BaseMenu): self.ms_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.fl_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.fl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.mc_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.mc_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.fc_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.fc_remote = f"{COLOR_WHITE}{RESET_FORMAT}" def print_menu(self): self.fetch_update_status() @@ -87,22 +89,20 @@ class UpdateMenu(BaseMenu): | 1) Klipper | {self.kl_local:<22} | {self.kl_remote:<22} | | 2) Moonraker | {self.mr_local:<22} | {self.mr_remote:<22} | | | | | - | Klipper Webinterface: |---------------|---------------| + | Webinterface: |---------------|---------------| | 3) Mainsail | {self.ms_local:<22} | {self.ms_remote:<22} | | 4) Fluidd | {self.fl_local:<22} | {self.fl_remote:<22} | | | | | - | Touchscreen GUI: |---------------|---------------| - | 5) KlipperScreen | | | + | Client-Config: |---------------|---------------| + | 5) Mainsail-Config | {self.mc_local:<22} | {self.mc_remote:<22} | + | 6) Fluidd-Config | {self.fc_local:<22} | {self.fc_remote:<22} | | | | | | Other: |---------------|---------------| - | 6) PrettyGCode | | | - | 7) Telegram Bot | | | - | 8) Obico for Klipper | | | - | 9) OctoEverywhere | | | - | 10) Mobileraker | | | - | 11) Crowsnest | | | + | 7) KlipperScreen | | | + | 8) Mobileraker | | | + | 9) Crowsnest | | | | |-------------------------------| - | 12) System | | + | 10) System | | """ )[1:] print(menu, end="") @@ -117,34 +117,24 @@ class UpdateMenu(BaseMenu): update_moonraker() def update_mainsail(self, **kwargs): - update_mainsail() + update_client(load_client_data("mainsail")) + + def update_mainsail_config(self, **kwargs): + update_client_config(load_client_data("mainsail")) def update_fluidd(self, **kwargs): - update_fluidd() + update_client(load_client_data("fluidd")) - def update_klipperscreen(self, **kwargs): - print("update_klipperscreen") + def update_fluidd_config(self, **kwargs): + update_client_config(load_client_data("fluidd")) - def update_pgc_for_klipper(self, **kwargs): - print("update_pgc_for_klipper") + def update_klipperscreen(self, **kwargs): ... - def update_telegram_bot(self, **kwargs): - print("update_telegram_bot") + def update_mobileraker(self, **kwargs): ... - def update_moonraker_obico(self, **kwargs): - print("update_moonraker_obico") + def update_crowsnest(self, **kwargs): ... - def update_octoeverywhere(self, **kwargs): - print("update_octoeverywhere") - - def update_mobileraker(self, **kwargs): - print("update_mobileraker") - - def update_crowsnest(self, **kwargs): - print("update_crowsnest") - - def upgrade_system_packages(self, **kwargs): - print("upgrade_system_packages") + def upgrade_system_packages(self, **kwargs): ... def fetch_update_status(self): # klipper @@ -166,18 +156,38 @@ class UpdateMenu(BaseMenu): self.mr_local = f"{COLOR_YELLOW}{self.mr_local}{RESET_FORMAT}" self.mr_remote = f"{COLOR_GREEN}{self.mr_remote}{RESET_FORMAT}" # mainsail - self.ms_local = get_mainsail_local_version() - self.ms_remote = get_mainsail_remote_version() + mainsail_client_data = load_client_data("mainsail") + self.ms_local = get_local_client_version(mainsail_client_data) + self.ms_remote = get_remote_client_version(mainsail_client_data) if self.ms_local == self.ms_remote: self.ms_local = f"{COLOR_GREEN}{self.ms_local}{RESET_FORMAT}" else: self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}" self.ms_remote = f"{COLOR_GREEN if self.ms_remote != 'ERROR' else COLOR_RED}{self.ms_remote}{RESET_FORMAT}" # fluidd - self.fl_local = get_fluidd_local_version() - self.fl_remote = get_fluidd_remote_version() + fluidd_client_data = load_client_data("fluidd") + self.fl_local = get_local_client_version(fluidd_client_data) + self.fl_remote = get_remote_client_version(fluidd_client_data) if self.fl_local == self.fl_remote: self.fl_local = f"{COLOR_GREEN}{self.fl_local}{RESET_FORMAT}" else: self.fl_local = f"{COLOR_YELLOW}{self.fl_local}{RESET_FORMAT}" self.fl_remote = f"{COLOR_GREEN if self.fl_remote != 'ERROR' else COLOR_RED}{self.fl_remote}{RESET_FORMAT}" + # mainsail-config + mc_status = get_client_config_status(load_client_data("mainsail")) + self.mc_local = mc_status.get("local") + self.mc_remote = mc_status.get("remote") + if self.mc_local == self.mc_remote: + self.mc_local = f"{COLOR_GREEN}{self.mc_local}{RESET_FORMAT}" + else: + self.mc_local = f"{COLOR_YELLOW}{self.mc_local}{RESET_FORMAT}" + self.mc_remote = f"{COLOR_GREEN}{self.mc_remote}{RESET_FORMAT}" + # fluidd-config + fc_status = get_client_config_status(load_client_data("fluidd")) + self.fc_local = fc_status.get("local") + self.fc_remote = fc_status.get("remote") + if self.fc_local == self.mc_remote: + self.fc_local = f"{COLOR_GREEN}{self.fc_local}{RESET_FORMAT}" + else: + self.fc_local = f"{COLOR_YELLOW}{self.fc_local}{RESET_FORMAT}" + self.fc_remote = f"{COLOR_GREEN}{self.fc_remote}{RESET_FORMAT}" diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index 14ad26f..b7fed2f 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 + # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -7,14 +8,20 @@ # # # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # + import re import shutil import subprocess from pathlib import Path from zipfile import ZipFile -from typing import List +from typing import List, Type, TypeVar, Union, Tuple +from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker +from core.config_manager.config_manager import ConfigManager +from core.instance_manager.base_instance import BaseInstance +from core.instance_manager.instance_manager import InstanceManager from utils import ( NGINX_SITES_AVAILABLE, MODULE_PATH, @@ -24,6 +31,10 @@ from utils import ( from utils.logger import Logger +B = TypeVar('B', bound='BaseInstance') +ConfigOption = Tuple[str, str] + + def check_file_exist(file_path: Path, sudo=False) -> bool: """ Helper function for checking the existence of a file | @@ -169,3 +180,102 @@ def get_next_free_port(ports_in_use: List[str]) -> str: used_ports = set(map(int, ports_in_use)) return str(min(valid_ports - used_ports)) + + +def add_config_section(section: str, instances: List[B], options: List[ConfigOption] = None) -> None: + for instance in instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Add section '[{section}]' to '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + continue + + cm = ConfigManager(cfg_file) + if cm.config.has_section(section): + Logger.print_info("Section already exist. Skipped ...") + continue + + cm.config.add_section(section) + + if options is not None: + for option in options: + cm.config.set(section, option[0], option[1]) + + cm.write_config() + + +def remove_config_section(section: str, instances: List[B]) -> None: + for instance in instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Remove section '[{section}]' from '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + continue + + cm = ConfigManager(cfg_file) + if not cm.config.has_section(section): + Logger.print_info("Section does not exist. Skipped ...") + continue + + cm.config.remove_section(section) + cm.write_config() + + +def patch_moonraker_conf( + moonraker_instances: List[Moonraker], + name: str, + section_name: str, + template_file: str, +) -> None: + for instance in moonraker_instances: + cfg_file = instance.cfg_file + Logger.print_status(f"Add {name} update section to '{cfg_file}' ...") + + if not Path(cfg_file).exists(): + Logger.print_warn(f"'{cfg_file}' not found!") + return + + cm = ConfigManager(cfg_file) + if cm.config.has_section(section_name): + Logger.print_info("Section already exist. Skipped ...") + return + + template = MODULE_PATH.joinpath("assets", template_file) + with open(template, "r") as t: + template_content = "\n" + template_content += t.read() + + with open(cfg_file, "a") as f: + f.write(template_content) + + +def remove_nginx_config(name: str) -> None: + Logger.print_status(f"Removing NGINX config for {name.capitalize()} ...") + try: + remove_file(NGINX_SITES_AVAILABLE.joinpath(name), True) + remove_file(NGINX_SITES_ENABLED.joinpath(name), True) + + except subprocess.CalledProcessError as e: + log = f"Unable to remove NGINX config '{name}':\n{e.stderr.decode()}" + Logger.print_error(log) + + +def remove_nginx_logs(name: str) -> None: + Logger.print_status(f"Removing NGINX logs for {name.capitalize()} ...") + try: + remove_file(Path(f"/var/log/nginx/{name}-access.log"), True) + remove_file(Path(f"/var/log/nginx/{name}-error.log"), True) + + im = InstanceManager(Klipper) + instances: List[Klipper] = im.instances + if not instances: + return + + for instance in instances: + remove_file(instance.log_dir.joinpath(f"{name}-access.log")) + remove_file(instance.log_dir.joinpath(f"{name}-error.log")) + + except (OSError, subprocess.CalledProcessError) as e: + Logger.print_error(f"Unable to remove NGINX logs:\n{e}") From 5c37b684638bdfcf51e16396d1e8e6d202a2781e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 2 Mar 2024 21:25:17 +0100 Subject: [PATCH 114/137] fix(webclients): default to port 80 if none/invalid configured in kiauh.cfg Signed-off-by: Dominik Willner --- kiauh/components/webui_client/client_dialogs.py | 1 - kiauh/components/webui_client/client_setup.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kiauh/components/webui_client/client_dialogs.py b/kiauh/components/webui_client/client_dialogs.py index 9fb98e9..1053a58 100644 --- a/kiauh/components/webui_client/client_dialogs.py +++ b/kiauh/components/webui_client/client_dialogs.py @@ -71,7 +71,6 @@ def print_client_port_select_dialog(name: str, port: str, ports_in_use: List[str | {line2:<54}| | port, you can set it now. Make sure the port is not | | used by any other application on your system! | - \\=======================================================/ """ )[1:] diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py index a783507..e2a4ae3 100644 --- a/kiauh/components/webui_client/client_setup.py +++ b/kiauh/components/webui_client/client_setup.py @@ -107,7 +107,8 @@ def install_client(client_name: ClientName) -> None: install_client_cfg = get_confirm(question, allow_go_back=False) cm = ConfigManager(cfg_file=KIAUH_CFG) - client_port = cm.get_value(client.get("name"), "port") + default_port = cm.get_value(client.get("name"), "port") + client_port = default_port if default_port and default_port.isdigit() else "80" ports_in_use = read_ports_from_nginx_configs() # check if configured port is a valid number and not in use already From 8df75dc8d04aefeea62e2222bd88c44900369ed1 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 2 Mar 2024 21:47:27 +0100 Subject: [PATCH 115/137] fix(webclients): print to screen if symlink does not exist Signed-off-by: Dominik Willner --- .../webui_client/client_config/client_config_remove.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kiauh/components/webui_client/client_config/client_config_remove.py b/kiauh/components/webui_client/client_config/client_config_remove.py index 41203a3..af5fe27 100644 --- a/kiauh/components/webui_client/client_config/client_config_remove.py +++ b/kiauh/components/webui_client/client_config/client_config_remove.py @@ -53,12 +53,16 @@ def remove_client_config_dir(client_config: ClientConfigData) -> None: def remove_client_config_symlink(client_config: ClientConfigData) -> None: - Logger.print_status(f"Removing {client_config.get('cfg_filename')} symlinks ...") im = InstanceManager(Klipper) instances: List[Klipper] = im.instances for instance in instances: Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") + symlink = instance.cfg_dir.joinpath(client_config.get("cfg_filename")) + if not symlink.exists(): + Logger.print_info(f"'{symlink}' does not exist. Skipping ...") + continue + try: - remove_file(instance.cfg_dir.joinpath(client_config.get("cfg_filename"))) + remove_file(symlink) except subprocess.CalledProcessError: Logger.print_error("Failed to remove symlink!") From bd1aa1ae2bd7ee24de77a1d464f832545eaef104 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 11:04:06 +0100 Subject: [PATCH 116/137] refactor(klipper): add existing client configs to example config upon creation Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper_setup.py | 7 +++- kiauh/components/klipper/klipper_utils.py | 15 ++++++-- .../client_config/client_config_setup.py | 34 +++++++++++++------ kiauh/components/webui_client/client_setup.py | 4 +-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index e9761e9..e417c38 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -11,6 +11,9 @@ from pathlib import Path +from components.webui_client.client_config.client_config_setup import ( + get_existing_client_config, +) from kiauh import KIAUH_CFG from components.klipper import ( EXIT_KLIPPER_SETUP, @@ -180,5 +183,7 @@ def create_klipper_instance(name: str, create_example_cfg: bool) -> None: kl_im.create_instance() kl_im.enable_instance() if create_example_cfg: - create_example_printer_cfg(new_instance) + # if a client-config is installed, include it in the new example cfg + client_configs = get_existing_client_config() + create_example_printer_cfg(new_instance, client_configs) kl_im.start_instance() diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index e2ff73a..dfc749b 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -16,7 +16,7 @@ import shutil import subprocess import textwrap from pathlib import Path -from typing import List, Union, Literal, Dict +from typing import List, Union, Literal, Dict, Optional from components.klipper import ( MODULE_PATH, @@ -33,6 +33,7 @@ from components.klipper.klipper_dialogs import ( ) from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker_utils import moonraker_to_multi_conversion +from components.webui_client import ClientData from core.backup_manager.backup_manager import BackupManager from core.config_manager.config_manager import ConfigManager from core.instance_manager.base_instance import BaseInstance @@ -261,7 +262,9 @@ def get_highest_index(instance_list: List[Klipper]) -> int: return max(indices) -def create_example_printer_cfg(instance: Klipper) -> None: +def create_example_printer_cfg( + instance: Klipper, client_configs: Optional[List[ClientData]] = None +) -> None: Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'") if instance.cfg_file.is_file(): Logger.print_info(f"'{instance.cfg_file}' already exists.") @@ -277,7 +280,15 @@ def create_example_printer_cfg(instance: Klipper) -> None: cm = ConfigManager(target) cm.set_value("virtual_sdcard", "path", str(instance.gcodes_dir)) + + # include existing client configs in the example config + if client_configs is not None and len(client_configs) > 0: + for c in client_configs: + section = c.get("client_config").get("printer_cfg_section") + cm.config.add_section(section=section) + cm.write_config() + Logger.print_ok(f"Example printer.cfg created in '{instance.cfg_dir}'") diff --git a/kiauh/components/webui_client/client_config/client_config_setup.py b/kiauh/components/webui_client/client_config/client_config_setup.py index d893198..5f8b17a 100644 --- a/kiauh/components/webui_client/client_config/client_config_setup.py +++ b/kiauh/components/webui_client/client_config/client_config_setup.py @@ -12,7 +12,7 @@ import shutil import subprocess from pathlib import Path -from typing import List, get_args +from typing import List, get_args, Union, Set from kiauh import KIAUH_CFG from components.klipper.klipper import Klipper @@ -40,7 +40,7 @@ def install_client_config(client_name: ClientName) -> None: client_config: ClientConfigData = client.get("client_config") d_name = client_config.get("display_name") - if check_existing_client_config_install(client_name): + if config_for_other_client_exist(client_name): Logger.print_info("Another Client-Config is already installed! Skipped ...") return @@ -80,19 +80,31 @@ def install_client_config(client_name: ClientName) -> None: Logger.print_ok(f"{d_name} installation complete!", start="\n") -def check_existing_client_config_install(client_name: ClientName) -> bool: - # check if any other client-configs are present - # as they can conflict each other, or are at least - # redundant to have, so we skip the installation - client_list = list(get_args(ClientName)) - client_list.remove(client_name) - for c in client_list: +def config_for_other_client_exist(client_to_ignore: ClientName) -> bool: + """ + Check if any other client configs are present on the system. + It is usually not harmful, but chances are they can conflict each other. + Multiple client configs are, at least, redundant to have them installed + :param client_to_ignore: The client name to ignore for the check + :return: True, if other client configs were found, else False + """ + + clients = set([c["name"] for c in get_existing_client_config()]) + clients = clients - {client_to_ignore} + + return True if len(clients) > 0 else False + + +def get_existing_client_config() -> List[ClientData]: + clients = list(get_args(ClientName)) + installed_client_configs: List[ClientData] = [] + for c in clients: c_data: ClientData = load_client_data(c) c_config_data: ClientConfigData = c_data.get("client_config") if c_config_data.get("dir").exists(): - return True + installed_client_configs.append(c_data) - return False + return installed_client_configs def download_client_config(client_config: ClientConfigData) -> None: diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py index e2a4ae3..84762ca 100644 --- a/kiauh/components/webui_client/client_setup.py +++ b/kiauh/components/webui_client/client_setup.py @@ -21,7 +21,7 @@ from components.webui_client import ( from components.moonraker.moonraker import Moonraker from components.webui_client.client_config.client_config_setup import ( install_client_config, - check_existing_client_config_install, + config_for_other_client_exist, ) from components.webui_client.client_dialogs import ( print_moonraker_not_found_dialog, @@ -100,7 +100,7 @@ def install_client(client_name: ClientName) -> None: if ( kl_instances and not client_config.get("dir").exists() - and not check_existing_client_config_install(client.get("name")) + and not config_for_other_client_exist(client_to_ignore=client.get("name")) ): print_install_client_config_dialog(client) question = f"Download the recommended {client_config.get('display_name')}?" From 14aafd558ae7f9ac76963a4aa187d8e3fa221d76 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 11:33:49 +0100 Subject: [PATCH 117/137] refactor(moonraker): add existing client and client configs to example config upon creation Signed-off-by: Dominik Willner --- kiauh/components/moonraker/moonraker_setup.py | 5 ++- kiauh/components/moonraker/moonraker_utils.py | 38 +++++++++++++++++-- .../client_config/client_config_setup.py | 11 ++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index a4c6cf2..dab18b3 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import List from components.webui_client import MAINSAIL_DIR +from components.webui_client.client_config.client_config_setup import get_existing_client_config, get_existing_clients from components.webui_client.client_utils import enable_mainsail_remotemode from kiauh import KIAUH_CFG from components.klipper.klipper import Klipper @@ -104,7 +105,9 @@ def install_moonraker() -> None: mr_im.enable_instance() if create_example_cfg: - create_example_moonraker_conf(current_instance, used_ports_map) + # if a webclient and/or it's config is installed, patch its update section to the config + clients = get_existing_clients() + create_example_moonraker_conf(current_instance, used_ports_map, clients) mr_im.start_instance() diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index 8f89364..e7ea965 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -10,7 +10,7 @@ # ======================================================================= # import shutil -from typing import Dict, Literal, List, Union +from typing import Dict, Literal, List, Union, Optional from components.moonraker import ( DEFAULT_MOONRAKER_PORT, @@ -21,7 +21,7 @@ from components.moonraker import ( MOONRAKER_DB_BACKUP_DIR, ) from components.moonraker.moonraker import Moonraker -from components.webui_client import MAINSAIL_DIR +from components.webui_client import MAINSAIL_DIR, ClientData from components.webui_client.client_utils import enable_mainsail_remotemode from core.backup_manager.backup_manager import BackupManager from core.config_manager.config_manager import ConfigManager @@ -50,7 +50,9 @@ def get_moonraker_status() -> Dict[ def create_example_moonraker_conf( - instance: Moonraker, ports_map: Dict[str, int] + instance: Moonraker, + ports_map: Dict[str, int], + clients: Optional[List[ClientData]] = None, ) -> None: Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'") if instance.cfg_file.is_file(): @@ -94,6 +96,36 @@ def create_example_moonraker_conf( cm.set_value("server", "klippy_uds_address", str(uds)) cm.set_value("authorization", "trusted_clients", trusted_clients) + # add existing client and client configs in the update section + if clients is not None and len(clients) > 0: + for c in clients: + # client part + c_section = f"update_manager {c.get('name')}" + c_options = [ + ("type", "web"), + ("channel", "stable"), + ("repo", c.get("mr_conf_repo")), + ("path", c.get("mr_conf_path")), + ] + cm.config.add_section(section=c_section) + for option in c_options: + cm.config.set(c_section, option[0], option[1]) + + # client config part + c_config = c.get("client_config") + if c_config.get("dir").exists(): + c_config_section = f"update_manager {c_config.get('name')}" + c_config_options = [ + ("type", "git_repo"), + ("primary_branch", "master"), + ("path", c_config.get("mr_conf_path")), + ("origin", c_config.get("mr_conf_origin")), + ("managed_services", "klipper"), + ] + cm.config.add_section(section=c_config_section) + for option in c_config_options: + cm.config.set(c_config_section, option[0], option[1]) + cm.write_config() Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'") diff --git a/kiauh/components/webui_client/client_config/client_config_setup.py b/kiauh/components/webui_client/client_config/client_config_setup.py index 5f8b17a..3dd5de5 100644 --- a/kiauh/components/webui_client/client_config/client_config_setup.py +++ b/kiauh/components/webui_client/client_config/client_config_setup.py @@ -95,6 +95,17 @@ def config_for_other_client_exist(client_to_ignore: ClientName) -> bool: return True if len(clients) > 0 else False +def get_existing_clients() -> List[ClientData]: + clients = list(get_args(ClientName)) + installed_clients: List[ClientData] = [] + for c in clients: + c_data: ClientData = load_client_data(c) + if c_data.get("dir").exists(): + installed_clients.append(c_data) + + return installed_clients + + def get_existing_client_config() -> List[ClientData]: clients = list(get_args(ClientName)) installed_client_configs: List[ClientData] = [] From 655b781aef42f8d16dac402c6173ac3aae24eef2 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 11:42:27 +0100 Subject: [PATCH 118/137] refactor(KIAUH): move util functions into util modules Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper_setup.py | 4 +- kiauh/components/moonraker/moonraker_setup.py | 3 +- .../client_config/client_config_setup.py | 44 ++----------------- kiauh/components/webui_client/client_setup.py | 5 +-- kiauh/components/webui_client/client_utils.py | 42 +++++++++++++++++- 5 files changed, 47 insertions(+), 51 deletions(-) diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index e417c38..724d16a 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -11,9 +11,7 @@ from pathlib import Path -from components.webui_client.client_config.client_config_setup import ( - get_existing_client_config, -) +from components.webui_client.client_utils import get_existing_client_config from kiauh import KIAUH_CFG from components.klipper import ( EXIT_KLIPPER_SETUP, diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index dab18b3..c7b80ac 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -15,8 +15,7 @@ from pathlib import Path from typing import List from components.webui_client import MAINSAIL_DIR -from components.webui_client.client_config.client_config_setup import get_existing_client_config, get_existing_clients -from components.webui_client.client_utils import enable_mainsail_remotemode +from components.webui_client.client_utils import enable_mainsail_remotemode, get_existing_clients from kiauh import KIAUH_CFG from components.klipper.klipper import Klipper from components.klipper.klipper_dialogs import print_instance_overview diff --git a/kiauh/components/webui_client/client_config/client_config_setup.py b/kiauh/components/webui_client/client_config/client_config_setup.py index 3dd5de5..13f1062 100644 --- a/kiauh/components/webui_client/client_config/client_config_setup.py +++ b/kiauh/components/webui_client/client_config/client_config_setup.py @@ -12,7 +12,7 @@ import shutil import subprocess from pathlib import Path -from typing import List, get_args, Union, Set +from typing import List from kiauh import KIAUH_CFG from components.klipper.klipper import Klipper @@ -21,8 +21,8 @@ from components.webui_client import ClientConfigData, ClientName, ClientData from components.webui_client.client_dialogs import print_client_already_installed_dialog from components.webui_client.client_utils import ( load_client_data, - backup_client_config_data, -) + backup_client_config_data, config_for_other_client_exist, + ) from core.config_manager.config_manager import ConfigManager from core.instance_manager.instance_manager import InstanceManager @@ -80,44 +80,6 @@ def install_client_config(client_name: ClientName) -> None: Logger.print_ok(f"{d_name} installation complete!", start="\n") -def config_for_other_client_exist(client_to_ignore: ClientName) -> bool: - """ - Check if any other client configs are present on the system. - It is usually not harmful, but chances are they can conflict each other. - Multiple client configs are, at least, redundant to have them installed - :param client_to_ignore: The client name to ignore for the check - :return: True, if other client configs were found, else False - """ - - clients = set([c["name"] for c in get_existing_client_config()]) - clients = clients - {client_to_ignore} - - return True if len(clients) > 0 else False - - -def get_existing_clients() -> List[ClientData]: - clients = list(get_args(ClientName)) - installed_clients: List[ClientData] = [] - for c in clients: - c_data: ClientData = load_client_data(c) - if c_data.get("dir").exists(): - installed_clients.append(c_data) - - return installed_clients - - -def get_existing_client_config() -> List[ClientData]: - clients = list(get_args(ClientName)) - installed_client_configs: List[ClientData] = [] - for c in clients: - c_data: ClientData = load_client_data(c) - c_config_data: ClientConfigData = c_data.get("client_config") - if c_config_data.get("dir").exists(): - installed_client_configs.append(c_data) - - return installed_client_configs - - def download_client_config(client_config: ClientConfigData) -> None: try: Logger.print_status(f"Downloading {client_config.get('display_name')} ...") diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py index 84762ca..2744cef 100644 --- a/kiauh/components/webui_client/client_setup.py +++ b/kiauh/components/webui_client/client_setup.py @@ -21,7 +21,6 @@ from components.webui_client import ( from components.moonraker.moonraker import Moonraker from components.webui_client.client_config.client_config_setup import ( install_client_config, - config_for_other_client_exist, ) from components.webui_client.client_dialogs import ( print_moonraker_not_found_dialog, @@ -33,8 +32,8 @@ from components.webui_client.client_utils import ( restore_mainsail_config_json, enable_mainsail_remotemode, symlink_webui_nginx_log, - load_client_data, -) + load_client_data, config_for_other_client_exist, + ) from core.config_manager.config_manager import ConfigManager from core.instance_manager.instance_manager import InstanceManager from kiauh import KIAUH_CFG diff --git a/kiauh/components/webui_client/client_utils.py b/kiauh/components/webui_client/client_utils.py index 40207e5..4dd53d0 100644 --- a/kiauh/components/webui_client/client_utils.py +++ b/kiauh/components/webui_client/client_utils.py @@ -13,7 +13,7 @@ import json import shutil from json import JSONDecodeError from pathlib import Path -from typing import List, Optional, Dict, Literal, Union +from typing import List, Optional, Dict, Literal, Union, get_args import urllib.request @@ -43,7 +43,7 @@ from components.webui_client import ( from core.backup_manager.backup_manager import BackupManager from core.repo_manager.repo_manager import RepoManager from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD -from utils.common import get_install_status_webui, get_install_status_common +from utils.common import get_install_status_webui from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW from utils.logger import Logger @@ -235,3 +235,41 @@ def backup_client_config_data(client: ClientData) -> None: target = client_config.get("backup_dir") bm = BackupManager() bm.backup_directory(name, source, target) + + +def get_existing_clients() -> List[ClientData]: + clients = list(get_args(ClientName)) + installed_clients: List[ClientData] = [] + for c in clients: + c_data: ClientData = load_client_data(c) + if c_data.get("dir").exists(): + installed_clients.append(c_data) + + return installed_clients + + +def get_existing_client_config() -> List[ClientData]: + clients = list(get_args(ClientName)) + installed_client_configs: List[ClientData] = [] + for c in clients: + c_data: ClientData = load_client_data(c) + c_config_data: ClientConfigData = c_data.get("client_config") + if c_config_data.get("dir").exists(): + installed_client_configs.append(c_data) + + return installed_client_configs + + +def config_for_other_client_exist(client_to_ignore: ClientName) -> bool: + """ + Check if any other client configs are present on the system. + It is usually not harmful, but chances are they can conflict each other. + Multiple client configs are, at least, redundant to have them installed + :param client_to_ignore: The client name to ignore for the check + :return: True, if other client configs were found, else False + """ + + clients = set([c["name"] for c in get_existing_client_config()]) + clients = clients - {client_to_ignore} + + return True if len(clients) > 0 else False From 9dc556e7e4fe16789b91823396d820ea321cea56 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 20:26:01 +0100 Subject: [PATCH 119/137] fix(webclients): correctly remove symlink Signed-off-by: Dominik Willner --- .../webui_client/client_config/client_config_remove.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kiauh/components/webui_client/client_config/client_config_remove.py b/kiauh/components/webui_client/client_config/client_config_remove.py index af5fe27..dc3eb39 100644 --- a/kiauh/components/webui_client/client_config/client_config_remove.py +++ b/kiauh/components/webui_client/client_config/client_config_remove.py @@ -56,9 +56,9 @@ def remove_client_config_symlink(client_config: ClientConfigData) -> None: im = InstanceManager(Klipper) instances: List[Klipper] = im.instances for instance in instances: - Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...") + Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...") symlink = instance.cfg_dir.joinpath(client_config.get("cfg_filename")) - if not symlink.exists(): + if not symlink.is_symlink(): Logger.print_info(f"'{symlink}' does not exist. Skipping ...") continue From 7c754de08e9417c6c1a8e7920c4143a269e0446c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 20:31:30 +0100 Subject: [PATCH 120/137] refactor(utils): re-arrange message printing Signed-off-by: Dominik Willner --- kiauh/utils/system_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index 718607a..a79465c 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -127,11 +127,14 @@ def install_python_requirements(target: Path, requirements: Path) -> None: :param requirements: Path to the requirements.txt file :return: None """ - Logger.print_status("Installing Python requirements ...") try: + # always update pip before installing requirements update_python_pip(target) + + Logger.print_status("Installing Python requirements ...") command = [target.joinpath("bin/pip"), "install", "-r", f"{requirements}"] result = subprocess.run(command, stderr=subprocess.PIPE, text=True) + if result.returncode != 0 or result.stderr: Logger.print_error(f"{result.stderr}", False) Logger.print_error("Installing Python requirements failed!") From c0caab13b30fa33a0362d6bd34eb6ed70f46874e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 20:34:21 +0100 Subject: [PATCH 121/137] fix(kiauh): typo Signed-off-by: Dominik Willner --- kiauh/components/log_uploads/log_upload_utils.py | 2 +- kiauh/core/backup_manager/backup_manager.py | 4 ++-- kiauh/core/repo_manager/repo_manager.py | 4 ++-- .../gcode_shell_cmd/gcode_shell_cmd_extension.py | 2 +- kiauh/utils/system_utils.py | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kiauh/components/log_uploads/log_upload_utils.py b/kiauh/components/log_uploads/log_upload_utils.py index daa67e4..2454443 100644 --- a/kiauh/components/log_uploads/log_upload_utils.py +++ b/kiauh/components/log_uploads/log_upload_utils.py @@ -49,7 +49,7 @@ def upload_logfile(logfile: LogFile) -> None: try: response = urllib.request.urlopen(req) link = response.read().decode("utf-8") - Logger.print_ok("Upload successfull! Access it via the following link:") + Logger.print_ok("Upload successful! Access it via the following link:") Logger.print_ok(f">>>> {link}", False) except Exception as e: Logger.print_error(f"Uploading logfile failed!") diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 1c27266..249bf1e 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -56,7 +56,7 @@ class BackupManager: try: Path(target).mkdir(exist_ok=True) shutil.copyfile(file, target.joinpath(filename)) - Logger.print_ok("Backup successfull!") + Logger.print_ok("Backup successful!") except OSError as e: Logger.print_error(f"Unable to backup '{file}':\n{e}") else: @@ -77,7 +77,7 @@ class BackupManager: target.joinpath(f"{name.lower()}-{date}-{time}"), ignore=self.ignore_folders_func, ) - Logger.print_ok("Backup successfull!") + Logger.print_ok("Backup successful!") except OSError as e: Logger.print_error(f"Unable to backup directory '{source}':\n{e}") return diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index a8f9eec..153deef 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -140,7 +140,7 @@ class RepoManager: command = ["git", "clone", self.repo, self.target_dir] subprocess.run(command, check=True) - Logger.print_ok("Clone successfull!") + Logger.print_ok("Clone successful!") except subprocess.CalledProcessError as e: log = f"Error cloning repository {self.repo}: {e.stderr.decode()}" Logger.print_error(log) @@ -151,7 +151,7 @@ class RepoManager: command = ["git", "checkout", f"{self.branch}"] subprocess.run(command, cwd=self.target_dir, check=True) - Logger.print_ok("Checkout successfull!") + Logger.print_ok("Checkout successful!") except subprocess.CalledProcessError as e: log = f"Error checking out branch {self.branch}: {e.stderr.decode()}" Logger.print_error(log) diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py index bf0a977..8d004ec 100644 --- a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -68,7 +68,7 @@ class GcodeShellCmdExtension(BaseExtension): im.start_all_instance() - Logger.print_ok("Installing G-Code Shell Command extension successfull!") + Logger.print_ok("Installing G-Code Shell Command extension successful!") def remove_extension(self, **kwargs) -> None: extension_installed = check_file_exist(EXTENSION_TARGET_PATH) diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index a79465c..d140ead 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -71,7 +71,7 @@ def create_python_venv(target: Path) -> None: if not target.exists(): try: venv.create(target, with_pip=True) - Logger.print_ok("Setup of virtualenv successfull!") + Logger.print_ok("Setup of virtualenv successful!") except OSError as e: Logger.print_error(f"Error setting up virtualenv:\n{e}") raise @@ -111,7 +111,7 @@ def update_python_pip(target: Path) -> None: Logger.print_error("Updating pip failed!") return - Logger.print_ok("Updating pip successfull!") + Logger.print_ok("Updating pip successful!") except FileNotFoundError as e: Logger.print_error(e) raise @@ -140,7 +140,7 @@ def install_python_requirements(target: Path, requirements: Path) -> None: Logger.print_error("Installing Python requirements failed!") return - Logger.print_ok("Installing Python requirements successfull!") + Logger.print_ok("Installing Python requirements successful!") except subprocess.CalledProcessError as e: log = f"Error installing Python requirements:\n{e.output.decode()}" Logger.print_error(log) @@ -183,7 +183,7 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None: Logger.print_error("Updating system package list failed!") return - Logger.print_ok("System package list updated successfully!") + Logger.print_ok("System package list update successful!") except subprocess.CalledProcessError as e: kill(f"Error updating system package list:\n{e.stderr.decode()}") From 601ccb219165be1cd6a5bb50ac769400c1919def Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 20:53:49 +0100 Subject: [PATCH 122/137] fix(webclients): add symlink to added klipper instances Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper_setup.py | 6 +++--- kiauh/components/klipper/klipper_utils.py | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index 724d16a..591cf08 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -11,7 +11,7 @@ from pathlib import Path -from components.webui_client.client_utils import get_existing_client_config +from components.webui_client.client_utils import get_existing_client_config, get_existing_clients from kiauh import KIAUH_CFG from components.klipper import ( EXIT_KLIPPER_SETUP, @@ -182,6 +182,6 @@ def create_klipper_instance(name: str, create_example_cfg: bool) -> None: kl_im.enable_instance() if create_example_cfg: # if a client-config is installed, include it in the new example cfg - client_configs = get_existing_client_config() - create_example_printer_cfg(new_instance, client_configs) + clients = get_existing_clients() + create_example_printer_cfg(new_instance, clients) kl_im.start_instance() diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index dfc749b..6b2bea8 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -34,6 +34,7 @@ from components.klipper.klipper_dialogs import ( from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker_utils import moonraker_to_multi_conversion from components.webui_client import ClientData +from components.webui_client.client_config.client_config_setup import create_client_config_symlink from core.backup_manager.backup_manager import BackupManager from core.config_manager.config_manager import ConfigManager from core.instance_manager.base_instance import BaseInstance @@ -263,7 +264,7 @@ def get_highest_index(instance_list: List[Klipper]) -> int: def create_example_printer_cfg( - instance: Klipper, client_configs: Optional[List[ClientData]] = None + instance: Klipper, clients: Optional[List[ClientData]] = None ) -> None: Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'") if instance.cfg_file.is_file(): @@ -282,10 +283,12 @@ def create_example_printer_cfg( cm.set_value("virtual_sdcard", "path", str(instance.gcodes_dir)) # include existing client configs in the example config - if client_configs is not None and len(client_configs) > 0: - for c in client_configs: - section = c.get("client_config").get("printer_cfg_section") + if clients is not None and len(clients) > 0: + for c in clients: + client_config = c.get("client_config") + section = client_config.get("printer_cfg_section") cm.config.add_section(section=section) + create_client_config_symlink(client_config,[instance]) cm.write_config() From 682baaa1051b31f1ad432898cb12187be49f1d60 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 20:56:14 +0100 Subject: [PATCH 123/137] refactor(kiauh): remove unused function Signed-off-by: Dominik Willner --- kiauh/components/moonraker/moonraker_setup.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index c7b80ac..f27580b 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -193,17 +193,6 @@ def install_moonraker_polkit() -> None: Logger.print_error(log) -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 update_moonraker() -> None: if not get_confirm("Update Moonraker now?"): return From 758a783ede788fb9ed396a6da810837ef018807c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 3 Mar 2024 21:01:30 +0100 Subject: [PATCH 124/137] refactor(moonraker): allow re-running installer if instance count already matches Signed-off-by: Dominik Willner --- kiauh/components/moonraker/moonraker_setup.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index f27580b..2cfdd0d 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -130,12 +130,6 @@ def check_moonraker_install_requirements() -> bool: Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") return False - mr_instance_count = len(InstanceManager(Moonraker).instances) - if mr_instance_count >= kl_instance_count: - Logger.print_warn("Unable to install more Moonraker instances!") - Logger.print_warn("More Klipper instances required.") - return False - return True From 5f823c2d3a1bedcbb834c6b53a6fc1b9eb1f6eb3 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Mon, 18 Mar 2024 22:18:45 +0100 Subject: [PATCH 125/137] refactor(klipper): use correct virtual_sdcard path after multi-conversion Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper_setup.py | 8 +++++-- kiauh/components/klipper/klipper_utils.py | 29 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index 591cf08..63b5cf9 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -11,7 +11,10 @@ from pathlib import Path -from components.webui_client.client_utils import get_existing_client_config, get_existing_clients +from components.webui_client.client_utils import ( + get_existing_client_config, + get_existing_clients, +) from kiauh import KIAUH_CFG from components.klipper import ( EXIT_KLIPPER_SETUP, @@ -96,7 +99,8 @@ def install_klipper() -> None: kl_im.reload_daemon() - except Exception: + except Exception as e: + Logger.print_error(e) Logger.print_error("Klipper installation failed!") return diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 6b2bea8..fe36979 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -34,13 +34,16 @@ from components.klipper.klipper_dialogs import ( from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker_utils import moonraker_to_multi_conversion from components.webui_client import ClientData -from components.webui_client.client_config.client_config_setup import create_client_config_symlink +from components.webui_client.client_config.client_config_setup import ( + create_client_config_symlink, +) from core.backup_manager.backup_manager import BackupManager from core.config_manager.config_manager import ConfigManager from core.instance_manager.base_instance import BaseInstance from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.name_scheme import NameScheme from core.repo_manager.repo_manager import RepoManager +from utils import PRINTER_CFG_BACKUP_DIR from utils.common import get_install_status_common from utils.constants import CURRENT_USER from utils.input_utils import get_confirm, get_string_input, get_number_input @@ -161,6 +164,15 @@ def klipper_to_multi_conversion(new_name: str) -> None: im.current_instance = im.instances[0] # temporarily store the data dir path old_data_dir = im.instances[0].data_dir + old_data_dir_name = im.instances[0].data_dir_name + # backup the old data_dir + bm = BackupManager() + name = f"config-{old_data_dir_name}" + bm.backup_directory( + name, + source=im.current_instance.cfg_dir, + target=PRINTER_CFG_BACKUP_DIR, + ) # remove the old single instance im.stop_instance() im.disable_instance() @@ -169,13 +181,20 @@ def klipper_to_multi_conversion(new_name: str) -> None: im.current_instance = Klipper(suffix=new_name) new_data_dir: Path = im.current_instance.data_dir - # rename the old data dir and use it for the new instance - Logger.print_status(f"Rename '{old_data_dir}' to '{new_data_dir}' ...") if not new_data_dir.is_dir(): + # rename the old data dir and use it for the new instance + Logger.print_status(f"Rename '{old_data_dir}' to '{new_data_dir}' ...") old_data_dir.rename(new_data_dir) else: - Logger.print_info(f"'{new_data_dir}' already exist. Skipped ...") + Logger.print_info(f"Existing '{new_data_dir}' found ...") + # patch the virtual_sdcard sections path value to match the new printer_data foldername + cm = ConfigManager(im.current_instance.cfg_file) + if cm.config.has_section("virtual_sdcard"): + cm.set_value("virtual_sdcard", "path", str(im.current_instance.gcodes_dir)) + cm.write_config() + + # finalize creating the new instance im.create_instance() im.enable_instance() im.start_instance() @@ -288,7 +307,7 @@ def create_example_printer_cfg( client_config = c.get("client_config") section = client_config.get("printer_cfg_section") cm.config.add_section(section=section) - create_client_config_symlink(client_config,[instance]) + create_client_config_symlink(client_config, [instance]) cm.write_config() From 84cda99af899ee1d3e9668a91755809b16039459 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 20 Mar 2024 21:39:20 +0100 Subject: [PATCH 126/137] fix(klipper): patch virtual_sdcard section when converting single to multi instance Signed-off-by: Dominik Willner --- kiauh/components/klipper/klipper_utils.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index fe36979..5e2314f 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -162,9 +162,11 @@ def klipper_to_multi_conversion(new_name: str) -> None: Logger.print_status("Convert Klipper single to multi instance ...") im = InstanceManager(Klipper) im.current_instance = im.instances[0] + # temporarily store the data dir path old_data_dir = im.instances[0].data_dir old_data_dir_name = im.instances[0].data_dir_name + # backup the old data_dir bm = BackupManager() name = f"config-{old_data_dir_name}" @@ -173,25 +175,27 @@ def klipper_to_multi_conversion(new_name: str) -> None: source=im.current_instance.cfg_dir, target=PRINTER_CFG_BACKUP_DIR, ) + # remove the old single instance im.stop_instance() im.disable_instance() im.delete_instance() - # create a new klipper instance with the new name - im.current_instance = Klipper(suffix=new_name) - new_data_dir: Path = im.current_instance.data_dir - if not new_data_dir.is_dir(): + # create a new klipper instance with the new name + new_instance = Klipper(suffix=new_name) + im.current_instance = new_instance + + if not new_instance.data_dir.is_dir(): # rename the old data dir and use it for the new instance - Logger.print_status(f"Rename '{old_data_dir}' to '{new_data_dir}' ...") - old_data_dir.rename(new_data_dir) + Logger.print_status(f"Rename '{old_data_dir}' to '{new_instance.data_dir}' ...") + old_data_dir.rename(new_instance.data_dir) else: - Logger.print_info(f"Existing '{new_data_dir}' found ...") + Logger.print_info(f"Existing '{new_instance.data_dir}' found ...") # patch the virtual_sdcard sections path value to match the new printer_data foldername - cm = ConfigManager(im.current_instance.cfg_file) + cm = ConfigManager(new_instance.cfg_file) if cm.config.has_section("virtual_sdcard"): - cm.set_value("virtual_sdcard", "path", str(im.current_instance.gcodes_dir)) + cm.set_value("virtual_sdcard", "path", str(new_instance.gcodes_dir)) cm.write_config() # finalize creating the new instance From 9eb0531cdf94b4c7de7b93718c1a374debcee33d Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 20 Mar 2024 21:40:03 +0100 Subject: [PATCH 127/137] fix(moonraker): patch klippy_uds_address section when converting single to multi instance Signed-off-by: Dominik Willner --- kiauh/components/moonraker/moonraker.py | 5 +++++ kiauh/components/moonraker/moonraker_utils.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/kiauh/components/moonraker/moonraker.py b/kiauh/components/moonraker/moonraker.py index aa0e03b..994cba0 100644 --- a/kiauh/components/moonraker/moonraker.py +++ b/kiauh/components/moonraker/moonraker.py @@ -35,12 +35,17 @@ class Moonraker(BaseInstance): self.backup_dir = self.data_dir.joinpath("backup") self.certs_dir = self.data_dir.joinpath("certs") self._db_dir = self.data_dir.joinpath("database") + self._comms_dir = self.data_dir.joinpath("comms") self.log = self.log_dir.joinpath("moonraker.log") @property def db_dir(self) -> Path: return self._db_dir + @property + def comms_dir(self) -> Path: + return self._comms_dir + def create(self, create_example_cfg: bool = False) -> None: Logger.print_status("Creating new Moonraker Instance ...") service_template_path = MODULE_PATH.joinpath("assets/moonraker.service") diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index e7ea965..a2a8c22 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -150,13 +150,27 @@ def moonraker_to_multi_conversion(new_name: str) -> None: return Logger.print_status("Convert Moonraker single to multi instance ...") + # remove the old single instance im.current_instance = im.instances[0] im.stop_instance() im.disable_instance() im.delete_instance() - # create a new klipper instance with the new name - im.current_instance = Moonraker(suffix=new_name) + + # create a new moonraker instance with the new name + new_instance = Moonraker(suffix=new_name) + im.current_instance = new_instance + + # patch the server sections klippy_uds_address value to match the new printer_data foldername + cm = ConfigManager(new_instance.cfg_file) + if cm.config.has_section("server"): + cm.set_value( + "server", + "klippy_uds_address", + str(new_instance.comms_dir.joinpath("klippy.sock")), + ) + cm.write_config() + # create, enable and start the new moonraker instance im.create_instance() im.enable_instance() From 7ec055f5628d175b06587a08dce552f884dcf82c Mon Sep 17 00:00:00 2001 From: dw-0 Date: Wed, 20 Mar 2024 22:10:51 +0100 Subject: [PATCH 128/137] refactor(webclients): always remove config sections Signed-off-by: Dominik Willner --- .../client_config/client_config_remove.py | 10 ++---- .../components/webui_client/client_remove.py | 13 ++++---- .../webui_client/menus/client_remove_menu.py | 32 ++----------------- 3 files changed, 11 insertions(+), 44 deletions(-) diff --git a/kiauh/components/webui_client/client_config/client_config_remove.py b/kiauh/components/webui_client/client_config/client_config_remove.py index dc3eb39..22d00fb 100644 --- a/kiauh/components/webui_client/client_config/client_config_remove.py +++ b/kiauh/components/webui_client/client_config/client_config_remove.py @@ -24,19 +24,13 @@ from utils.logger import Logger def run_client_config_removal( client_config: ClientConfigData, - remove_moonraker_conf_section: bool, - remove_printer_cfg_include: bool, kl_instances: List[Klipper], mr_instances: List[Moonraker], ) -> None: remove_client_config_dir(client_config) remove_client_config_symlink(client_config) - if remove_moonraker_conf_section: - remove_config_section( - f"update_manager {client_config.get('name')}", mr_instances - ) - if remove_printer_cfg_include: - remove_config_section(client_config.get("printer_cfg_section"), kl_instances) + remove_config_section(f"update_manager {client_config.get('name')}", mr_instances) + remove_config_section(client_config.get("printer_cfg_section"), kl_instances) def remove_client_config_dir(client_config: ClientConfigData) -> None: diff --git a/kiauh/components/webui_client/client_remove.py b/kiauh/components/webui_client/client_remove.py index 49bc694..7a8648c 100644 --- a/kiauh/components/webui_client/client_remove.py +++ b/kiauh/components/webui_client/client_remove.py @@ -35,28 +35,27 @@ def run_client_removal( rm_client: bool, rm_client_config: bool, backup_ms_config_json: bool, - rm_moonraker_conf_section: bool, - rm_printer_cfg_section: bool, ) -> None: mr_im = InstanceManager(Moonraker) mr_instances: List[Moonraker] = mr_im.instances kl_im = InstanceManager(Klipper) kl_instances: List[Klipper] = kl_im.instances + if backup_ms_config_json and client.get("name") == "mainsail": backup_mainsail_config_json() + if rm_client: client_name = client.get("name") remove_client_dir(client) remove_nginx_config(client_name) remove_nginx_logs(client_name) - if rm_moonraker_conf_section: - section = f"update_manager {client_name}" - remove_config_section(section, mr_instances) + + section = f"update_manager {client_name}" + remove_config_section(section, mr_instances) + if rm_client_config: run_client_config_removal( client.get("client_config"), - rm_moonraker_conf_section, - rm_printer_cfg_section, kl_instances, mr_instances, ) diff --git a/kiauh/components/webui_client/menus/client_remove_menu.py b/kiauh/components/webui_client/menus/client_remove_menu.py index b4648d6..6db4097 100644 --- a/kiauh/components/webui_client/menus/client_remove_menu.py +++ b/kiauh/components/webui_client/menus/client_remove_menu.py @@ -25,8 +25,6 @@ class ClientRemoveMenu(BaseMenu): self.rm_client = False self.rm_client_config = False self.backup_mainsail_config_json = False - self.rm_moonraker_conf_section = False - self.rm_printer_cfg_section = False super().__init__( header=False, @@ -39,12 +37,10 @@ class ClientRemoveMenu(BaseMenu): "0": self.toggle_all, "1": self.toggle_rm_client, "2": self.toggle_rm_client_config, - "3": self.toggle_rm_printer_cfg_section, - "4": self.toggle_rm_moonraker_conf_section, "c": self.run_removal_process, } if self.client.get("name") == "mainsail": - options["5"] = self.toggle_backup_mainsail_config_json + options["3"] = self.toggle_backup_mainsail_config_json return options @@ -60,8 +56,6 @@ class ClientRemoveMenu(BaseMenu): unchecked = "[ ]" o1 = checked if self.rm_client else unchecked o2 = checked if self.rm_client_config else unchecked - o3 = checked if self.rm_printer_cfg_section else unchecked - o4 = checked if self.rm_moonraker_conf_section else unchecked menu = textwrap.dedent( f""" /=======================================================\\ @@ -74,20 +68,14 @@ class ClientRemoveMenu(BaseMenu): |-------------------------------------------------------| | 1) {o1} Remove {client_name:16} | | 2) {o2} Remove {client_config_name:24} | - | | - | printer.cfg & moonraker.conf | - | 3) {o3} Remove printer.cfg include | - | 4) {o4} Remove Moonraker update section | """ )[1:] if self.client.get("name") == "mainsail": - o5 = checked if self.backup_mainsail_config_json else unchecked + o3 = checked if self.backup_mainsail_config_json else unchecked menu += textwrap.dedent( f""" - | | - | Mainsail config.json | - | 5) {o5} Backup config.json | + | 3) {o3} Backup config.json | """ )[1:] @@ -103,8 +91,6 @@ class ClientRemoveMenu(BaseMenu): self.rm_client = True self.rm_client_config = True self.backup_mainsail_config_json = True - self.rm_moonraker_conf_section = True - self.rm_printer_cfg_section = True def toggle_rm_client(self, **kwargs) -> None: self.rm_client = not self.rm_client @@ -115,19 +101,11 @@ class ClientRemoveMenu(BaseMenu): def toggle_backup_mainsail_config_json(self, **kwargs) -> None: self.backup_mainsail_config_json = not self.backup_mainsail_config_json - def toggle_rm_moonraker_conf_section(self, **kwargs) -> None: - self.rm_moonraker_conf_section = not self.rm_moonraker_conf_section - - def toggle_rm_printer_cfg_section(self, **kwargs) -> None: - self.rm_printer_cfg_section = not self.rm_printer_cfg_section - def run_removal_process(self, **kwargs) -> None: if ( not self.rm_client and not self.rm_client_config and not self.backup_mainsail_config_json - and not self.rm_moonraker_conf_section - and not self.rm_printer_cfg_section ): error = f"{COLOR_RED}Nothing selected ...{RESET_FORMAT}" print(error) @@ -138,12 +116,8 @@ class ClientRemoveMenu(BaseMenu): rm_client=self.rm_client, rm_client_config=self.rm_client_config, backup_ms_config_json=self.backup_mainsail_config_json, - rm_moonraker_conf_section=self.rm_moonraker_conf_section, - rm_printer_cfg_section=self.rm_printer_cfg_section, ) self.rm_client = False self.rm_client_config = False self.backup_mainsail_config_json = False - self.rm_moonraker_conf_section = False - self.rm_printer_cfg_section = False From ed35dc9e03a06c3ed61486b12095bd929256f06e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 21 Mar 2024 20:38:14 +0100 Subject: [PATCH 129/137] chore: add mypy config to pyproject.toml Signed-off-by: Dominik Willner --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index be1fddf..40eae37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,3 +11,5 @@ exclude = ''' | scripts/ ) ''' +[tool.mypy] +mypy_path = "./kiauh" From 4ffa05793160ac27e0feaec5cd8c251e917b83d7 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 21 Mar 2024 21:50:10 +0100 Subject: [PATCH 130/137] chore: improve type hinting Signed-off-by: Dominik Willner --- kiauh/core/instance_manager/base_instance.py | 27 +++++++++----------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/kiauh/core/instance_manager/base_instance.py b/kiauh/core/instance_manager/base_instance.py index 95e24dc..8655258 100644 --- a/kiauh/core/instance_manager/base_instance.py +++ b/kiauh/core/instance_manager/base_instance.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -9,14 +7,13 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +from __future__ import annotations from abc import abstractmethod, ABC from pathlib import Path -from typing import List, Type, TypeVar +from typing import List, Optional from utils.constants import SYSTEMD, CURRENT_USER -B = TypeVar(name="B", bound="BaseInstance", covariant=True) - class BaseInstance(ABC): @classmethod @@ -26,7 +23,7 @@ class BaseInstance(ABC): def __init__( self, suffix: str, - instance_type: B = B, + instance_type: BaseInstance, ): self._instance_type = instance_type self._suffix = suffix @@ -40,11 +37,11 @@ class BaseInstance(ABC): self._gcodes_dir = self.data_dir.joinpath("gcodes") @property - def instance_type(self) -> Type["BaseInstance"]: + def instance_type(self) -> BaseInstance: return self._instance_type @instance_type.setter - def instance_type(self, value: Type["BaseInstance"]) -> None: + def instance_type(self, value: BaseInstance) -> None: self._instance_type = value @property @@ -76,7 +73,7 @@ class BaseInstance(ABC): return self._data_dir @data_dir.setter - def data_dir(self, value: str) -> None: + def data_dir(self, value: Path) -> None: self._data_dir = value @property @@ -84,7 +81,7 @@ class BaseInstance(ABC): return self._cfg_dir @cfg_dir.setter - def cfg_dir(self, value: str) -> None: + def cfg_dir(self, value: Path) -> None: self._cfg_dir = value @property @@ -92,7 +89,7 @@ class BaseInstance(ABC): return self._log_dir @log_dir.setter - def log_dir(self, value: str) -> None: + def log_dir(self, value: Path) -> None: self._log_dir = value @property @@ -100,7 +97,7 @@ class BaseInstance(ABC): return self._comms_dir @comms_dir.setter - def comms_dir(self, value: str) -> None: + def comms_dir(self, value: Path) -> None: self._comms_dir = value @property @@ -108,7 +105,7 @@ class BaseInstance(ABC): return self._sysd_dir @sysd_dir.setter - def sysd_dir(self, value: str) -> None: + def sysd_dir(self, value: Path) -> None: self._sysd_dir = value @property @@ -116,7 +113,7 @@ class BaseInstance(ABC): return self._gcodes_dir @gcodes_dir.setter - def gcodes_dir(self, value: str) -> None: + def gcodes_dir(self, value: Path) -> None: self._gcodes_dir = value @abstractmethod @@ -127,7 +124,7 @@ class BaseInstance(ABC): def delete(self) -> None: raise NotImplementedError("Subclasses must implement the delete method") - def create_folders(self, add_dirs: List[Path] = None) -> None: + def create_folders(self, add_dirs: Optional[List[Path]] = None) -> None: dirs = [ self.data_dir, self.cfg_dir, From a5dce136f342b5e660813faa4de8d70a30be5b62 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 21 Mar 2024 21:55:35 +0100 Subject: [PATCH 131/137] chore: remove shebang from most files because it is not needed Signed-off-by: Dominik Willner --- kiauh/__init__.py | 2 -- kiauh/components/klipper/__init__.py | 2 -- kiauh/components/klipper/klipper.py | 2 -- kiauh/components/klipper/klipper_dialogs.py | 2 -- kiauh/components/klipper/klipper_remove.py | 2 -- kiauh/components/klipper/klipper_setup.py | 3 --- kiauh/components/klipper/klipper_utils.py | 3 --- kiauh/components/klipper/menus/klipper_remove_menu.py | 2 -- kiauh/components/log_uploads/__init__.py | 2 -- kiauh/components/log_uploads/log_upload_utils.py | 2 -- kiauh/components/log_uploads/menus/log_upload_menu.py | 2 -- kiauh/components/moonraker/__init__.py | 2 -- kiauh/components/moonraker/menus/moonraker_remove_menu.py | 2 -- kiauh/components/moonraker/moonraker.py | 2 -- kiauh/components/moonraker/moonraker_dialogs.py | 2 -- kiauh/components/moonraker/moonraker_remove.py | 2 -- kiauh/components/moonraker/moonraker_setup.py | 4 ---- kiauh/components/moonraker/moonraker_utils.py | 2 -- kiauh/components/webui_client/__init__.py | 2 -- .../webui_client/client_config/client_config_remove.py | 2 -- kiauh/components/webui_client/client_dialogs.py | 2 -- kiauh/components/webui_client/client_remove.py | 2 -- kiauh/components/webui_client/client_setup.py | 7 +++---- kiauh/components/webui_client/client_utils.py | 2 -- kiauh/components/webui_client/menus/client_remove_menu.py | 2 -- kiauh/core/backup_manager/__init__.py | 2 -- kiauh/core/backup_manager/backup_manager.py | 2 -- kiauh/core/base_extension.py | 2 -- kiauh/core/config_manager/config_manager.py | 2 -- kiauh/core/instance_manager/instance_manager.py | 2 -- kiauh/core/menus/__init__.py | 2 -- kiauh/core/menus/advanced_menu.py | 2 -- kiauh/core/menus/backup_menu.py | 2 -- kiauh/core/menus/base_menu.py | 2 -- kiauh/core/menus/extensions_menu.py | 2 -- kiauh/core/menus/install_menu.py | 2 -- kiauh/core/menus/main_menu.py | 2 -- kiauh/core/menus/remove_menu.py | 2 -- kiauh/core/menus/settings_menu.py | 2 -- kiauh/core/menus/update_menu.py | 2 -- kiauh/core/repo_manager/repo_manager.py | 2 -- kiauh/extensions/gcode_shell_cmd/__init__.py | 2 -- .../gcode_shell_cmd/gcode_shell_cmd_extension.py | 2 -- kiauh/main.py | 2 -- kiauh/utils/__init__.py | 2 -- kiauh/utils/common.py | 2 -- kiauh/utils/constants.py | 2 -- kiauh/utils/input_utils.py | 2 -- kiauh/utils/logger.py | 2 -- kiauh/utils/system_utils.py | 2 -- 50 files changed, 3 insertions(+), 106 deletions(-) diff --git a/kiauh/__init__.py b/kiauh/__init__.py index f93f7de..105b426 100644 --- a/kiauh/__init__.py +++ b/kiauh/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/klipper/__init__.py b/kiauh/components/klipper/__init__.py index 9f46783..aee9c25 100644 --- a/kiauh/components/klipper/__init__.py +++ b/kiauh/components/klipper/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/klipper/klipper.py b/kiauh/components/klipper/klipper.py index 05eef56..2847ae5 100644 --- a/kiauh/components/klipper/klipper.py +++ b/kiauh/components/klipper/klipper.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/klipper/klipper_dialogs.py b/kiauh/components/klipper/klipper_dialogs.py index 30892c5..2cfc672 100644 --- a/kiauh/components/klipper/klipper_dialogs.py +++ b/kiauh/components/klipper/klipper_dialogs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/klipper/klipper_remove.py b/kiauh/components/klipper/klipper_remove.py index d802d4b..7245201 100644 --- a/kiauh/components/klipper/klipper_remove.py +++ b/kiauh/components/klipper/klipper_remove.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/klipper/klipper_setup.py b/kiauh/components/klipper/klipper_setup.py index 63b5cf9..db329cb 100644 --- a/kiauh/components/klipper/klipper_setup.py +++ b/kiauh/components/klipper/klipper_setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -12,7 +10,6 @@ from pathlib import Path from components.webui_client.client_utils import ( - get_existing_client_config, get_existing_clients, ) from kiauh import KIAUH_CFG diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 5e2314f..e71cb3b 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -15,7 +13,6 @@ import re import shutil import subprocess import textwrap -from pathlib import Path from typing import List, Union, Literal, Dict, Optional from components.klipper import ( diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index dc92b33..379005a 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/log_uploads/__init__.py b/kiauh/components/log_uploads/__init__.py index 7672138..1d87ba8 100644 --- a/kiauh/components/log_uploads/__init__.py +++ b/kiauh/components/log_uploads/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/log_uploads/log_upload_utils.py b/kiauh/components/log_uploads/log_upload_utils.py index 2454443..3a8029a 100644 --- a/kiauh/components/log_uploads/log_upload_utils.py +++ b/kiauh/components/log_uploads/log_upload_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index baccce5..df2c268 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/moonraker/__init__.py b/kiauh/components/moonraker/__init__.py index 9341992..6c4ff89 100644 --- a/kiauh/components/moonraker/__init__.py +++ b/kiauh/components/moonraker/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index f740b45..607d04e 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/moonraker/moonraker.py b/kiauh/components/moonraker/moonraker.py index 994cba0..8cffeb3 100644 --- a/kiauh/components/moonraker/moonraker.py +++ b/kiauh/components/moonraker/moonraker.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/moonraker/moonraker_dialogs.py b/kiauh/components/moonraker/moonraker_dialogs.py index 4f13d10..c047e4a 100644 --- a/kiauh/components/moonraker/moonraker_dialogs.py +++ b/kiauh/components/moonraker/moonraker_dialogs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/moonraker/moonraker_remove.py b/kiauh/components/moonraker/moonraker_remove.py index db10bf4..04a9872 100644 --- a/kiauh/components/moonraker/moonraker_remove.py +++ b/kiauh/components/moonraker/moonraker_remove.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index 2cfdd0d..d55fd63 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -12,13 +10,11 @@ import subprocess import sys from pathlib import Path -from typing import List from components.webui_client import MAINSAIL_DIR from components.webui_client.client_utils import enable_mainsail_remotemode, get_existing_clients from kiauh import KIAUH_CFG from components.klipper.klipper import Klipper -from components.klipper.klipper_dialogs import print_instance_overview from components.moonraker import ( EXIT_MOONRAKER_SETUP, DEFAULT_MOONRAKER_REPO_URL, diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index a2a8c22..ca1a15c 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/webui_client/__init__.py b/kiauh/components/webui_client/__init__.py index b0d44f3..6f21f4a 100644 --- a/kiauh/components/webui_client/__init__.py +++ b/kiauh/components/webui_client/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/webui_client/client_config/client_config_remove.py b/kiauh/components/webui_client/client_config/client_config_remove.py index 22d00fb..e61c313 100644 --- a/kiauh/components/webui_client/client_config/client_config_remove.py +++ b/kiauh/components/webui_client/client_config/client_config_remove.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/webui_client/client_dialogs.py b/kiauh/components/webui_client/client_dialogs.py index 1053a58..5270c42 100644 --- a/kiauh/components/webui_client/client_dialogs.py +++ b/kiauh/components/webui_client/client_dialogs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/webui_client/client_remove.py b/kiauh/components/webui_client/client_remove.py index 7a8648c..a55a25f 100644 --- a/kiauh/components/webui_client/client_remove.py +++ b/kiauh/components/webui_client/client_remove.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py index 2744cef..9a148a3 100644 --- a/kiauh/components/webui_client/client_setup.py +++ b/kiauh/components/webui_client/client_setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -32,8 +30,9 @@ from components.webui_client.client_utils import ( restore_mainsail_config_json, enable_mainsail_remotemode, symlink_webui_nginx_log, - load_client_data, config_for_other_client_exist, - ) + load_client_data, + config_for_other_client_exist, +) from core.config_manager.config_manager import ConfigManager from core.instance_manager.instance_manager import InstanceManager from kiauh import KIAUH_CFG diff --git a/kiauh/components/webui_client/client_utils.py b/kiauh/components/webui_client/client_utils.py index 4dd53d0..d2c4e1f 100644 --- a/kiauh/components/webui_client/client_utils.py +++ b/kiauh/components/webui_client/client_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/components/webui_client/menus/client_remove_menu.py b/kiauh/components/webui_client/menus/client_remove_menu.py index 6db4097..6cf5638 100644 --- a/kiauh/components/webui_client/menus/client_remove_menu.py +++ b/kiauh/components/webui_client/menus/client_remove_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/backup_manager/__init__.py b/kiauh/core/backup_manager/__init__.py index 7bbbe1e..642c8aa 100644 --- a/kiauh/core/backup_manager/__init__.py +++ b/kiauh/core/backup_manager/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py index 249bf1e..e87710d 100644 --- a/kiauh/core/backup_manager/backup_manager.py +++ b/kiauh/core/backup_manager/backup_manager.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/base_extension.py b/kiauh/core/base_extension.py index 09245ee..af80929 100644 --- a/kiauh/core/base_extension.py +++ b/kiauh/core/base_extension.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/config_manager/config_manager.py b/kiauh/core/config_manager/config_manager.py index 12d4eb0..7167cb9 100644 --- a/kiauh/core/config_manager/config_manager.py +++ b/kiauh/core/config_manager/config_manager.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/instance_manager/instance_manager.py b/kiauh/core/instance_manager/instance_manager.py index 9750086..0a5750b 100644 --- a/kiauh/core/instance_manager/instance_manager.py +++ b/kiauh/core/instance_manager/instance_manager.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/__init__.py b/kiauh/core/menus/__init__.py index afca077..14f4f7b 100644 --- a/kiauh/core/menus/__init__.py +++ b/kiauh/core/menus/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 27eaacd..8fa89de 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index 459461a..4f404a7 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 06ad576..8a27978 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/extensions_menu.py b/kiauh/core/menus/extensions_menu.py index a46b00c..15ed229 100644 --- a/kiauh/core/menus/extensions_menu.py +++ b/kiauh/core/menus/extensions_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index a90e235..6400645 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 4ab3b31..8d20036 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index ad53c94..8c13efe 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index 66f352c..c74a384 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index ffa74ba..7cd944b 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/core/repo_manager/repo_manager.py b/kiauh/core/repo_manager/repo_manager.py index 153deef..6a7cfa0 100644 --- a/kiauh/core/repo_manager/repo_manager.py +++ b/kiauh/core/repo_manager/repo_manager.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/extensions/gcode_shell_cmd/__init__.py b/kiauh/extensions/gcode_shell_cmd/__init__.py index 27ffecb..95336dc 100644 --- a/kiauh/extensions/gcode_shell_cmd/__init__.py +++ b/kiauh/extensions/gcode_shell_cmd/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py index 8d004ec..e726565 100644 --- a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/main.py b/kiauh/main.py index 0ca361c..dd6f068 100644 --- a/kiauh/main.py +++ b/kiauh/main.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/utils/__init__.py b/kiauh/utils/__init__.py index afc69aa..2435e5d 100644 --- a/kiauh/utils/__init__.py +++ b/kiauh/utils/__init__.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 4e5bbee..89b2ec5 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/utils/constants.py b/kiauh/utils/constants.py index a2dd612..cfb8477 100644 --- a/kiauh/utils/constants.py +++ b/kiauh/utils/constants.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index d0a3b48..25a1b71 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/utils/logger.py b/kiauh/utils/logger.py index 66f9326..9bb303b 100644 --- a/kiauh/utils/logger.py +++ b/kiauh/utils/logger.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # diff --git a/kiauh/utils/system_utils.py b/kiauh/utils/system_utils.py index d140ead..3b46913 100644 --- a/kiauh/utils/system_utils.py +++ b/kiauh/utils/system_utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # From 00665109c2f4b808dbc52e704a875f0e28ab6935 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Mar 2024 22:11:56 +0100 Subject: [PATCH 132/137] feat: allow sections to be added to the top of a config file Signed-off-by: Dominik Willner --- .../client_config/client_config_setup.py | 15 +++++---- kiauh/utils/filesystem_utils.py | 33 ++++++++++++++++--- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/kiauh/components/webui_client/client_config/client_config_setup.py b/kiauh/components/webui_client/client_config/client_config_setup.py index 13f1062..01ca5cc 100644 --- a/kiauh/components/webui_client/client_config/client_config_setup.py +++ b/kiauh/components/webui_client/client_config/client_config_setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # # # @@ -21,16 +19,17 @@ from components.webui_client import ClientConfigData, ClientName, ClientData from components.webui_client.client_dialogs import print_client_already_installed_dialog from components.webui_client.client_utils import ( load_client_data, - backup_client_config_data, config_for_other_client_exist, - ) + backup_client_config_data, + config_for_other_client_exist, +) from core.config_manager.config_manager import ConfigManager from core.instance_manager.instance_manager import InstanceManager from core.repo_manager.repo_manager import RepoManager from utils.filesystem_utils import ( create_symlink, - add_config_section, -) + add_config_section, add_config_section_at_top, + ) from utils.input_utils import get_confirm from utils.logger import Logger @@ -70,7 +69,9 @@ def install_client_config(client_name: ClientName) -> None: ("managed_services", "klipper"), ], ) - add_config_section(client_config.get("printer_cfg_section"), kl_instances) + add_config_section_at_top( + client_config.get("printer_cfg_section"), kl_instances + ) kl_im.restart_all_instance() except Exception as e: diff --git a/kiauh/utils/filesystem_utils.py b/kiauh/utils/filesystem_utils.py index b7fed2f..0406bc2 100644 --- a/kiauh/utils/filesystem_utils.py +++ b/kiauh/utils/filesystem_utils.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import os # ======================================================================= # # Copyright (C) 2020 - 2024 Dominik Willner # @@ -12,15 +13,15 @@ import re import shutil import subprocess +import tempfile from pathlib import Path from zipfile import ZipFile -from typing import List, Type, TypeVar, Union, Tuple +from typing import List, TypeVar, Tuple, Optional from components.klipper.klipper import Klipper from components.moonraker.moonraker import Moonraker from core.config_manager.config_manager import ConfigManager -from core.instance_manager.base_instance import BaseInstance from core.instance_manager.instance_manager import InstanceManager from utils import ( NGINX_SITES_AVAILABLE, @@ -31,7 +32,7 @@ from utils import ( from utils.logger import Logger -B = TypeVar('B', bound='BaseInstance') +B = TypeVar("B", Klipper, Moonraker) ConfigOption = Tuple[str, str] @@ -182,7 +183,11 @@ def get_next_free_port(ports_in_use: List[str]) -> str: return str(min(valid_ports - used_ports)) -def add_config_section(section: str, instances: List[B], options: List[ConfigOption] = None) -> None: +def add_config_section( + section: str, + instances: List[B], + options: Optional[List[ConfigOption]] = None, +) -> None: for instance in instances: cfg_file = instance.cfg_file Logger.print_status(f"Add section '[{section}]' to '{cfg_file}' ...") @@ -204,6 +209,26 @@ def add_config_section(section: str, instances: List[B], options: List[ConfigOpt cm.write_config() +def add_config_section_at_top( + section: str, + instances: List[B]): + for instance in instances: + tmp_cfg = tempfile.NamedTemporaryFile(mode="w" ,delete=False) + tmp_cfg_path = Path(tmp_cfg.name) + cmt = ConfigManager(tmp_cfg_path) + cmt.config.add_section(section) + cmt.write_config() + tmp_cfg.close() + + cfg_file = instance.cfg_file + with open(cfg_file, "r") as org: + org_content = org.readlines() + with open(tmp_cfg_path, "a") as tmp: + tmp.writelines(org_content) + + cfg_file.unlink() + tmp_cfg_path.rename(cfg_file) + def remove_config_section(section: str, instances: List[B]) -> None: for instance in instances: From 2acd74cbd9f8dbfccac8590d7a9a8a687842ece2 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Mar 2024 22:20:13 +0100 Subject: [PATCH 133/137] refactor(webclients): make a backup before modification of config files Signed-off-by: Dominik Willner --- .../webui_client/client_config/client_config_setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kiauh/components/webui_client/client_config/client_config_setup.py b/kiauh/components/webui_client/client_config/client_config_setup.py index 01ca5cc..02f25a2 100644 --- a/kiauh/components/webui_client/client_config/client_config_setup.py +++ b/kiauh/components/webui_client/client_config/client_config_setup.py @@ -26,6 +26,7 @@ from core.config_manager.config_manager import ConfigManager from core.instance_manager.instance_manager import InstanceManager from core.repo_manager.repo_manager import RepoManager +from utils.common import backup_printer_config_dir from utils.filesystem_utils import ( create_symlink, add_config_section, add_config_section_at_top, @@ -58,6 +59,9 @@ def install_client_config(client_name: ClientName) -> None: try: download_client_config(client_config) create_client_config_symlink(client_config, kl_instances) + + backup_printer_config_dir() + add_config_section( section=f"update_manager {client_config.get('name')}", instances=mr_instances, From ef13c130e03967e0e915dc44a4fb077f95406d3f Mon Sep 17 00:00:00 2001 From: dw-0 Date: Fri, 22 Mar 2024 22:44:07 +0100 Subject: [PATCH 134/137] chore: remove mypy from project Signed-off-by: Dominik Willner --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 40eae37..be1fddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,5 +11,3 @@ exclude = ''' | scripts/ ) ''' -[tool.mypy] -mypy_path = "./kiauh" From 5c1c98b6b8c5e4a22306e549c5c6090a42692231 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 23 Mar 2024 12:59:26 +0100 Subject: [PATCH 135/137] refactor: update advanced menu layout Signed-off-by: Dominik Willner --- kiauh/core/menus/advanced_menu.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 8fa89de..30f783a 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -27,14 +27,18 @@ class AdvancedMenu(BaseMenu): /=======================================================\\ | {color}{header:~^{count}}{RESET_FORMAT} | |-------------------------------------------------------| - | Klipper & API: | Mainsail: | - | 0) [Rollback] | 5) [Theme installer] | - | | | - | Firmware: | System: | - | 1) [Build only] | 6) [Change hostname] | - | 2) [Flash only] | | - | 3) [Build + Flash] | Extras: | - | 4) [Get MCU ID] | 7) [G-Code Shell Command] | + | Repo Rollback: | + | 1) [Klipper] | + | 2) [Moonraker] | + | | + | Klipper Firmware: | + | 3) [Build] | + | 4) [Flash] | + | 5) [Build + Flash] | + | 6) [Get MCU ID] | + | | + | Mainsail: | + | 7) [Theme installer] | """ )[1:] print(menu, end="") From 03c3ed20f3c8d520c061e2e60792beffb40d6e03 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 23 Mar 2024 17:55:25 +0100 Subject: [PATCH 136/137] refactor: disable header printing in extension menu Signed-off-by: Dominik Willner --- kiauh/core/menus/extensions_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/core/menus/extensions_menu.py b/kiauh/core/menus/extensions_menu.py index 15ed229..3db2aea 100644 --- a/kiauh/core/menus/extensions_menu.py +++ b/kiauh/core/menus/extensions_menu.py @@ -26,7 +26,7 @@ class ExtensionsMenu(BaseMenu): def __init__(self): self.extensions = self.discover_extensions() super().__init__( - header=True, + header=False, options=self.get_options(), footer_type=BACK_FOOTER, ) From 0183988d5dedd7f36168f6ac973790a99068797b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 23 Mar 2024 18:40:35 +0100 Subject: [PATCH 137/137] fix(LogUpload): fix bug in menu options Signed-off-by: Dominik Willner --- kiauh/components/log_uploads/menus/log_upload_menu.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index df2c268..fd8f558 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -20,7 +20,7 @@ from utils.constants import RESET_FORMAT, COLOR_YELLOW class LogUploadMenu(BaseMenu): def __init__(self): self.logfile_list = get_logfile_list() - options = {index: self.upload for index in range(len(self.logfile_list))} + options = {f"{index}": self.upload for index in range(len(self.logfile_list))} super().__init__( header=True, options=options, @@ -49,4 +49,5 @@ class LogUploadMenu(BaseMenu): print(menu, end="") def upload(self, **kwargs): - upload_logfile(self.logfile_list[kwargs.get("opt_index")]) + index = int(kwargs.get("opt_index")) + upload_logfile(self.logfile_list[index])