Compare commits

...

11 Commits

Author SHA1 Message Date
dw-0
ad0dbf63b8 refactor(Mainsail): enable remote mode if moonraker multi instance
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-27 00:11:11 +01:00
dw-0
9dedf38079 refactor(KIAUH): big refactor of instance handling
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-26 23:37:35 +01:00
dw-0
1b4c76d080 fix(KIAUH): more file path handling improvements
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 22:31:18 +01:00
dw-0
d20d82aeac fix(Mainsail): proper check if config exists
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 22:06:40 +01:00
dw-0
16a28ffda0 fix(Klipper/Moonraker): config files now always have a Path, are never None anymore
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 22:03:44 +01:00
dw-0
a9367cc064 fix(Klipper): remove obsolete method parameter
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 22:01:18 +01:00
dw-0
b165d88855 fix(Moonraker): missing return statement if all requirements met
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 20:57:22 +01:00
dw-0
6c59d58193 refactor(KIAUH): use red dash instead of "Unknown" if repo info not available
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 20:44:04 +01:00
dw-0
b4f5c3c1ac refactor(Mainsail): remove mainsail.zip after extracting content
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 20:16:38 +01:00
dw-0
b69ecbc9b5 fix(KIAUH): wrong logic in status detection
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 19:56:43 +01:00
dw-0
fc9fa39eee refactor(Mainsail): use same wording in MainsailRemoveMenu
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-12-25 19:43:30 +01:00
14 changed files with 254 additions and 245 deletions

View File

@@ -11,7 +11,7 @@
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from pathlib import Path 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 from kiauh.utils.constants import SYSTEMD, CURRENT_USER
@@ -25,7 +25,7 @@ class BaseInstance(ABC):
def __init__( def __init__(
self, self,
suffix: Optional[str], suffix: str,
instance_type: B = B, instance_type: B = B,
): ):
self._instance_type = instance_type self._instance_type = instance_type
@@ -52,7 +52,7 @@ class BaseInstance(ABC):
return self._suffix return self._suffix
@suffix.setter @suffix.setter
def suffix(self, value: Union[str, None]) -> None: def suffix(self, value: str) -> None:
self._suffix = value self._suffix = value
@property @property
@@ -144,7 +144,7 @@ class BaseInstance(ABC):
def get_service_file_name(self, extension: bool = False) -> str: def get_service_file_name(self, extension: bool = False) -> str:
name = f"{self.__class__.__name__.lower()}" name = f"{self.__class__.__name__.lower()}"
if self.suffix is not None: if self.suffix != "":
name += f"-{self.suffix}" name += f"-{self.suffix}"
return name if not extension else f"{name}.service" 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)) return SYSTEMD.joinpath(self.get_service_file_name(extension=True))
def get_data_dir_name_from_suffix(self) -> str: def get_data_dir_name_from_suffix(self) -> str:
if self._suffix is None: if self._suffix == "":
return "printer" return "printer"
elif self._suffix.isdigit(): elif self._suffix.isdigit():
return f"printer_{self._suffix}" return f"printer_{self._suffix}"

View File

@@ -207,10 +207,8 @@ class InstanceManager:
return instance_list return instance_list
def _get_instance_suffix(self, file_path: Path) -> Union[str, None]: def _get_instance_suffix(self, file_path: Path) -> str:
full_name = file_path.name.split(".")[0] return file_path.stem.split("-")[-1] if "-" in file_path.stem else ""
return full_name.split("-")[-1] if "-" in full_name else None
def _sort_instance_list(self, s: Union[int, str, None]): def _sort_instance_list(self, s: Union[int, str, None]):
if s is None: if s is None:

View File

@@ -0,0 +1,8 @@
from enum import unique, Enum
@unique
class NameScheme(Enum):
SINGLE = "SINGLE"
INDEX = "INDEX"
CUSTOM = "CUSTOM"

View File

