From a2a3e92b5013c1a8a77ad76eae9fa0af53842db6 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 18 Jan 2025 17:38:00 +0100 Subject: [PATCH 1/7] refactor: remove `BASE_USER` argument from crowsnest install command (#617) Signed-off-by: Dominik Willner --- kiauh/components/crowsnest/crowsnest.py | 3 +-- scripts/crowsnest.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/kiauh/components/crowsnest/crowsnest.py b/kiauh/components/crowsnest/crowsnest.py index a330973..36d9793 100644 --- a/kiauh/components/crowsnest/crowsnest.py +++ b/kiauh/components/crowsnest/crowsnest.py @@ -27,7 +27,6 @@ from components.crowsnest import ( ) from components.klipper.klipper import Klipper from core.backup_manager.backup_manager import BackupManager -from core.constants import CURRENT_USER from core.logger import DialogType, Logger from core.settings.kiauh_settings import KiauhSettings from core.types.component_status import ComponentStatus @@ -73,7 +72,7 @@ def install_crowsnest() -> None: Logger.print_info("Installer will prompt you for sudo password!") try: run( - f"sudo make install BASE_USER={CURRENT_USER}", + f"sudo make install", cwd=CROWSNEST_DIR, shell=True, check=True, diff --git a/scripts/crowsnest.sh b/scripts/crowsnest.sh index 0ec857e..788468f 100644 --- a/scripts/crowsnest.sh +++ b/scripts/crowsnest.sh @@ -105,7 +105,7 @@ function install_crowsnest(){ pushd "${HOME}/crowsnest" &> /dev/null || exit 1 title_msg "Installer will prompt you for sudo password!" status_msg "Launching crowsnest installer ..." - if ! sudo make install BASE_USER=$USER; then + if ! sudo make install; then error_msg "Something went wrong! Please try again..." exit 1 fi From 2a08e3eb15f15e7e5c581679f0f7d8af8e6d8b2f Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 18 Jan 2025 17:38:40 +0100 Subject: [PATCH 2/7] refactor: omit port 80 for IP in success message after webclient installation (#618) Signed-off-by: Dominik Willner --- kiauh/components/webui_client/client_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py index 6456469..4a6cb8e 100644 --- a/kiauh/components/webui_client/client_setup.py +++ b/kiauh/components/webui_client/client_setup.py @@ -144,7 +144,7 @@ def install_client( custom_color=Color.GREEN, center_content=True, content=[ - f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{port}", + f"Open {client.display_name} now on: http://{get_ipv4_addr()}{'' if port == 80 else f':{port}'}", ], ) From 8330f90b561cc1e0c929f31f67f5d56d6d7780e6 Mon Sep 17 00:00:00 2001 From: Mathijs Groothuis Date: Wed, 5 Feb 2025 19:07:19 +0100 Subject: [PATCH 3/7] Fix typo: tyoing > typing Fix typo: tyoing > typing Co-authored-by: dw-0 Co-authored-by: dw-0 --- kiauh/procedures/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/procedures/system.py b/kiauh/procedures/system.py index 515054d..187c270 100644 --- a/kiauh/procedures/system.py +++ b/kiauh/procedures/system.py @@ -31,7 +31,7 @@ def change_system_hostname() -> None: "http://.local", "\n\n", "Example: If you set your hostname to 'my-printer', you can access an " - "installed webinterface by tyoing 'http://my-printer.local' in the " + "installed webinterface by typing 'http://my-printer.local' in the " "browser.", ], custom_title="CHANGE SYSTEM HOSTNAME", From 4978f221011a0857217eb9099a17f0e511031a9f Mon Sep 17 00:00:00 2001 From: Mathijs Groothuis Date: Sat, 8 Feb 2025 13:30:37 +0100 Subject: [PATCH 4/7] fix(typo): Successfull > Successful Co-authored-by: dw-0 Co-authored-by: dw-0 --- kiauh/components/klipper_firmware/firmware_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiauh/components/klipper_firmware/firmware_utils.py b/kiauh/components/klipper_firmware/firmware_utils.py index 435a96d..024ef63 100644 --- a/kiauh/components/klipper_firmware/firmware_utils.py +++ b/kiauh/components/klipper_firmware/firmware_utils.py @@ -165,7 +165,7 @@ def start_flash_process(flash_options: FlashOptions) -> None: if rc != 0: raise Exception(f"Flashing failed with returncode: {rc}") else: - Logger.print_ok("Flashing successfull!", start="\n", end="\n\n") + Logger.print_ok("Flashing successful!", start="\n", end="\n\n") except (Exception, CalledProcessError): Logger.print_error("Flashing failed!", start="\n") From d8f47c09604de0c0e15da34d7adcdf65b02cec59 Mon Sep 17 00:00:00 2001 From: CODeRUS Date: Sun, 9 Feb 2025 21:45:05 +0700 Subject: [PATCH 5/7] feature: save and select kconfig (#621) * feature: save and select kconfig Signed-off-by: Andrey Kozhevnikov * chore: clean up and sort imports Signed-off-by: Dominik Willner * refactor: replace os.path with Pathlib - use config paths as type Paths instead of strings. - tweak some menu visuals. Signed-off-by: Dominik Willner --------- Signed-off-by: Andrey Kozhevnikov Signed-off-by: Dominik Willner Co-authored-by: dw-0 --- kiauh/components/klipper/__init__.py | 1 + .../klipper_firmware/firmware_utils.py | 14 +- .../klipper_firmware/flash_options.py | 9 + .../menus/klipper_build_menu.py | 176 +++++++++++++++++- .../menus/klipper_flash_menu.py | 9 + kiauh/core/menus/advanced_menu.py | 4 + 6 files changed, 200 insertions(+), 13 deletions(-) diff --git a/kiauh/components/klipper/__init__.py b/kiauh/components/klipper/__init__.py index 908ed4e..fd63b5c 100644 --- a/kiauh/components/klipper/__init__.py +++ b/kiauh/components/klipper/__init__.py @@ -25,6 +25,7 @@ KLIPPER_SERVICE_NAME = "klipper.service" # directories KLIPPER_DIR = Path.home().joinpath("klipper") +KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs") KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") diff --git a/kiauh/components/klipper_firmware/firmware_utils.py b/kiauh/components/klipper_firmware/firmware_utils.py index 024ef63..fa3c6e8 100644 --- a/kiauh/components/klipper_firmware/firmware_utils.py +++ b/kiauh/components/klipper_firmware/firmware_utils.py @@ -7,6 +7,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # import re +from pathlib import Path from subprocess import ( DEVNULL, PIPE, @@ -138,6 +139,7 @@ def start_flash_process(flash_options: FlashOptions) -> None: if flash_options.flash_method is FlashMethod.REGULAR: cmd = [ "make", + f"KCONFIG_CONFIG={flash_options.selected_kconfig}", flash_options.flash_command.value, f"FLASH_DEVICE={flash_options.selected_mcu}", ] @@ -172,10 +174,10 @@ def start_flash_process(flash_options: FlashOptions) -> None: Logger.print_error("See the console output above!", end="\n\n") -def run_make_clean() -> None: +def run_make_clean(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None: try: run( - "make clean", + f"make KCONFIG_CONFIG={kconfig} clean", cwd=KLIPPER_DIR, shell=True, check=True, @@ -185,10 +187,10 @@ def run_make_clean() -> None: raise -def run_make_menuconfig() -> None: +def run_make_menuconfig(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None: try: run( - "make PYTHON=python3 menuconfig", + f"make PYTHON=python3 KCONFIG_CONFIG={kconfig} menuconfig", cwd=KLIPPER_DIR, shell=True, check=True, @@ -198,10 +200,10 @@ def run_make_menuconfig() -> None: raise -def run_make() -> None: +def run_make(kconfig=Path(KLIPPER_DIR.joinpath(".config"))) -> None: try: run( - "make PYTHON=python3", + f"make PYTHON=python3 KCONFIG_CONFIG={kconfig}", cwd=KLIPPER_DIR, shell=True, check=True, diff --git a/kiauh/components/klipper_firmware/flash_options.py b/kiauh/components/klipper_firmware/flash_options.py index 023df67..22e3fd9 100644 --- a/kiauh/components/klipper_firmware/flash_options.py +++ b/kiauh/components/klipper_firmware/flash_options.py @@ -39,6 +39,7 @@ class FlashOptions: _selected_mcu: str = "" _selected_board: str = "" _selected_baudrate: int = 250000 + _selected_kconfig: str = ".config" def __new__(cls, *args, **kwargs): if not cls._instance: @@ -104,3 +105,11 @@ class FlashOptions: @selected_baudrate.setter def selected_baudrate(self, value: int) -> None: self._selected_baudrate = value + + @property + def selected_kconfig(self) -> str: + return self._selected_kconfig + + @selected_kconfig.setter + def selected_kconfig(self, value: str) -> None: + self._selected_kconfig = value diff --git a/kiauh/components/klipper_firmware/menus/klipper_build_menu.py b/kiauh/components/klipper_firmware/menus/klipper_build_menu.py index 1237eed..60128d8 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_build_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_build_menu.py @@ -9,18 +9,22 @@ from __future__ import annotations import textwrap +from pathlib import Path +from shutil import copyfile from typing import List, Set, Type -from components.klipper import KLIPPER_DIR +from components.klipper import KLIPPER_DIR, KLIPPER_KCONFIGS_DIR from components.klipper_firmware.firmware_utils import ( run_make, run_make_clean, run_make_menuconfig, ) -from core.logger import Logger +from components.klipper_firmware.flash_options import FlashOptions +from core.logger import DialogType, Logger from core.menus import Option from core.menus.base_menu import BaseMenu from core.types.color import Color +from utils.input_utils import get_confirm, get_string_input from utils.sys_utils import ( check_package_install, install_system_packages, @@ -30,14 +34,110 @@ from utils.sys_utils import ( # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic -class KlipperBuildFirmwareMenu(BaseMenu): +class KlipperKConfigMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): + super().__init__() + self.title = "Firmware Config Menu" + self.title_color = Color.CYAN + self.previous_menu: Type[BaseMenu] | None = previous_menu + self.flash_options = FlashOptions() + self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR + self.kconfig_default = KLIPPER_DIR.joinpath(".config") + self.configs: List[Path] = [] + self.kconfig = ( + self.kconfig_default if not Path(self.kconfigs_dirname).is_dir() else None + ) + + def run(self) -> None: + if not self.kconfig: + super().run() + else: + self.flash_options.selected_kconfig = self.kconfig + + def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: + from core.menus.advanced_menu import AdvancedMenu + + self.previous_menu = ( + previous_menu if previous_menu is not None else AdvancedMenu + ) + + def set_options(self) -> None: + if not Path(self.kconfigs_dirname).is_dir(): + return + + self.input_label_txt = "Select config or action to continue (default=N)" + self.default_option = Option( + method=self.select_config, opt_data=self.kconfig_default + ) + + option_index = 1 + for kconfig in Path(self.kconfigs_dirname).iterdir(): + if not kconfig.name.endswith(".config"): + continue + kconfig_path = self.kconfigs_dirname.joinpath(kconfig) + if Path(kconfig_path).is_file(): + self.configs += [kconfig] + self.options[str(option_index)] = Option( + method=self.select_config, opt_data=kconfig_path + ) + option_index += 1 + self.options["n"] = Option( + method=self.select_config, opt_data=self.kconfig_default + ) + + def print_menu(self) -> None: + cfg_found_str = Color.apply( + "Previously saved firmware configs found!", Color.GREEN + ) + menu = textwrap.dedent( + f""" + ╟───────────────────────────────────────────────────────╢ + ║ {cfg_found_str:^62} ║ + ║ ║ + ║ Select an existing config or create a new one. ║ + ╟───────────────────────────────────────────────────────╢ + ║ Available firmware configs: ║ + """ + )[1:] + + start_index = 1 + for i, s in enumerate(self.configs): + line = f"{start_index + i}) {s.name}" + menu += f"║ {line:<54}║\n" + + new_config = Color.apply("N) Create new firmware config", Color.GREEN) + menu += "║ ║\n" + menu += f"║ {new_config:<62} ║\n" + + menu += "╟───────────────────────────────────────────────────────╢\n" + + print(menu, end="") + + def select_config(self, **kwargs) -> None: + selection: str | None = kwargs.get("opt_data", None) + if selection is None: + raise Exception("opt_data is None") + if not Path(selection).is_file() and selection != self.kconfig_default: + raise Exception("opt_data does not exists") + self.kconfig = selection + + +# noinspection PyUnusedLocal +# noinspection PyMethodMayBeStatic +class KlipperBuildFirmwareMenu(BaseMenu): + def __init__( + self, kconfig: str | None = None, previous_menu: Type[BaseMenu] | None = None + ): super().__init__() self.title = "Build Firmware Menu" self.title_color = Color.CYAN self.previous_menu: Type[BaseMenu] | None = previous_menu self.deps: Set[str] = {"build-essential", "dpkg-dev", "make"} self.missing_deps: List[str] = check_package_install(self.deps) + self.flash_options = FlashOptions() + self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR + self.kconfig_default = KLIPPER_DIR.joinpath(".config") + self.kconfig = self.flash_options.selected_kconfig def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: from core.menus.advanced_menu import AdvancedMenu @@ -67,7 +167,7 @@ class KlipperBuildFirmwareMenu(BaseMenu): status_ok = Color.apply("*INSTALLED*", Color.GREEN) status_missing = Color.apply("*MISSING*", Color.RED) status = status_missing if d in self.missing_deps else status_ok - padding = 39 - len(d) + len(status) + (len(status_ok) - len(status)) + padding = 40 - len(d) + len(status) + (len(status_ok) - len(status)) d = Color.apply(f"● {d}", Color.CYAN) menu += f"║ {d}{status:>{padding}} ║\n" menu += "║ ║\n" @@ -98,13 +198,16 @@ class KlipperBuildFirmwareMenu(BaseMenu): def start_build_process(self, **kwargs) -> None: try: - run_make_clean() - run_make_menuconfig() - run_make() + run_make_clean(self.kconfig) + run_make_menuconfig(self.kconfig) + run_make(self.kconfig) Logger.print_ok("Firmware successfully built!") Logger.print_ok(f"Firmware file located in '{KLIPPER_DIR}/out'!") + if self.kconfig == self.kconfig_default: + self.save_firmware_config() + except Exception as e: Logger.print_error(e) Logger.print_error("Building Klipper Firmware failed!") @@ -112,3 +215,62 @@ class KlipperBuildFirmwareMenu(BaseMenu): finally: if self.previous_menu is not None: self.previous_menu().run() + + def save_firmware_config(self) -> None: + Logger.print_dialog( + DialogType.CUSTOM, + [ + "You can save the firmware build configs for multiple MCUs," + " and use them to update the firmware after a Klipper version upgrade" + ], + custom_title="Save firmware config", + ) + if not get_confirm( + "Do you want to save firmware config?", default_choice=False + ): + return + + filename = self.kconfig_default + while True: + Logger.print_dialog( + DialogType.CUSTOM, + [ + "Allowed characters: a-z, 0-9 and '-'", + "The name must not contain the following:", + "\n\n", + "● Any special characters", + "● No leading or trailing '-'", + ], + ) + input_name = get_string_input( + "Enter the new firmware config name", + regex=r"^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$", + ) + filename = self.kconfigs_dirname.joinpath(f"{input_name}.config") + + if Path(filename).is_file(): + if get_confirm( + f"Firmware config {input_name} already exists, overwrite?", + default_choice=False, + ): + break + + if Path(filename).is_dir(): + Logger.print_error(f"Path {filename} exists and it's a directory") + + if not Path(filename).exists(): + break + + if not get_confirm( + f"Save firmware config to '{filename}'?", default_choice=True + ): + Logger.print_info("Aborted saving firmware config ...") + return + + if not Path(self.kconfigs_dirname).exists(): + Path(self.kconfigs_dirname).mkdir() + + copyfile(self.kconfig_default, filename) + + Logger.print_ok() + Logger.print_ok(f"Firmware config successfully saved to {filename}") diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py index b7d741b..00d0765 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py @@ -10,6 +10,7 @@ from __future__ import annotations import textwrap import time +from pathlib import Path from typing import Type from components.klipper_firmware.firmware_utils import ( @@ -420,6 +421,7 @@ class KlipperFlashOverviewMenu(BaseMenu): mcu = self.flash_options.selected_mcu.split("/")[-1] board = self.flash_options.selected_board baudrate = self.flash_options.selected_baudrate + kconfig = Path(self.flash_options.selected_kconfig).name color = Color.CYAN subheader = f"[{Color.apply('Overview', color)}]" menu = textwrap.dedent( @@ -452,6 +454,13 @@ class KlipperFlashOverviewMenu(BaseMenu): """ )[1:] + if self.flash_options.flash_method is FlashMethod.REGULAR: + menu += textwrap.dedent( + f""" + ║ Firmware config: {Color.apply(f"{kconfig:<36}", color)} ║ + """ + )[1:] + menu += textwrap.dedent( """ ║ ║ diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 4c52536..22cf641 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -15,6 +15,7 @@ from components.klipper import KLIPPER_DIR from components.klipper.klipper import Klipper from components.klipper_firmware.menus.klipper_build_menu import ( KlipperBuildFirmwareMenu, + KlipperKConfigMenu, ) from components.klipper_firmware.menus.klipper_flash_menu import ( KlipperFlashMethodMenu, @@ -76,12 +77,15 @@ class AdvancedMenu(BaseMenu): rollback_repository(MOONRAKER_DIR, Moonraker) def build(self, **kwargs) -> None: + KlipperKConfigMenu().run() KlipperBuildFirmwareMenu(previous_menu=self.__class__).run() def flash(self, **kwargs) -> None: + KlipperKConfigMenu().run() KlipperFlashMethodMenu(previous_menu=self.__class__).run() def build_flash(self, **kwargs) -> None: + KlipperKConfigMenu().run() KlipperBuildFirmwareMenu(previous_menu=KlipperFlashMethodMenu).run() KlipperFlashMethodMenu(previous_menu=self.__class__).run() From 033916216cac39a8f376d6c2399bc3ae47a9ad34 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 9 Feb 2025 17:04:46 +0100 Subject: [PATCH 6/7] refactor: skip build firmware dependency screen if all are met (#629) Signed-off-by: Dominik Willner --- .../menus/klipper_build_menu.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/kiauh/components/klipper_firmware/menus/klipper_build_menu.py b/kiauh/components/klipper_firmware/menus/klipper_build_menu.py index 60128d8..c6a0f6b 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_build_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_build_menu.py @@ -147,16 +147,22 @@ class KlipperBuildFirmwareMenu(BaseMenu): ) def set_options(self) -> None: + self.input_label_txt = "Press ENTER to install dependencies" + self.default_option = Option(method=self.install_missing_deps) + + def run(self): + # immediately start the build process if all dependencies are met if len(self.missing_deps) == 0: - self.input_label_txt = "Press ENTER to continue" - self.default_option = Option(method=self.start_build_process) + self.start_build_process() else: - self.input_label_txt = "Press ENTER to install dependencies" - self.default_option = Option(method=self.install_missing_deps) + super().run() def print_menu(self) -> None: + txt = Color.apply("Dependencies are missing!", Color.RED) menu = textwrap.dedent( - """ + f""" + ╟───────────────────────────────────────────────────────╢ + ║ {txt:^62} ║ ╟───────────────────────────────────────────────────────╢ ║ The following dependencies are required: ║ ║ ║ @@ -170,16 +176,8 @@ class KlipperBuildFirmwareMenu(BaseMenu): padding = 40 - len(d) + len(status) + (len(status_ok) - len(status)) d = Color.apply(f"● {d}", Color.CYAN) menu += f"║ {d}{status:>{padding}} ║\n" + menu += "║ ║\n" - - color = Color.GREEN if len(self.missing_deps) == 0 else Color.RED - txt = ( - "All dependencies are met!" - if len(self.missing_deps) == 0 - else "Dependencies are missing!" - ) - - menu += f"║ {Color.apply(txt, color):<62} ║\n" menu += "╟───────────────────────────────────────────────────────╢\n" print(menu, end="") From ed1bfcdeb4fdd8549fcdee1306693a09176fd47a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 9 Feb 2025 21:12:59 +0100 Subject: [PATCH 7/7] fix(moonraker): correctly install ubuntu 24.10 dependencies (#630) Signed-off-by: Dominik Willner --- kiauh/components/moonraker/moonraker_setup.py | 34 +++++++++++++++-- kiauh/components/moonraker/moonraker_utils.py | 37 ++++++++++++++++++- kiauh/utils/sys_utils.py | 7 ++++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/kiauh/components/moonraker/moonraker_setup.py b/kiauh/components/moonraker/moonraker_setup.py index d7b8a15..a13791b 100644 --- a/kiauh/components/moonraker/moonraker_setup.py +++ b/kiauh/components/moonraker/moonraker_setup.py @@ -8,7 +8,6 @@ # ======================================================================= # from __future__ import annotations -import json import subprocess from typing import List @@ -31,6 +30,7 @@ from components.moonraker.moonraker_dialogs import print_moonraker_overview from components.moonraker.moonraker_utils import ( backup_moonraker_dir, create_example_moonraker_conf, + parse_sysdeps_file, ) from components.webui_client.client_utils import ( enable_mainsail_remotemode, @@ -53,6 +53,8 @@ from utils.sys_utils import ( cmd_sysctl_manage, cmd_sysctl_service, create_python_venv, + get_distro_name, + get_distro_version, install_python_requirements, parse_packages_from_file, ) @@ -157,9 +159,35 @@ def install_moonraker_packages() -> None: moonraker_deps = [] if MOONRAKER_DEPS_JSON_FILE.exists(): - with open(MOONRAKER_DEPS_JSON_FILE, "r") as deps: - moonraker_deps = json.load(deps).get("debian", []) + Logger.print_status( + f"Parsing system dependencies from {MOONRAKER_DEPS_JSON_FILE.name} ..." + ) + parsed_sysdeps = parse_sysdeps_file(MOONRAKER_DEPS_JSON_FILE) + distro_name = get_distro_name().lower() + distro_version = get_distro_version() + + for dep in parsed_sysdeps.get(distro_name, []): + pkg = dep[0].strip() + comparator = dep[1].strip() + req_version = dep[2].strip() + + comparisons = { + "": lambda x, y: True, + "<": lambda x, y: x < y, + ">": lambda x, y: x > y, + "<=": lambda x, y: x <= y, + ">=": lambda x, y: x >= y, + "==": lambda x, y: x == y, + "!=": lambda x, y: x != y, + } + + if comparisons[comparator](float(distro_version), float(req_version or 0)): + moonraker_deps.append(pkg) + elif MOONRAKER_INSTALL_SCRIPT.exists(): + Logger.print_status( + f"Parsing system dependencies from {MOONRAKER_INSTALL_SCRIPT.name} ..." + ) moonraker_deps = parse_packages_from_file(MOONRAKER_INSTALL_SCRIPT) if not moonraker_deps: diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index 3241edb..f800c8a 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -6,9 +6,11 @@ # # # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # - +import json +import re import shutil -from typing import Dict, List, Optional +from pathlib import Path +from typing import Dict, List, Optional, Tuple from components.moonraker import ( MODULE_PATH, @@ -138,3 +140,34 @@ def backup_moonraker_db_dir() -> None: bm.backup_directory( name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR ) + + +# This function is from sync_dependencies.py script from the Moonraker project on GitHub: +# https://github.com/Arksine/moonraker/blob/master/scripts/sync_dependencies.py +# Thanks to Arksine for his work on this project! +def parse_sysdeps_file(sysdeps_file: Path) -> Dict[str, List[Tuple[str, str, str]]]: + """ + Parses the system dependencies file and returns a dictionary with the parsed dependencies. + :param sysdeps_file: The path to the system dependencies file. + :return: A dictionary with the parsed dependencies in the format {distro: [(package, comparator, version)]}. + """ + base_deps: Dict[str, List[str]] = json.loads(sysdeps_file.read_bytes()) + parsed_deps: Dict[str, List[Tuple[str, str, str]]] = {} + + for distro, pkgs in base_deps.items(): + parsed_deps[distro] = [] + for dep in pkgs: + parts = dep.split(";", maxsplit=1) + if len(parts) == 1: + parsed_deps[distro].append((dep.strip(), "", "")) + else: + pkg_name = parts[0].strip() + dep_parts = re.split(r"(==|!=|<=|>=|<|>)", parts[1].strip()) + comp_var = dep_parts[0].strip().lower() + if len(dep_parts) != 3 or comp_var != "distro_version": + continue + operator = dep_parts[1].strip() + req_version = dep_parts[2].strip() + parsed_deps[distro].append((pkg_name, operator, req_version)) + + return parsed_deps diff --git a/kiauh/utils/sys_utils.py b/kiauh/utils/sys_utils.py index d93a527..5df8cb9 100644 --- a/kiauh/utils/sys_utils.py +++ b/kiauh/utils/sys_utils.py @@ -539,3 +539,10 @@ def get_service_file_path(instance_type: type, suffix: str) -> Path: file_path: Path = SYSTEMD.joinpath(f"{name}.service") return file_path + +def get_distro_name() -> str: + return check_output(["lsb_release", "-is"]).decode().strip() + + +def get_distro_version() -> str: + return check_output(["lsb_release", "-rs"]).decode().strip()