mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-31 19:53:36 +05:00
Compare commits
23 Commits
348f6a2794
...
b640aa37ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b640aa37ab | ||
|
|
f00d41b1bf | ||
|
|
f578247b74 | ||
|
|
a7c67721b6 | ||
|
|
32742943a0 | ||
|
|
871bedb76b | ||
|
|
fee2dd0bda | ||
|
|
e5bcab5d85 | ||
|
|
31ea6c2e5a | ||
|
|
1384f7328a | ||
|
|
6bf55b5f69 | ||
|
|
398705b176 | ||
|
|
ed2e318d0e | ||
|
|
75ac8a22d5 | ||
|
|
005e2d3339 | ||
|
|
bdb2c85e9b | ||
|
|
7e251eb31e | ||
|
|
64ea337e7e | ||
|
|
a929c6983d | ||
|
|
bce92001a6 | ||
|
|
7993b98ee1 | ||
|
|
62296e112e | ||
|
|
a374ac8fac |
@@ -10,7 +10,21 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from core.backup_manager import BACKUP_ROOT_DIR
|
from core.backup_manager import BACKUP_ROOT_DIR
|
||||||
|
from utils.constants import SYSTEMD
|
||||||
|
|
||||||
CROWSNEST_DIR = Path.home().joinpath("crowsnest")
|
# repo
|
||||||
CROWSNEST_REPO = "https://github.com/mainsail-crew/crowsnest.git"
|
CROWSNEST_REPO = "https://github.com/mainsail-crew/crowsnest.git"
|
||||||
|
|
||||||
|
# names
|
||||||
|
CROWSNEST_SERVICE_NAME = "crowsnest.service"
|
||||||
|
|
||||||
|
# directories
|
||||||
|
CROWSNEST_DIR = Path.home().joinpath("crowsnest")
|
||||||
CROWSNEST_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("crowsnest-backups")
|
CROWSNEST_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("crowsnest-backups")
|
||||||
|
|
||||||
|
# files
|
||||||
|
CROWSNEST_MULTI_CONFIG = CROWSNEST_DIR.joinpath("tools/.config")
|
||||||
|
CROWSNEST_INSTALL_SCRIPT = CROWSNEST_DIR.joinpath("tools/install.sh")
|
||||||
|
CROWSNEST_BIN_FILE = Path("/usr/local/bin/crowsnest")
|
||||||
|
CROWSNEST_LOGROTATE_FILE = Path("/etc/logrotate.d/crowsnest")
|
||||||
|
CROWSNEST_SERVICE_FILE = SYSTEMD.joinpath(CROWSNEST_SERVICE_NAME)
|
||||||
|
|||||||
@@ -14,7 +14,17 @@ from pathlib import Path
|
|||||||
from subprocess import CalledProcessError, run
|
from subprocess import CalledProcessError, run
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from components.crowsnest import CROWSNEST_BACKUP_DIR, CROWSNEST_DIR, CROWSNEST_REPO
|
from components.crowsnest import (
|
||||||
|
CROWSNEST_BACKUP_DIR,
|
||||||
|
CROWSNEST_BIN_FILE,
|
||||||
|
CROWSNEST_DIR,
|
||||||
|
CROWSNEST_INSTALL_SCRIPT,
|
||||||
|
CROWSNEST_LOGROTATE_FILE,
|
||||||
|
CROWSNEST_MULTI_CONFIG,
|
||||||
|
CROWSNEST_REPO,
|
||||||
|
CROWSNEST_SERVICE_FILE,
|
||||||
|
CROWSNEST_SERVICE_NAME,
|
||||||
|
)
|
||||||
from components.klipper.klipper import Klipper
|
from components.klipper.klipper import Klipper
|
||||||
from core.backup_manager.backup_manager import BackupManager
|
from core.backup_manager.backup_manager import BackupManager
|
||||||
from core.instance_manager.instance_manager import InstanceManager
|
from core.instance_manager.instance_manager import InstanceManager
|
||||||
@@ -75,7 +85,6 @@ def install_crowsnest() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def print_multi_instance_warning(instances: List[Klipper]) -> None:
|
def print_multi_instance_warning(instances: List[Klipper]) -> None:
|
||||||
_instances = [f"● {instance.data_dir_name}" for instance in instances]
|
|
||||||
Logger.print_dialog(
|
Logger.print_dialog(
|
||||||
DialogType.WARNING,
|
DialogType.WARNING,
|
||||||
[
|
[
|
||||||
@@ -86,13 +95,12 @@ def print_multi_instance_warning(instances: List[Klipper]) -> None:
|
|||||||
"this instance to set up your 'crowsnest.conf' and steering it's service.",
|
"this instance to set up your 'crowsnest.conf' and steering it's service.",
|
||||||
"\n\n",
|
"\n\n",
|
||||||
"The following instances were found:",
|
"The following instances were found:",
|
||||||
*_instances,
|
*[f"● {instance.data_dir_name}" for instance in instances],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def configure_multi_instance() -> None:
|
def configure_multi_instance() -> None:
|
||||||
config = Path(CROWSNEST_DIR).joinpath("tools/.config")
|
|
||||||
try:
|
try:
|
||||||
run(
|
run(
|
||||||
"make config",
|
"make config",
|
||||||
@@ -102,17 +110,17 @@ def configure_multi_instance() -> None:
|
|||||||
)
|
)
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
Logger.print_error(f"Something went wrong! Please try again...\n{e}")
|
Logger.print_error(f"Something went wrong! Please try again...\n{e}")
|
||||||
if config.exists():
|
if CROWSNEST_MULTI_CONFIG.exists():
|
||||||
Path.unlink(config)
|
Path.unlink(CROWSNEST_MULTI_CONFIG)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not config.exists():
|
if not CROWSNEST_MULTI_CONFIG.exists():
|
||||||
Logger.print_error("Generating .config failed, installation aborted")
|
Logger.print_error("Generating .config failed, installation aborted")
|
||||||
|
|
||||||
|
|
||||||
def update_crowsnest() -> None:
|
def update_crowsnest() -> None:
|
||||||
try:
|
try:
|
||||||
cmd_sysctl_service("crowsnest", "stop")
|
cmd_sysctl_service(CROWSNEST_SERVICE_NAME, "stop")
|
||||||
|
|
||||||
if not CROWSNEST_DIR.exists():
|
if not CROWSNEST_DIR.exists():
|
||||||
git_clone_wrapper(CROWSNEST_REPO, CROWSNEST_DIR, "master")
|
git_clone_wrapper(CROWSNEST_REPO, CROWSNEST_DIR, "master")
|
||||||
@@ -123,18 +131,17 @@ def update_crowsnest() -> None:
|
|||||||
if settings.kiauh.backup_before_update:
|
if settings.kiauh.backup_before_update:
|
||||||
bm = BackupManager()
|
bm = BackupManager()
|
||||||
bm.backup_directory(
|
bm.backup_directory(
|
||||||
"crowsnest",
|
CROWSNEST_DIR.name,
|
||||||
source=CROWSNEST_DIR,
|
source=CROWSNEST_DIR,
|
||||||
target=CROWSNEST_BACKUP_DIR,
|
target=CROWSNEST_BACKUP_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
git_pull_wrapper(CROWSNEST_REPO, CROWSNEST_DIR)
|
git_pull_wrapper(CROWSNEST_REPO, CROWSNEST_DIR)
|
||||||
|
|
||||||
script = CROWSNEST_DIR.joinpath("tools/install.sh")
|
deps = parse_packages_from_file(CROWSNEST_INSTALL_SCRIPT)
|
||||||
deps = parse_packages_from_file(script)
|
|
||||||
check_install_dependencies(deps)
|
check_install_dependencies(deps)
|
||||||
|
|
||||||
cmd_sysctl_service("crowsnest", "restart")
|
cmd_sysctl_service(CROWSNEST_SERVICE_NAME, "restart")
|
||||||
|
|
||||||
Logger.print_ok("Crowsnest updated successfully.", end="\n\n")
|
Logger.print_ok("Crowsnest updated successfully.", end="\n\n")
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
@@ -144,9 +151,9 @@ def update_crowsnest() -> None:
|
|||||||
|
|
||||||
def get_crowsnest_status() -> ComponentStatus:
|
def get_crowsnest_status() -> ComponentStatus:
|
||||||
files = [
|
files = [
|
||||||
Path("/usr/local/bin/crowsnest"),
|
CROWSNEST_BIN_FILE,
|
||||||
Path("/etc/logrotate.d/crowsnest"),
|
CROWSNEST_LOGROTATE_FILE,
|
||||||
Path("/etc/systemd/system/crowsnest.service"),
|
CROWSNEST_SERVICE_FILE,
|
||||||
]
|
]
|
||||||
return get_install_status(CROWSNEST_DIR, files=files)
|
return get_install_status(CROWSNEST_DIR, files=files)
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import CalledProcessError, run
|
from subprocess import CalledProcessError, run
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from components.klipper import (
|
from components.klipper import (
|
||||||
KLIPPER_CFG_NAME,
|
KLIPPER_CFG_NAME,
|
||||||
@@ -27,35 +26,24 @@ from utils.logger import Logger
|
|||||||
|
|
||||||
|
|
||||||
# noinspection PyMethodMayBeStatic
|
# noinspection PyMethodMayBeStatic
|
||||||
|
@dataclass
|
||||||
class Klipper(BaseInstance):
|
class Klipper(BaseInstance):
|
||||||
@classmethod
|
klipper_dir: Path = KLIPPER_DIR
|
||||||
def blacklist(cls) -> List[str]:
|
env_dir: Path = KLIPPER_ENV_DIR
|
||||||
return ["None", "mcu"]
|
cfg_file: Path = None
|
||||||
|
log: Path = None
|
||||||
|
serial: Path = None
|
||||||
|
uds: Path = None
|
||||||
|
|
||||||
def __init__(self, suffix: str = ""):
|
def __init__(self, suffix: str = "") -> None:
|
||||||
super().__init__(instance_type=self, suffix=suffix)
|
super().__init__(instance_type=self, suffix=suffix)
|
||||||
self.klipper_dir: Path = KLIPPER_DIR
|
|
||||||
self.env_dir: Path = KLIPPER_ENV_DIR
|
|
||||||
self._cfg_file = self.cfg_dir.joinpath(KLIPPER_CFG_NAME)
|
|
||||||
self._log = self.log_dir.joinpath(KLIPPER_LOG_NAME)
|
|
||||||
self._serial = self.comms_dir.joinpath(KLIPPER_SERIAL_NAME)
|
|
||||||
self._uds = self.comms_dir.joinpath(KLIPPER_UDS_NAME)
|
|
||||||
|
|
||||||
@property
|
def __post_init__(self) -> None:
|
||||||
def cfg_file(self) -> Path:
|
super().__post_init__()
|
||||||
return self._cfg_file
|
self.cfg_file = self.cfg_dir.joinpath(KLIPPER_CFG_NAME)
|
||||||
|
self.log = self.log_dir.joinpath(KLIPPER_LOG_NAME)
|
||||||
@property
|
self.serial = self.comms_dir.joinpath(KLIPPER_SERIAL_NAME)
|
||||||
def log(self) -> Path:
|
self.uds = self.comms_dir.joinpath(KLIPPER_UDS_NAME)
|
||||||
return self._log
|
|
||||||
|
|
||||||
@property
|
|
||||||
def serial(self) -> Path:
|
|
||||||
return self._serial
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uds(self) -> Path:
|
|
||||||
return self._uds
|
|
||||||
|
|
||||||
def create(self) -> None:
|
def create(self) -> None:
|
||||||
from utils.sys_utils import create_env_file, create_service_file
|
from utils.sys_utils import create_env_file, create_service_file
|
||||||
|
|||||||
@@ -90,9 +90,19 @@ def print_select_custom_name_dialog():
|
|||||||
dialog = textwrap.dedent(
|
dialog = textwrap.dedent(
|
||||||
f"""
|
f"""
|
||||||
╔═══════════════════════════════════════════════════════╗
|
╔═══════════════════════════════════════════════════════╗
|
||||||
║ You can now assign a custom name to each instance. ║
|
║ Do you want to assign a custom name to each instance? ║
|
||||||
|
║ ║
|
||||||
|
║ Assigning a custom name will create a Klipper service ║
|
||||||
|
║ and a printer directory with the chosen name. ║
|
||||||
|
║ ║
|
||||||
|
║ Example for custom name 'kiauh': ║
|
||||||
|
║ ● Klipper service: klipper-kiauh.service ║
|
||||||
|
║ ● Printer directory: printer_kiauh_data ║
|
||||||
|
║ ║
|
||||||
║ If skipped, each instance will get an index assigned ║
|
║ If skipped, each instance will get an index assigned ║
|
||||||
║ in ascending order, starting at index '1'. ║
|
║ in ascending order, starting at '1' in case of a new ║
|
||||||
|
║ installation. Otherwise, the index will be derived ║
|
||||||
|
║ from amount of already existing instances. ║
|
||||||
║ ║
|
║ ║
|
||||||
║ {line1:<63}║
|
║ {line1:<63}║
|
||||||
║ {line2:<63}║
|
║ {line2:<63}║
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def select_instances_to_remove(
|
|||||||
) -> Union[List[Klipper], None]:
|
) -> Union[List[Klipper], None]:
|
||||||
start_index = 1
|
start_index = 1
|
||||||
options = [str(i + start_index) for i in range(len(instances))]
|
options = [str(i + start_index) for i in range(len(instances))]
|
||||||
options.extend(["a", "A", "b", "B"])
|
options.extend(["a", "b"])
|
||||||
instance_map = {options[i]: instances[i] for i in range(len(instances))}
|
instance_map = {options[i]: instances[i] for i in range(len(instances))}
|
||||||
|
|
||||||
print_instance_overview(
|
print_instance_overview(
|
||||||
@@ -64,9 +64,9 @@ def select_instances_to_remove(
|
|||||||
selection = get_selection_input("Select Klipper instance to remove", options)
|
selection = get_selection_input("Select Klipper instance to remove", options)
|
||||||
|
|
||||||
instances_to_remove = []
|
instances_to_remove = []
|
||||||
if selection == "b".lower():
|
if selection == "b":
|
||||||
return None
|
return None
|
||||||
elif selection == "a".lower():
|
elif selection == "a":
|
||||||
instances_to_remove.extend(instances)
|
instances_to_remove.extend(instances)
|
||||||
else:
|
else:
|
||||||
instances_to_remove.append(instance_map[selection])
|
instances_to_remove.append(instance_map[selection])
|
||||||
@@ -78,6 +78,9 @@ def remove_instances(
|
|||||||
instance_manager: InstanceManager,
|
instance_manager: InstanceManager,
|
||||||
instance_list: List[Klipper],
|
instance_list: List[Klipper],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if not instance_list:
|
||||||
|
return
|
||||||
|
|
||||||
for instance in instance_list:
|
for instance in instance_list:
|
||||||
Logger.print_status(f"Removing instance {instance.get_service_file_name()} ...")
|
Logger.print_status(f"Removing instance {instance.get_service_file_name()} ...")
|
||||||
instance_manager.current_instance = instance
|
instance_manager.current_instance = instance
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
# #
|
# #
|
||||||
# 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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
from components.klipper import (
|
from components.klipper import (
|
||||||
EXIT_KLIPPER_SETUP,
|
EXIT_KLIPPER_SETUP,
|
||||||
@@ -17,18 +19,16 @@ from components.klipper import (
|
|||||||
KLIPPER_REQ_FILE,
|
KLIPPER_REQ_FILE,
|
||||||
)
|
)
|
||||||
from components.klipper.klipper import Klipper
|
from components.klipper.klipper import Klipper
|
||||||
|
from components.klipper.klipper_dialogs import (
|
||||||
|
print_select_custom_name_dialog,
|
||||||
|
)
|
||||||
from components.klipper.klipper_utils import (
|
from components.klipper.klipper_utils import (
|
||||||
add_to_existing,
|
assign_custom_name,
|
||||||
backup_klipper_dir,
|
backup_klipper_dir,
|
||||||
check_is_single_to_multi_conversion,
|
|
||||||
check_user_groups,
|
check_user_groups,
|
||||||
create_example_printer_cfg,
|
create_example_printer_cfg,
|
||||||
get_install_count,
|
get_install_count,
|
||||||
handle_disruptive_system_packages,
|
handle_disruptive_system_packages,
|
||||||
handle_instance_naming,
|
|
||||||
handle_to_multi_instance_conversion,
|
|
||||||
init_name_scheme,
|
|
||||||
update_name_scheme,
|
|
||||||
)
|
)
|
||||||
from components.moonraker.moonraker import Moonraker
|
from components.moonraker.moonraker import Moonraker
|
||||||
from components.webui_client.client_utils import (
|
from components.webui_client.client_utils import (
|
||||||
@@ -49,57 +49,65 @@ from utils.sys_utils import (
|
|||||||
|
|
||||||
|
|
||||||
def install_klipper() -> None:
|
def install_klipper() -> None:
|
||||||
kl_im = InstanceManager(Klipper)
|
Logger.print_status("Installing Klipper ...")
|
||||||
|
|
||||||
# ask to add new instances, if there are existing ones
|
klipper_list: List[Klipper] = InstanceManager(Klipper).instances
|
||||||
if kl_im.instances and not add_to_existing():
|
moonraker_list: List[Moonraker] = InstanceManager(Moonraker).instances
|
||||||
Logger.print_status(EXIT_KLIPPER_SETUP)
|
match_moonraker: bool = False
|
||||||
return
|
|
||||||
|
|
||||||
install_count = get_install_count()
|
# if there are more moonraker instances than klipper instances, ask the user to
|
||||||
if install_count is None:
|
# match the klipper instance count to the count of moonraker instances with the same suffix
|
||||||
Logger.print_status(EXIT_KLIPPER_SETUP)
|
if len(moonraker_list) > len(klipper_list):
|
||||||
return
|
is_confirmed = display_moonraker_info(moonraker_list)
|
||||||
|
if not is_confirmed:
|
||||||
|
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||||
|
return
|
||||||
|
match_moonraker = True
|
||||||
|
|
||||||
# create a dict of the size of the existing instances + install count
|
install_count, name_dict = get_install_count_and_name_dict(
|
||||||
name_dict = {c: "" for c in range(len(kl_im.instances) + install_count)}
|
klipper_list, moonraker_list
|
||||||
name_scheme = init_name_scheme(kl_im.instances, install_count)
|
|
||||||
mr_im = InstanceManager(Moonraker)
|
|
||||||
name_scheme = update_name_scheme(
|
|
||||||
name_scheme, name_dict, kl_im.instances, mr_im.instances
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handle_instance_naming(name_dict, name_scheme)
|
if install_count == 0:
|
||||||
|
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||||
|
return
|
||||||
|
|
||||||
|
is_multi_install = install_count > 1 or (len(name_dict) >= 1 and install_count >= 1)
|
||||||
|
if not name_dict and install_count == 1:
|
||||||
|
name_dict = {0: ""}
|
||||||
|
elif is_multi_install and not match_moonraker:
|
||||||
|
custom_names = use_custom_names_or_go_back()
|
||||||
|
if custom_names is None:
|
||||||
|
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||||
|
return
|
||||||
|
|
||||||
|
handle_instance_names(install_count, name_dict, custom_names)
|
||||||
|
|
||||||
create_example_cfg = get_confirm("Create example printer.cfg?")
|
create_example_cfg = get_confirm("Create example printer.cfg?")
|
||||||
|
# run the actual installation
|
||||||
try:
|
try:
|
||||||
if not kl_im.instances:
|
run_klipper_setup(klipper_list, name_dict, create_example_cfg)
|
||||||
check_install_dependencies(["git"])
|
|
||||||
setup_klipper_prerequesites()
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
for name in name_dict:
|
|
||||||
if name_dict[name] in [n.suffix for n in kl_im.instances]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if check_is_single_to_multi_conversion(kl_im.instances):
|
|
||||||
handle_to_multi_instance_conversion(name_dict[name])
|
|
||||||
continue
|
|
||||||
|
|
||||||
count += 1
|
|
||||||
create_klipper_instance(name_dict[name], create_example_cfg)
|
|
||||||
|
|
||||||
if count == install_count:
|
|
||||||
break
|
|
||||||
|
|
||||||
cmd_sysctl_manage("daemon-reload")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.print_error(e)
|
Logger.print_error(e)
|
||||||
Logger.print_error("Klipper installation failed!")
|
Logger.print_error("Klipper installation failed!")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def run_klipper_setup(
|
||||||
|
klipper_list: List[Klipper], name_dict: Dict[int, str], example_cfg: bool
|
||||||
|
) -> None:
|
||||||
|
if not klipper_list:
|
||||||
|
setup_klipper_prerequesites()
|
||||||
|
|
||||||
|
for i in name_dict:
|
||||||
|
# skip this iteration if there is already an instance with the name
|
||||||
|
if name_dict[i] in [n.suffix for n in klipper_list]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
create_klipper_instance(name_dict[i], example_cfg)
|
||||||
|
|
||||||
|
cmd_sysctl_manage("daemon-reload")
|
||||||
|
|
||||||
# step 4: check/handle conflicting packages/services
|
# step 4: check/handle conflicting packages/services
|
||||||
handle_disruptive_system_packages()
|
handle_disruptive_system_packages()
|
||||||
|
|
||||||
@@ -107,6 +115,35 @@ def install_klipper() -> None:
|
|||||||
check_user_groups()
|
check_user_groups()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_instance_names(
|
||||||
|
install_count: int, name_dict: Dict[int, str], custom_names: bool
|
||||||
|
) -> None:
|
||||||
|
for i in range(install_count): # 3
|
||||||
|
key = max(name_dict.keys()) + 1
|
||||||
|
if custom_names:
|
||||||
|
assign_custom_name(key, name_dict)
|
||||||
|
else:
|
||||||
|
name_dict[key] = str(len(name_dict) + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_install_count_and_name_dict(
|
||||||
|
klipper_list: List[Klipper], moonraker_list: List[Moonraker]
|
||||||
|
) -> Tuple[int, Dict[int, str]]:
|
||||||
|
if len(moonraker_list) > len(klipper_list):
|
||||||
|
install_count = len(moonraker_list)
|
||||||
|
name_dict = {i: moonraker.suffix for i, moonraker in enumerate(moonraker_list)}
|
||||||
|
|
||||||
|
else:
|
||||||
|
install_count = get_install_count()
|
||||||
|
name_dict = {i: klipper.suffix for i, klipper in enumerate(klipper_list)}
|
||||||
|
|
||||||
|
if install_count is None:
|
||||||
|
Logger.print_status(EXIT_KLIPPER_SETUP)
|
||||||
|
return 0, {}
|
||||||
|
|
||||||
|
return install_count, name_dict
|
||||||
|
|
||||||
|
|
||||||
def setup_klipper_prerequesites() -> None:
|
def setup_klipper_prerequesites() -> None:
|
||||||
settings = KiauhSettings()
|
settings = KiauhSettings()
|
||||||
repo = settings.klipper.repo_url
|
repo = settings.klipper.repo_url
|
||||||
@@ -127,7 +164,6 @@ def setup_klipper_prerequesites() -> None:
|
|||||||
def install_klipper_packages() -> None:
|
def install_klipper_packages() -> None:
|
||||||
script = KLIPPER_INSTALL_SCRIPT
|
script = KLIPPER_INSTALL_SCRIPT
|
||||||
packages = parse_packages_from_file(script)
|
packages = parse_packages_from_file(script)
|
||||||
packages.append("python3-venv") # todo: remove once switched to virtualenv
|
|
||||||
|
|
||||||
# Add dbus requirement for DietPi distro
|
# Add dbus requirement for DietPi distro
|
||||||
if Path("/boot/dietpi/.version").exists():
|
if Path("/boot/dietpi/.version").exists():
|
||||||
@@ -177,3 +213,29 @@ def create_klipper_instance(name: str, create_example_cfg: bool) -> None:
|
|||||||
clients = get_existing_clients()
|
clients = get_existing_clients()
|
||||||
create_example_printer_cfg(new_instance, clients)
|
create_example_printer_cfg(new_instance, clients)
|
||||||
kl_im.start_instance()
|
kl_im.start_instance()
|
||||||
|
|
||||||
|
|
||||||
|
def use_custom_names_or_go_back() -> bool | None:
|
||||||
|
print_select_custom_name_dialog()
|
||||||
|
return get_confirm(
|
||||||
|
"Assign custom names?",
|
||||||
|
False,
|
||||||
|
allow_go_back=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def display_moonraker_info(moonraker_list: List[Moonraker]) -> bool:
|
||||||
|
# todo: only show the klipper instances that are not already installed
|
||||||
|
Logger.print_dialog(
|
||||||
|
DialogType.INFO,
|
||||||
|
[
|
||||||
|
"Existing Moonraker instances detected:",
|
||||||
|
*[f"● {m.get_service_file_name()}" for m in moonraker_list],
|
||||||
|
"\n\n",
|
||||||
|
"The following Klipper instances will be installed:",
|
||||||
|
*[f"● klipper-{m.suffix}" for m in moonraker_list],
|
||||||
|
],
|
||||||
|
padding_top=0,
|
||||||
|
padding_bottom=0,
|
||||||
|
)
|
||||||
|
return get_confirm("Proceed with installation?")
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
# #
|
# #
|
||||||
# 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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import grp
|
import grp
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
from subprocess import CalledProcessError, run
|
from subprocess import CalledProcessError, run
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Dict, List
|
||||||
|
|
||||||
from components.klipper import (
|
from components.klipper import (
|
||||||
KLIPPER_BACKUP_DIR,
|
KLIPPER_BACKUP_DIR,
|
||||||
@@ -23,23 +23,17 @@ from components.klipper import (
|
|||||||
from components.klipper.klipper import Klipper
|
from components.klipper.klipper import Klipper
|
||||||
from components.klipper.klipper_dialogs import (
|
from components.klipper.klipper_dialogs import (
|
||||||
print_instance_overview,
|
print_instance_overview,
|
||||||
print_select_custom_name_dialog,
|
|
||||||
print_select_instance_count_dialog,
|
print_select_instance_count_dialog,
|
||||||
)
|
)
|
||||||
from components.moonraker.moonraker import Moonraker
|
|
||||||
from components.moonraker.moonraker_utils import moonraker_to_multi_conversion
|
|
||||||
from components.webui_client.base_data import BaseWebClient
|
from components.webui_client.base_data import BaseWebClient
|
||||||
from components.webui_client.client_config.client_config_setup import (
|
from components.webui_client.client_config.client_config_setup import (
|
||||||
create_client_config_symlink,
|
create_client_config_symlink,
|
||||||
)
|
)
|
||||||
from core.backup_manager.backup_manager import BackupManager
|
from core.backup_manager.backup_manager import BackupManager
|
||||||
from core.instance_manager.base_instance import BaseInstance
|
|
||||||
from core.instance_manager.instance_manager import InstanceManager
|
from core.instance_manager.instance_manager import InstanceManager
|
||||||
from core.instance_manager.name_scheme import NameScheme
|
|
||||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||||
SimpleConfigParser,
|
SimpleConfigParser,
|
||||||
)
|
)
|
||||||
from utils import PRINTER_CFG_BACKUP_DIR
|
|
||||||
from utils.common import get_install_status
|
from utils.common import get_install_status
|
||||||
from utils.constants import CURRENT_USER
|
from utils.constants import CURRENT_USER
|
||||||
from utils.input_utils import get_confirm, get_number_input, get_string_input
|
from utils.input_utils import get_confirm, get_number_input, get_string_input
|
||||||
@@ -52,75 +46,13 @@ def get_klipper_status() -> ComponentStatus:
|
|||||||
return get_install_status(KLIPPER_DIR, KLIPPER_ENV_DIR, Klipper)
|
return get_install_status(KLIPPER_DIR, KLIPPER_ENV_DIR, Klipper)
|
||||||
|
|
||||||
|
|
||||||
def check_is_multi_install(
|
|
||||||
existing_instances: List[Klipper], install_count: int
|
|
||||||
) -> bool:
|
|
||||||
return not existing_instances and install_count > 1
|
|
||||||
|
|
||||||
|
|
||||||
def check_is_single_to_multi_conversion(
|
|
||||||
existing_instances: List[Klipper],
|
|
||||||
) -> bool:
|
|
||||||
return len(existing_instances) == 1 and existing_instances[0].suffix == ""
|
|
||||||
|
|
||||||
|
|
||||||
def init_name_scheme(
|
|
||||||
existing_instances: List[Klipper], install_count: int
|
|
||||||
) -> NameScheme:
|
|
||||||
if check_is_multi_install(
|
|
||||||
existing_instances, install_count
|
|
||||||
) or check_is_single_to_multi_conversion(existing_instances):
|
|
||||||
print_select_custom_name_dialog()
|
|
||||||
if get_confirm("Assign custom names?", False, allow_go_back=True):
|
|
||||||
return NameScheme.CUSTOM
|
|
||||||
else:
|
|
||||||
return NameScheme.INDEX
|
|
||||||
else:
|
|
||||||
return NameScheme.SINGLE
|
|
||||||
|
|
||||||
|
|
||||||
def update_name_scheme(
|
|
||||||
name_scheme: NameScheme,
|
|
||||||
name_dict: Dict[int, str],
|
|
||||||
klipper_instances: List[Klipper],
|
|
||||||
moonraker_instances: List[Moonraker],
|
|
||||||
) -> NameScheme:
|
|
||||||
# if there are more moonraker instances installed
|
|
||||||
# than klipper, we load their names into the name_dict,
|
|
||||||
# as we will detect and enforce that naming scheme
|
|
||||||
if len(moonraker_instances) > len(klipper_instances):
|
|
||||||
update_name_dict(name_dict, moonraker_instances)
|
|
||||||
return detect_name_scheme(moonraker_instances)
|
|
||||||
elif len(klipper_instances) > 1:
|
|
||||||
update_name_dict(name_dict, klipper_instances)
|
|
||||||
return detect_name_scheme(klipper_instances)
|
|
||||||
else:
|
|
||||||
return name_scheme
|
|
||||||
|
|
||||||
|
|
||||||
def update_name_dict(name_dict: Dict[int, str], instances: List[BaseInstance]) -> None:
|
|
||||||
for k, v in enumerate(instances):
|
|
||||||
name_dict[k] = v.suffix
|
|
||||||
|
|
||||||
|
|
||||||
def handle_instance_naming(name_dict: Dict[int, str], name_scheme: NameScheme) -> None:
|
|
||||||
if name_scheme == NameScheme.SINGLE:
|
|
||||||
return
|
|
||||||
|
|
||||||
for k in name_dict:
|
|
||||||
if name_dict[k] == "" and name_scheme == NameScheme.INDEX:
|
|
||||||
name_dict[k] = str(k + 1)
|
|
||||||
elif name_dict[k] == "" and name_scheme == NameScheme.CUSTOM:
|
|
||||||
assign_custom_name(k, name_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_existing() -> bool:
|
def add_to_existing() -> bool:
|
||||||
kl_instances = InstanceManager(Klipper).instances
|
kl_instances = InstanceManager(Klipper).instances
|
||||||
print_instance_overview(kl_instances)
|
print_instance_overview(kl_instances)
|
||||||
return get_confirm("Add new instances?", allow_go_back=True)
|
return get_confirm("Add new instances?", allow_go_back=True)
|
||||||
|
|
||||||
|
|
||||||
def get_install_count() -> Union[int, None]:
|
def get_install_count() -> int | None:
|
||||||
"""
|
"""
|
||||||
Print a dialog for selecting the amount of Klipper instances
|
Print a dialog for selecting the amount of Klipper instances
|
||||||
to set up with an option to navigate back. Returns None if the
|
to set up with an option to navigate back. Returns None if the
|
||||||
@@ -141,62 +73,10 @@ def assign_custom_name(key: int, name_dict: Dict[int, str]) -> None:
|
|||||||
existing_names = []
|
existing_names = []
|
||||||
existing_names.extend(Klipper.blacklist())
|
existing_names.extend(Klipper.blacklist())
|
||||||
existing_names.extend(name_dict[n] for n in name_dict)
|
existing_names.extend(name_dict[n] for n in name_dict)
|
||||||
question = f"Enter name for instance {key + 1}"
|
pattern = r"^[a-zA-Z0-9]+$"
|
||||||
name_dict[key] = get_string_input(question, exclude=existing_names)
|
|
||||||
|
|
||||||
|
question = f"Enter name for instance {key}"
|
||||||
def handle_to_multi_instance_conversion(new_name: str) -> None:
|
name_dict[key] = get_string_input(question, exclude=existing_names, regex=pattern)
|
||||||
Logger.print_status("Converting single instance to multi instances ...")
|
|
||||||
klipper_to_multi_conversion(new_name)
|
|
||||||
moonraker_to_multi_conversion(new_name)
|
|
||||||
|
|
||||||
|
|
||||||
def klipper_to_multi_conversion(new_name: str) -> None:
|
|
||||||
Logger.print_status("Convert Klipper single to multi instance ...")
|
|
||||||
im = InstanceManager(Klipper)
|
|
||||||
im.current_instance = im.instances[0]
|
|
||||||
|
|
||||||
# temporarily store the data dir path
|
|
||||||
old_data_dir = im.instances[0].data_dir
|
|
||||||
old_data_dir_name = im.instances[0].data_dir_name
|
|
||||||
|
|
||||||
# backup the old data_dir
|
|
||||||
bm = BackupManager()
|
|
||||||
name = f"config-{old_data_dir_name}"
|
|
||||||
bm.backup_directory(
|
|
||||||
name,
|
|
||||||
source=im.current_instance.cfg_dir,
|
|
||||||
target=PRINTER_CFG_BACKUP_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
# remove the old single instance
|
|
||||||
im.stop_instance()
|
|
||||||
im.disable_instance()
|
|
||||||
im.delete_instance()
|
|
||||||
|
|
||||||
# create a new klipper instance with the new name
|
|
||||||
new_instance = Klipper(suffix=new_name)
|
|
||||||
im.current_instance = new_instance
|
|
||||||
|
|
||||||
if not new_instance.data_dir.is_dir():
|
|
||||||
# rename the old data dir and use it for the new instance
|
|
||||||
Logger.print_status(f"Rename '{old_data_dir}' to '{new_instance.data_dir}' ...")
|
|
||||||
old_data_dir.rename(new_instance.data_dir)
|
|
||||||
else:
|
|
||||||
Logger.print_info(f"Existing '{new_instance.data_dir}' found ...")
|
|
||||||
|
|
||||||
# patch the virtual_sdcard sections path
|
|
||||||
# value to match the new printer_data foldername
|
|
||||||
scp = SimpleConfigParser()
|
|
||||||
scp.read(new_instance.cfg_file)
|
|
||||||
if scp.has_section("virtual_sdcard"):
|
|
||||||
scp.set("virtual_sdcard", "path", str(new_instance.gcodes_dir))
|
|
||||||
scp.write(new_instance.cfg_file)
|
|
||||||
|
|
||||||
# finalize creating the new instance
|
|
||||||
im.create_instance()
|
|
||||||
im.enable_instance()
|
|
||||||
im.start_instance()
|
|
||||||
|
|
||||||
|
|
||||||
def check_user_groups():
|
def check_user_groups():
|
||||||
@@ -275,21 +155,8 @@ def handle_disruptive_system_packages() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def detect_name_scheme(instance_list: List[BaseInstance]) -> NameScheme:
|
|
||||||
pattern = re.compile("^\d+$")
|
|
||||||
for instance in instance_list:
|
|
||||||
if not pattern.match(instance.suffix):
|
|
||||||
return NameScheme.CUSTOM
|
|
||||||
|
|
||||||
return NameScheme.INDEX
|
|
||||||
|
|
||||||
|
|
||||||
def get_highest_index(instance_list: List[Klipper]) -> int:
|
|
||||||
return max([int(instance.suffix.split("-")[-1]) for instance in instance_list])
|
|
||||||
|
|
||||||
|
|
||||||
def create_example_printer_cfg(
|
def create_example_printer_cfg(
|
||||||
instance: Klipper, clients: Optional[List[BaseWebClient]] = None
|
instance: Klipper, clients: List[BaseWebClient] | None = None
|
||||||
) -> None:
|
) -> 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_file():
|
if instance.cfg_file.is_file():
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
# #
|
# #
|
||||||
# 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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Union
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class FlashMethod(Enum):
|
class FlashMethod(Enum):
|
||||||
@@ -30,9 +31,9 @@ class ConnectionType(Enum):
|
|||||||
|
|
||||||
class FlashOptions:
|
class FlashOptions:
|
||||||
_instance = None
|
_instance = None
|
||||||
_flash_method: Union[FlashMethod, None] = None
|
_flash_method: FlashMethod | None = None
|
||||||
_flash_command: Union[FlashCommand, None] = None
|
_flash_command: FlashCommand | None = None
|
||||||
_connection_type: Union[ConnectionType, None] = None
|
_connection_type: ConnectionType | None = None
|
||||||
_mcu_list: List[str] = field(default_factory=list)
|
_mcu_list: List[str] = field(default_factory=list)
|
||||||
_selected_mcu: str = ""
|
_selected_mcu: str = ""
|
||||||
_selected_board: str = ""
|
_selected_board: str = ""
|
||||||
@@ -48,27 +49,27 @@ class FlashOptions:
|
|||||||
cls._instance = None
|
cls._instance = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def flash_method(self) -> Union[FlashMethod, None]:
|
def flash_method(self) -> FlashMethod | None:
|
||||||
return self._flash_method
|
return self._flash_method
|
||||||
|
|
||||||
@flash_method.setter
|
@flash_method.setter
|
||||||
def flash_method(self, value: Union[FlashMethod, None]):
|
def flash_method(self, value: FlashMethod | None):
|
||||||
self._flash_method = value
|
self._flash_method = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def flash_command(self) -> Union[FlashCommand, None]:
|
def flash_command(self) -> FlashCommand | None:
|
||||||
return self._flash_command
|
return self._flash_command
|
||||||
|
|
||||||
@flash_command.setter
|
@flash_command.setter
|
||||||
def flash_command(self, value: Union[FlashCommand, None]):
|
def flash_command(self, value: FlashCommand | None):
|
||||||
self._flash_command = value
|
self._flash_command = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connection_type(self) -> Union[ConnectionType, None]:
|
def connection_type(self) -> ConnectionType | None:
|
||||||
return self._connection_type
|
return self._connection_type
|
||||||
|
|
||||||
@connection_type.setter
|
@connection_type.setter
|
||||||
def connection_type(self, value: Union[ConnectionType, None]):
|
def connection_type(self, value: ConnectionType | None):
|
||||||
self._connection_type = value
|
self._connection_type = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -9,8 +9,26 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from core.backup_manager import BACKUP_ROOT_DIR
|
from core.backup_manager import BACKUP_ROOT_DIR
|
||||||
|
from utils.constants import SYSTEMD
|
||||||
|
|
||||||
|
# repo
|
||||||
KLIPPERSCREEN_REPO = "https://github.com/KlipperScreen/KlipperScreen.git"
|
KLIPPERSCREEN_REPO = "https://github.com/KlipperScreen/KlipperScreen.git"
|
||||||
|
|
||||||
|
# names
|
||||||
|
KLIPPERSCREEN_SERVICE_NAME = "KlipperScreen.service"
|
||||||
|
KLIPPERSCREEN_UPDATER_SECTION_NAME = "update_manager KlipperScreen"
|
||||||
|
KLIPPERSCREEN_LOG_NAME = "KlipperScreen.log"
|
||||||
|
|
||||||
|
# directories
|
||||||
KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen")
|
KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen")
|
||||||
KLIPPERSCREEN_ENV = Path.home().joinpath(".KlipperScreen-env")
|
KLIPPERSCREEN_ENV_DIR = Path.home().joinpath(".KlipperScreen-env")
|
||||||
KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups")
|
KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups")
|
||||||
|
|
||||||
|
# files
|
||||||
|
KLIPPERSCREEN_REQ_FILE = KLIPPERSCREEN_DIR.joinpath(
|
||||||
|
"scripts/KlipperScreen-requirements.txt"
|
||||||
|
)
|
||||||
|
KLIPPERSCREEN_INSTALL_SCRIPT = KLIPPERSCREEN_DIR.joinpath(
|
||||||
|
"scripts/KlipperScreen-install.sh"
|
||||||
|
)
|
||||||
|
KLIPPERSCREEN_SERVICE_FILE = SYSTEMD.joinpath(KLIPPERSCREEN_SERVICE_NAME)
|
||||||
|
|||||||
@@ -15,8 +15,14 @@ from components.klipper.klipper import Klipper
|
|||||||
from components.klipperscreen import (
|
from components.klipperscreen import (
|
||||||
KLIPPERSCREEN_BACKUP_DIR,
|
KLIPPERSCREEN_BACKUP_DIR,
|
||||||
KLIPPERSCREEN_DIR,
|
KLIPPERSCREEN_DIR,
|
||||||
KLIPPERSCREEN_ENV,
|
KLIPPERSCREEN_ENV_DIR,
|
||||||
|
KLIPPERSCREEN_INSTALL_SCRIPT,
|
||||||
|
KLIPPERSCREEN_LOG_NAME,
|
||||||
KLIPPERSCREEN_REPO,
|
KLIPPERSCREEN_REPO,
|
||||||
|
KLIPPERSCREEN_REQ_FILE,
|
||||||
|
KLIPPERSCREEN_SERVICE_FILE,
|
||||||
|
KLIPPERSCREEN_SERVICE_NAME,
|
||||||
|
KLIPPERSCREEN_UPDATER_SECTION_NAME,
|
||||||
)
|
)
|
||||||
from components.moonraker.moonraker import Moonraker
|
from components.moonraker.moonraker import Moonraker
|
||||||
from core.backup_manager.backup_manager import BackupManager
|
from core.backup_manager.backup_manager import BackupManager
|
||||||
@@ -37,9 +43,9 @@ from utils.input_utils import get_confirm
|
|||||||
from utils.logger import DialogType, Logger
|
from utils.logger import DialogType, Logger
|
||||||
from utils.sys_utils import (
|
from utils.sys_utils import (
|
||||||
check_python_version,
|
check_python_version,
|
||||||
cmd_sysctl_manage,
|
|
||||||
cmd_sysctl_service,
|
cmd_sysctl_service,
|
||||||
install_python_requirements,
|
install_python_requirements,
|
||||||
|
remove_service_file,
|
||||||
)
|
)
|
||||||
from utils.types import ComponentStatus
|
from utils.types import ComponentStatus
|
||||||
|
|
||||||
@@ -78,8 +84,7 @@ def install_klipperscreen() -> None:
|
|||||||
git_clone_wrapper(KLIPPERSCREEN_REPO, KLIPPERSCREEN_DIR)
|
git_clone_wrapper(KLIPPERSCREEN_REPO, KLIPPERSCREEN_DIR)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
script = f"{KLIPPERSCREEN_DIR}/scripts/KlipperScreen-install.sh"
|
run(KLIPPERSCREEN_INSTALL_SCRIPT.as_posix(), shell=True, check=True)
|
||||||
run(script, shell=True, check=True)
|
|
||||||
if mr_instances:
|
if mr_instances:
|
||||||
patch_klipperscreen_update_manager(mr_instances)
|
patch_klipperscreen_update_manager(mr_instances)
|
||||||
mr_im.restart_all_instance()
|
mr_im.restart_all_instance()
|
||||||
@@ -95,34 +100,30 @@ def install_klipperscreen() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def patch_klipperscreen_update_manager(instances: List[Moonraker]) -> None:
|
def patch_klipperscreen_update_manager(instances: List[Moonraker]) -> None:
|
||||||
env_py = f"{KLIPPERSCREEN_ENV}/bin/python"
|
|
||||||
add_config_section(
|
add_config_section(
|
||||||
section="update_manager KlipperScreen",
|
section=KLIPPERSCREEN_UPDATER_SECTION_NAME,
|
||||||
instances=instances,
|
instances=instances,
|
||||||
options=[
|
options=[
|
||||||
("type", "git_repo"),
|
("type", "git_repo"),
|
||||||
("path", str(KLIPPERSCREEN_DIR)),
|
("path", KLIPPERSCREEN_DIR.as_posix()),
|
||||||
("orgin", KLIPPERSCREEN_REPO),
|
("orgin", KLIPPERSCREEN_REPO),
|
||||||
("env", env_py),
|
("manages_servcies", "KlipperScreen"),
|
||||||
("requirements", "scripts/KlipperScreen-requirements.txt"),
|
("env", f"{KLIPPERSCREEN_ENV_DIR}/bin/python"),
|
||||||
("install_script", "scripts/KlipperScreen-install.sh"),
|
("requirements", KLIPPERSCREEN_REQ_FILE.as_posix()),
|
||||||
|
("install_script", KLIPPERSCREEN_INSTALL_SCRIPT.as_posix()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_klipperscreen() -> None:
|
def update_klipperscreen() -> None:
|
||||||
|
if not KLIPPERSCREEN_DIR.exists():
|
||||||
|
Logger.print_info("KlipperScreen does not seem to be installed! Skipping ...")
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd_sysctl_service("KlipperScreen", "stop")
|
|
||||||
|
|
||||||
if not KLIPPERSCREEN_DIR.exists():
|
|
||||||
Logger.print_info(
|
|
||||||
"KlipperScreen does not seem to be installed! Skipping ..."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
Logger.print_status("Updating KlipperScreen ...")
|
Logger.print_status("Updating KlipperScreen ...")
|
||||||
|
|
||||||
cmd_sysctl_service("KlipperScreen", "stop")
|
cmd_sysctl_service(KLIPPERSCREEN_SERVICE_NAME, "stop")
|
||||||
|
|
||||||
settings = KiauhSettings()
|
settings = KiauhSettings()
|
||||||
if settings.kiauh.backup_before_update:
|
if settings.kiauh.backup_before_update:
|
||||||
@@ -130,12 +131,9 @@ def update_klipperscreen() -> None:
|
|||||||
|
|
||||||
git_pull_wrapper(KLIPPERSCREEN_REPO, KLIPPERSCREEN_DIR)
|
git_pull_wrapper(KLIPPERSCREEN_REPO, KLIPPERSCREEN_DIR)
|
||||||
|
|
||||||
requirements = KLIPPERSCREEN_DIR.joinpath(
|
install_python_requirements(KLIPPERSCREEN_ENV_DIR, KLIPPERSCREEN_REQ_FILE)
|
||||||
"/scripts/KlipperScreen-requirements.txt"
|
|
||||||
)
|
|
||||||
install_python_requirements(KLIPPERSCREEN_ENV, requirements)
|
|
||||||
|
|
||||||
cmd_sysctl_service("KlipperScreen", "start")
|
cmd_sysctl_service(KLIPPERSCREEN_SERVICE_NAME, "start")
|
||||||
|
|
||||||
Logger.print_ok("KlipperScreen updated successfully.", end="\n\n")
|
Logger.print_ok("KlipperScreen updated successfully.", end="\n\n")
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
@@ -146,8 +144,8 @@ def update_klipperscreen() -> None:
|
|||||||
def get_klipperscreen_status() -> ComponentStatus:
|
def get_klipperscreen_status() -> ComponentStatus:
|
||||||
return get_install_status(
|
return get_install_status(
|
||||||
KLIPPERSCREEN_DIR,
|
KLIPPERSCREEN_DIR,
|
||||||
KLIPPERSCREEN_ENV,
|
KLIPPERSCREEN_ENV_DIR,
|
||||||
files=[SYSTEMD.joinpath("KlipperScreen.service")],
|
files=[SYSTEMD.joinpath(KLIPPERSCREEN_SERVICE_NAME)],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -161,24 +159,20 @@ def remove_klipperscreen() -> None:
|
|||||||
else:
|
else:
|
||||||
Logger.print_warn("KlipperScreen directory not found!")
|
Logger.print_warn("KlipperScreen directory not found!")
|
||||||
|
|
||||||
if KLIPPERSCREEN_ENV.exists():
|
if KLIPPERSCREEN_ENV_DIR.exists():
|
||||||
Logger.print_status("Removing KlipperScreen environment ...")
|
Logger.print_status("Removing KlipperScreen environment ...")
|
||||||
shutil.rmtree(KLIPPERSCREEN_ENV)
|
shutil.rmtree(KLIPPERSCREEN_ENV_DIR)
|
||||||
Logger.print_ok("KlipperScreen environment successfully removed!")
|
Logger.print_ok("KlipperScreen environment successfully removed!")
|
||||||
else:
|
else:
|
||||||
Logger.print_warn("KlipperScreen environment not found!")
|
Logger.print_warn("KlipperScreen environment not found!")
|
||||||
|
|
||||||
service = SYSTEMD.joinpath("KlipperScreen.service")
|
if KLIPPERSCREEN_SERVICE_FILE.exists():
|
||||||
if service.exists():
|
remove_service_file(
|
||||||
Logger.print_status("Removing KlipperScreen service ...")
|
KLIPPERSCREEN_SERVICE_NAME,
|
||||||
cmd_sysctl_service(service, "stop")
|
KLIPPERSCREEN_SERVICE_FILE,
|
||||||
cmd_sysctl_service(service, "disable")
|
)
|
||||||
remove_with_sudo(service)
|
|
||||||
cmd_sysctl_manage("daemon-reload")
|
|
||||||
cmd_sysctl_manage("reset-failed")
|
|
||||||
Logger.print_ok("KlipperScreen service successfully removed!")
|
|
||||||
|
|
||||||
logfile = Path("/tmp/KlipperScreen.log")
|
logfile = Path(f"/tmp/{KLIPPERSCREEN_LOG_NAME}")
|
||||||
if logfile.exists():
|
if logfile.exists():
|
||||||
Logger.print_status("Removing KlipperScreen log file ...")
|
Logger.print_status("Removing KlipperScreen log file ...")
|
||||||
remove_with_sudo(logfile)
|
remove_with_sudo(logfile)
|
||||||
@@ -187,7 +181,7 @@ def remove_klipperscreen() -> None:
|
|||||||
kl_im = InstanceManager(Klipper)
|
kl_im = InstanceManager(Klipper)
|
||||||
kl_instances: List[Klipper] = kl_im.instances
|
kl_instances: List[Klipper] = kl_im.instances
|
||||||
for instance in kl_instances:
|
for instance in kl_instances:
|
||||||
logfile = instance.log_dir.joinpath("KlipperScreen.log")
|
logfile = instance.log_dir.joinpath(KLIPPERSCREEN_LOG_NAME)
|
||||||
if logfile.exists():
|
if logfile.exists():
|
||||||
Logger.print_status(f"Removing {logfile} ...")
|
Logger.print_status(f"Removing {logfile} ...")
|
||||||
Path(logfile).unlink()
|
Path(logfile).unlink()
|
||||||
@@ -209,12 +203,12 @@ def remove_klipperscreen() -> None:
|
|||||||
def backup_klipperscreen_dir() -> None:
|
def backup_klipperscreen_dir() -> None:
|
||||||
bm = BackupManager()
|
bm = BackupManager()
|
||||||
bm.backup_directory(
|
bm.backup_directory(
|
||||||
"KlipperScreen",
|
KLIPPERSCREEN_DIR.name,
|
||||||
source=KLIPPERSCREEN_DIR,
|
source=KLIPPERSCREEN_DIR,
|
||||||
target=KLIPPERSCREEN_BACKUP_DIR,
|
target=KLIPPERSCREEN_BACKUP_DIR,
|
||||||
)
|
)
|
||||||
bm.backup_directory(
|
bm.backup_directory(
|
||||||
"KlipperScreen-env",
|
KLIPPERSCREEN_ENV_DIR.name,
|
||||||
source=KLIPPERSCREEN_ENV,
|
source=KLIPPERSCREEN_ENV_DIR,
|
||||||
target=KLIPPERSCREEN_BACKUP_DIR,
|
target=KLIPPERSCREEN_BACKUP_DIR,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,17 +11,20 @@ from pathlib import Path
|
|||||||
from core.backup_manager import BACKUP_ROOT_DIR
|
from core.backup_manager import BACKUP_ROOT_DIR
|
||||||
from utils.constants import SYSTEMD
|
from utils.constants import SYSTEMD
|
||||||
|
|
||||||
# names
|
# repo
|
||||||
MOBILERAKER_REPO = "https://github.com/Clon1998/mobileraker_companion.git"
|
MOBILERAKER_REPO = "https://github.com/Clon1998/mobileraker_companion.git"
|
||||||
|
|
||||||
|
# names
|
||||||
|
MOBILERAKER_SERVICE_NAME = "mobileraker.service"
|
||||||
MOBILERAKER_UPDATER_SECTION_NAME = "update_manager mobileraker"
|
MOBILERAKER_UPDATER_SECTION_NAME = "update_manager mobileraker"
|
||||||
MOBILERAKER_LOG_NAME = "mobileraker.log"
|
MOBILERAKER_LOG_NAME = "mobileraker.log"
|
||||||
|
|
||||||
# directories
|
# directories
|
||||||
MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion")
|
MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion")
|
||||||
|
MOBILERAKER_ENV_DIR = Path.home().joinpath("mobileraker-env")
|
||||||
MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups")
|
MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups")
|
||||||
|
|
||||||
# files
|
# files
|
||||||
MOBILERAKER_ENV = Path.home().joinpath("mobileraker-env")
|
|
||||||
MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh")
|
MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh")
|
||||||
MOBILERAKER_REQ_FILE = MOBILERAKER_DIR.joinpath("scripts/mobileraker-requirements.txt")
|
MOBILERAKER_REQ_FILE = MOBILERAKER_DIR.joinpath("scripts/mobileraker-requirements.txt")
|
||||||
MOBILERAKER_SERVICE_FILE = SYSTEMD.joinpath("mobileraker.service")
|
MOBILERAKER_SERVICE_FILE = SYSTEMD.joinpath(MOBILERAKER_SERVICE_NAME)
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ from components.klipper.klipper import Klipper
|
|||||||
from components.mobileraker import (
|
from components.mobileraker import (
|
||||||
MOBILERAKER_BACKUP_DIR,
|
MOBILERAKER_BACKUP_DIR,
|
||||||
MOBILERAKER_DIR,
|
MOBILERAKER_DIR,
|
||||||
MOBILERAKER_ENV,
|
MOBILERAKER_ENV_DIR,
|
||||||
MOBILERAKER_INSTALL_SCRIPT,
|
MOBILERAKER_INSTALL_SCRIPT,
|
||||||
MOBILERAKER_LOG_NAME,
|
MOBILERAKER_LOG_NAME,
|
||||||
MOBILERAKER_REPO,
|
MOBILERAKER_REPO,
|
||||||
MOBILERAKER_REQ_FILE,
|
MOBILERAKER_REQ_FILE,
|
||||||
MOBILERAKER_SERVICE_FILE,
|
MOBILERAKER_SERVICE_FILE,
|
||||||
|
MOBILERAKER_SERVICE_NAME,
|
||||||
MOBILERAKER_UPDATER_SECTION_NAME,
|
MOBILERAKER_UPDATER_SECTION_NAME,
|
||||||
)
|
)
|
||||||
from components.moonraker.moonraker import Moonraker
|
from components.moonraker.moonraker import Moonraker
|
||||||
@@ -29,7 +30,6 @@ from core.instance_manager.instance_manager import InstanceManager
|
|||||||
from core.settings.kiauh_settings import KiauhSettings
|
from core.settings.kiauh_settings import KiauhSettings
|
||||||
from utils.common import check_install_dependencies, get_install_status
|
from utils.common import check_install_dependencies, get_install_status
|
||||||
from utils.config_utils import add_config_section, remove_config_section
|
from utils.config_utils import add_config_section, remove_config_section
|
||||||
from utils.fs_utils import remove_with_sudo
|
|
||||||
from utils.git_utils import (
|
from utils.git_utils import (
|
||||||
git_clone_wrapper,
|
git_clone_wrapper,
|
||||||
git_pull_wrapper,
|
git_pull_wrapper,
|
||||||
@@ -38,9 +38,9 @@ from utils.input_utils import get_confirm
|
|||||||
from utils.logger import DialogType, Logger
|
from utils.logger import DialogType, Logger
|
||||||
from utils.sys_utils import (
|
from utils.sys_utils import (
|
||||||
check_python_version,
|
check_python_version,
|
||||||
cmd_sysctl_manage,
|
|
||||||
cmd_sysctl_service,
|
cmd_sysctl_service,
|
||||||
install_python_requirements,
|
install_python_requirements,
|
||||||
|
remove_service_file,
|
||||||
)
|
)
|
||||||
from utils.types import ComponentStatus
|
from utils.types import ComponentStatus
|
||||||
|
|
||||||
@@ -70,8 +70,7 @@ def install_mobileraker() -> None:
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
package_list = ["git", "wget", "curl", "unzip", "dfu-util"]
|
check_install_dependencies()
|
||||||
check_install_dependencies(package_list)
|
|
||||||
|
|
||||||
git_clone_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR)
|
git_clone_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR)
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ def patch_mobileraker_update_manager(instances: List[Moonraker]) -> None:
|
|||||||
("origin", MOBILERAKER_REPO),
|
("origin", MOBILERAKER_REPO),
|
||||||
("primary_branch", "main"),
|
("primary_branch", "main"),
|
||||||
("managed_services", "mobileraker"),
|
("managed_services", "mobileraker"),
|
||||||
("env", f"{MOBILERAKER_ENV}/bin/python"),
|
("env", f"{MOBILERAKER_ENV_DIR}/bin/python"),
|
||||||
("requirements", MOBILERAKER_REQ_FILE.as_posix()),
|
("requirements", MOBILERAKER_REQ_FILE.as_posix()),
|
||||||
("install_script", MOBILERAKER_INSTALL_SCRIPT.as_posix()),
|
("install_script", MOBILERAKER_INSTALL_SCRIPT.as_posix()),
|
||||||
],
|
],
|
||||||
@@ -118,7 +117,7 @@ def update_mobileraker() -> None:
|
|||||||
|
|
||||||
Logger.print_status("Updating Mobileraker's companion ...")
|
Logger.print_status("Updating Mobileraker's companion ...")
|
||||||
|
|
||||||
cmd_sysctl_service("mobileraker", "stop")
|
cmd_sysctl_service(MOBILERAKER_SERVICE_NAME, "stop")
|
||||||
|
|
||||||
settings = KiauhSettings()
|
settings = KiauhSettings()
|
||||||
if settings.kiauh.backup_before_update:
|
if settings.kiauh.backup_before_update:
|
||||||
@@ -126,9 +125,9 @@ def update_mobileraker() -> None:
|
|||||||
|
|
||||||
git_pull_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR)
|
git_pull_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR)
|
||||||
|
|
||||||
install_python_requirements(MOBILERAKER_ENV, MOBILERAKER_REQ_FILE)
|
install_python_requirements(MOBILERAKER_ENV_DIR, MOBILERAKER_REQ_FILE)
|
||||||
|
|
||||||
cmd_sysctl_service("mobileraker", "start")
|
cmd_sysctl_service(MOBILERAKER_SERVICE_NAME, "start")
|
||||||
|
|
||||||
Logger.print_ok("Mobileraker's companion updated successfully.", end="\n\n")
|
Logger.print_ok("Mobileraker's companion updated successfully.", end="\n\n")
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
@@ -139,7 +138,7 @@ def update_mobileraker() -> None:
|
|||||||
def get_mobileraker_status() -> ComponentStatus:
|
def get_mobileraker_status() -> ComponentStatus:
|
||||||
return get_install_status(
|
return get_install_status(
|
||||||
MOBILERAKER_DIR,
|
MOBILERAKER_DIR,
|
||||||
MOBILERAKER_ENV,
|
MOBILERAKER_ENV_DIR,
|
||||||
files=[MOBILERAKER_SERVICE_FILE],
|
files=[MOBILERAKER_SERVICE_FILE],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,21 +153,18 @@ def remove_mobileraker() -> None:
|
|||||||
else:
|
else:
|
||||||
Logger.print_warn("Mobileraker's companion directory not found!")
|
Logger.print_warn("Mobileraker's companion directory not found!")
|
||||||
|
|
||||||
if MOBILERAKER_ENV.exists():
|
if MOBILERAKER_ENV_DIR.exists():
|
||||||
Logger.print_status("Removing Mobileraker's companion environment ...")
|
Logger.print_status("Removing Mobileraker's companion environment ...")
|
||||||
shutil.rmtree(MOBILERAKER_ENV)
|
shutil.rmtree(MOBILERAKER_ENV_DIR)
|
||||||
Logger.print_ok("Mobileraker's companion environment successfully removed!")
|
Logger.print_ok("Mobileraker's companion environment successfully removed!")
|
||||||
else:
|
else:
|
||||||
Logger.print_warn("Mobileraker's companion environment not found!")
|
Logger.print_warn("Mobileraker's companion environment not found!")
|
||||||
|
|
||||||
if MOBILERAKER_SERVICE_FILE.exists():
|
if MOBILERAKER_SERVICE_FILE.exists():
|
||||||
Logger.print_status("Removing mobileraker service ...")
|
remove_service_file(
|
||||||
cmd_sysctl_service(MOBILERAKER_SERVICE_FILE, "stop")
|
MOBILERAKER_SERVICE_NAME,
|
||||||
cmd_sysctl_service(MOBILERAKER_SERVICE_FILE, "disable")
|
MOBILERAKER_SERVICE_FILE,
|
||||||
remove_with_sudo(MOBILERAKER_SERVICE_FILE)
|
)
|
||||||
cmd_sysctl_manage("daemon-reload")
|
|
||||||
cmd_sysctl_manage("reset-failed")
|
|
||||||
Logger.print_ok("Mobileraker's companion service successfully removed!")
|
|
||||||
|
|
||||||
kl_im = InstanceManager(Klipper)
|
kl_im = InstanceManager(Klipper)
|
||||||
kl_instances: List[Klipper] = kl_im.instances
|
kl_instances: List[Klipper] = kl_im.instances
|
||||||
@@ -204,7 +200,7 @@ def backup_mobileraker_dir() -> None:
|
|||||||
target=MOBILERAKER_BACKUP_DIR,
|
target=MOBILERAKER_BACKUP_DIR,
|
||||||
)
|
)
|
||||||
bm.backup_directory(
|
bm.backup_directory(
|
||||||
MOBILERAKER_ENV.name,
|
MOBILERAKER_ENV_DIR.name,
|
||||||
source=MOBILERAKER_ENV,
|
source=MOBILERAKER_ENV_DIR,
|
||||||
target=MOBILERAKER_BACKUP_DIR,
|
target=MOBILERAKER_BACKUP_DIR,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,14 +27,17 @@ MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups")
|
|||||||
MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups")
|
MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups")
|
||||||
|
|
||||||
# files
|
# files
|
||||||
|
MOONRAKER_INSTALL_SCRIPT = MOONRAKER_DIR.joinpath("scripts/install-moonraker.sh")
|
||||||
MOONRAKER_REQ_FILE = MOONRAKER_DIR.joinpath("scripts/moonraker-requirements.txt")
|
MOONRAKER_REQ_FILE = MOONRAKER_DIR.joinpath("scripts/moonraker-requirements.txt")
|
||||||
|
MOONRAKER_SPEEDUPS_REQ_FILE = MOONRAKER_DIR.joinpath("scripts/moonraker-speedups.txt")
|
||||||
|
MOONRAKER_DEPS_JSON_FILE = MOONRAKER_DIR.joinpath("scripts/system-dependencies.json")
|
||||||
# introduced due to
|
# introduced due to
|
||||||
# https://github.com/Arksine/moonraker/issues/349
|
# https://github.com/Arksine/moonraker/issues/349
|
||||||
# https://github.com/Arksine/moonraker/pull/346
|
# https://github.com/Arksine/moonraker/pull/346
|
||||||
POLKIT_LEGACY_FILE = Path("/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla")
|
POLKIT_LEGACY_FILE = Path("/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla")
|
||||||
POLKIT_FILE = Path("/etc/polkit-1/rules.d/moonraker.rules")
|
POLKIT_FILE = Path("/etc/polkit-1/rules.d/moonraker.rules")
|
||||||
POLKIT_USR_FILE = Path("/usr/share/polkit-1/rules.d/moonraker.rules")
|
POLKIT_USR_FILE = Path("/usr/share/polkit-1/rules.d/moonraker.rules")
|
||||||
POLKIT_SCRIPT = Path.home().joinpath("moonraker/scripts/set-policykit-rules.sh")
|
POLKIT_SCRIPT = MOONRAKER_DIR.joinpath("scripts/set-policykit-rules.sh")
|
||||||
MOONRAKER_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_SERVICE_NAME}")
|
MOONRAKER_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_SERVICE_NAME}")
|
||||||
MOONRAKER_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_ENV_FILE_NAME}")
|
MOONRAKER_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_ENV_FILE_NAME}")
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import CalledProcessError, run
|
from subprocess import CalledProcessError, run
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from components.moonraker import (
|
from components.moonraker import (
|
||||||
MOONRAKER_CFG_NAME,
|
MOONRAKER_CFG_NAME,
|
||||||
@@ -29,38 +29,36 @@ from utils.logger import Logger
|
|||||||
|
|
||||||
|
|
||||||
# noinspection PyMethodMayBeStatic
|
# noinspection PyMethodMayBeStatic
|
||||||
|
@dataclass
|
||||||
class Moonraker(BaseInstance):
|
class Moonraker(BaseInstance):
|
||||||
@classmethod
|
moonraker_dir: Path = MOONRAKER_DIR
|
||||||
def blacklist(cls) -> List[str]:
|
env_dir: Path = MOONRAKER_ENV_DIR
|
||||||
return ["None", "mcu", "obico"]
|
cfg_file: Path = None
|
||||||
|
port: int = None
|
||||||
|
backup_dir: Path = None
|
||||||
|
certs_dir: Path = None
|
||||||
|
db_dir: Path = None
|
||||||
|
log: Path = None
|
||||||
|
|
||||||
def __init__(self, suffix: str = ""):
|
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.env_dir: Path = MOONRAKER_ENV_DIR
|
def __post_init__(self) -> None:
|
||||||
|
super().__post_init__()
|
||||||
self.cfg_file = self.cfg_dir.joinpath(MOONRAKER_CFG_NAME)
|
self.cfg_file = self.cfg_dir.joinpath(MOONRAKER_CFG_NAME)
|
||||||
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")
|
||||||
self._db_dir = self.data_dir.joinpath("database")
|
self.db_dir = self.data_dir.joinpath("database")
|
||||||
self._comms_dir = self.data_dir.joinpath("comms")
|
|
||||||
self.log = self.log_dir.joinpath(MOONRAKER_LOG_NAME)
|
self.log = self.log_dir.joinpath(MOONRAKER_LOG_NAME)
|
||||||
|
|
||||||
@property
|
|
||||||
def db_dir(self) -> Path:
|
|
||||||
return self._db_dir
|
|
||||||
|
|
||||||
@property
|
|
||||||
def comms_dir(self) -> Path:
|
|
||||||
return self._comms_dir
|
|
||||||
|
|
||||||
def create(self, create_example_cfg: bool = False) -> None:
|
def create(self, create_example_cfg: bool = False) -> None:
|
||||||
from utils.sys_utils import create_env_file, create_service_file
|
from utils.sys_utils import create_env_file, create_service_file
|
||||||
|
|
||||||
Logger.print_status("Creating new Moonraker Instance ...")
|
Logger.print_status("Creating new Moonraker Instance ...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.create_folders([self.backup_dir, self.certs_dir, self._db_dir])
|
self.create_folders([self.backup_dir, self.certs_dir, self.db_dir])
|
||||||
create_service_file(
|
create_service_file(
|
||||||
name=self.get_service_file_name(extension=True),
|
name=self.get_service_file_name(extension=True),
|
||||||
content=self._prep_service_file_content(),
|
content=self._prep_service_file_content(),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ def print_moonraker_overview(
|
|||||||
for i, k in enumerate(instance_map):
|
for i, k in enumerate(instance_map):
|
||||||
mr_name = instance_map.get(k)
|
mr_name = instance_map.get(k)
|
||||||
m = f"<-> {mr_name}" if mr_name != "" else ""
|
m = f"<-> {mr_name}" if mr_name != "" else ""
|
||||||
line = f"{COLOR_CYAN}{f'{i})' if show_index else '●'} {k} {m} {RESET_FORMAT}"
|
line = f"{COLOR_CYAN}{f'{i+1})' if show_index else '●'} {k} {m} {RESET_FORMAT}"
|
||||||
dialog += f"║ {line:<63}║\n"
|
dialog += f"║ {line:<63}║\n"
|
||||||
|
|
||||||
warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}"
|
warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def select_instances_to_remove(
|
|||||||
) -> Union[List[Moonraker], None]:
|
) -> Union[List[Moonraker], None]:
|
||||||
start_index = 1
|
start_index = 1
|
||||||
options = [str(i + start_index) for i in range(len(instances))]
|
options = [str(i + start_index) for i in range(len(instances))]
|
||||||
options.extend(["a", "A", "b", "B"])
|
options.extend(["a", "b"])
|
||||||
instance_map = {options[i]: instances[i] for i in range(len(instances))}
|
instance_map = {options[i]: instances[i] for i in range(len(instances))}
|
||||||
|
|
||||||
print_instance_overview(
|
print_instance_overview(
|
||||||
@@ -72,9 +72,9 @@ def select_instances_to_remove(
|
|||||||
selection = get_selection_input("Select Moonraker instance to remove", options)
|
selection = get_selection_input("Select Moonraker instance to remove", options)
|
||||||
|
|
||||||
instances_to_remove = []
|
instances_to_remove = []
|
||||||
if selection == "b".lower():
|
if selection == "b":
|
||||||
return None
|
return None
|
||||||
elif selection == "a".lower():
|
elif selection == "a":
|
||||||
instances_to_remove.extend(instances)
|
instances_to_remove.extend(instances)
|
||||||
else:
|
else:
|
||||||
instances_to_remove.append(instance_map[selection])
|
instances_to_remove.append(instance_map[selection])
|
||||||
|
|||||||
@@ -6,16 +6,21 @@
|
|||||||
# #
|
# #
|
||||||
# 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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from typing import List
|
||||||
|
|
||||||
from components.klipper.klipper import Klipper
|
from components.klipper.klipper import Klipper
|
||||||
from components.moonraker import (
|
from components.moonraker import (
|
||||||
EXIT_MOONRAKER_SETUP,
|
EXIT_MOONRAKER_SETUP,
|
||||||
|
MOONRAKER_DEPS_JSON_FILE,
|
||||||
MOONRAKER_DIR,
|
MOONRAKER_DIR,
|
||||||
MOONRAKER_ENV_DIR,
|
MOONRAKER_ENV_DIR,
|
||||||
|
MOONRAKER_INSTALL_SCRIPT,
|
||||||
MOONRAKER_REQ_FILE,
|
MOONRAKER_REQ_FILE,
|
||||||
|
MOONRAKER_SPEEDUPS_REQ_FILE,
|
||||||
POLKIT_FILE,
|
POLKIT_FILE,
|
||||||
POLKIT_LEGACY_FILE,
|
POLKIT_LEGACY_FILE,
|
||||||
POLKIT_SCRIPT,
|
POLKIT_SCRIPT,
|
||||||
@@ -55,47 +60,45 @@ def install_moonraker() -> None:
|
|||||||
if not check_moonraker_install_requirements():
|
if not check_moonraker_install_requirements():
|
||||||
return
|
return
|
||||||
|
|
||||||
kl_im = InstanceManager(Klipper)
|
klipper_list: List[Klipper] = InstanceManager(Klipper).instances
|
||||||
klipper_instances = kl_im.instances
|
|
||||||
mr_im = InstanceManager(Moonraker)
|
mr_im = InstanceManager(Moonraker)
|
||||||
moonraker_instances = mr_im.instances
|
moonraker_list: List[Moonraker] = mr_im.instances
|
||||||
|
|
||||||
selected_klipper_instance = 0
|
instance_names = []
|
||||||
if len(klipper_instances) > 1:
|
selected_option: str | Klipper
|
||||||
|
|
||||||
|
if len(klipper_list) == 0:
|
||||||
|
instance_names.append(klipper_list[0].suffix)
|
||||||
|
else:
|
||||||
print_moonraker_overview(
|
print_moonraker_overview(
|
||||||
klipper_instances,
|
klipper_list,
|
||||||
moonraker_instances,
|
moonraker_list,
|
||||||
show_index=True,
|
show_index=True,
|
||||||
show_select_all=True,
|
show_select_all=True,
|
||||||
)
|
)
|
||||||
options = [str(i) for i in range(len(klipper_instances))]
|
options = {str(i + 1): k for i, k in enumerate(klipper_list)}
|
||||||
options.extend(["a", "A", "b", "B"])
|
additional_options = {"a": None, "b": None}
|
||||||
|
options = {**options, **additional_options}
|
||||||
question = "Select Klipper instance to setup Moonraker for"
|
question = "Select Klipper instance to setup Moonraker for"
|
||||||
selected_klipper_instance = get_selection_input(question, options).lower()
|
selected_option = get_selection_input(question, options)
|
||||||
|
|
||||||
instance_names = []
|
if selected_option == "b":
|
||||||
if selected_klipper_instance == "b":
|
Logger.print_status(EXIT_MOONRAKER_SETUP)
|
||||||
Logger.print_status(EXIT_MOONRAKER_SETUP)
|
return
|
||||||
return
|
|
||||||
|
|
||||||
elif selected_klipper_instance == "a":
|
if selected_option == "a":
|
||||||
for instance in klipper_instances:
|
instance_names.extend([k.suffix for k in klipper_list])
|
||||||
instance_names.append(instance.suffix)
|
else:
|
||||||
|
instance_names.append(options.get(selected_option).suffix)
|
||||||
else:
|
|
||||||
index = int(selected_klipper_instance)
|
|
||||||
instance_names.append(klipper_instances[index].suffix)
|
|
||||||
|
|
||||||
create_example_cfg = get_confirm("Create example moonraker.conf?")
|
create_example_cfg = get_confirm("Create example moonraker.conf?")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
check_install_dependencies(["git"])
|
check_install_dependencies()
|
||||||
setup_moonraker_prerequesites()
|
setup_moonraker_prerequesites()
|
||||||
install_moonraker_polkit()
|
install_moonraker_polkit()
|
||||||
|
|
||||||
used_ports_map = {
|
used_ports_map = {m.suffix: m.port for m in moonraker_list}
|
||||||
instance.suffix: instance.port for instance in moonraker_instances
|
|
||||||
}
|
|
||||||
for name in instance_names:
|
for name in instance_names:
|
||||||
current_instance = Moonraker(suffix=name)
|
current_instance = Moonraker(suffix=name)
|
||||||
|
|
||||||
@@ -143,21 +146,20 @@ def setup_moonraker_prerequesites() -> None:
|
|||||||
git_clone_wrapper(repo, MOONRAKER_DIR, branch)
|
git_clone_wrapper(repo, MOONRAKER_DIR, branch)
|
||||||
|
|
||||||
# install moonraker dependencies and create python virtualenv
|
# install moonraker dependencies and create python virtualenv
|
||||||
install_moonraker_packages(MOONRAKER_DIR)
|
install_moonraker_packages()
|
||||||
create_python_venv(MOONRAKER_ENV_DIR)
|
create_python_venv(MOONRAKER_ENV_DIR)
|
||||||
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
|
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
|
||||||
|
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_SPEEDUPS_REQ_FILE)
|
||||||
|
|
||||||
|
|
||||||
def install_moonraker_packages(moonraker_dir: Path) -> None:
|
def install_moonraker_packages() -> None:
|
||||||
install_script = moonraker_dir.joinpath("scripts/install-moonraker.sh")
|
|
||||||
deps_json = MOONRAKER_DIR.joinpath("scripts/system-dependencies.json")
|
|
||||||
moonraker_deps = []
|
moonraker_deps = []
|
||||||
|
|
||||||
if deps_json.exists():
|
if MOONRAKER_DEPS_JSON_FILE.exists():
|
||||||
with open(deps_json, "r") as deps:
|
with open(MOONRAKER_DEPS_JSON_FILE, "r") as deps:
|
||||||
moonraker_deps = json.load(deps).get("debian", [])
|
moonraker_deps = json.load(deps).get("debian", [])
|
||||||
elif install_script.exists():
|
elif MOONRAKER_INSTALL_SCRIPT.exists():
|
||||||
moonraker_deps = parse_packages_from_file(install_script)
|
moonraker_deps = parse_packages_from_file(MOONRAKER_INSTALL_SCRIPT)
|
||||||
|
|
||||||
if not moonraker_deps:
|
if not moonraker_deps:
|
||||||
raise ValueError("Error reading Moonraker dependencies!")
|
raise ValueError("Error reading Moonraker dependencies!")
|
||||||
@@ -209,7 +211,7 @@ def update_moonraker() -> None:
|
|||||||
git_pull_wrapper(repo=settings.moonraker.repo_url, target_dir=MOONRAKER_DIR)
|
git_pull_wrapper(repo=settings.moonraker.repo_url, target_dir=MOONRAKER_DIR)
|
||||||
|
|
||||||
# install possible new system packages
|
# install possible new system packages
|
||||||
install_moonraker_packages(MOONRAKER_DIR)
|
install_moonraker_packages()
|
||||||
# install possible new python dependencies
|
# install possible new python dependencies
|
||||||
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
|
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ from components.moonraker import (
|
|||||||
)
|
)
|
||||||
from components.moonraker.moonraker import Moonraker
|
from components.moonraker.moonraker import Moonraker
|
||||||
from components.webui_client.base_data import BaseWebClient
|
from components.webui_client.base_data import BaseWebClient
|
||||||
from components.webui_client.client_utils import enable_mainsail_remotemode
|
|
||||||
from components.webui_client.mainsail_data import MainsailData
|
|
||||||
from core.backup_manager.backup_manager import BackupManager
|
from core.backup_manager.backup_manager import BackupManager
|
||||||
from core.instance_manager.instance_manager import InstanceManager
|
from core.instance_manager.instance_manager import InstanceManager
|
||||||
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
|
||||||
@@ -128,58 +126,6 @@ def create_example_moonraker_conf(
|
|||||||
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 moonraker instance with the new name
|
|
||||||
new_instance = Moonraker(suffix=new_name)
|
|
||||||
im.current_instance = new_instance
|
|
||||||
|
|
||||||
# patch the server sections klippy_uds_address value to match the new printer_data foldername
|
|
||||||
scp = SimpleConfigParser()
|
|
||||||
scp.read(new_instance.cfg_file)
|
|
||||||
if scp.has_section("server"):
|
|
||||||
scp.set(
|
|
||||||
"server",
|
|
||||||
"klippy_uds_address",
|
|
||||||
str(new_instance.comms_dir.joinpath("klippy.sock")),
|
|
||||||
)
|
|
||||||
scp.write(new_instance.cfg_file)
|
|
||||||
|
|
||||||
# create, enable and start the new moonraker instance
|
|
||||||
im.create_instance()
|
|
||||||
im.enable_instance()
|
|
||||||
im.start_instance()
|
|
||||||
|
|
||||||
# if mainsail is installed, we enable mainsails remote mode
|
|
||||||
if MainsailData().client_dir.exists() and len(im.instances) > 1:
|
|
||||||
enable_mainsail_remotemode()
|
|
||||||
|
|
||||||
|
|
||||||
def backup_moonraker_dir():
|
def backup_moonraker_dir():
|
||||||
bm = BackupManager()
|
bm = BackupManager()
|
||||||
bm.backup_directory("moonraker", source=MOONRAKER_DIR, target=MOONRAKER_BACKUP_DIR)
|
bm.backup_directory("moonraker", source=MOONRAKER_DIR, target=MOONRAKER_BACKUP_DIR)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ OE_REQ_FILE = OE_DIR.joinpath("requirements.txt")
|
|||||||
OE_DEPS_JSON_FILE = OE_DIR.joinpath("moonraker-system-dependencies.json")
|
OE_DEPS_JSON_FILE = OE_DIR.joinpath("moonraker-system-dependencies.json")
|
||||||
OE_INSTALL_SCRIPT = OE_DIR.joinpath("install.sh")
|
OE_INSTALL_SCRIPT = OE_DIR.joinpath("install.sh")
|
||||||
OE_UPDATE_SCRIPT = OE_DIR.joinpath("update.sh")
|
OE_UPDATE_SCRIPT = OE_DIR.joinpath("update.sh")
|
||||||
|
OE_INSTALLER_LOG_FILE = Path.home().joinpath("octoeverywhere-installer.log")
|
||||||
|
|
||||||
# filenames
|
# filenames
|
||||||
OE_CFG_NAME = "octoeverywhere.conf"
|
OE_CFG_NAME = "octoeverywhere.conf"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from pathlib import Path
|
|||||||
from subprocess import CalledProcessError, run
|
from subprocess import CalledProcessError, run
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from components.moonraker import MOONRAKER_CFG_NAME
|
||||||
from components.octoeverywhere import (
|
from components.octoeverywhere import (
|
||||||
OE_CFG_NAME,
|
OE_CFG_NAME,
|
||||||
OE_DIR,
|
OE_DIR,
|
||||||
@@ -55,7 +56,7 @@ class Octoeverywhere(BaseInstance):
|
|||||||
Logger.print_status("Creating OctoEverywhere for Klipper Instance ...")
|
Logger.print_status("Creating OctoEverywhere for Klipper Instance ...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd = f"{OE_INSTALL_SCRIPT} {self.cfg_dir}/moonraker.conf"
|
cmd = f"{OE_INSTALL_SCRIPT} {self.cfg_dir}/{MOONRAKER_CFG_NAME}"
|
||||||
run(cmd, check=True, shell=True)
|
run(cmd, check=True, shell=True)
|
||||||
|
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
@@ -65,7 +66,7 @@ class Octoeverywhere(BaseInstance):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def update():
|
def update():
|
||||||
try:
|
try:
|
||||||
run(str(OE_UPDATE_SCRIPT), check=True, shell=True, cwd=OE_DIR)
|
run(OE_UPDATE_SCRIPT.as_posix(), check=True, shell=True, cwd=OE_DIR)
|
||||||
|
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
Logger.print_error(f"Error updating OctoEverywhere for Klipper: {e}")
|
Logger.print_error(f"Error updating OctoEverywhere for Klipper: {e}")
|
||||||
@@ -82,6 +83,7 @@ class Octoeverywhere(BaseInstance):
|
|||||||
try:
|
try:
|
||||||
command = ["sudo", "rm", "-f", service_file_path]
|
command = ["sudo", "rm", "-f", service_file_path]
|
||||||
run(command, check=True)
|
run(command, check=True)
|
||||||
|
self.delete_logfiles(OE_LOG_NAME)
|
||||||
Logger.print_ok(f"Service file deleted: {service_file_path}")
|
Logger.print_ok(f"Service file deleted: {service_file_path}")
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
Logger.print_error(f"Error deleting service file: {e}")
|
Logger.print_error(f"Error deleting service file: {e}")
|
||||||
|
|||||||
@@ -7,7 +7,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 json
|
import json
|
||||||
from pathlib import Path
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from components.moonraker.moonraker import Moonraker
|
from components.moonraker.moonraker import Moonraker
|
||||||
@@ -16,7 +15,7 @@ from components.octoeverywhere import (
|
|||||||
OE_DIR,
|
OE_DIR,
|
||||||
OE_ENV_DIR,
|
OE_ENV_DIR,
|
||||||
OE_INSTALL_SCRIPT,
|
OE_INSTALL_SCRIPT,
|
||||||
OE_LOG_NAME,
|
OE_INSTALLER_LOG_FILE,
|
||||||
OE_REPO,
|
OE_REPO,
|
||||||
OE_REQ_FILE,
|
OE_REQ_FILE,
|
||||||
OE_SYS_CFG_NAME,
|
OE_SYS_CFG_NAME,
|
||||||
@@ -147,7 +146,7 @@ def remove_octoeverywhere() -> None:
|
|||||||
remove_oe_dir()
|
remove_oe_dir()
|
||||||
remove_oe_env()
|
remove_oe_env()
|
||||||
remove_config_section(f"include {OE_SYS_CFG_NAME}", mr_instances)
|
remove_config_section(f"include {OE_SYS_CFG_NAME}", mr_instances)
|
||||||
delete_oe_logs(ob_instances)
|
run_remove_routines(OE_INSTALLER_LOG_FILE)
|
||||||
Logger.print_dialog(
|
Logger.print_dialog(
|
||||||
DialogType.SUCCESS,
|
DialogType.SUCCESS,
|
||||||
["OctoEverywhere for Klipper successfully removed!"],
|
["OctoEverywhere for Klipper successfully removed!"],
|
||||||
@@ -209,23 +208,3 @@ def remove_oe_env() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
run_remove_routines(OE_ENV_DIR)
|
run_remove_routines(OE_ENV_DIR)
|
||||||
|
|
||||||
|
|
||||||
def delete_oe_logs(instances: List[Octoeverywhere]) -> None:
|
|
||||||
Logger.print_status("Removing OctoEverywhere logs ...")
|
|
||||||
|
|
||||||
all_logfiles = []
|
|
||||||
for instance in instances:
|
|
||||||
all_logfiles = list(instance.log_dir.glob(f"{OE_LOG_NAME}*"))
|
|
||||||
|
|
||||||
install_log = Path.home().joinpath("octoeverywhere-installer.log")
|
|
||||||
if install_log.exists():
|
|
||||||
all_logfiles.append(install_log)
|
|
||||||
|
|
||||||
if not all_logfiles:
|
|
||||||
Logger.print_info("No OctoEverywhere logs found. Skipped ...")
|
|
||||||
return
|
|
||||||
|
|
||||||
for log in all_logfiles:
|
|
||||||
Logger.print_status(f"Remove '{log}'")
|
|
||||||
run_remove_routines(log)
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ def install_client(client: BaseWebClient) -> None:
|
|||||||
)
|
)
|
||||||
valid_port = is_valid_port(port, ports_in_use)
|
valid_port = is_valid_port(port, ports_in_use)
|
||||||
|
|
||||||
check_install_dependencies(["nginx", "unzip"])
|
check_install_dependencies(["nginx"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
download_client(client)
|
download_client(client)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
@@ -17,106 +19,32 @@ from utils.constants import CURRENT_USER, SYSTEMD
|
|||||||
from utils.logger import Logger
|
from utils.logger import Logger
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class BaseInstance(ABC):
|
class BaseInstance(ABC):
|
||||||
|
instance_type: BaseInstance
|
||||||
|
suffix: str
|
||||||
|
user: str = field(default=CURRENT_USER, init=False)
|
||||||
|
data_dir: Path = None
|
||||||
|
data_dir_name: str = ""
|
||||||
|
is_legacy_instance: bool = False
|
||||||
|
cfg_dir: Path = None
|
||||||
|
log_dir: Path = None
|
||||||
|
comms_dir: Path = None
|
||||||
|
sysd_dir: Path = None
|
||||||
|
gcodes_dir: Path = None
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
self._set_data_dir()
|
||||||
|
self._set_is_legacy_instance()
|
||||||
|
self.cfg_dir = self.data_dir.joinpath("config")
|
||||||
|
self.log_dir = self.data_dir.joinpath("logs")
|
||||||
|
self.comms_dir = self.data_dir.joinpath("comms")
|
||||||
|
self.sysd_dir = self.data_dir.joinpath("systemd")
|
||||||
|
self.gcodes_dir = self.data_dir.joinpath("gcodes")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def blacklist(cls) -> List[str]:
|
def blacklist(cls) -> List[str]:
|
||||||
return []
|
return ["None", "mcu", "obico", "bambu", "companion"]
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
suffix: str,
|
|
||||||
instance_type: BaseInstance,
|
|
||||||
):
|
|
||||||
self._instance_type = instance_type
|
|
||||||
self._suffix = suffix
|
|
||||||
self._user = CURRENT_USER
|
|
||||||
self._data_dir_name = self.get_data_dir_name_from_suffix()
|
|
||||||
self._data_dir = Path.home().joinpath(f"{self._data_dir_name}_data")
|
|
||||||
self._cfg_dir = self.data_dir.joinpath("config")
|
|
||||||
self._log_dir = self.data_dir.joinpath("logs")
|
|
||||||
self._comms_dir = self.data_dir.joinpath("comms")
|
|
||||||
self._sysd_dir = self.data_dir.joinpath("systemd")
|
|
||||||
self._gcodes_dir = self.data_dir.joinpath("gcodes")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def instance_type(self) -> BaseInstance:
|
|
||||||
return self._instance_type
|
|
||||||
|
|
||||||
@instance_type.setter
|
|
||||||
def instance_type(self, value: BaseInstance) -> None:
|
|
||||||
self._instance_type = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def suffix(self) -> str:
|
|
||||||
return self._suffix
|
|
||||||
|
|
||||||
@suffix.setter
|
|
||||||
def suffix(self, value: str) -> None:
|
|
||||||
self._suffix = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user(self) -> str:
|
|
||||||
return self._user
|
|
||||||
|
|
||||||
@user.setter
|
|
||||||
def user(self, value: str) -> None:
|
|
||||||
self._user = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data_dir_name(self) -> str:
|
|
||||||
return self._data_dir_name
|
|
||||||
|
|
||||||
@data_dir_name.setter
|
|
||||||
def data_dir_name(self, value: str) -> None:
|
|
||||||
self._data_dir_name = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data_dir(self) -> Path:
|
|
||||||
return self._data_dir
|
|
||||||
|
|
||||||
@data_dir.setter
|
|
||||||
def data_dir(self, value: Path) -> None:
|
|
||||||
self._data_dir = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cfg_dir(self) -> Path:
|
|
||||||
return self._cfg_dir
|
|
||||||
|
|
||||||
@cfg_dir.setter
|
|
||||||
def cfg_dir(self, value: Path) -> None:
|
|
||||||
self._cfg_dir = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def log_dir(self) -> Path:
|
|
||||||
return self._log_dir
|
|
||||||
|
|
||||||
@log_dir.setter
|
|
||||||
def log_dir(self, value: Path) -> None:
|
|
||||||
self._log_dir = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def comms_dir(self) -> Path:
|
|
||||||
return self._comms_dir
|
|
||||||
|
|
||||||
@comms_dir.setter
|
|
||||||
def comms_dir(self, value: Path) -> None:
|
|
||||||
self._comms_dir = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sysd_dir(self) -> Path:
|
|
||||||
return self._sysd_dir
|
|
||||||
|
|
||||||
@sysd_dir.setter
|
|
||||||
def sysd_dir(self, value: Path) -> None:
|
|
||||||
self._sysd_dir = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gcodes_dir(self) -> Path:
|
|
||||||
return self._gcodes_dir
|
|
||||||
|
|
||||||
@gcodes_dir.setter
|
|
||||||
def gcodes_dir(self, value: Path) -> None:
|
|
||||||
self._gcodes_dir = value
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create(self) -> None:
|
def create(self) -> None:
|
||||||
@@ -133,6 +61,7 @@ class BaseInstance(ABC):
|
|||||||
self.log_dir,
|
self.log_dir,
|
||||||
self.comms_dir,
|
self.comms_dir,
|
||||||
self.sysd_dir,
|
self.sysd_dir,
|
||||||
|
self.gcodes_dir,
|
||||||
]
|
]
|
||||||
|
|
||||||
if add_dirs:
|
if add_dirs:
|
||||||
@@ -141,6 +70,7 @@ class BaseInstance(ABC):
|
|||||||
for _dir in dirs:
|
for _dir in dirs:
|
||||||
_dir.mkdir(exist_ok=True)
|
_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# todo: refactor into a set method and access the value by accessing the property
|
||||||
def get_service_file_name(self, extension: bool = False) -> str:
|
def get_service_file_name(self, extension: bool = False) -> str:
|
||||||
from utils.common import convert_camelcase_to_kebabcase
|
from utils.common import convert_camelcase_to_kebabcase
|
||||||
|
|
||||||
@@ -150,22 +80,42 @@ class BaseInstance(ABC):
|
|||||||
|
|
||||||
return name if not extension else f"{name}.service"
|
return name if not extension else f"{name}.service"
|
||||||
|
|
||||||
|
# todo: refactor into a set method and access the value by accessing the property
|
||||||
def get_service_file_path(self) -> Path:
|
def get_service_file_path(self) -> Path:
|
||||||
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:
|
|
||||||
if self._suffix == "":
|
|
||||||
return "printer"
|
|
||||||
elif self._suffix.isdigit():
|
|
||||||
return f"printer_{self._suffix}"
|
|
||||||
else:
|
|
||||||
return self._suffix
|
|
||||||
|
|
||||||
def delete_logfiles(self, log_name: str) -> None:
|
def delete_logfiles(self, log_name: str) -> None:
|
||||||
from utils.fs_utils import run_remove_routines
|
from utils.fs_utils import run_remove_routines
|
||||||
|
|
||||||
|
if not self.log_dir.exists():
|
||||||
|
return
|
||||||
|
|
||||||
files = self.log_dir.iterdir()
|
files = self.log_dir.iterdir()
|
||||||
logs = [f for f in files if f.name.startswith(log_name)]
|
logs = [f for f in files if f.name.startswith(log_name)]
|
||||||
for log in logs:
|
for log in logs:
|
||||||
Logger.print_status(f"Remove '{log}'")
|
Logger.print_status(f"Remove '{log}'")
|
||||||
run_remove_routines(log)
|
run_remove_routines(log)
|
||||||
|
|
||||||
|
def _set_data_dir(self) -> None:
|
||||||
|
if self.suffix == "":
|
||||||
|
self.data_dir = Path.home().joinpath("printer_data")
|
||||||
|
else:
|
||||||
|
self.data_dir = Path.home().joinpath(f"printer_{self.suffix}_data")
|
||||||
|
|
||||||
|
if self.get_service_file_path().exists():
|
||||||
|
with open(self.get_service_file_path(), "r") as service_file:
|
||||||
|
service_content = service_file.read()
|
||||||
|
pattern = re.compile("^EnvironmentFile=(.+)(/systemd/.+\.env)")
|
||||||
|
match = re.search(pattern, service_content)
|
||||||
|
if match:
|
||||||
|
self.data_dir = Path(match.group(1))
|
||||||
|
|
||||||
|
def _set_is_legacy_instance(self) -> None:
|
||||||
|
if (
|
||||||
|
self.suffix != ""
|
||||||
|
and not self.data_dir_name.startswith("printer_")
|
||||||
|
and not self.data_dir_name.endswith("_data")
|
||||||
|
):
|
||||||
|
self.is_legacy_instance = True
|
||||||
|
else:
|
||||||
|
self.is_legacy_instance = False
|
||||||
|
|||||||
@@ -185,8 +185,10 @@ class InstanceManager:
|
|||||||
suffix = file_path.stem[len(name) :]
|
suffix = file_path.stem[len(name) :]
|
||||||
return suffix[1:] if suffix else ""
|
return suffix[1:] if suffix else ""
|
||||||
|
|
||||||
def _sort_instance_list(self, s: Union[int, str, None]):
|
def _sort_instance_list(self, suffix: Union[int, str, None]):
|
||||||
if s is None:
|
if suffix is None:
|
||||||
return
|
return
|
||||||
|
elif suffix.isdigit():
|
||||||
return int(s) if s.isdigit() else s
|
return f"{int(suffix):04}"
|
||||||
|
else:
|
||||||
|
return suffix
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class MainMenu(BaseMenu):
|
|||||||
self.kl_status = self.kl_repo = self.mr_status = self.mr_repo = ""
|
self.kl_status = self.kl_repo = self.mr_status = self.mr_repo = ""
|
||||||
self.ms_status = self.fl_status = self.ks_status = self.mb_status = ""
|
self.ms_status = self.fl_status = self.ks_status = self.mb_status = ""
|
||||||
self.cn_status = self.cc_status = self.oe_status = ""
|
self.cn_status = self.cc_status = self.oe_status = ""
|
||||||
self.init_status()
|
self._init_status()
|
||||||
|
|
||||||
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
|
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
|
||||||
"""MainMenu does not have a previous menu"""
|
"""MainMenu does not have a previous menu"""
|
||||||
@@ -74,7 +74,7 @@ class MainMenu(BaseMenu):
|
|||||||
"s": Option(method=self.settings_menu, menu=True),
|
"s": Option(method=self.settings_menu, menu=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init_status(self) -> None:
|
def _init_status(self) -> None:
|
||||||
status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn", "oe"]
|
status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn", "oe"]
|
||||||
for var in status_vars:
|
for var in status_vars:
|
||||||
setattr(
|
setattr(
|
||||||
@@ -83,7 +83,7 @@ class MainMenu(BaseMenu):
|
|||||||
f"{COLOR_RED}Not installed{RESET_FORMAT}",
|
f"{COLOR_RED}Not installed{RESET_FORMAT}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def fetch_status(self) -> None:
|
def _fetch_status(self) -> None:
|
||||||
self._get_component_status("kl", get_klipper_status)
|
self._get_component_status("kl", get_klipper_status)
|
||||||
self._get_component_status("mr", get_moonraker_status)
|
self._get_component_status("mr", get_moonraker_status)
|
||||||
self._get_component_status("ms", get_client_status, MainsailData())
|
self._get_component_status("ms", get_client_status, MainsailData())
|
||||||
@@ -102,7 +102,7 @@ class MainMenu(BaseMenu):
|
|||||||
instance_count: int = status_data.instances
|
instance_count: int = status_data.instances
|
||||||
|
|
||||||
count_txt: str = ""
|
count_txt: str = ""
|
||||||
if instance_count > 0 and code == 1:
|
if instance_count > 0 and code == 2:
|
||||||
count_txt = f": {instance_count}"
|
count_txt = f": {instance_count}"
|
||||||
|
|
||||||
setattr(self, f"{name}_status", self._format_by_code(code, status, count_txt))
|
setattr(self, f"{name}_status", self._format_by_code(code, status, count_txt))
|
||||||
@@ -120,7 +120,7 @@ class MainMenu(BaseMenu):
|
|||||||
return f"{color}{status}{count}{RESET_FORMAT}"
|
return f"{color}{status}{count}{RESET_FORMAT}"
|
||||||
|
|
||||||
def print_menu(self):
|
def print_menu(self):
|
||||||
self.fetch_status()
|
self._fetch_status()
|
||||||
|
|
||||||
header = " [ Main Menu ] "
|
header = " [ Main Menu ] "
|
||||||
footer1 = f"{COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT}"
|
footer1 = f"{COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT}"
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ def get_printer_selection(
|
|||||||
instances: List[BaseInstance], is_install: bool
|
instances: List[BaseInstance], is_install: bool
|
||||||
) -> Union[List[BaseInstance], None]:
|
) -> Union[List[BaseInstance], None]:
|
||||||
options = [str(i) for i in range(len(instances))]
|
options = [str(i) for i in range(len(instances))]
|
||||||
options.extend(["a", "A", "b", "B"])
|
options.extend(["a", "b"])
|
||||||
|
|
||||||
if is_install:
|
if is_install:
|
||||||
q = "Select the printer to install the theme for"
|
q = "Select the printer to install the theme for"
|
||||||
@@ -166,9 +166,9 @@ def get_printer_selection(
|
|||||||
selection = get_selection_input(q, options)
|
selection = get_selection_input(q, options)
|
||||||
|
|
||||||
install_for = []
|
install_for = []
|
||||||
if selection == "b".lower():
|
if selection == "b":
|
||||||
return None
|
return None
|
||||||
elif selection == "a".lower():
|
elif selection == "a":
|
||||||
install_for.extend(instances)
|
install_for.extend(instances)
|
||||||
else:
|
else:
|
||||||
instance = instances[int(selection)]
|
instance = instances[int(selection)]
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ObicoExtension(BaseExtension):
|
|||||||
obico_instances: List[MoonrakerObico] = obico_im.instances
|
obico_instances: List[MoonrakerObico] = obico_im.instances
|
||||||
if obico_instances:
|
if obico_instances:
|
||||||
self._print_is_already_installed()
|
self._print_is_already_installed()
|
||||||
options = ["l", "L", "r", "R", "b", "B"]
|
options = ["l", "r", "b"]
|
||||||
action = get_selection_input("Perform action", option_list=options)
|
action = get_selection_input("Perform action", option_list=options)
|
||||||
if action.lower() == "b":
|
if action.lower() == "b":
|
||||||
Logger.print_info("Exiting Obico for Klipper installation ...")
|
Logger.print_info("Exiting Obico for Klipper installation ...")
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ MODULE_PATH = Path(__file__).resolve().parent
|
|||||||
INVALID_CHOICE = "Invalid choice. Please select a valid value."
|
INVALID_CHOICE = "Invalid choice. Please select a valid value."
|
||||||
PRINTER_CFG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-cfg-backups")
|
PRINTER_CFG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-cfg-backups")
|
||||||
|
|
||||||
|
GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"]
|
||||||
|
|
||||||
# ================== NGINX =====================#
|
# ================== NGINX =====================#
|
||||||
NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available")
|
NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available")
|
||||||
NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled")
|
NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled")
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
# #
|
# #
|
||||||
# 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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -14,7 +16,7 @@ from typing import Dict, List, Literal, Optional, Type
|
|||||||
from components.klipper.klipper import Klipper
|
from components.klipper.klipper import Klipper
|
||||||
from core.instance_manager.base_instance import BaseInstance
|
from core.instance_manager.base_instance import BaseInstance
|
||||||
from core.instance_manager.instance_manager import InstanceManager
|
from core.instance_manager.instance_manager import InstanceManager
|
||||||
from utils import PRINTER_CFG_BACKUP_DIR
|
from utils import GLOBAL_DEPS, PRINTER_CFG_BACKUP_DIR
|
||||||
from utils.constants import (
|
from utils.constants import (
|
||||||
COLOR_CYAN,
|
COLOR_CYAN,
|
||||||
RESET_FORMAT,
|
RESET_FORMAT,
|
||||||
@@ -45,19 +47,22 @@ def get_current_date() -> Dict[Literal["date", "time"], str]:
|
|||||||
return {"date": date, "time": time}
|
return {"date": date, "time": time}
|
||||||
|
|
||||||
|
|
||||||
def check_install_dependencies(deps: List[str]) -> None:
|
def check_install_dependencies(deps: List[str] | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Common helper method to check if dependencies are installed
|
Common helper method to check if dependencies are installed
|
||||||
and if not, install them automatically |
|
and if not, install them automatically |
|
||||||
:param deps: List of strings of package names to check if installed
|
:param deps: List of strings of package names to check if installed
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
requirements = check_package_install(deps)
|
if deps is None:
|
||||||
|
deps = []
|
||||||
|
|
||||||
|
requirements = check_package_install({*GLOBAL_DEPS, *deps})
|
||||||
if requirements:
|
if requirements:
|
||||||
Logger.print_status("Installing dependencies ...")
|
Logger.print_status("Installing dependencies ...")
|
||||||
Logger.print_info("The following packages need installation:")
|
Logger.print_info("The following packages need installation:")
|
||||||
for _ in requirements:
|
for r in requirements:
|
||||||
print(f"{COLOR_CYAN}● {_}{RESET_FORMAT}")
|
print(f"{COLOR_CYAN}● {r}{RESET_FORMAT}")
|
||||||
update_system_package_lists(silent=False)
|
update_system_package_lists(silent=False)
|
||||||
install_system_packages(requirements)
|
install_system_packages(requirements)
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
# #
|
# #
|
||||||
# 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 #
|
||||||
# ======================================================================= #
|
# ======================================================================= #
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import List, Union
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
from utils import INVALID_CHOICE
|
from utils import INVALID_CHOICE
|
||||||
from utils.constants import COLOR_CYAN, RESET_FORMAT
|
from utils.constants import COLOR_CYAN, RESET_FORMAT
|
||||||
from utils.logger import Logger
|
from utils.logger import Logger
|
||||||
|
|
||||||
|
|
||||||
def get_confirm(
|
def get_confirm(question: str, default_choice=True, allow_go_back=False) -> bool | None:
|
||||||
question: str, default_choice=True, allow_go_back=False
|
|
||||||
) -> Union[bool, None]:
|
|
||||||
"""
|
"""
|
||||||
Helper method for validating confirmation (yes/no) user input. |
|
Helper method for validating confirmation (yes/no) user input. |
|
||||||
:param question: The question to display
|
:param question: The question to display
|
||||||
@@ -56,7 +56,7 @@ def get_number_input(
|
|||||||
max_count=None,
|
max_count=None,
|
||||||
default=None,
|
default=None,
|
||||||
allow_go_back=False,
|
allow_go_back=False,
|
||||||
) -> Union[int, None]:
|
) -> int | None:
|
||||||
"""
|
"""
|
||||||
Helper method to get a number input from the user
|
Helper method to get a number input from the user
|
||||||
:param question: The question to display
|
:param question: The question to display
|
||||||
@@ -120,7 +120,7 @@ def get_string_input(
|
|||||||
Logger.print_error(INVALID_CHOICE)
|
Logger.print_error(INVALID_CHOICE)
|
||||||
|
|
||||||
|
|
||||||
def get_selection_input(question: str, option_list: List, default=None) -> str:
|
def get_selection_input(question: str, option_list: List | Dict, default=None) -> str:
|
||||||
"""
|
"""
|
||||||
Helper method to get a selection from a list of options from the user
|
Helper method to get a selection from a list of options from the user
|
||||||
:param question: The question to display
|
:param question: The question to display
|
||||||
@@ -129,10 +129,16 @@ def get_selection_input(question: str, option_list: List, default=None) -> str:
|
|||||||
:return: The option that was selected by the user
|
:return: The option that was selected by the user
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
_input = input(format_question(question, default)).strip()
|
_input = input(format_question(question, default)).strip().lower()
|
||||||
|
|
||||||
if _input in option_list:
|
if isinstance(option_list, list):
|
||||||
return _input
|
if _input in option_list:
|
||||||
|
return _input
|
||||||
|
elif isinstance(option_list, dict):
|
||||||
|
if _input in option_list.keys():
|
||||||
|
return _input
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid option_list type")
|
||||||
|
|
||||||
Logger.print_error(INVALID_CHOICE)
|
Logger.print_error(INVALID_CHOICE)
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,20 @@ class Logger:
|
|||||||
padding_top: int = 1,
|
padding_top: int = 1,
|
||||||
padding_bottom: int = 1,
|
padding_bottom: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Prints a dialog with the given title and content.
|
||||||
|
Those dialogs should be used to display verbose messages to the user which
|
||||||
|
require simple interaction like confirmation or input. Do not use this for
|
||||||
|
navigating through the application.
|
||||||
|
|
||||||
|
:param title: The type of the dialog.
|
||||||
|
:param content: The content of the dialog.
|
||||||
|
:param center_content: Whether to center the content or not.
|
||||||
|
:param custom_title: A custom title for the dialog.
|
||||||
|
:param custom_color: A custom color for the dialog.
|
||||||
|
:param padding_top: The number of empty lines to print before the dialog.
|
||||||
|
:param padding_bottom: The number of empty lines to print after the dialog.
|
||||||
|
"""
|
||||||
dialog_color = Logger._get_dialog_color(title, custom_color)
|
dialog_color = Logger._get_dialog_color(title, custom_color)
|
||||||
dialog_title = Logger._get_dialog_title(title, custom_title)
|
dialog_title = Logger._get_dialog_title(title, custom_title)
|
||||||
dialog_title_formatted = Logger._format_dialog_title(dialog_title)
|
dialog_title_formatted = Logger._format_dialog_title(dialog_title)
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import venv
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import DEVNULL, PIPE, CalledProcessError, Popen, run
|
from subprocess import DEVNULL, PIPE, CalledProcessError, Popen, run
|
||||||
from typing import List, Literal
|
from typing import List, Literal, Set
|
||||||
|
|
||||||
from utils.constants import SYSTEMD
|
from utils.constants import SYSTEMD
|
||||||
from utils.fs_utils import check_file_exist
|
from utils.fs_utils import check_file_exist, remove_with_sudo
|
||||||
from utils.input_utils import get_confirm
|
from utils.input_utils import get_confirm
|
||||||
from utils.logger import Logger
|
from utils.logger import Logger
|
||||||
|
|
||||||
@@ -96,13 +95,11 @@ def create_python_venv(target: Path) -> None:
|
|||||||
Logger.print_status("Set up Python virtual environment ...")
|
Logger.print_status("Set up Python virtual environment ...")
|
||||||
if not target.exists():
|
if not target.exists():
|
||||||
try:
|
try:
|
||||||
venv.create(target, with_pip=True)
|
cmd = ["virtualenv", "-p", "/usr/bin/python3", target.as_posix()]
|
||||||
|
run(cmd, check=True)
|
||||||
Logger.print_ok("Setup of virtualenv successful!")
|
Logger.print_ok("Setup of virtualenv successful!")
|
||||||
except OSError as e:
|
|
||||||
Logger.print_error(f"Error setting up virtualenv:\n{e}")
|
|
||||||
raise
|
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
Logger.print_error(f"Error setting up virtualenv:\n{e.output.decode()}")
|
Logger.print_error(f"Error setting up virtualenv:\n{e}")
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
if get_confirm("Virtualenv already exists. Re-create?", default_choice=False):
|
if get_confirm("Virtualenv already exists. Re-create?", default_choice=False):
|
||||||
@@ -220,7 +217,7 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def check_package_install(packages: List[str]) -> List[str]:
|
def check_package_install(packages: Set[str]) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Checks the system for installed packages |
|
Checks the system for installed packages |
|
||||||
:param packages: List of strings of package names
|
:param packages: List of strings of package names
|
||||||
@@ -438,3 +435,23 @@ def create_env_file(path: Path, content: str) -> None:
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
Logger.print_error(f"Error creating env file: {e}")
|
Logger.print_error(f"Error creating env file: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def remove_service_file(service_name: str, service_file: Path) -> None:
|
||||||
|
"""
|
||||||
|
Removes a systemd service file at the provided path with the provided name.
|
||||||
|
:param service_name: the name of the service
|
||||||
|
:param service_file: the path of the service file
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Logger.print_status(f"Removing {service_name} ...")
|
||||||
|
cmd_sysctl_service(service_name, "stop")
|
||||||
|
cmd_sysctl_service(service_name, "disable")
|
||||||
|
remove_with_sudo(service_file)
|
||||||
|
cmd_sysctl_manage("daemon-reload")
|
||||||
|
cmd_sysctl_manage("reset-failed")
|
||||||
|
Logger.print_ok(f"{service_name} successfully removed!")
|
||||||
|
except Exception as e:
|
||||||
|
Logger.print_error(f"Error removing {service_name}:\n{e}")
|
||||||
|
raise
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ function backup_config_dir() {
|
|||||||
for folder in ${config_pathes}; do
|
for folder in ${config_pathes}; do
|
||||||
if [[ -d ${folder} ]]; then
|
if [[ -d ${folder} ]]; then
|
||||||
status_msg "Create backup of ${folder} ..."
|
status_msg "Create backup of ${folder} ..."
|
||||||
|
|
||||||
folder_name=$(echo "${folder}" | rev | cut -d"/" -f2 | rev)
|
folder_name=$(echo "${folder}" | rev | cut -d"/" -f2 | rev)
|
||||||
target_dir="${BACKUP_DIR}/configs/${current_date}/${folder_name}"
|
target_dir="${BACKUP_DIR}/configs/${current_date}/${folder_name}"
|
||||||
mkdir -p "${target_dir}"
|
mkdir -p "${target_dir}"
|
||||||
cp -r "${folder}" "${target_dir}"
|
cp -r "${folder}" "${target_dir}"
|
||||||
i=$(( i + 1 ))
|
i=$(( i + 1 ))
|
||||||
|
|
||||||
ok_msg "Backup created in:\n${target_dir}"
|
ok_msg "Backup created in:\n${target_dir}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -213,3 +213,19 @@ function backup_octoeverywhere() {
|
|||||||
print_error "Can't back up OctoEverywhere directory!\n Not found!"
|
print_error "Can't back up OctoEverywhere directory!\n Not found!"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function backup_spoolman() {
|
||||||
|
local current_date
|
||||||
|
|
||||||
|
if [[ -d ${SPOOLMAN_DIR} ]] ; then
|
||||||
|
status_msg "Creating Spoolman backup ..."
|
||||||
|
check_for_backup_dir
|
||||||
|
current_date=$(get_date)
|
||||||
|
status_msg "Timestamp: ${current_date}"
|
||||||
|
mkdir -p "${BACKUP_DIR}/Spoolman-backups/${current_date}"
|
||||||
|
cp -r "${SPOOLMAN_DIR}" "${_}" && cp -r "${SPOOLMAN_DB_DIR}/spoolman.db" "${_}"
|
||||||
|
print_confirm "Spoolman backup complete!"
|
||||||
|
else
|
||||||
|
print_error "Can't back up Spoolman directory!\n Not found!"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function install_fluidd() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
### checking dependencies
|
### checking dependencies
|
||||||
local dep=(wget nginx)
|
local dep=(wget nginx unzip)
|
||||||
dependency_check "${dep[@]}"
|
dependency_check "${dep[@]}"
|
||||||
### detect conflicting Haproxy and Apache2 installations
|
### detect conflicting Haproxy and Apache2 installations
|
||||||
detect_conflicting_packages
|
detect_conflicting_packages
|
||||||
|
|||||||
@@ -87,4 +87,9 @@ function set_globals() {
|
|||||||
OCTOAPP_ENV="${HOME}/octoapp-env"
|
OCTOAPP_ENV="${HOME}/octoapp-env"
|
||||||
OCTOAPP_DIR="${HOME}/octoapp"
|
OCTOAPP_DIR="${HOME}/octoapp"
|
||||||
OCTOAPP_REPO="https://github.com/crysxd/OctoApp-Plugin.git"
|
OCTOAPP_REPO="https://github.com/crysxd/OctoApp-Plugin.git"
|
||||||
|
|
||||||
|
#=============== Spoolman ================#
|
||||||
|
SPOOLMAN_DIR="${HOME}/Spoolman"
|
||||||
|
SPOOLMAN_DB_DIR="${HOME}/.local/share/spoolman"
|
||||||
|
SPOOLMAN_REPO="https://api.github.com/repos/Donkie/Spoolman/releases/latest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function install_mainsail() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
### checking dependencies
|
### checking dependencies
|
||||||
local dep=(wget nginx)
|
local dep=(wget nginx unzip)
|
||||||
dependency_check "${dep[@]}"
|
dependency_check "${dep[@]}"
|
||||||
### detect conflicting Haproxy and Apache2 installations
|
### detect conflicting Haproxy and Apache2 installations
|
||||||
detect_conflicting_packages
|
detect_conflicting_packages
|
||||||
|
|||||||
281
scripts/spoolman.sh
Normal file
281
scripts/spoolman.sh
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#=======================================================================#
|
||||||
|
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
|
||||||
|
# #
|
||||||
|
# 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 #
|
||||||
|
#=======================================================================#
|
||||||
|
|
||||||
|
# Error Handling
|
||||||
|
set -e
|
||||||
|
|
||||||
|
function install_spoolman() {
|
||||||
|
|
||||||
|
pushd "${HOME}" &> /dev/null || exit 1
|
||||||
|
|
||||||
|
dependency_check curl jq
|
||||||
|
|
||||||
|
if [[ ! -d "${SPOOLMAN_DIR}" && -z "$(ls -A "${SPOOLMAN_DIR}" 2> /dev/null)" ]]; then
|
||||||
|
status_msg "Downloading spoolman..."
|
||||||
|
setup_spoolman_folder
|
||||||
|
status_msg "Downloading complete"
|
||||||
|
start_install_script
|
||||||
|
advanced_config_prompt
|
||||||
|
else
|
||||||
|
### In case spoolman is "incomplete" rerun install script
|
||||||
|
if get_spoolman_status | grep -q "Incomplete!"; then
|
||||||
|
start_install_script
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok_msg "Spoolman already installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
enable_moonraker_integration_prompt
|
||||||
|
patch_spoolman_update_manager
|
||||||
|
|
||||||
|
do_action_service "restart" "moonraker"
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_spoolman() {
|
||||||
|
### stop and disable old spoolman service
|
||||||
|
do_action_service "stop" "Spoolman"
|
||||||
|
do_action_service "disable" "Spoolman"
|
||||||
|
|
||||||
|
mv "${SPOOLMAN_DIR}" "${SPOOLMAN_DIR}_old"
|
||||||
|
|
||||||
|
setup_spoolman_folder
|
||||||
|
cp "${SPOOLMAN_DIR}_old/.env" "${SPOOLMAN_DIR}/.env"
|
||||||
|
|
||||||
|
start_install_script
|
||||||
|
|
||||||
|
rm -rf "${SPOOLMAN_DIR}_old"
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_spoolman(){
|
||||||
|
if [[ -d "${SPOOLMAN_DIR}" ]]; then
|
||||||
|
status_msg "Removing spoolman service..."
|
||||||
|
do_action_service "stop" "Spoolman"
|
||||||
|
do_action_service "disable" "Spoolman"
|
||||||
|
sudo rm -f "${SYSTEMD}/Spoolman.service"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl reset-failed
|
||||||
|
ok_msg "Spoolman service removed!"
|
||||||
|
|
||||||
|
status_msg "Removing spoolman directory..."
|
||||||
|
rm -rf "${SPOOLMAN_DIR}"
|
||||||
|
ok_msg "Spoolman directory removed!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_confirm "Spoolman successfully removed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_moonraker_configs() {
|
||||||
|
local moonraker_configs regex
|
||||||
|
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/moonraker\.conf"
|
||||||
|
moonraker_configs=$(find "${HOME}" -maxdepth 3 -type f -regextype posix-extended -regex "${regex}" | sort)
|
||||||
|
|
||||||
|
for conf in ${moonraker_configs}; do
|
||||||
|
if ! grep -Eq "^\[update_manager Spoolman\]\s*$" "${conf}"; then
|
||||||
|
### add new line to conf if it doesn't end with one
|
||||||
|
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
|
||||||
|
/bin/sh -c "cat >> ${conf}" << MOONRAKER_CONF
|
||||||
|
${1}
|
||||||
|
MOONRAKER_CONF
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable_moonraker_integration() {
|
||||||
|
local integration_str env_port
|
||||||
|
# get spoolman port from .env
|
||||||
|
env_port=$(grep "^SPOOLMAN_PORT=" "${SPOOLMAN_DIR}/.env" | cut -d"=" -f2)
|
||||||
|
|
||||||
|
integration_str="
|
||||||
|
[spoolman]
|
||||||
|
server: http://$(hostname -I | cut -d" " -f1):${env_port}
|
||||||
|
"
|
||||||
|
|
||||||
|
status_msg "Adding Spoolman integration..."
|
||||||
|
update_moonraker_configs "${integration_str}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function patch_spoolman_update_manager() {
|
||||||
|
local updater_str
|
||||||
|
updater_str="
|
||||||
|
[update_manager Spoolman]
|
||||||
|
type: zip
|
||||||
|
channel: stable
|
||||||
|
repo: Donkie/Spoolman
|
||||||
|
path: ${SPOOLMAN_DIR}
|
||||||
|
virtualenv: .venv
|
||||||
|
requirements: requirements.txt
|
||||||
|
persistent_files:
|
||||||
|
.venv
|
||||||
|
.env
|
||||||
|
managed_services: Spoolman
|
||||||
|
"
|
||||||
|
|
||||||
|
update_moonraker_configs "${updater_str}"
|
||||||
|
|
||||||
|
# add spoolman service to moonraker.asvc
|
||||||
|
local moonraker_asvc regex
|
||||||
|
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/moonraker\.asvc"
|
||||||
|
moonraker_asvc=$(find "${HOME}" -maxdepth 2 -type f -regextype posix-extended -regex "${regex}" | sort)
|
||||||
|
|
||||||
|
if [[ -n ${moonraker_asvc} ]]; then
|
||||||
|
status_msg "Adding Spoolman service to moonraker.asvc..."
|
||||||
|
/bin/sh -c "echo 'Spoolman' >> ${moonraker_asvc}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function advanced_config_prompt() {
|
||||||
|
local reply
|
||||||
|
while true; do
|
||||||
|
read -erp "${cyan}###### Continue with default configuration? (Y/n):${white} " reply
|
||||||
|
case "${reply}" in
|
||||||
|
Y|y|Yes|yes|"")
|
||||||
|
select_msg "Yes"
|
||||||
|
break;;
|
||||||
|
N|n|No|no)
|
||||||
|
select_msg "No"
|
||||||
|
advanced_config
|
||||||
|
break;;
|
||||||
|
*)
|
||||||
|
error_msg "Invalid Input!\n";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable_moonraker_integration_prompt() {
|
||||||
|
local reply
|
||||||
|
while true; do
|
||||||
|
read -erp "${cyan}###### Enable Moonraker integration? (Y/n):${white} " reply
|
||||||
|
case "${reply}" in
|
||||||
|
Y|y|Yes|yes|"")
|
||||||
|
select_msg "Yes"
|
||||||
|
enable_moonraker_integration
|
||||||
|
break;;
|
||||||
|
N|n|No|no)
|
||||||
|
select_msg "No"
|
||||||
|
break;;
|
||||||
|
*)
|
||||||
|
error_msg "Invalid Input!\n";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function advanced_config() {
|
||||||
|
status_msg "###### Advanced configuration"
|
||||||
|
|
||||||
|
local reply
|
||||||
|
while true; do
|
||||||
|
read -erp "${cyan}###### Select spoolman port (7912):${white} " reply
|
||||||
|
### set default
|
||||||
|
if [[ -z "${reply}" ]]; then
|
||||||
|
reply="7912"
|
||||||
|
fi
|
||||||
|
|
||||||
|
select_msg "${reply}"
|
||||||
|
### check if port is valid
|
||||||
|
if ! [[ "${reply}" =~ ^[0-9]+$ && "${reply}" -ge 1024 && "${reply}" -le 65535 ]]; then
|
||||||
|
error_msg "Invalid port number!\n"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
### update .env
|
||||||
|
sed -i "s/^SPOOLMAN_PORT=.*$/SPOOLMAN_PORT=${reply}/" "${SPOOLMAN_DIR}/.env"
|
||||||
|
do_action_service "restart" "Spoolman"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_spoolman_folder() {
|
||||||
|
local source_url
|
||||||
|
### get latest spoolman release url
|
||||||
|
source_url="$(curl -s "${SPOOLMAN_REPO}" | jq -r '.assets[] | select(.name == "spoolman.zip").browser_download_url')"
|
||||||
|
|
||||||
|
mkdir -p "${SPOOLMAN_DIR}"
|
||||||
|
curl -sSL "${source_url}" -o /tmp/temp.zip
|
||||||
|
unzip /tmp/temp.zip -d "${SPOOLMAN_DIR}" &> /dev/null
|
||||||
|
rm /tmp/temp.zip
|
||||||
|
|
||||||
|
chmod +x "${SPOOLMAN_DIR}"/scripts/install.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_install_script() {
|
||||||
|
|
||||||
|
pushd "${SPOOLMAN_DIR}" &> /dev/null || exit 1
|
||||||
|
|
||||||
|
if bash ./scripts/install.sh; then
|
||||||
|
ok_msg "Spoolman successfully installed!"
|
||||||
|
else
|
||||||
|
print_error "Spoolman installation failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_spoolman_status() {
|
||||||
|
local -a files
|
||||||
|
files=(
|
||||||
|
"${SPOOLMAN_DIR}"
|
||||||
|
"${SYSTEMD}/Spoolman.service"
|
||||||
|
"${SPOOLMAN_DB_DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
local count
|
||||||
|
count=0
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
[[ -e "${file}" ]] && count=$(( count +1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${count}" -eq "${#files[*]}" ]]; then
|
||||||
|
echo "Installed"
|
||||||
|
elif [[ "${count}" -gt 0 ]]; then
|
||||||
|
echo "Incomplete!"
|
||||||
|
else
|
||||||
|
echo "Not installed!"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_local_spoolman_version() {
|
||||||
|
[[ ! -d "${SPOOLMAN_DIR}" ]] && return
|
||||||
|
|
||||||
|
local version
|
||||||
|
version=$(grep -o '"version":\s*"[^"]*' "${SPOOLMAN_DIR}"/release_info.json | cut -d'"' -f4)
|
||||||
|
echo "${version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_remote_spoolman_version() {
|
||||||
|
[[ ! -d "${SPOOLMAN_DIR}" ]] && return
|
||||||
|
|
||||||
|
local version
|
||||||
|
version=$(curl -s "${SPOOLMAN_REPO}" | grep -o '"tag_name":\s*"v[^"]*"' | cut -d'"' -f4)
|
||||||
|
echo "${version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare_spoolman_versions() {
|
||||||
|
local local_ver remote_ver
|
||||||
|
local_ver="$(get_local_spoolman_version)"
|
||||||
|
remote_ver="$(get_remote_spoolman_version)"
|
||||||
|
|
||||||
|
if [[ ${local_ver} != "${remote_ver}" ]]; then
|
||||||
|
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
|
||||||
|
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
|
||||||
|
# add spoolman to application_updates_available in kiauh.ini
|
||||||
|
add_to_application_updates "spoolman"
|
||||||
|
else
|
||||||
|
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
|
||||||
|
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${versions}"
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ function backup_ui() {
|
|||||||
echo -e "| Klipper Webinterface: | Other: |"
|
echo -e "| Klipper Webinterface: | Other: |"
|
||||||
echo -e "| 5) [Mainsail] | 9) [Telegram Bot] |"
|
echo -e "| 5) [Mainsail] | 9) [Telegram Bot] |"
|
||||||
echo -e "| 6) [Fluidd] | 10) [OctoEverywhere] |"
|
echo -e "| 6) [Fluidd] | 10) [OctoEverywhere] |"
|
||||||
|
echo -e "| | 11) [Spoolman] |"
|
||||||
back_footer
|
back_footer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ function backup_menu() {
|
|||||||
do_action "backup_telegram_bot" "backup_ui";;
|
do_action "backup_telegram_bot" "backup_ui";;
|
||||||
10)
|
10)
|
||||||
do_action "backup_octoeverywhere" "backup_ui";;
|
do_action "backup_octoeverywhere" "backup_ui";;
|
||||||
|
11)
|
||||||
|
do_action "backup_spoolman" "backup_ui";;
|
||||||
B|b)
|
B|b)
|
||||||
clear; main_menu; break;;
|
clear; main_menu; break;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ function install_ui() {
|
|||||||
echo -e "| all necessary dependencies for the various |"
|
echo -e "| all necessary dependencies for the various |"
|
||||||
echo -e "| functions on a completely fresh system. |"
|
echo -e "| functions on a completely fresh system. |"
|
||||||
hr
|
hr
|
||||||
echo -e "| Firmware & API: | 3rd Party Webinterface: |"
|
echo -e "| Firmware & API: | Other: |"
|
||||||
echo -e "| 1) [Klipper] | 6) [OctoPrint] |"
|
echo -e "| 1) [Klipper] | 7) [PrettyGCode] |"
|
||||||
echo -e "| 2) [Moonraker] | |"
|
echo -e "| 2) [Moonraker] | 8) [Telegram Bot] |"
|
||||||
echo -e "| | Other: |"
|
echo -e "| | 9) $(obico_install_title) |"
|
||||||
echo -e "| Klipper Webinterface: | 7) [PrettyGCode] |"
|
echo -e "| Klipper Webinterface: | 10) [OctoEverywhere] |"
|
||||||
echo -e "| 3) [Mainsail] | 8) [Telegram Bot] |"
|
echo -e "| 3) [Mainsail] | 11) [Mobileraker] |"
|
||||||
echo -e "| 4) [Fluidd] | 9) $(obico_install_title) |"
|
echo -e "| 4) [Fluidd] | 12) [OctoApp for Klipper] |"
|
||||||
echo -e "| | 10) [OctoEverywhere] |"
|
echo -e "| | 13) [Spoolman] |"
|
||||||
echo -e "| | 11) [Mobileraker] |"
|
echo -e "| Touchscreen GUI: | |"
|
||||||
echo -e "| Touchscreen GUI: | 12) [OctoApp for Klipper] |"
|
echo -e "| 5) [KlipperScreen] | Webcam Streamer: |"
|
||||||
echo -e "| 5) [KlipperScreen] | |"
|
echo -e "| | 14) [Crowsnest] |"
|
||||||
echo -e "| | Webcam Streamer: |"
|
echo -e "| 3rd Party Webinterface: | |"
|
||||||
echo -e "| | 13) [Crowsnest] |"
|
echo -e "| 6) [OctoPrint] | |"
|
||||||
back_footer
|
back_footer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +75,8 @@ function install_menu() {
|
|||||||
12)
|
12)
|
||||||
do_action "octoapp_setup_dialog" "install_ui";;
|
do_action "octoapp_setup_dialog" "install_ui";;
|
||||||
13)
|
13)
|
||||||
|
do_action "install_spoolman" "install_ui";;
|
||||||
|
14)
|
||||||
do_action "install_crowsnest" "install_ui";;
|
do_action "install_crowsnest" "install_ui";;
|
||||||
B|b)
|
B|b)
|
||||||
clear; main_menu; break;;
|
clear; main_menu; break;;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function main_ui() {
|
|||||||
echo -e "| | OctoEverywhere: $(print_status "octoeverywhere")|"
|
echo -e "| | OctoEverywhere: $(print_status "octoeverywhere")|"
|
||||||
echo -e "| | Mobileraker: $(print_status "mobileraker")|"
|
echo -e "| | Mobileraker: $(print_status "mobileraker")|"
|
||||||
echo -e "| | OctoApp: $(print_status "octoapp")|"
|
echo -e "| | OctoApp: $(print_status "octoapp")|"
|
||||||
|
echo -e "| | Spoolman: $(print_status "spoolman")|"
|
||||||
echo -e "| | |"
|
echo -e "| | |"
|
||||||
echo -e "| | Octoprint: $(print_status "octoprint")|"
|
echo -e "| | Octoprint: $(print_status "octoprint")|"
|
||||||
hr
|
hr
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ function remove_ui() {
|
|||||||
hr
|
hr
|
||||||
echo -e "| ${yellow}INFO: Configurations and/or any backups will be kept!${white} |"
|
echo -e "| ${yellow}INFO: Configurations and/or any backups will be kept!${white} |"
|
||||||
hr
|
hr
|
||||||
echo -e "| Firmware & API: | 3rd Party Webinterface: |"
|
echo -e "| Firmware & API: | Webcam Streamer: |"
|
||||||
echo -e "| 1) [Klipper] | 8) [OctoPrint] |"
|
echo -e "| 1) [Klipper] | 9) [Crowsnest] |"
|
||||||
echo -e "| 2) [Moonraker] | |"
|
echo -e "| 2) [Moonraker] | 10) [MJPG-Streamer] |"
|
||||||
echo -e "| | Webcam Streamer: |"
|
echo -e "| | |"
|
||||||
echo -e "| Klipper Webinterface: | 9) [Crowsnest] |"
|
echo -e "| Klipper Webinterface: | Other: |"
|
||||||
echo -e "| 3) [Mainsail] | 10) [MJPG-Streamer] |"
|
echo -e "| 3) [Mainsail] | 11) [PrettyGCode] |"
|
||||||
echo -e "| 4) [Mainsail-Config] | |"
|
echo -e "| 4) [Mainsail-Config] | 12) [Telegram Bot] |"
|
||||||
echo -e "| 5) [Fluidd] | Other: |"
|
echo -e "| 5) [Fluidd] | 13) [Obico for Klipper] |"
|
||||||
echo -e "| 6) [Fluidd-Config] | 11) [PrettyGCode] |"
|
echo -e "| 6) [Fluidd-Config] | 14) [OctoEverywhere] |"
|
||||||
echo -e "| | 12) [Telegram Bot] |"
|
|
||||||
echo -e "| Touchscreen GUI: | 13) [Obico for Klipper] |"
|
|
||||||
echo -e "| 7) [KlipperScreen] | 14) [OctoEverywhere] |"
|
|
||||||
echo -e "| | 15) [Mobileraker] |"
|
echo -e "| | 15) [Mobileraker] |"
|
||||||
echo -e "| | 16) [NGINX] |"
|
echo -e "| Touchscreen GUI: | 16) [NGINX] |"
|
||||||
echo -e "| | 17) [OctoApp] |"
|
echo -e "| 7) [KlipperScreen] | 17) [OctoApp] |"
|
||||||
|
echo -e "| | 18) [Spoolman] |"
|
||||||
|
echo -e "| 3rd Party Webinterface: | |"
|
||||||
|
echo -e "| 8) [OctoPrint] | |"
|
||||||
back_footer
|
back_footer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +76,8 @@ function remove_menu() {
|
|||||||
do_action "remove_nginx" "remove_ui";;
|
do_action "remove_nginx" "remove_ui";;
|
||||||
17)
|
17)
|
||||||
do_action "remove_octoapp" "remove_ui";;
|
do_action "remove_octoapp" "remove_ui";;
|
||||||
|
18)
|
||||||
|
do_action "remove_spoolman" "remove_ui";;
|
||||||
B|b)
|
B|b)
|
||||||
clear; main_menu; break;;
|
clear; main_menu; break;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@@ -36,15 +36,16 @@ function update_ui() {
|
|||||||
echo -e "| 10) [Mobileraker] |$(compare_mobileraker_versions)|"
|
echo -e "| 10) [Mobileraker] |$(compare_mobileraker_versions)|"
|
||||||
echo -e "| 11) [Crowsnest] |$(compare_crowsnest_versions)|"
|
echo -e "| 11) [Crowsnest] |$(compare_crowsnest_versions)|"
|
||||||
echo -e "| 12) [OctoApp] |$(compare_octoapp_versions)|"
|
echo -e "| 12) [OctoApp] |$(compare_octoapp_versions)|"
|
||||||
|
echo -e "| 13) [Spoolman] |$(compare_spoolman_versions)|"
|
||||||
echo -e "| |------------------------------|"
|
echo -e "| |------------------------------|"
|
||||||
echo -e "| 13) [System] | $(check_system_updates) |"
|
echo -e "| 14) [System] | $(check_system_updates) |"
|
||||||
back_footer
|
back_footer
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_menu() {
|
function update_menu() {
|
||||||
clear -x && sudo true && clear -x # (re)cache sudo credentials so password prompt doesn't bork ui
|
clear -x && sudo true && clear -x # (re)cache sudo credentials so password prompt doesn't bork ui
|
||||||
do_action "" "update_ui"
|
do_action "" "update_ui"
|
||||||
|
|
||||||
local action
|
local action
|
||||||
while true; do
|
while true; do
|
||||||
read -p "${cyan}####### Perform action:${white} " action
|
read -p "${cyan}####### Perform action:${white} " action
|
||||||
@@ -76,6 +77,8 @@ function update_menu() {
|
|||||||
12)
|
12)
|
||||||
do_action "update_octoapp" "update_ui";;
|
do_action "update_octoapp" "update_ui";;
|
||||||
13)
|
13)
|
||||||
|
do_action "update_spoolman" "update_ui";;
|
||||||
|
14)
|
||||||
do_action "upgrade_system_packages" "update_ui";;
|
do_action "upgrade_system_packages" "update_ui";;
|
||||||
a)
|
a)
|
||||||
do_action "update_all" "update_ui";;
|
do_action "update_all" "update_ui";;
|
||||||
@@ -101,7 +104,7 @@ function update_all() {
|
|||||||
print_confirm "Everything is already up-to-date!"
|
print_confirm "Everything is already up-to-date!"
|
||||||
echo; break
|
echo; break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
top_border
|
top_border
|
||||||
echo -e "| The following installations will be updated: |"
|
echo -e "| The following installations will be updated: |"
|
||||||
@@ -121,6 +124,9 @@ function update_all() {
|
|||||||
[[ "${update_arr[*]}" =~ "klipperscreen" ]] && \
|
[[ "${update_arr[*]}" =~ "klipperscreen" ]] && \
|
||||||
echo -e "| ${cyan}● KlipperScreen${white} |"
|
echo -e "| ${cyan}● KlipperScreen${white} |"
|
||||||
|
|
||||||
|
[[ "${update_arr[*]}" =~ "spoolman" ]] && \
|
||||||
|
echo -e "| ${cyan}● SpoolMan${white} |"
|
||||||
|
|
||||||
[[ "${update_arr[*]}" =~ "pgc_for_klipper" ]] && \
|
[[ "${update_arr[*]}" =~ "pgc_for_klipper" ]] && \
|
||||||
echo -e "| ${cyan}● PrettyGCode for Klipper${white} |"
|
echo -e "| ${cyan}● PrettyGCode for Klipper${white} |"
|
||||||
|
|
||||||
@@ -140,7 +146,7 @@ function update_all() {
|
|||||||
echo -e "| ${cyan}● System${white} |"
|
echo -e "| ${cyan}● System${white} |"
|
||||||
|
|
||||||
bottom_border
|
bottom_border
|
||||||
|
|
||||||
local yn
|
local yn
|
||||||
read -p "${cyan}###### Do you want to proceed? (Y/n):${white} " yn
|
read -p "${cyan}###### Do you want to proceed? (Y/n):${white} " yn
|
||||||
case "${yn}" in
|
case "${yn}" in
|
||||||
|
|||||||
Reference in New Issue
Block a user