@@ -9,10 +9,9 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List
from kiauh.core.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, MODULE_PATH from kiauh.modules.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH
@@ -26,11 +25,11 @@ class Klipper(BaseInstance):
def blacklist(cls) -> List[str]: def blacklist(cls) -> List[str]:
return ["None", "mcu"] return ["None", "mcu"]
def __init__(self, suffix: str = None): def __init__(self, suffix: str = ""):
super().__init__(instance_type=self, suffix=suffix) super().__init__(instance_type=self, suffix=suffix)
self.klipper_dir: Path = KLIPPER_DIR self.klipper_dir: Path = KLIPPER_DIR
self.env_dir: Path = KLIPPER_ENV_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._log = self.log_dir.joinpath("klippy.log")
self._serial = self.comms_dir.joinpath("klippy.serial") self._serial = self.comms_dir.joinpath("klippy.serial")
self._uds = self.comms_dir.joinpath("klippy.sock") self._uds = self.comms_dir.joinpath("klippy.sock")
@@ -153,9 +152,3 @@ class Klipper(BaseInstance):
env_file_content = env_file_content.replace("%LOG%", str(self.log)) env_file_content = env_file_content.replace("%LOG%", str(self.log))
env_file_content = env_file_content.replace("%UDS%", str(self.uds)) env_file_content = env_file_content.replace("%UDS%", str(self.uds))
return env_file_content 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

View File

