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))