@@ -10,12 +10,13 @@
# ======================================================================= # # ======================================================================= #
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List
from kiauh import KIAUH_CFG from kiauh import KIAUH_CFG
from kiauh.core.backup_manager.backup_manager import BackupManager from kiauh.core.backup_manager.backup_manager import BackupManager
from kiauh.core.config_manager.config_manager import ConfigManager from kiauh.core.config_manager.config_manager import ConfigManager
from kiauh.core.instance_manager.instance_manager import InstanceManager from kiauh.core.instance_manager.instance_manager import InstanceManager
from kiauh.core.instance_manager.name_scheme import NameScheme
from kiauh.modules.klipper import ( from kiauh.modules.klipper import (
EXIT_KLIPPER_SETUP, EXIT_KLIPPER_SETUP,
DEFAULT_KLIPPER_REPO_URL, 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 import Klipper
from kiauh.modules.klipper.klipper_dialogs import ( from kiauh.modules.klipper.klipper_dialogs import (
print_instance_overview,
print_select_instance_count_dialog,
print_update_warn_dialog, print_update_warn_dialog,
print_select_custom_name_dialog,
) )
from kiauh.modules.klipper.klipper_utils import ( 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, handle_disruptive_system_packages,
check_user_groups, check_user_groups,
handle_single_to_multi_conversion, handle_to_multi_instance_conversion,
create_example_printer_cfg, 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.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, get_number_input
from kiauh.utils.logger import Logger from kiauh.utils.logger import Logger
from kiauh.utils.system_utils import ( from kiauh.utils.system_utils import (
@@ -50,51 +52,82 @@ from kiauh.utils.system_utils import (
) )
# TODO: this method needs refactoring! (but it works for now)
def install_klipper() -> None: def install_klipper() -> None:
im = InstanceManager(Klipper) im = InstanceManager(Klipper)
kl_instances: List[Klipper] = im.instances
add_additional = handle_existing_instances(im.instances) # ask to add new instances, if there are existing ones
if len(im.instances) > 0 and not add_additional: if kl_instances and not add_to_existing():
Logger.print_status(EXIT_KLIPPER_SETUP) Logger.print_status(EXIT_KLIPPER_SETUP)
return return
print_select_instance_count_dialog() install_count = get_install_count()
question = f"Number of{' additional' if len(im.instances) > 0 else ''} Klipper instances to set up" # install_count = None -> user entered "b" to go back
install_count = get_number_input(question, 1, default=1, allow_go_back=True)
if install_count is None: if install_count is None:
Logger.print_status(EXIT_KLIPPER_SETUP) Logger.print_status(EXIT_KLIPPER_SETUP)
return return
instance_names = set_instance_suffix(im.instances, install_count) # create a dict of the size of the existing instances + install count
if instance_names is None: name_scheme = NameScheme.SINGLE
Logger.print_status(EXIT_KLIPPER_SETUP) single_to_multi = len(kl_instances) == 1 and kl_instances[0].suffix == ""
return 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?") create_example_cfg = get_confirm("Create example printer.cfg?")
if len(im.instances) < 1: if not kl_instances:
setup_klipper_prerequesites() setup_klipper_prerequesites()
convert_single_to_multi = ( count = 0
len(im.instances) == 1 and im.instances[0].suffix is None and install_count >= 1 for name in name_dict:
) if name_dict[name] in [n.suffix for n in kl_instances]:
continue
for name in instance_names:
if convert_single_to_multi:
current_instance = handle_single_to_multi_conversion(im, name)
convert_single_to_multi = False
else: else:
current_instance = Klipper(suffix=name) count += 1
im.current_instance = current_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.create_instance()
im.enable_instance() im.enable_instance()
if create_example_cfg: if create_example_cfg:
create_example_printer_cfg(current_instance) create_example_printer_cfg(new_instance)
im.start_instance() im.start_instance()
if count == install_count:
break
im.reload_daemon() im.reload_daemon()
# step 4: check/handle conflicting packages/services # step 4: check/handle conflicting packages/services
@@ -137,41 +170,6 @@ def install_klipper_packages(klipper_dir: Path) -> None:
install_system_packages(packages) 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: def update_klipper() -> None:
print_update_warn_dialog() print_update_warn_dialog()
if not get_confirm("Update Klipper now?"): if not get_confirm("Update Klipper now?"):

View File

@@ -20,16 +20,20 @@ from pathlib import Path
from typing import List, Union, Literal, Dict from typing import List, Union, Literal, Dict
from kiauh.core.config_manager.config_manager import ConfigManager 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.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 import MODULE_PATH, KLIPPER_DIR, KLIPPER_ENV_DIR
from kiauh.modules.klipper.klipper import Klipper from kiauh.modules.klipper.klipper import Klipper
from kiauh.modules.klipper.klipper_dialogs import ( from kiauh.modules.klipper.klipper_dialogs import (
print_missing_usergroup_dialog, 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.common import get_install_status_common, get_repo_name
from kiauh.utils.constants import CURRENT_USER 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.logger import Logger
from kiauh.utils.system_utils import mask_system_service 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( def add_to_existing() -> bool:
instance_count: int, install_count: int, instance_list: List[Klipper] = None kl_instances = InstanceManager(Klipper).instances
) -> List[str]: print_instance_overview(kl_instances)
instance_names = [] return get_confirm("Add new instances?", allow_go_back=True)
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 handle_convert_single_to_multi_instance_names( def get_install_count() -> Union[int, None]:
install_count: int, kl_instances = InstanceManager(Klipper).instances
) -> Union[List[str], None]: print_select_instance_count_dialog()
print_select_custom_name_dialog() question = f"Number of{' additional' if len(kl_instances) > 0 else ''} Klipper instances to set up"
choice = get_confirm("Assign custom names?", False, allow_go_back=True) return get_number_input(question, 1, default=1, 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( def assign_custom_name(key: int, name_dict: Dict[int, str]) -> None:
instance_count: int, install_count: int existing_names = []
) -> Union[List[str], None]: existing_names.extend(Klipper.blacklist())
print_select_custom_name_dialog() existing_names.extend(name_dict[n] for n in name_dict)
choice = get_confirm("Assign custom names?", False, allow_go_back=True) question = f"Enter name for instance {key + 1}"
if choice is True: name_dict[key] = get_string_input(question, exclude=existing_names)
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( def handle_to_multi_instance_conversion(new_name: str) -> None:
instance_count: int, install_count: int, instance_list: List[Klipper] Logger.print_status("Converting single instance to multi instances ...")
) -> List[str]: klipper_to_multi_conversion(new_name)
if has_custom_names(instance_list): moonraker_to_multi_conversion(new_name)
return assign_custom_names(instance_count, install_count, instance_list)
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: else:
start = get_highest_index(instance_list) + 1 Logger.print_info(f"'{new_data_dir}' already exist. Skipped ...")
_range = range(start, start + install_count)
return [str(i) for i in _range]
im.create_instance()
def handle_single_to_multi_conversion( im.enable_instance()
instance_manager: InstanceManager, name: str im.start_instance()
) -> 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(del_remnants=False)
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)
def check_user_groups(): def check_user_groups():
@@ -189,13 +166,13 @@ def handle_disruptive_system_packages() -> None:
Logger.print_warn(warn_msg) 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+$") pattern = re.compile("^\d+$")
for instance in instance_list: for instance in instance_list:
if not pattern.match(instance.suffix): 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: def get_highest_index(instance_list: List[Klipper]) -> int:
@@ -205,12 +182,12 @@ def get_highest_index(instance_list: List[Klipper]) -> int:
def create_example_printer_cfg(instance: Klipper) -> None: def create_example_printer_cfg(instance: Klipper) -> None:
Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'") Logger.print_status(f"Creating example printer.cfg in '{instance.cfg_dir}'")
if instance.cfg_file is not None: if instance.cfg_file.is_file():
Logger.print_info(f"printer.cfg in '{instance.cfg_dir}' already exists.") Logger.print_info(f"'{instance.cfg_file}' already exists.")
return return
source = MODULE_PATH.joinpath("res/printer.cfg") source = MODULE_PATH.joinpath("res/printer.cfg")
target = instance.cfg_dir.joinpath("printer.cfg") target = instance.cfg_file
try: try:
shutil.copy(source, target) shutil.copy(source, target)
except OSError as e: except OSError as e:

View File

@@ -52,7 +52,7 @@ def run_mainsail_removal(
def remove_mainsail_dir() -> None: def remove_mainsail_dir() -> None:
Logger.print_status("Removing Mainsail ...") 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 ...") Logger.print_info(f"'{MAINSAIL_DIR}' does not exist. Skipping ...")
return return
@@ -80,12 +80,13 @@ def remove_nginx_logs() -> None:
remove_file(Path("/var/log/nginx/mainsail-error.log"), True) remove_file(Path("/var/log/nginx/mainsail-error.log"), True)
im = InstanceManager(Klipper) im = InstanceManager(Klipper)
if not im.instances: instances: List[Klipper] = im.instances
if not instances:
return return
for instance in im.instances: for instance in instances:
remove_file(Path(instance.log_dir, "mainsail-access.log")) remove_file(instance.log_dir.joinpath("mainsail-access.log"))
remove_file(Path(instance.log_dir, "mainsail-error.log")) remove_file(instance.log_dir.joinpath("mainsail-error.log"))
except (OSError, subprocess.CalledProcessError) as e: except (OSError, subprocess.CalledProcessError) as e:
Logger.print_error(f"Unable to NGINX logs:\n{e}") Logger.print_error(f"Unable to NGINX logs:\n{e}")
@@ -94,16 +95,16 @@ def remove_nginx_logs() -> None:
def remove_updater_section(name: str) -> None: def remove_updater_section(name: str) -> None:
Logger.print_status("Remove updater section from moonraker.conf ...") Logger.print_status("Remove updater section from moonraker.conf ...")
im = InstanceManager(Moonraker) im = InstanceManager(Moonraker)
if not im.instances: instances: List[Moonraker] = im.instances
Logger.print_info("Moonraker not installed. Skipping ...") if not instances:
Logger.print_info("Moonraker not installed. Skipped ...")
return return
for instance in im.instances: for instance in instances:
log = f"Remove section '{name}' in '{instance.cfg_file}' ..." Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...")
Logger.print_status(log)
if not Path(instance.cfg_file).exists(): if not instance.cfg_file.is_file():
Logger.print_info("Section not present. Skipping ...") Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...")
continue continue
cm = ConfigManager(instance.cfg_file) cm = ConfigManager(instance.cfg_file)
@@ -117,7 +118,7 @@ def remove_updater_section(name: str) -> None:
def remove_mainsail_cfg_dir() -> None: def remove_mainsail_cfg_dir() -> None:
Logger.print_status("Removing mainsail-config ...") 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 ...") Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...")
return return
@@ -129,11 +130,12 @@ def remove_mainsail_cfg_dir() -> None:
def remove_mainsail_cfg_symlink() -> None: def remove_mainsail_cfg_symlink() -> None:
Logger.print_status("Removing mainsail.cfg symlinks ...") Logger.print_status("Removing mainsail.cfg symlinks ...")
im = InstanceManager(Moonraker) im = InstanceManager(Klipper)
for instance in im.instances: instances: List[Klipper] = im.instances
Logger.print_status(f"Removing symlink from '{instance.cfg_dir}' ...") for instance in instances:
Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...")
try: try:
remove_file(Path(instance.cfg_dir, "mainsail.cfg")) remove_file(instance.cfg_dir.joinpath("mainsail.cfg"))
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
Logger.print_error("Failed to remove symlink!") Logger.print_error("Failed to remove symlink!")
@@ -141,15 +143,16 @@ def remove_mainsail_cfg_symlink() -> None:
def remove_printer_cfg_include() -> None: def remove_printer_cfg_include() -> None:
Logger.print_status("Remove mainsail-config include from printer.cfg ...") Logger.print_status("Remove mainsail-config include from printer.cfg ...")
im = InstanceManager(Klipper) im = InstanceManager(Klipper)
if not im.instances: instances: List[Klipper] = im.instances
if not instances:
Logger.print_info("Klipper not installed. Skipping ...") Logger.print_info("Klipper not installed. Skipping ...")
return return
for instance in im.instances: for instance in instances:
log = f"Removing include from '{instance.cfg_file}' ..." log = f"Removing include from '{instance.cfg_file}' ..."
Logger.print_status(log) Logger.print_status(log)
if not Path(instance.cfg_file).exists(): if not instance.cfg_file.is_file():
continue continue
cm = ConfigManager(instance.cfg_file) cm = ConfigManager(instance.cfg_file)

View File

@@ -59,21 +59,22 @@ from kiauh.utils.system_utils import (
def run_mainsail_installation() -> None: def run_mainsail_installation() -> None:
im_mr = InstanceManager(Moonraker) mr_im = InstanceManager(Moonraker)
is_moonraker_installed = len(im_mr.instances) > 0 mr_instances: List[Moonraker] = mr_im.instances
enable_remotemode = False enable_remotemode = False
if not is_moonraker_installed: if not mr_instances:
print_moonraker_not_found_dialog() print_moonraker_not_found_dialog()
do_continue = get_confirm("Continue Mainsail installation?", allow_go_back=True) if not get_confirm("Continue Mainsail installation?", allow_go_back=True):
if do_continue:
enable_remotemode = True
else:
return 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 do_reinstall = False
if is_mainsail_installed: if Path.home().joinpath("mainsail").exists():
print_mainsail_already_installed_dialog() print_mainsail_already_installed_dialog()
do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True) do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True)
if do_reinstall: if do_reinstall:
@@ -81,10 +82,10 @@ def run_mainsail_installation() -> None:
else: else:
return return
im_kl = InstanceManager(Klipper) kl_im = InstanceManager(Klipper)
is_klipper_installed = len(im_kl.instances) > 0 kl_instances = kl_im.instances
install_ms_config = False install_ms_config = False
if is_klipper_installed: if kl_instances:
print_install_mainsail_config_dialog() print_install_mainsail_config_dialog()
question = "Download the recommended macros?" question = "Download the recommended macros?"
install_ms_config = get_confirm(question, allow_go_back=False) install_ms_config = get_confirm(question, allow_go_back=False)
@@ -108,31 +109,31 @@ def run_mainsail_installation() -> None:
restore_config_json() restore_config_json()
if enable_remotemode: if enable_remotemode:
enable_mainsail_remotemode() enable_mainsail_remotemode()
if is_moonraker_installed: if mr_instances:
patch_moonraker_conf( patch_moonraker_conf(
im_mr.instances, mr_instances,
"Mainsail", "Mainsail",
"update_manager mainsail", "update_manager mainsail",
"mainsail-updater.conf", "mainsail-updater.conf",
) )
im_mr.restart_all_instance() mr_im.restart_all_instance()
if install_ms_config and is_klipper_installed: if install_ms_config and kl_instances:
download_mainsail_cfg() download_mainsail_cfg()
create_mainsail_cfg_symlink(im_kl.instances) create_mainsail_cfg_symlink(kl_instances)
patch_moonraker_conf( patch_moonraker_conf(
im_mr.instances, mr_instances,
"mainsail-config", "mainsail-config",
"update_manager mainsail-config", "update_manager mainsail-config",
"mainsail-config-updater.conf", "mainsail-config-updater.conf",
) )
patch_printer_config(im_kl.instances) patch_printer_config(kl_instances)
im_kl.restart_all_instance() kl_im.restart_all_instance()
copy_upstream_nginx_cfg() copy_upstream_nginx_cfg()
copy_common_vars_nginx_cfg() copy_common_vars_nginx_cfg()
create_mainsail_nginx_cfg(mainsail_port) create_mainsail_nginx_cfg(mainsail_port)
if is_klipper_installed: if kl_instances:
symlink_webui_nginx_log(im_kl.instances) symlink_webui_nginx_log(kl_instances)
control_systemd_service("nginx", "restart") control_systemd_service("nginx", "restart")
except Exception as e: except Exception as e:
@@ -147,11 +148,13 @@ def run_mainsail_installation() -> None:
def download_mainsail() -> None: def download_mainsail() -> None:
try: try:
Logger.print_status("Downloading Mainsail ...") 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_ok("Download complete!")
Logger.print_status("Extracting mainsail.zip ...") Logger.print_status("Extracting mainsail.zip ...")
unzip(Path.home().joinpath("mainsail.zip"), MAINSAIL_DIR) unzip(Path.home().joinpath("mainsail.zip"), MAINSAIL_DIR)
target.unlink(missing_ok=True)
Logger.print_ok("OK!") Logger.print_ok("OK!")
except Exception: except Exception:

View File

@@ -57,17 +57,17 @@ class MainsailRemoveMenu(BaseMenu):
| Enter a number and hit enter to select / deselect | | Enter a number and hit enter to select / deselect |
| the specific option for removal. | | 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 | | 2) {o2} Remove mainsail-config |
| 3) {o3} Backup Mainsail config.json | | 3) {o3} Backup config.json |
| | | |
| printer.cfg & moonraker.conf | | printer.cfg & moonraker.conf |
| 4) {o4} Remove Moonraker updater section | | 4) {o4} Remove Moonraker update section |
| 5) {o5} Remove printer.cfg include | | 5) {o5} Remove printer.cfg include |
|-------------------------------------------------------| |-------------------------------------------------------|
| 6) Start removal | | 6) Continue |
""" """
)[1:] )[1:]
print(menu, end="") print(menu, end="")

View File

@@ -9,7 +9,6 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List, Union
@@ -27,11 +26,11 @@ class Moonraker(BaseInstance):
def blacklist(cls) -> List[str]: def blacklist(cls) -> List[str]:
return ["None", "mcu"] return ["None", "mcu"]
def __init__(self, suffix: str = None): def __init__(self, suffix: str = ""):
super().__init__(instance_type=self, suffix=suffix) super().__init__(instance_type=self, suffix=suffix)
self.moonraker_dir: Path = MOONRAKER_DIR self.moonraker_dir: Path = MOONRAKER_DIR
self.env_dir: Path = MOONRAKER_ENV_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.port = self._get_port()
self.backup_dir = self.data_dir.joinpath("backup") self.backup_dir = self.data_dir.joinpath("backup")
self.certs_dir = self.data_dir.joinpath("certs") self.certs_dir = self.data_dir.joinpath("certs")
@@ -138,14 +137,8 @@ class Moonraker(BaseInstance):
) )
return env_file_content 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]: def _get_port(self) -> Union[int, None]:
if self.cfg_file is None: if not self.cfg_file.is_file():
return None return None
cm = ConfigManager(cfg_file=self.cfg_file) cm = ConfigManager(cfg_file=self.cfg_file)

View File

@@ -54,21 +54,25 @@ from kiauh.utils.system_utils import (
def check_moonraker_install_requirements() -> bool: 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): if not (sys.version_info.major >= 3 and sys.version_info.minor >= 7):
Logger.print_error("Versioncheck failed!") Logger.print_error("Versioncheck failed!")
Logger.print_error("Python 3.7 or newer required to run Moonraker.") Logger.print_error("Python 3.7 or newer required to run Moonraker.")
return False return False
is_klipper_installed = kl_instance_count > 0 kl_instance_count = len(InstanceManager(Klipper).instances)
if not is_klipper_installed: if kl_instance_count < 1:
Logger.print_warn("Klipper not installed!") Logger.print_warn("Klipper not installed!")
Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") Logger.print_warn("Moonraker cannot be installed! Install Klipper first.")
return False 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: def install_moonraker() -> None:
if not check_moonraker_install_requirements(): if not check_moonraker_install_requirements():

View File

@@ -10,9 +10,10 @@
# ======================================================================= # # ======================================================================= #
import shutil 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.config_manager.config_manager import ConfigManager
from kiauh.core.instance_manager.instance_manager import InstanceManager
from kiauh.modules.moonraker import ( from kiauh.modules.moonraker import (
DEFAULT_MOONRAKER_PORT, DEFAULT_MOONRAKER_PORT,
MODULE_PATH, MODULE_PATH,
@@ -40,12 +41,12 @@ def create_example_moonraker_conf(
instance: Moonraker, ports_map: Dict[str, int] instance: Moonraker, ports_map: Dict[str, int]
) -> None: ) -> None:
Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'") Logger.print_status(f"Creating example moonraker.conf in '{instance.cfg_dir}'")
if instance.cfg_file is not None: if instance.cfg_file.is_file():
Logger.print_info(f"moonraker.conf in '{instance.cfg_dir}' already exists.") Logger.print_info(f"'{instance.cfg_file}' already exists.")
return return
source = MODULE_PATH.joinpath("res/moonraker.conf") source = MODULE_PATH.joinpath("res/moonraker.conf")
target = instance.cfg_dir.joinpath("moonraker.conf") target = instance.cfg_file
try: try:
shutil.copy(source, target) shutil.copy(source, target)
except OSError as e: except OSError as e:
@@ -83,3 +84,36 @@ def create_example_moonraker_conf(
cm.write_config() cm.write_config()
Logger.print_ok(f"Example moonraker.conf created in '{instance.cfg_dir}'") 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()

View File

@@ -68,7 +68,7 @@ def get_repo_name(repo_dir: Path) -> str:
result = "/".join(result.decode().strip().split("/")[-2:]) result = "/".join(result.decode().strip().split("/")[-2:])
return f"{COLOR_CYAN}{result}{RESET_FORMAT}" return f"{COLOR_CYAN}{result}{RESET_FORMAT}"
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return f"{COLOR_YELLOW}Unknown{RESET_FORMAT}" return f"{COLOR_RED}-{RESET_FORMAT}"
def get_install_status_common( def get_install_status_common(
@@ -111,10 +111,12 @@ def get_install_status_webui(
nginx_cfg_exist = check_file_exist(nginx_cfg) nginx_cfg_exist = check_file_exist(nginx_cfg)
upstreams_cfg_exist = check_file_exist(upstreams_cfg) upstreams_cfg_exist = check_file_exist(upstreams_cfg)
common_cfg_exist = check_file_exist(common_cfg) common_cfg_exist = check_file_exist(common_cfg)
status = [dir_exist, nginx_cfg_exist, upstreams_cfg_exist, common_cfg_exist] status = [dir_exist, nginx_cfg_exist]
if all(status): general_nginx_status = [upstreams_cfg_exist, common_cfg_exist]
if all(status) and all(general_nginx_status):
return f"{COLOR_GREEN}Installed!{RESET_FORMAT}" return f"{COLOR_GREEN}Installed!{RESET_FORMAT}"
elif not any(status): elif not all(status):
return f"{COLOR_RED}Not installed!{RESET_FORMAT}" return f"{COLOR_RED}Not installed!{RESET_FORMAT}"
else: else:
return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}" return f"{COLOR_YELLOW}Incomplete!{RESET_FORMAT}"

View File

@@ -246,24 +246,20 @@ def get_ipv4_addr() -> str:
s.close() s.close()
def download_file( def download_file(url: str, target: Path, show_progress=True) -> None:
url: str, target_folder: Path, target_name: str, show_progress=True
) -> None:
""" """
Helper method for downloading files from a provided URL | Helper method for downloading files from a provided URL |
:param url: the url to the file :param url: the url to the file
:param target_folder: the target folder to download the file into :param target: the target path incl filename
:param target_name: the name of the downloaded file
:param show_progress: show download progress or not :param show_progress: show download progress or not
:return: None :return: None
""" """
target_path = target_folder.joinpath(target_name)
try: try:
if show_progress: if show_progress:
urllib.request.urlretrieve(url, target_path, download_progress) urllib.request.urlretrieve(url, target, download_progress)
sys.stdout.write("\n") sys.stdout.write("\n")
else: else:
urllib.request.urlretrieve(url, target_path) urllib.request.urlretrieve(url, target)
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
Logger.print_error(f"Download failed! HTTP error occured: {e}") Logger.print_error(f"Download failed! HTTP error occured: {e}")
raise raise