refactor(KIAUH): full refactor of client and client-config installation

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-03-02 17:22:37 +01:00
parent 7fd91e6cef
commit 1620efe56c
35 changed files with 1248 additions and 1587 deletions

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent
FLUIDD_DIR = Path.home().joinpath("fluidd")
FLUIDD_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
FLUIDD_CONFIG_DIR = Path.home().joinpath("fluidd-config")
FLUIDD_NGINX_CFG = Path("/etc/nginx/sites-enabled/fluidd")
FLUIDD_URL = "https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip"
FLUIDD_UNSTABLE_URL = (
"https://github.com/fluidd-core/fluidd/releases/download/%TAG%/fluidd.zip"
)
FLUIDD_CONFIG_REPO_URL = "https://github.com/fluidd-core/fluidd-config.git"

View File

@@ -1,6 +0,0 @@
[update_manager fluidd-config]
type: git_repo
primary_branch: master
path: ~/fluidd-config
origin: https://github.com/fluidd-core/fluidd-config.git
managed_services: klipper

View File

@@ -1,5 +0,0 @@
[update_manager fluidd]
type: web
channel: stable
repo: fluidd-core/fluidd
path: ~/fluidd

View File

@@ -1,160 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import shutil
import subprocess
from pathlib import Path
from typing import List
from components.klipper.klipper import Klipper
from components.fluidd import FLUIDD_DIR, FLUIDD_CONFIG_DIR
from components.moonraker.moonraker import Moonraker
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager
from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
from utils.filesystem_utils import remove_file
from utils.logger import Logger
def run_fluidd_removal(
remove_fluidd: bool,
remove_fl_config: bool,
remove_mr_updater_section: bool,
remove_flc_printer_cfg_include: bool,
) -> None:
if remove_fluidd:
remove_fluidd_dir()
remove_nginx_config()
remove_nginx_logs()
if remove_mr_updater_section:
remove_updater_section("update_manager fluidd")
if remove_fl_config:
remove_fluidd_cfg_dir()
remove_fluidd_cfg_symlink()
if remove_mr_updater_section:
remove_updater_section("update_manager fluidd-config")
if remove_flc_printer_cfg_include:
remove_printer_cfg_include()
def remove_fluidd_dir() -> None:
Logger.print_status("Removing Fluidd ...")
if not FLUIDD_DIR.exists():
Logger.print_info(f"'{FLUIDD_DIR}' does not exist. Skipping ...")
return
try:
shutil.rmtree(FLUIDD_DIR)
except OSError as e:
Logger.print_error(f"Unable to delete '{FLUIDD_DIR}':\n{e}")
def remove_nginx_config() -> None:
Logger.print_status("Removing Fluidd NGINX config ...")
try:
remove_file(NGINX_SITES_AVAILABLE.joinpath("fluidd"), True)
remove_file(NGINX_SITES_ENABLED.joinpath("fluidd"), True)
except subprocess.CalledProcessError as e:
log = f"Unable to remove Fluidd NGINX config:\n{e.stderr.decode()}"
Logger.print_error(log)
def remove_nginx_logs() -> None:
Logger.print_status("Removing Fluidd NGINX logs ...")
try:
remove_file(Path("/var/log/nginx/fluidd-access.log"), True)
remove_file(Path("/var/log/nginx/fluidd-error.log"), True)
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
if not instances:
return
for instance in instances:
remove_file(instance.log_dir.joinpath("fluidd-access.log"))
remove_file(instance.log_dir.joinpath("fluidd-error.log"))
except (OSError, subprocess.CalledProcessError) as e:
Logger.print_error(f"Unable to NGINX logs:\n{e}")
def remove_updater_section(name: str) -> None:
Logger.print_status("Remove updater section from moonraker.conf ...")
im = InstanceManager(Moonraker)
instances: List[Moonraker] = im.instances
if not instances:
Logger.print_info("Moonraker not installed. Skipped ...")
return
for instance in instances:
Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...")
if not instance.cfg_file.is_file():
Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...")
continue
cm = ConfigManager(instance.cfg_file)
if not cm.config.has_section(name):
Logger.print_info("Section not present. Skipped ...")
continue
cm.config.remove_section(name)
cm.write_config()
def remove_fluidd_cfg_dir() -> None:
Logger.print_status("Removing fluidd-config ...")
if not FLUIDD_CONFIG_DIR.exists():
Logger.print_info(f"'{FLUIDD_CONFIG_DIR}' does not exist. Skipping ...")
return
try:
shutil.rmtree(FLUIDD_CONFIG_DIR)
except OSError as e:
Logger.print_error(f"Unable to delete '{FLUIDD_CONFIG_DIR}':\n{e}")
def remove_fluidd_cfg_symlink() -> None:
Logger.print_status("Removing fluidd.cfg symlinks ...")
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
for instance in instances:
Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...")
try:
remove_file(instance.cfg_dir.joinpath("fluidd.cfg"))
except subprocess.CalledProcessError:
Logger.print_error("Failed to remove symlink!")
def remove_printer_cfg_include() -> None:
Logger.print_status("Remove fluidd-config include from printer.cfg ...")
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
if not instances:
Logger.print_info("Klipper not installed. Skipping ...")
return
for instance in instances:
log = f"Removing include from '{instance.cfg_file}' ..."
Logger.print_status(log)
if not instance.cfg_file.is_file():
continue
cm = ConfigManager(instance.cfg_file)
if not cm.config.has_section("include fluidd.cfg"):
Logger.print_info("Section not present. Skipped ...")
continue
cm.config.remove_section("include fluidd.cfg")
cm.write_config()

View File

@@ -1,242 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import subprocess
from pathlib import Path
from typing import List
from components.fluidd import (
FLUIDD_URL,
FLUIDD_CONFIG_REPO_URL,
FLUIDD_CONFIG_DIR,
FLUIDD_DIR,
MODULE_PATH,
)
from components.fluidd.fluidd_dialogs import (
print_fluidd_already_installed_dialog,
print_install_fluidd_config_dialog,
print_fluidd_port_select_dialog,
print_moonraker_not_found_dialog,
)
from components.fluidd.fluidd_utils import symlink_webui_nginx_log
from kiauh import KIAUH_CFG
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager
from core.repo_manager.repo_manager import RepoManager
from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
from utils.common import check_install_dependencies
from utils.filesystem_utils import (
unzip,
copy_upstream_nginx_cfg,
copy_common_vars_nginx_cfg,
create_nginx_cfg,
create_symlink,
remove_file,
read_ports_from_nginx_configs,
get_next_free_port, is_valid_port,
)
from utils.input_utils import get_confirm, get_number_input
from utils.logger import Logger
from utils.system_utils import (
download_file,
set_nginx_permissions,
get_ipv4_addr,
control_systemd_service,
)
def install_fluidd() -> None:
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
if not mr_instances:
print_moonraker_not_found_dialog()
if not get_confirm("Continue Fluidd installation?", allow_go_back=True):
return
if Path.home().joinpath("fluidd").exists():
print_fluidd_already_installed_dialog()
do_reinstall = get_confirm("Re-install Fluidd?", allow_go_back=True)
if not do_reinstall:
return
kl_im = InstanceManager(Klipper)
kl_instances = kl_im.instances
install_fl_config = False
if kl_instances:
print_install_fluidd_config_dialog()
question = "Download the recommended macros?"
install_fl_config = get_confirm(question, allow_go_back=False)
cm = ConfigManager(cfg_file=KIAUH_CFG)
fluidd_port = cm.get_value("fluidd", "port")
ports_in_use = read_ports_from_nginx_configs()
# check if configured port is a valid number and not in use already
valid_port = is_valid_port(fluidd_port, ports_in_use)
while not valid_port:
next_port = get_next_free_port(ports_in_use)
print_fluidd_port_select_dialog(next_port, ports_in_use)
fluidd_port = str(get_number_input(
"Configure Fluidd for port",
min_count=int(next_port),
default=next_port,
))
valid_port = is_valid_port(fluidd_port, ports_in_use)
check_install_dependencies(["nginx"])
try:
download_fluidd()
if mr_instances:
patch_moonraker_conf(
mr_instances,
"Fluidd",
"update_manager fluidd",
"fluidd-updater.conf",
)
mr_im.restart_all_instance()
if install_fl_config and kl_instances:
download_fluidd_cfg()
create_fluidd_cfg_symlink(kl_instances)
patch_moonraker_conf(
mr_instances,
"fluidd-config",
"update_manager fluidd-config",
"fluidd-config-updater.conf",
)
patch_printer_config(kl_instances)
kl_im.restart_all_instance()
copy_upstream_nginx_cfg()
copy_common_vars_nginx_cfg()
create_fluidd_nginx_cfg(fluidd_port)
if kl_instances:
symlink_webui_nginx_log(kl_instances)
control_systemd_service("nginx", "restart")
except Exception as e:
Logger.print_error(f"Fluidd installation failed!\n{e}")
return
log = f"Open Fluidd now on: http://{get_ipv4_addr()}:{fluidd_port}"
Logger.print_ok("Fluidd installation complete!", start="\n")
Logger.print_ok(log, prefix=False, end="\n\n")
def download_fluidd() -> None:
try:
Logger.print_status("Downloading Fluidd ...")
target = Path.home().joinpath("fluidd.zip")
download_file(FLUIDD_URL, target, True)
Logger.print_ok("Download complete!")
Logger.print_status("Extracting fluidd.zip ...")
unzip(Path.home().joinpath("fluidd.zip"), FLUIDD_DIR)
target.unlink(missing_ok=True)
Logger.print_ok("OK!")
except Exception:
Logger.print_error("Downloading Fluidd failed!")
raise
def update_fluidd() -> None:
Logger.print_status("Updating Fluidd ...")
download_fluidd()
def download_fluidd_cfg() -> None:
try:
Logger.print_status("Downloading fluidd-config ...")
rm = RepoManager(FLUIDD_CONFIG_REPO_URL, target_dir=FLUIDD_CONFIG_DIR)
rm.clone_repo()
except Exception:
Logger.print_error("Downloading fluidd-config failed!")
raise
def create_fluidd_cfg_symlink(klipper_instances: List[Klipper]) -> None:
Logger.print_status("Create symlink of fluidd.cfg ...")
source = Path(FLUIDD_CONFIG_DIR, "fluidd.cfg")
for instance in klipper_instances:
target = instance.cfg_dir
Logger.print_status(f"Linking {source} to {target}")
try:
create_symlink(source, target)
except subprocess.CalledProcessError:
Logger.print_error("Creating symlink failed!")
def create_fluidd_nginx_cfg(port: int) -> None:
root_dir = FLUIDD_DIR
source = NGINX_SITES_AVAILABLE.joinpath("fluidd")
target = NGINX_SITES_ENABLED.joinpath("fluidd")
try:
Logger.print_status("Creating NGINX config for Fluidd ...")
remove_file(Path("/etc/nginx/sites-enabled/default"), True)
create_nginx_cfg("fluidd", port, root_dir)
create_symlink(source, target, True)
set_nginx_permissions()
Logger.print_ok("NGINX config for Fluidd successfully created.")
except Exception:
Logger.print_error("Creating NGINX config for Fluidd failed!")
raise
# TODO: could be fully extracted, its webui agnostic, and used for mainsail and fluidd
def patch_moonraker_conf(
moonraker_instances: List[Moonraker],
name: str,
section_name: str,
template_file: str,
) -> None:
for instance in moonraker_instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Add {name} update section to '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
return
cm = ConfigManager(cfg_file)
if cm.config.has_section(section_name):
Logger.print_info("Section already exist. Skipped ...")
return
template = MODULE_PATH.joinpath("assets", template_file)
with open(template, "r") as t:
template_content = "\n"
template_content += t.read()
with open(cfg_file, "a") as f:
f.write(template_content)
# TODO: could be made fully webui agnostic and extracted, and used for mainsail and fluidd
def patch_printer_config(klipper_instances: List[Klipper]) -> None:
for instance in klipper_instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Including fluidd-config in '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
return
cm = ConfigManager(cfg_file)
if cm.config.has_section("include fluidd.cfg"):
Logger.print_info("Section already exist. Skipped ...")
return
with open(cfg_file, "a") as f:
f.write("\n[include fluidd.cfg]")

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import json
import urllib.request
from json import JSONDecodeError
from pathlib import Path
from typing import List
from components.fluidd import FLUIDD_DIR, FLUIDD_BACKUP_DIR
from components.klipper.klipper import Klipper
from core.backup_manager.backup_manager import BackupManager
from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD
from utils.common import get_install_status_webui
from utils.logger import Logger
# TODO: could be extracted and made generic
def get_fluidd_status() -> str:
return get_install_status_webui(
FLUIDD_DIR,
NGINX_SITES_AVAILABLE.joinpath("fluidd"),
NGINX_CONFD.joinpath("upstreams.conf"),
NGINX_CONFD.joinpath("common_vars.conf"),
)
# TODO: could be extracted and made generic
def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None:
Logger.print_status("Link NGINX logs into log directory ...")
access_log = Path("/var/log/nginx/fluidd-access.log")
error_log = Path("/var/log/nginx/fluidd-error.log")
for instance in klipper_instances:
desti_access = instance.log_dir.joinpath("fluidd-access.log")
if not desti_access.exists():
desti_access.symlink_to(access_log)
desti_error = instance.log_dir.joinpath("fluidd-error.log")
if not desti_error.exists():
desti_error.symlink_to(error_log)
# TODO: could be extracted and made generic
def get_fluidd_local_version() -> str:
relinfo_file = FLUIDD_DIR.joinpath("release_info.json")
if not relinfo_file.is_file():
return "-"
with open(relinfo_file, "r") as f:
return json.load(f)["version"]
# TODO: could be extracted and made generic
def get_fluidd_remote_version() -> str:
url = "https://api.github.com/repos/fluidd-core/fluidd/tags"
try:
with urllib.request.urlopen(url) as response:
data = json.loads(response.read())
return data[0]["name"]
except (JSONDecodeError, TypeError):
return "ERROR"
# TODO: could be extracted and made generic
def backup_fluidd_data() -> None:
with open(FLUIDD_DIR.joinpath(".version"), "r") as v:
version = v.readlines()[0]
bm = BackupManager()
bm.backup_directory(f"fluidd-{version}", FLUIDD_DIR, FLUIDD_BACKUP_DIR)
bm.backup_file(NGINX_SITES_AVAILABLE.joinpath("fluidd"), FLUIDD_BACKUP_DIR)

View File

@@ -1,111 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import textwrap
from components.fluidd import fluidd_remove
from core.menus import BACK_HELP_FOOTER
from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal
class FluiddRemoveMenu(BaseMenu):
def __init__(self):
super().__init__(
header=False,
options={
"0": self.toggle_all,
"1": self.toggle_remove_fluidd,
"2": self.toggle_remove_fl_config,
"3": self.toggle_remove_updater_section,
"4": self.toggle_remove_printer_cfg_include,
"5": self.run_removal_process,
},
footer_type=BACK_HELP_FOOTER,
)
self.remove_fluidd = False
self.remove_fl_config = False
self.remove_updater_section = False
self.remove_printer_cfg_include = False
def print_menu(self) -> None:
header = " [ Remove Fluidd ] "
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]"
unchecked = "[ ]"
o1 = checked if self.remove_fluidd else unchecked
o2 = checked if self.remove_fl_config else unchecked
o3 = checked if self.remove_updater_section else unchecked
o4 = checked if self.remove_printer_cfg_include else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Enter a number and hit enter to select / deselect |
| the specific option for removal. |
|-------------------------------------------------------|
| 0) Select everything |
|-------------------------------------------------------|
| 1) {o1} Remove Fluidd |
| 2) {o2} Remove fluidd-config |
| |
| printer.cfg & moonraker.conf |
| 3) {o3} Remove Moonraker update section |
| 4) {o4} Remove printer.cfg include |
|-------------------------------------------------------|
| 5) Continue |
"""
)[1:]
print(menu, end="")
def toggle_all(self, **kwargs) -> None:
self.remove_fluidd = True
self.remove_fl_config = True
self.remove_updater_section = True
self.remove_printer_cfg_include = True
def toggle_remove_fluidd(self, **kwargs) -> None:
self.remove_fluidd = not self.remove_fluidd
def toggle_remove_fl_config(self, **kwargs) -> None:
self.remove_fl_config = not self.remove_fl_config
def toggle_remove_updater_section(self, **kwargs) -> None:
self.remove_updater_section = not self.remove_updater_section
def toggle_remove_printer_cfg_include(self, **kwargs) -> None:
self.remove_printer_cfg_include = not self.remove_printer_cfg_include
def run_removal_process(self, **kwargs) -> None:
if (
not self.remove_fluidd
and not self.remove_fl_config
and not self.remove_updater_section
and not self.remove_printer_cfg_include
):
error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}"
print(error)
return
fluidd_remove.run_fluidd_removal(
remove_fluidd=self.remove_fluidd,
remove_fl_config=self.remove_fl_config,
remove_mr_updater_section=self.remove_updater_section,
remove_flc_printer_cfg_include=self.remove_printer_cfg_include,
)
self.remove_fluidd = False
self.remove_fl_config = False
self.remove_updater_section = False
self.remove_printer_cfg_include = False

View File

@@ -28,7 +28,7 @@ class KlipperRemoveMenu(BaseMenu):
"2": self.toggle_remove_klipper_dir,
"3": self.toggle_remove_klipper_env,
"4": self.toggle_delete_klipper_logs,
"5": self.run_removal_process,
"c": self.run_removal_process,
},
footer_type=BACK_HELP_FOOTER,
)
@@ -62,7 +62,7 @@ class KlipperRemoveMenu(BaseMenu):
| 3) {o3} Remove Python Environment |
| 4) {o4} Delete all Log-Files |
|-------------------------------------------------------|
| 5) Continue |
| C) Continue |
"""
)[1:]
print(menu, end="")

View File

@@ -1,27 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent
MAINSAIL_DIR = Path.home().joinpath("mainsail")
MAINSAIL_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
MAINSAIL_CONFIG_DIR = Path.home().joinpath("mainsail-config")
MAINSAIL_CONFIG_JSON = MAINSAIL_DIR.joinpath("config.json")
MAINSAIL_URL = (
"https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
)
MAINSAIL_UNSTABLE_URL = (
"https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip"
)
MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git"

View File

@@ -1,6 +0,0 @@
[update_manager mainsail-config]
type: git_repo
primary_branch: master
path: ~/mainsail-config
origin: https://github.com/mainsail-crew/mainsail-config.git
managed_services: klipper

View File

@@ -1,5 +0,0 @@
[update_manager mainsail]
type: web
channel: stable
repo: mainsail-crew/mainsail
path: ~/mainsail

View File

@@ -1,95 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import textwrap
from core.menus.base_menu import print_back_footer
from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN
def print_moonraker_not_found_dialog():
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
line2 = f"{COLOR_YELLOW}No local Moonraker installation was found!{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| {line1:<63}|
| {line2:<63}|
|-------------------------------------------------------|
| It is possible to install Mainsail without a local |
| Moonraker installation. If you continue, you need to |
| make sure, that Moonraker is installed on another |
| machine in your network. Otherwise Mainsail will NOT |
| work correctly. |
"""
)[1:]
print(dialog, end="")
print_back_footer()
def print_mainsail_already_installed_dialog():
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
line2 = f"{COLOR_YELLOW}Mainsail seems to be already installed!{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| {line1:<63}|
| {line2:<63}|
|-------------------------------------------------------|
| If you continue, your current Mainsail installation |
| will be overwritten. You will not loose any printer |
| configurations and the Moonraker database will remain |
| untouched. |
"""
)[1:]
print(dialog, end="")
print_back_footer()
def print_install_mainsail_config_dialog():
dialog = textwrap.dedent(
f"""
/=======================================================\\
| It is recommended to use special macros in order to |
| have Mainsail fully functional and working. |
| |
| The recommended macros for Mainsail can be seen here: |
| https://github.com/mainsail-crew/mainsail-config |
| |
| If you already use these macros skip this step. |
| Otherwise you should consider to answer with 'Y' to |
| download the recommended macros. |
\\=======================================================/
"""
)[1:]
print(dialog, end="")
def print_mainsail_port_select_dialog(port: str):
port = f"{COLOR_CYAN}{port}{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| Please select the port, Mainsail should be served on. |
| If you are unsure what to select, hit Enter to apply |
| the suggested value of: {port:38} |
| |
| In case you need Mainsail to be served on a specific |
| port, you can set it now. Make sure the port is not |
| used by any other application on your system! |
\\=======================================================/
"""
)[1:]
print(dialog, end="")

View File

@@ -1,164 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import shutil
import subprocess
from pathlib import Path
from typing import List
from components.klipper.klipper import Klipper
from components.mainsail import MAINSAIL_DIR, MAINSAIL_CONFIG_DIR
from components.mainsail.mainsail_utils import backup_config_json
from components.moonraker.moonraker import Moonraker
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager
from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
from utils.filesystem_utils import remove_file
from utils.logger import Logger
def run_mainsail_removal(
remove_mainsail: bool,
remove_ms_config: bool,
backup_ms_config_json: bool,
remove_mr_updater_section: bool,
remove_msc_printer_cfg_include: bool,
) -> None:
if backup_ms_config_json:
backup_config_json()
if remove_mainsail:
remove_mainsail_dir()
remove_nginx_config()
remove_nginx_logs()
if remove_mr_updater_section:
remove_updater_section("update_manager mainsail")
if remove_ms_config:
remove_mainsail_cfg_dir()
remove_mainsail_cfg_symlink()
if remove_mr_updater_section:
remove_updater_section("update_manager mainsail-config")
if remove_msc_printer_cfg_include:
remove_printer_cfg_include()
def remove_mainsail_dir() -> None:
Logger.print_status("Removing Mainsail ...")
if not MAINSAIL_DIR.exists():
Logger.print_info(f"'{MAINSAIL_DIR}' does not exist. Skipping ...")
return
try:
shutil.rmtree(MAINSAIL_DIR)
except OSError as e:
Logger.print_error(f"Unable to delete '{MAINSAIL_DIR}':\n{e}")
def remove_nginx_config() -> None:
Logger.print_status("Removing Mainsails NGINX config ...")
try:
remove_file(NGINX_SITES_AVAILABLE.joinpath("mainsail"), True)
remove_file(NGINX_SITES_ENABLED.joinpath("mainsail"), True)
except subprocess.CalledProcessError as e:
log = f"Unable to remove Mainsail NGINX config:\n{e.stderr.decode()}"
Logger.print_error(log)
def remove_nginx_logs() -> None:
Logger.print_status("Removing Mainsails NGINX logs ...")
try:
remove_file(Path("/var/log/nginx/mainsail-access.log"), True)
remove_file(Path("/var/log/nginx/mainsail-error.log"), True)
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
if not instances:
return
for instance in instances:
remove_file(instance.log_dir.joinpath("mainsail-access.log"))
remove_file(instance.log_dir.joinpath("mainsail-error.log"))
except (OSError, subprocess.CalledProcessError) as e:
Logger.print_error(f"Unable to NGINX logs:\n{e}")
def remove_updater_section(name: str) -> None:
Logger.print_status("Remove updater section from moonraker.conf ...")
im = InstanceManager(Moonraker)
instances: List[Moonraker] = im.instances
if not instances:
Logger.print_info("Moonraker not installed. Skipped ...")
return
for instance in instances:
Logger.print_status(f"Remove section '{name}' in '{instance.cfg_file}' ...")
if not instance.cfg_file.is_file():
Logger.print_info(f"'{instance.cfg_file}' does not exist. Skipped ...")
continue
cm = ConfigManager(instance.cfg_file)
if not cm.config.has_section(name):
Logger.print_info("Section not present. Skipped ...")
continue
cm.config.remove_section(name)
cm.write_config()
def remove_mainsail_cfg_dir() -> None:
Logger.print_status("Removing mainsail-config ...")
if not MAINSAIL_CONFIG_DIR.exists():
Logger.print_info(f"'{MAINSAIL_CONFIG_DIR}' does not exist. Skipping ...")
return
try:
shutil.rmtree(MAINSAIL_CONFIG_DIR)
except OSError as e:
Logger.print_error(f"Unable to delete '{MAINSAIL_CONFIG_DIR}':\n{e}")
def remove_mainsail_cfg_symlink() -> None:
Logger.print_status("Removing mainsail.cfg symlinks ...")
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
for instance in instances:
Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...")
try:
remove_file(instance.cfg_dir.joinpath("mainsail.cfg"))
except subprocess.CalledProcessError:
Logger.print_error("Failed to remove symlink!")
def remove_printer_cfg_include() -> None:
Logger.print_status("Remove mainsail-config include from printer.cfg ...")
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
if not instances:
Logger.print_info("Klipper not installed. Skipping ...")
return
for instance in instances:
log = f"Removing include from '{instance.cfg_file}' ..."
Logger.print_status(log)
if not instance.cfg_file.is_file():
continue
cm = ConfigManager(instance.cfg_file)
if not cm.config.has_section("include mainsail.cfg"):
Logger.print_info("Section not present. Skipped ...")
continue
cm.config.remove_section("include mainsail.cfg")
cm.write_config()

View File

@@ -1,256 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import subprocess
from pathlib import Path
from typing import List
from kiauh import KIAUH_CFG
from components.klipper.klipper import Klipper
from components.mainsail import (
MAINSAIL_URL,
MAINSAIL_DIR,
MAINSAIL_CONFIG_DIR,
MAINSAIL_CONFIG_REPO_URL,
MODULE_PATH,
)
from components.mainsail.mainsail_dialogs import (
print_moonraker_not_found_dialog,
print_mainsail_already_installed_dialog,
print_install_mainsail_config_dialog,
print_mainsail_port_select_dialog,
)
from components.mainsail.mainsail_utils import (
restore_config_json,
enable_mainsail_remotemode,
backup_config_json,
symlink_webui_nginx_log,
)
from components.moonraker.moonraker import Moonraker
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager
from core.repo_manager.repo_manager import RepoManager
from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
from utils.common import check_install_dependencies
from utils.filesystem_utils import (
unzip,
copy_upstream_nginx_cfg,
copy_common_vars_nginx_cfg,
create_nginx_cfg,
create_symlink,
remove_file,
)
from utils.input_utils import get_confirm, get_number_input
from utils.logger import Logger
from utils.system_utils import (
download_file,
set_nginx_permissions,
get_ipv4_addr,
control_systemd_service,
)
def install_mainsail() -> None:
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
enable_remotemode = False
if not mr_instances:
print_moonraker_not_found_dialog()
if not get_confirm("Continue Mainsail installation?", allow_go_back=True):
return
# if moonraker is not installed or multiple instances
# are installed we enable mainsails remote mode
if not mr_instances or len(mr_instances) > 1:
enable_remotemode = True
do_reinstall = False
if Path.home().joinpath("mainsail").exists():
print_mainsail_already_installed_dialog()
do_reinstall = get_confirm("Re-install Mainsail?", allow_go_back=True)
if do_reinstall:
backup_config_json(is_temp=True)
else:
return
kl_im = InstanceManager(Klipper)
kl_instances = kl_im.instances
install_ms_config = False
if kl_instances:
print_install_mainsail_config_dialog()
question = "Download the recommended macros?"
install_ms_config = get_confirm(question, allow_go_back=False)
# if a default port is configured in the kiauh.cfg, we use that for the port
# otherwise we default to port 80, but show the user a dialog to confirm/change that port
cm = ConfigManager(cfg_file=KIAUH_CFG)
default_port = cm.get_value("mainsail", "default_port")
is_valid_port = default_port and default_port.isdigit()
mainsail_port = default_port if is_valid_port else "80"
if not is_valid_port:
print_mainsail_port_select_dialog(mainsail_port)
mainsail_port = get_number_input(
"Configure Mainsail for port",
min_count=mainsail_port,
default=mainsail_port,
)
check_install_dependencies(["nginx"])
try:
download_mainsail()
if do_reinstall:
restore_config_json()
if enable_remotemode:
enable_mainsail_remotemode()
if mr_instances:
patch_moonraker_conf(
mr_instances,
"Mainsail",
"update_manager mainsail",
"mainsail-updater.conf",
)
mr_im.restart_all_instance()
if install_ms_config and kl_instances:
download_mainsail_cfg()
create_mainsail_cfg_symlink(kl_instances)
patch_moonraker_conf(
mr_instances,
"mainsail-config",
"update_manager mainsail-config",
"mainsail-config-updater.conf",
)
patch_printer_config(kl_instances)
kl_im.restart_all_instance()
copy_upstream_nginx_cfg()
copy_common_vars_nginx_cfg()
create_mainsail_nginx_cfg(mainsail_port)
if kl_instances:
symlink_webui_nginx_log(kl_instances)
control_systemd_service("nginx", "restart")
except Exception as e:
Logger.print_error(f"Mainsail installation failed!\n{e}")
return
log = f"Open Mainsail now on: http://{get_ipv4_addr()}:{mainsail_port}"
Logger.print_ok("Mainsail installation complete!", start="\n")
Logger.print_ok(log, prefix=False, end="\n\n")
def download_mainsail() -> None:
try:
Logger.print_status("Downloading Mainsail ...")
target = Path.home().joinpath("mainsail.zip")
download_file(MAINSAIL_URL, target, True)
Logger.print_ok("Download complete!")
Logger.print_status("Extracting mainsail.zip ...")
unzip(Path.home().joinpath("mainsail.zip"), MAINSAIL_DIR)
target.unlink(missing_ok=True)
Logger.print_ok("OK!")
except Exception:
Logger.print_error("Downloading Mainsail failed!")
raise
def update_mainsail() -> None:
Logger.print_status("Updating Mainsail ...")
backup_config_json(is_temp=True)
download_mainsail()
restore_config_json()
def download_mainsail_cfg() -> None:
try:
Logger.print_status("Downloading mainsail-config ...")
rm = RepoManager(MAINSAIL_CONFIG_REPO_URL, target_dir=MAINSAIL_CONFIG_DIR)
rm.clone_repo()
except Exception:
Logger.print_error("Downloading mainsail-config failed!")
raise
def create_mainsail_cfg_symlink(klipper_instances: List[Klipper]) -> None:
Logger.print_status("Create symlink of mainsail.cfg ...")
source = Path(MAINSAIL_CONFIG_DIR, "mainsail.cfg")
for instance in klipper_instances:
target = instance.cfg_dir
Logger.print_status(f"Linking {source} to {target}")
try:
create_symlink(source, target)
except subprocess.CalledProcessError:
Logger.print_error("Creating symlink failed!")
def create_mainsail_nginx_cfg(port: int) -> None:
root_dir = MAINSAIL_DIR
source = NGINX_SITES_AVAILABLE.joinpath("mainsail")
target = NGINX_SITES_ENABLED.joinpath("mainsail")
try:
Logger.print_status("Creating NGINX config for Mainsail ...")
remove_file(Path("/etc/nginx/sites-enabled/default"), True)
create_nginx_cfg("mainsail", port, root_dir)
create_symlink(source, target, True)
set_nginx_permissions()
Logger.print_ok("NGINX config for Mainsail successfully created.")
except Exception:
Logger.print_error("Creating NGINX config for Mainsail failed!")
raise
def patch_moonraker_conf(
moonraker_instances: List[Moonraker],
name: str,
section_name: str,
template_file: str,
) -> None:
for instance in moonraker_instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Add {name} update section to '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
return
cm = ConfigManager(cfg_file)
if cm.config.has_section(section_name):
Logger.print_info("Section already exist. Skipped ...")
return
template = MODULE_PATH.joinpath("assets", template_file)
with open(template, "r") as t:
template_content = "\n"
template_content += t.read()
with open(cfg_file, "a") as f:
f.write(template_content)
def patch_printer_config(klipper_instances: List[Klipper]) -> None:
for instance in klipper_instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Including mainsail-config in '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
return
cm = ConfigManager(cfg_file)
if cm.config.has_section("include mainsail.cfg"):
Logger.print_info("Section already exist. Skipped ...")
return
with open(cfg_file, "a") as f:
f.write("\n[include mainsail.cfg]")

View File

@@ -1,117 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import json
import shutil
from json import JSONDecodeError
from pathlib import Path
from typing import List
import urllib.request
from components.klipper.klipper import Klipper
from components.mainsail import (
MAINSAIL_CONFIG_JSON,
MAINSAIL_DIR,
MAINSAIL_BACKUP_DIR,
)
from core.backup_manager.backup_manager import BackupManager
from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD
from utils.common import get_install_status_webui
from utils.logger import Logger
def get_mainsail_status() -> str:
return get_install_status_webui(
MAINSAIL_DIR,
NGINX_SITES_AVAILABLE.joinpath("mainsail"),
NGINX_CONFD.joinpath("upstreams.conf"),
NGINX_CONFD.joinpath("common_vars.conf"),
)
def backup_config_json(is_temp=False) -> None:
Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...")
bm = BackupManager()
if is_temp:
fn = Path.home().joinpath("config.json.kiauh.bak")
bm.backup_file(MAINSAIL_CONFIG_JSON, custom_filename=fn)
else:
bm.backup_file(MAINSAIL_CONFIG_JSON)
def restore_config_json() -> None:
try:
Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...")
source = Path.home().joinpath("config.json.kiauh.bak")
shutil.copy(source, MAINSAIL_CONFIG_JSON)
except OSError:
Logger.print_info("Unable to restore config.json. Skipped ...")
def enable_mainsail_remotemode() -> None:
Logger.print_status("Enable Mainsails remote mode ...")
with open(MAINSAIL_CONFIG_JSON, "r") as f:
config_data = json.load(f)
if config_data["instancesDB"] == "browser":
Logger.print_info("Remote mode already configured. Skipped ...")
return
Logger.print_status("Setting instance storage location to 'browser' ...")
config_data["instancesDB"] = "browser"
with open(MAINSAIL_CONFIG_JSON, "w") as f:
json.dump(config_data, f, indent=4)
Logger.print_ok("Mainsails remote mode enabled!")
def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None:
Logger.print_status("Link NGINX logs into log directory ...")
access_log = Path("/var/log/nginx/mainsail-access.log")
error_log = Path("/var/log/nginx/mainsail-error.log")
for instance in klipper_instances:
desti_access = instance.log_dir.joinpath("mainsail-access.log")
if not desti_access.exists():
desti_access.symlink_to(access_log)
desti_error = instance.log_dir.joinpath("mainsail-error.log")
if not desti_error.exists():
desti_error.symlink_to(error_log)
def get_mainsail_local_version() -> str:
relinfo_file = MAINSAIL_DIR.joinpath("release_info.json")
if not relinfo_file.is_file():
return "-"
with open(relinfo_file, "r") as f:
return json.load(f)["version"]
def get_mainsail_remote_version() -> str:
url = "https://api.github.com/repos/mainsail-crew/mainsail/tags"
try:
with urllib.request.urlopen(url) as response:
data = json.loads(response.read())
return data[0]["name"]
except (JSONDecodeError, TypeError):
return "ERROR"
def backup_mainsail_data() -> None:
with open(MAINSAIL_DIR.joinpath(".version"), "r") as v:
version = v.readlines()[0]
bm = BackupManager()
bm.backup_directory(f"mainsail-{version}", MAINSAIL_DIR, MAINSAIL_BACKUP_DIR)
bm.backup_file(MAINSAIL_CONFIG_JSON, MAINSAIL_BACKUP_DIR)
bm.backup_file(NGINX_SITES_AVAILABLE.joinpath("mainsail"), MAINSAIL_BACKUP_DIR)

View File

@@ -1,122 +0,0 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import textwrap
from components.mainsail import mainsail_remove
from core.menus import BACK_HELP_FOOTER
from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal
class MainsailRemoveMenu(BaseMenu):
def __init__(self):
super().__init__(
header=False,
options={
"0": self.toggle_all,
"1": self.toggle_remove_mainsail,
"2": self.toggle_remove_ms_config,
"3": self.toggle_backup_config_json,
"4": self.toggle_remove_updater_section,
"5": self.toggle_remove_printer_cfg_include,
"6": self.run_removal_process,
},
footer_type=BACK_HELP_FOOTER,
)
self.remove_mainsail = False
self.remove_ms_config = False
self.backup_config_json = False
self.remove_updater_section = False
self.remove_printer_cfg_include = False
def print_menu(self) -> None:
header = " [ Remove Mainsail ] "
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]"
unchecked = "[ ]"
o1 = checked if self.remove_mainsail else unchecked
o2 = checked if self.remove_ms_config else unchecked
o3 = checked if self.backup_config_json else unchecked
o4 = checked if self.remove_updater_section else unchecked
o5 = checked if self.remove_printer_cfg_include else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Enter a number and hit enter to select / deselect |
| the specific option for removal. |
|-------------------------------------------------------|
| 0) Select everything |
|-------------------------------------------------------|
| 1) {o1} Remove Mainsail |
| 2) {o2} Remove mainsail-config |
| 3) {o3} Backup config.json |
| |
| printer.cfg & moonraker.conf |
| 4) {o4} Remove Moonraker update section |
| 5) {o5} Remove printer.cfg include |
|-------------------------------------------------------|
| 6) Continue |
"""
)[1:]
print(menu, end="")
def toggle_all(self, **kwargs) -> None:
self.remove_mainsail = True
self.remove_ms_config = True
self.backup_config_json = True
self.remove_updater_section = True
self.remove_printer_cfg_include = True
def toggle_remove_mainsail(self, **kwargs) -> None:
self.remove_mainsail = not self.remove_mainsail
def toggle_remove_ms_config(self, **kwargs) -> None:
self.remove_ms_config = not self.remove_ms_config
def toggle_backup_config_json(self, **kwargs) -> None:
self.backup_config_json = not self.backup_config_json
def toggle_remove_updater_section(self, **kwargs) -> None:
self.remove_updater_section = not self.remove_updater_section
def toggle_remove_printer_cfg_include(self, **kwargs) -> None:
self.remove_printer_cfg_include = not self.remove_printer_cfg_include
def run_removal_process(self, **kwargs) -> None:
if (
not self.remove_mainsail
and not self.remove_ms_config
and not self.backup_config_json
and not self.remove_updater_section
and not self.remove_printer_cfg_include
):
error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}"
print(error)
return
mainsail_remove.run_mainsail_removal(
remove_mainsail=self.remove_mainsail,
remove_ms_config=self.remove_ms_config,
backup_ms_config_json=self.backup_config_json,
remove_mr_updater_section=self.remove_updater_section,
remove_msc_printer_cfg_include=self.remove_printer_cfg_include,
)
self.remove_mainsail = False
self.remove_ms_config = False
self.backup_config_json = False
self.remove_updater_section = False
self.remove_printer_cfg_include = False

View File

@@ -29,7 +29,7 @@ class MoonrakerRemoveMenu(BaseMenu):
"3": self.toggle_remove_moonraker_env,
"4": self.toggle_remove_moonraker_polkit,
"5": self.toggle_delete_moonraker_logs,
"6": self.run_removal_process,
"c": self.run_removal_process,
},
footer_type=BACK_HELP_FOOTER,
)
@@ -66,7 +66,7 @@ class MoonrakerRemoveMenu(BaseMenu):
| 4) {o4} Remove Policy Kit Rules |
| 5) {o5} Delete all Log-Files |
|-------------------------------------------------------|
| 6) Continue |
| C) Continue |
"""
)[1:]
print(menu, end="")

View File

@@ -14,11 +14,11 @@ import sys
from pathlib import Path
from typing import List
from components.webui_client import MAINSAIL_DIR
from components.webui_client.client_utils import enable_mainsail_remotemode
from kiauh import KIAUH_CFG
from components.klipper.klipper import Klipper
from components.klipper.klipper_dialogs import print_instance_overview
from components.mainsail import MAINSAIL_DIR
from components.mainsail.mainsail_utils import enable_mainsail_remotemode
from components.moonraker import (
EXIT_MOONRAKER_SETUP,
DEFAULT_MOONRAKER_REPO_URL,

View File

@@ -12,8 +12,6 @@
import shutil
from typing import Dict, Literal, List, Union
from components.mainsail import MAINSAIL_DIR
from components.mainsail.mainsail_utils import enable_mainsail_remotemode
from components.moonraker import (
DEFAULT_MOONRAKER_PORT,
MODULE_PATH,
@@ -23,6 +21,8 @@ from components.moonraker import (
MOONRAKER_DB_BACKUP_DIR,
)
from components.moonraker.moonraker import Moonraker
from components.webui_client import MAINSAIL_DIR
from components.webui_client.client_utils import enable_mainsail_remotemode
from core.backup_manager.backup_manager import BackupManager
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
from pathlib import Path
from typing import Literal, TypedDict, Set
from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent
###########
# MAINSAIL
###########
MAINSAIL_DIR = Path.home().joinpath("mainsail")
MAINSAIL_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
MAINSAIL_CONFIG_DIR = Path.home().joinpath("mainsail-config")
MAINSAIL_CONFIG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups")
MAINSAIL_CONFIG_REPO_URL = "https://github.com/mainsail-crew/mainsail-config.git"
MAINSAIL_CONFIG_JSON = MAINSAIL_DIR.joinpath("config.json")
MAINSAIL_URL = (
"https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
)
MAINSAIL_PRE_RLS_URL = (
"https://github.com/mainsail-crew/mainsail/releases/download/%TAG%/mainsail.zip"
)
MAINSAIL_TAGS_URL = "https://api.github.com/repos/mainsail-crew/mainsail/tags"
#########
# FLUIDD
#########
FLUIDD_DIR = Path.home().joinpath("fluidd")
FLUIDD_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
FLUIDD_CONFIG_DIR = Path.home().joinpath("fluidd-config")
FLUIDD_CONFIG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups")
FLUIDD_CONFIG_REPO_URL = "https://github.com/fluidd-core/fluidd-config.git"
FLUIDD_URL = "https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip"
FLUIDD_PRE_RLS_URL = (
"https://github.com/fluidd-core/fluidd/releases/download/%TAG%/fluidd.zip"
)
FLUIDD_TAGS_URL = "https://api.github.com/repos/fluidd-core/fluidd/tags"
ClientName = Literal["mainsail", "fluidd"]
ClientConfigName = Literal["mainsail-config", "fluidd-config"]
class ClientData(TypedDict):
name: ClientName
display_name: str
dir: Path
backup_dir: Path
url: str
pre_release_url: str
tags_url: str
remote_mode: bool # required only for Mainsail
mr_conf_repo: str
mr_conf_path: str
client_config: "ClientConfigData"
class ClientConfigData(TypedDict):
name: ClientConfigName
display_name: str
cfg_filename: str
dir: Path
backup_dir: Path
url: str
printer_cfg_section: str
mr_conf_path: str
mr_conf_origin: str

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import shutil
import subprocess
from typing import List
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from components.webui_client import ClientConfigData
from core.instance_manager.instance_manager import InstanceManager
from utils.filesystem_utils import remove_file, remove_config_section
from utils.logger import Logger
def run_client_config_removal(
client_config: ClientConfigData,
remove_moonraker_conf_section: bool,
remove_printer_cfg_include: bool,
kl_instances: List[Klipper],
mr_instances: List[Moonraker],
) -> None:
remove_client_config_dir(client_config)
remove_client_config_symlink(client_config)
if remove_moonraker_conf_section:
remove_config_section(
f"update_manager {client_config.get('name')}", mr_instances
)
if remove_printer_cfg_include:
remove_config_section(client_config.get("printer_cfg_section"), kl_instances)
def remove_client_config_dir(client_config: ClientConfigData) -> None:
Logger.print_status(f"Removing {client_config.get('name')} ...")
client_config_dir = client_config.get("dir")
if not client_config_dir.exists():
Logger.print_info(f"'{client_config_dir}' does not exist. Skipping ...")
return
try:
shutil.rmtree(client_config_dir)
except OSError as e:
Logger.print_error(f"Unable to delete '{client_config_dir}':\n{e}")
def remove_client_config_symlink(client_config: ClientConfigData) -> None:
Logger.print_status(f"Removing {client_config.get('cfg_filename')} symlinks ...")
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
for instance in instances:
Logger.print_status(f"Removing symlink from '{instance.cfg_file}' ...")
try:
remove_file(instance.cfg_dir.joinpath(client_config.get("cfg_filename")))
except subprocess.CalledProcessError:
Logger.print_error("Failed to remove symlink!")

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import shutil
import subprocess
from pathlib import Path
from typing import List, get_args
from kiauh import KIAUH_CFG
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from components.webui_client import ClientConfigData, ClientName, ClientData
from components.webui_client.client_dialogs import print_client_already_installed_dialog
from components.webui_client.client_utils import (
load_client_data,
backup_client_config_data,
)
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager
from core.repo_manager.repo_manager import RepoManager
from utils.filesystem_utils import (
create_symlink,
add_config_section,
)
from utils.input_utils import get_confirm
from utils.logger import Logger
def install_client_config(client_name: ClientName) -> None:
client: ClientData = load_client_data(client_name)
client_config: ClientConfigData = client.get("client_config")
d_name = client_config.get("display_name")
if check_existing_client_config_install(client_name):
Logger.print_info("Another Client-Config is already installed! Skipped ...")
return
if client_config.get("dir").exists():
print_client_already_installed_dialog(d_name)
if get_confirm(f"Re-install {d_name}?", allow_go_back=True):
shutil.rmtree(client_config.get("dir"))
else:
return
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
kl_im = InstanceManager(Klipper)
kl_instances = kl_im.instances
try:
download_client_config(client_config)
create_client_config_symlink(client_config, kl_instances)
add_config_section(
section=f"update_manager {client_config.get('name')}",
instances=mr_instances,
options=[
("type", "git_repo"),
("primary_branch", "master"),
("path", client_config.get("mr_conf_path")),
("origin", client_config.get("mr_conf_origin")),
("managed_services", "klipper"),
],
)
add_config_section(client_config.get("printer_cfg_section"), kl_instances)
kl_im.restart_all_instance()
except Exception as e:
Logger.print_error(f"{d_name} installation failed!\n{e}")
return
Logger.print_ok(f"{d_name} installation complete!", start="\n")
def check_existing_client_config_install(client_name: ClientName) -> bool:
# check if any other client-configs are present
# as they can conflict each other, or are at least
# redundant to have, so we skip the installation
client_list = list(get_args(ClientName))
client_list.remove(client_name)
for c in client_list:
c_data: ClientData = load_client_data(c)
c_config_data: ClientConfigData = c_data.get("client_config")
if c_config_data.get("dir").exists():
return True
return False
def download_client_config(client_config: ClientConfigData) -> None:
try:
Logger.print_status(f"Downloading {client_config.get('display_name')} ...")
rm = RepoManager(
client_config.get("url"), target_dir=str(client_config.get("dir"))
)
rm.clone_repo()
except Exception:
Logger.print_error(f"Downloading {client_config.get('display_name')} failed!")
raise
def update_client_config(client: ClientData) -> None:
client_config: ClientConfigData = client.get("client_config")
Logger.print_status(f"Updating {client_config.get('display_name')} ...")
cm = ConfigManager(cfg_file=KIAUH_CFG)
if cm.get_value("kiauh", "backup_before_update"):
backup_client_config_data(client)
repo_manager = RepoManager(
repo=client_config.get("url"),
branch="master",
target_dir=str(client_config.get("dir")),
)
repo_manager.pull_repo()
Logger.print_ok(f"Successfully updated {client_config.get('display_name')}.")
Logger.print_warn("Remember to restart Klipper to reload the configurations!")
def create_client_config_symlink(
client_config: ClientConfigData, klipper_instances: List[Klipper] = None
) -> None:
if klipper_instances is None:
kl_im = InstanceManager(Klipper)
klipper_instances = kl_im.instances
Logger.print_status(f"Create symlink for {client_config.get('cfg_filename')} ...")
source = Path(client_config.get("dir"), client_config.get("cfg_filename"))
for instance in klipper_instances:
target = instance.cfg_dir
Logger.print_status(f"Linking {source} to {target}")
try:
create_symlink(source, target)
except subprocess.CalledProcessError:
Logger.print_error("Creating symlink failed!")

View File

@@ -12,6 +12,7 @@
import textwrap
from typing import List
from components.webui_client import ClientData
from core.menus.base_menu import print_back_footer
from utils.constants import RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN
@@ -25,10 +26,10 @@ def print_moonraker_not_found_dialog():
| {line1:<63}|
| {line2:<63}|
|-------------------------------------------------------|
| It is possible to install Fluidd without a local |
| It is possible to install Mainsail without a local |
| Moonraker installation. If you continue, you need to |
| make sure, that Moonraker is installed on another |
| machine in your network. Otherwise Fluidd will NOT |
| machine in your network. Otherwise Mainsail will NOT |
| work correctly. |
"""
)[1:]
@@ -37,19 +38,18 @@ def print_moonraker_not_found_dialog():
print_back_footer()
def print_fluidd_already_installed_dialog():
def print_client_already_installed_dialog(name: str):
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
line2 = f"{COLOR_YELLOW}Fluidd seems to be already installed!{RESET_FORMAT}"
line2 = f"{COLOR_YELLOW}{name} seems to be already installed!{RESET_FORMAT}"
line3 = f"If you continue, your current {name}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| {line1:<63}|
| {line2:<63}|
|-------------------------------------------------------|
| If you continue, your current Fluidd installation |
| will be overwritten. You will not loose any printer |
| configurations and the Moonraker database will remain |
| untouched. |
| {line3:<54}|
| installation will be overwritten. |
"""
)[1:]
@@ -57,38 +57,21 @@ def print_fluidd_already_installed_dialog():
print_back_footer()
def print_install_fluidd_config_dialog():
dialog = textwrap.dedent(
f"""
/=======================================================\\
| It is recommended to use special macros in order to |
| have Fluidd fully functional and working. |
| |
| The recommended macros for Fluidd can be seen here: |
| https://github.com/fluidd-core/fluidd-config |
| |
| If you already use these macros skip this step. |
| Otherwise you should consider to answer with 'Y' to |
| download the recommended macros. |
\\=======================================================/
"""
)[1:]
print(dialog, end="")
def print_fluidd_port_select_dialog(port: str, ports_in_use: List[str]):
def print_client_port_select_dialog(name: str, port: str, ports_in_use: List[str]):
port = f"{COLOR_CYAN}{port}{RESET_FORMAT}"
line1 = f"Please select the port, {name} should be served on."
line2 = f"In case you need {name} to be served on a specific"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| Please select the port, Fluidd should be served on. |
| {line1:<54}|
| If you are unsure what to select, hit Enter to apply |
| the suggested value of: {port:38} |
| |
| In case you need Fluidd to be served on a specific |
| {line2:<54}|
| port, you can set it now. Make sure the port is not |
| used by any other application on your system! |
\\=======================================================/
"""
)[1:]
@@ -102,3 +85,27 @@ def print_fluidd_port_select_dialog(port: str, ports_in_use: List[str]):
dialog += "\\=======================================================/\n"
print(dialog, end="")
def print_install_client_config_dialog(client: ClientData):
name = client.get("display_name")
url = client.get("client_config").get("url").replace(".git", "")
line1 = f"have {name} fully functional and working."
line2 = f"The recommended macros for {name} can be seen here:"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| It is recommended to use special macros in order to |
| {line1:<54}|
| |
| {line2:<54}|
| {url:<54}|
| |
| If you already use these macros skip this step. |
| Otherwise you should consider to answer with 'Y' to |
| download the recommended macros. |
\\=======================================================/
"""
)[1:]
print(dialog, end="")

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import shutil
from typing import List
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from components.webui_client import ClientData
from components.webui_client.client_config.client_config_remove import (
run_client_config_removal,
)
from components.webui_client.client_utils import backup_mainsail_config_json
from core.instance_manager.instance_manager import InstanceManager
from utils.filesystem_utils import (
remove_nginx_config,
remove_nginx_logs,
remove_config_section,
)
from utils.logger import Logger
def run_client_removal(
client: ClientData,
rm_client: bool,
rm_client_config: bool,
backup_ms_config_json: bool,
rm_moonraker_conf_section: bool,
rm_printer_cfg_section: bool,
) -> None:
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
kl_im = InstanceManager(Klipper)
kl_instances: List[Klipper] = kl_im.instances
if backup_ms_config_json and client.get("name") == "mainsail":
backup_mainsail_config_json()
if rm_client:
client_name = client.get("name")
remove_client_dir(client)
remove_nginx_config(client_name)
remove_nginx_logs(client_name)
if rm_moonraker_conf_section:
section = f"update_manager {client_name}"
remove_config_section(section, mr_instances)
if rm_client_config:
run_client_config_removal(
client.get("client_config"),
rm_moonraker_conf_section,
rm_printer_cfg_section,
kl_instances,
mr_instances,
)
def remove_client_dir(client: ClientData) -> None:
Logger.print_status(f"Removing {client.get('display_name')} ...")
client_dir = client.get("dir")
if not client.get("dir").exists():
Logger.print_info(f"'{client_dir}' does not exist. Skipping ...")
return
try:
shutil.rmtree(client_dir)
except OSError as e:
Logger.print_error(f"Unable to delete '{client_dir}':\n{e}")

View File

@@ -0,0 +1,207 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
from pathlib import Path
from typing import List
from components.klipper.klipper import Klipper
from components.webui_client import (
ClientName,
ClientData,
)
from components.moonraker.moonraker import Moonraker
from components.webui_client.client_config.client_config_setup import (
install_client_config,
check_existing_client_config_install,
)
from components.webui_client.client_dialogs import (
print_moonraker_not_found_dialog,
print_client_port_select_dialog,
print_install_client_config_dialog,
)
from components.webui_client.client_utils import (
backup_mainsail_config_json,
restore_mainsail_config_json,
enable_mainsail_remotemode,
symlink_webui_nginx_log,
load_client_data,
)
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.instance_manager import InstanceManager
from kiauh import KIAUH_CFG
from utils import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
from utils.common import check_install_dependencies
from utils.filesystem_utils import (
unzip,
copy_upstream_nginx_cfg,
copy_common_vars_nginx_cfg,
create_nginx_cfg,
create_symlink,
remove_file,
add_config_section,
read_ports_from_nginx_configs,
is_valid_port,
get_next_free_port,
)
from utils.input_utils import get_confirm, get_number_input
from utils.logger import Logger
from utils.system_utils import (
download_file,
set_nginx_permissions,
get_ipv4_addr,
control_systemd_service,
)
def install_client(client_name: ClientName) -> None:
client: ClientData = load_client_data(client_name)
d_name = client.get("display_name")
if client is None:
Logger.print_error("Missing parameter client_name!")
return
if client.get("dir").exists():
Logger.print_info(
f"{client.get('display_name')} seems to be already installed! Skipped ..."
)
return
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
enable_remotemode = False
if not mr_instances:
print_moonraker_not_found_dialog()
if not get_confirm(
f"Continue {d_name} installation?",
allow_go_back=True,
):
return
# if moonraker is not installed or multiple instances
# are installed we enable mainsails remote mode
if client.get("remote_mode") and not mr_instances or len(mr_instances) > 1:
enable_remotemode = True
kl_im = InstanceManager(Klipper)
kl_instances = kl_im.instances
install_client_cfg = False
client_config = client.get("client_config")
if (
kl_instances
and not client_config.get("dir").exists()
and not check_existing_client_config_install(client.get("name"))
):
print_install_client_config_dialog(client)
question = f"Download the recommended {client_config.get('display_name')}?"
install_client_cfg = get_confirm(question, allow_go_back=False)
cm = ConfigManager(cfg_file=KIAUH_CFG)
client_port = cm.get_value(client.get("name"), "port")
ports_in_use = read_ports_from_nginx_configs()
# check if configured port is a valid number and not in use already
valid_port = is_valid_port(client_port, ports_in_use)
while not valid_port:
next_port = get_next_free_port(ports_in_use)
print_client_port_select_dialog(d_name, next_port, ports_in_use)
client_port = str(
get_number_input(
f"Configure {d_name} for port",
min_count=int(next_port),
default=next_port,
)
)
valid_port = is_valid_port(client_port, ports_in_use)
check_install_dependencies(["nginx"])
try:
download_client(client)
if enable_remotemode and client.get("name") == "mainsail":
enable_mainsail_remotemode()
if mr_instances:
add_config_section(
section=f"update_manager {client.get('name')}",
instances=mr_instances,
options=[
("type", "web"),
("channel", "stable"),
("repo", client.get("mr_conf_repo")),
("path", client.get("mr_conf_path")),
],
)
mr_im.restart_all_instance()
if install_client_cfg and kl_instances:
install_client_config(client.get("name"))
copy_upstream_nginx_cfg()
copy_common_vars_nginx_cfg()
create_client_nginx_cfg(client, client_port)
if kl_instances:
symlink_webui_nginx_log(kl_instances)
control_systemd_service("nginx", "restart")
except Exception as e:
Logger.print_error(f"{d_name} installation failed!\n{e}")
return
log = f"Open {d_name} now on: http://{get_ipv4_addr()}:{client_port}"
Logger.print_ok(f"{d_name} installation complete!", start="\n")
Logger.print_ok(log, prefix=False, end="\n\n")
def download_client(client: ClientData) -> None:
zipfile = f"{client.get('name').lower()}.zip"
target = Path().home().joinpath(zipfile)
try:
Logger.print_status(f"Downloading {zipfile} ...")
download_file(client.get("url"), target, True)
Logger.print_ok("Download complete!")
Logger.print_status(f"Extracting {zipfile} ...")
unzip(target, client.get("dir"))
target.unlink(missing_ok=True)
Logger.print_ok("OK!")
except Exception:
Logger.print_error(f"Downloading {zipfile} failed!")
raise
def update_client(client: ClientData) -> None:
Logger.print_status(f"Updating {client.get('display_name')} ...")
if client.get("name") == "mainsail":
backup_mainsail_config_json(is_temp=True)
download_client(client)
if client.get("name") == "mainsail":
restore_mainsail_config_json()
def create_client_nginx_cfg(client: ClientData, port: int) -> None:
d_name = client.get("display_name")
root_dir = client.get("dir")
source = NGINX_SITES_AVAILABLE.joinpath(client.get("name"))
target = NGINX_SITES_ENABLED.joinpath(client.get("name"))
try:
Logger.print_status(f"Creating NGINX config for {d_name} ...")
remove_file(Path("/etc/nginx/sites-enabled/default"), True)
create_nginx_cfg(client.get("name"), port, root_dir)
create_symlink(source, target, True)
set_nginx_permissions()
Logger.print_ok(f"NGINX config for {d_name} successfully created.")
except Exception:
Logger.print_error(f"Creating NGINX config for {d_name} failed!")
raise

View File

@@ -0,0 +1,237 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import json
import shutil
from json import JSONDecodeError
from pathlib import Path
from typing import List, Optional, Dict, Literal, Union
import urllib.request
from components.klipper.klipper import Klipper
from components.webui_client import (
MAINSAIL_CONFIG_JSON,
MAINSAIL_DIR,
MAINSAIL_BACKUP_DIR,
FLUIDD_PRE_RLS_URL,
FLUIDD_BACKUP_DIR,
FLUIDD_URL,
FLUIDD_DIR,
ClientData,
FLUIDD_CONFIG_REPO_URL,
FLUIDD_CONFIG_DIR,
ClientConfigData,
MAINSAIL_PRE_RLS_URL,
MAINSAIL_URL,
MAINSAIL_CONFIG_REPO_URL,
MAINSAIL_CONFIG_DIR,
ClientName,
MAINSAIL_TAGS_URL,
FLUIDD_TAGS_URL,
FLUIDD_CONFIG_BACKUP_DIR,
MAINSAIL_CONFIG_BACKUP_DIR,
)
from core.backup_manager.backup_manager import BackupManager
from core.repo_manager.repo_manager import RepoManager
from utils import NGINX_SITES_AVAILABLE, NGINX_CONFD
from utils.common import get_install_status_webui, get_install_status_common
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
from utils.logger import Logger
def load_client_data(client_name: ClientName) -> Optional[ClientData]:
client_data = None
if client_name == "mainsail":
client_config_data = ClientConfigData(
name="mainsail-config",
display_name="Mainsail-Config",
cfg_filename="mainsail.cfg",
dir=MAINSAIL_CONFIG_DIR,
backup_dir=MAINSAIL_CONFIG_BACKUP_DIR,
url=MAINSAIL_CONFIG_REPO_URL,
printer_cfg_section="include mainsail.cfg",
mr_conf_path="~/mainsail-config",
mr_conf_origin=MAINSAIL_CONFIG_REPO_URL,
)
client_data = ClientData(
name=client_name,
display_name=client_name.capitalize(),
dir=MAINSAIL_DIR,
backup_dir=MAINSAIL_BACKUP_DIR,
url=MAINSAIL_URL,
pre_release_url=MAINSAIL_PRE_RLS_URL,
tags_url=MAINSAIL_TAGS_URL,
remote_mode=True,
mr_conf_repo="mainsail-crew/mainsail",
mr_conf_path="~/mainsail",
client_config=client_config_data,
)
elif client_name == "fluidd":
client_config_data = ClientConfigData(
name="fluidd-config",
display_name="Fluidd-Config",
cfg_filename="fluidd.cfg",
dir=FLUIDD_CONFIG_DIR,
backup_dir=FLUIDD_CONFIG_BACKUP_DIR,
url=FLUIDD_CONFIG_REPO_URL,
printer_cfg_section="include fluidd.cfg",
mr_conf_path="~/fluidd-config",
mr_conf_origin=FLUIDD_CONFIG_REPO_URL,
)
client_data = ClientData(
name=client_name,
display_name=client_name.capitalize(),
dir=FLUIDD_DIR,
backup_dir=FLUIDD_BACKUP_DIR,
url=FLUIDD_URL,
pre_release_url=FLUIDD_PRE_RLS_URL,
tags_url=FLUIDD_TAGS_URL,
remote_mode=False,
mr_conf_repo="fluidd-core/fluidd",
mr_conf_path="~/fluidd",
client_config=client_config_data,
)
return client_data
def get_client_status(client: ClientData) -> str:
return get_install_status_webui(
client.get("dir"),
NGINX_SITES_AVAILABLE.joinpath(client.get("name")),
NGINX_CONFD.joinpath("upstreams.conf"),
NGINX_CONFD.joinpath("common_vars.conf"),
)
def get_client_config_status(client: ClientData) -> Dict[
Literal["repo", "local", "remote"],
Union[str, int],
]:
client_config = client.get("client_config")
client_config = client_config.get("dir")
return {
"repo": RepoManager.get_repo_name(client_config),
"local": RepoManager.get_local_commit(client_config),
"remote": RepoManager.get_remote_commit(client_config),
}
def get_current_client_config(clients: List[ClientData]) -> str:
installed = []
for client in clients:
client_config = client.get("client_config")
if client_config.get("dir").exists():
installed.append(client)
if len(installed) > 1:
return f"{COLOR_YELLOW}Conflict!{RESET_FORMAT}"
elif len(installed) == 1:
cfg = installed[0].get("client_config")
return f"{COLOR_CYAN}{cfg.get('display_name')}{RESET_FORMAT}"
return f"{COLOR_CYAN}-{RESET_FORMAT}"
def backup_mainsail_config_json(is_temp=False) -> None:
Logger.print_status(f"Backup '{MAINSAIL_CONFIG_JSON}' ...")
bm = BackupManager()
if is_temp:
fn = Path.home().joinpath("config.json.kiauh.bak")
bm.backup_file(MAINSAIL_CONFIG_JSON, custom_filename=fn)
else:
bm.backup_file(MAINSAIL_CONFIG_JSON)
def restore_mainsail_config_json() -> None:
try:
Logger.print_status(f"Restore '{MAINSAIL_CONFIG_JSON}' ...")
source = Path.home().joinpath("config.json.kiauh.bak")
shutil.copy(source, MAINSAIL_CONFIG_JSON)
except OSError:
Logger.print_info("Unable to restore config.json. Skipped ...")
def enable_mainsail_remotemode() -> None:
Logger.print_status("Enable Mainsails remote mode ...")
with open(MAINSAIL_CONFIG_JSON, "r") as f:
config_data = json.load(f)
if config_data["instancesDB"] == "browser":
Logger.print_info("Remote mode already configured. Skipped ...")
return
Logger.print_status("Setting instance storage location to 'browser' ...")
config_data["instancesDB"] = "browser"
with open(MAINSAIL_CONFIG_JSON, "w") as f:
json.dump(config_data, f, indent=4)
Logger.print_ok("Mainsails remote mode enabled!")
def symlink_webui_nginx_log(klipper_instances: List[Klipper]) -> None:
Logger.print_status("Link NGINX logs into log directory ...")
access_log = Path("/var/log/nginx/mainsail-access.log")
error_log = Path("/var/log/nginx/mainsail-error.log")
for instance in klipper_instances:
desti_access = instance.log_dir.joinpath("mainsail-access.log")
if not desti_access.exists():
desti_access.symlink_to(access_log)
desti_error = instance.log_dir.joinpath("mainsail-error.log")
if not desti_error.exists():
desti_error.symlink_to(error_log)
def get_local_client_version(client: ClientData) -> str:
relinfo_file = client.get("dir").joinpath("release_info.json")
if not relinfo_file.is_file():
return "-"
with open(relinfo_file, "r") as f:
return json.load(f)["version"]
def get_remote_client_version(client: ClientData) -> str:
try:
with urllib.request.urlopen(client.get("tags_url")) as response:
data = json.loads(response.read())
return data[0]["name"]
except (JSONDecodeError, TypeError):
return "ERROR"
def backup_client_data(client: ClientData) -> None:
name = client.get("name")
src = client.get("dir")
dest = client.get("backup_dir")
with open(src.joinpath(".version"), "r") as v:
version = v.readlines()[0]
bm = BackupManager()
bm.backup_directory(f"{name}-{version}", src, dest)
if name == "mainsail":
bm.backup_file(MAINSAIL_CONFIG_JSON, dest)
bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest)
def backup_client_config_data(client: ClientData) -> None:
client_config = client.get("client_config")
name = client_config.get("name")
source = client_config.get("dir")
target = client_config.get("backup_dir")
bm = BackupManager()
bm.backup_directory(name, source, target)

View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python3
# ======================================================================= #
# 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 #
# ======================================================================= #
import textwrap
from typing import Callable, Dict
from components.webui_client import client_remove, ClientData
from core.menus import BACK_HELP_FOOTER
from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal
class ClientRemoveMenu(BaseMenu):
def __init__(self, client: ClientData):
self.client = client
self.rm_client = False
self.rm_client_config = False
self.backup_mainsail_config_json = False
self.rm_moonraker_conf_section = False
self.rm_printer_cfg_section = False
super().__init__(
header=False,
options=self.get_options(),
footer_type=BACK_HELP_FOOTER,
)
def get_options(self) -> Dict[str, Callable]:
options = {
"0": self.toggle_all,
"1": self.toggle_rm_client,
"2": self.toggle_rm_client_config,
"3": self.toggle_rm_printer_cfg_section,
"4": self.toggle_rm_moonraker_conf_section,
"c": self.run_removal_process,
}
if self.client.get("name") == "mainsail":
options["5"] = self.toggle_backup_mainsail_config_json
return options
def print_menu(self) -> None:
client_name = self.client.get("display_name")
client_config = self.client.get("client_config")
client_config_name = client_config.get("display_name")
header = f" [ Remove {client_name} ] "
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]"
unchecked = "[ ]"
o1 = checked if self.rm_client else unchecked
o2 = checked if self.rm_client_config else unchecked
o3 = checked if self.rm_printer_cfg_section else unchecked
o4 = checked if self.rm_moonraker_conf_section else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Enter a number and hit enter to select / deselect |
| the specific option for removal. |
|-------------------------------------------------------|
| 0) Select everything |
|-------------------------------------------------------|
| 1) {o1} Remove {client_name:16} |
| 2) {o2} Remove {client_config_name:24} |
| |
| printer.cfg & moonraker.conf |
| 3) {o3} Remove printer.cfg include |
| 4) {o4} Remove Moonraker update section |
"""
)[1:]
if self.client.get("name") == "mainsail":
o5 = checked if self.backup_mainsail_config_json else unchecked
menu += textwrap.dedent(
f"""
| |
| Mainsail config.json |
| 5) {o5} Backup config.json |
"""
)[1:]
menu += textwrap.dedent(
"""
|-------------------------------------------------------|
| C) Continue |
"""
)[1:]
print(menu, end="")
def toggle_all(self, **kwargs) -> None:
self.rm_client = True
self.rm_client_config = True
self.backup_mainsail_config_json = True
self.rm_moonraker_conf_section = True
self.rm_printer_cfg_section = True
def toggle_rm_client(self, **kwargs) -> None:
self.rm_client = not self.rm_client
def toggle_rm_client_config(self, **kwargs) -> None:
self.rm_client_config = not self.rm_client_config
def toggle_backup_mainsail_config_json(self, **kwargs) -> None:
self.backup_mainsail_config_json = not self.backup_mainsail_config_json
def toggle_rm_moonraker_conf_section(self, **kwargs) -> None:
self.rm_moonraker_conf_section = not self.rm_moonraker_conf_section
def toggle_rm_printer_cfg_section(self, **kwargs) -> None:
self.rm_printer_cfg_section = not self.rm_printer_cfg_section
def run_removal_process(self, **kwargs) -> None:
if (
not self.rm_client
and not self.rm_client_config
and not self.backup_mainsail_config_json
and not self.rm_moonraker_conf_section
and not self.rm_printer_cfg_section
):
error = f"{COLOR_RED}Nothing selected ...{RESET_FORMAT}"
print(error)
return
client_remove.run_client_removal(
client=self.client,
rm_client=self.rm_client,
rm_client_config=self.rm_client_config,
backup_ms_config_json=self.backup_mainsail_config_json,
rm_moonraker_conf_section=self.rm_moonraker_conf_section,
rm_printer_cfg_section=self.rm_printer_cfg_section,
)
self.rm_client = False
self.rm_client_config = False
self.backup_mainsail_config_json = False
self.rm_moonraker_conf_section = False
self.rm_printer_cfg_section = False

View File

@@ -12,11 +12,15 @@
import textwrap
from components.klipper.klipper_utils import backup_klipper_dir
from components.mainsail.mainsail_utils import backup_mainsail_data
from components.moonraker.moonraker_utils import (
backup_moonraker_dir,
backup_moonraker_db_dir,
)
from components.webui_client.client_utils import (
backup_client_data,
load_client_data,
backup_client_config_data,
)
from core.menus import BACK_FOOTER
from core.menus.base_menu import BaseMenu
from utils.common import backup_printer_config_dir
@@ -35,6 +39,10 @@ class BackupMenu(BaseMenu):
"3": self.backup_printer_config,
"4": self.backup_moonraker_db,
"5": self.backup_mainsail,
"6": self.backup_fluidd,
"7": self.backup_mainsail_config,
"8": self.backup_fluidd_config,
"9": self.backup_klipperscreen,
},
footer_type=BACK_FOOTER,
)
@@ -51,15 +59,15 @@ class BackupMenu(BaseMenu):
|-------------------------------------------------------|
| {line1:^62} |
|-------------------------------------------------------|
| Klipper & Moonraker API: | Touchscreen GUI: |
| 1) [Klipper] | 7) [KlipperScreen] |
| 2) [Moonraker] | |
| 3) [Config Folder] | Other: |
| 4) [Moonraker Database] | 9) [Telegram Bot] |
| | |
| Klipper Webinterface: | |
| 5) [Mainsail] | |
| 6) [Fluidd] | |
| Klipper & Moonraker API: | Client-Config: |
| 1) [Klipper] | 7) [Mainsail-Config] |
| 2) [Moonraker] | 8) [Fluidd-Config] |
| 3) [Config Folder] | |
| 4) [Moonraker Database] | Touchscreen GUI: |
| | 9) [KlipperScreen] |
| Webinterface: | |
| 5) [Mainsail] | |
| 6) [Fluidd] | |
"""
)[1:]
print(menu, end="")
@@ -77,13 +85,16 @@ class BackupMenu(BaseMenu):
backup_moonraker_db_dir()
def backup_mainsail(self, **kwargs):
backup_mainsail_data()
backup_client_data(load_client_data("mainsail"))
def backup_fluidd(self, **kwargs):
pass
backup_client_data(load_client_data("fluidd"))
def backup_mainsail_config(self, **kwargs):
backup_client_config_data(load_client_data("mainsail"))
def backup_fluidd_config(self, **kwargs):
backup_client_config_data(load_client_data("fluidd"))
def backup_klipperscreen(self, **kwargs):
pass
def backup_telegram_bot(self, **kwargs):
pass

View File

@@ -11,10 +11,10 @@
import textwrap
from components.fluidd import fluidd_setup
from components.klipper import klipper_setup
from components.mainsail import mainsail_setup
from components.moonraker import moonraker_setup
from components.webui_client import client_setup
from components.webui_client.client_config import client_config_setup
from core.menus import BACK_FOOTER
from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_GREEN, RESET_FORMAT
@@ -31,13 +31,11 @@ class InstallMenu(BaseMenu):
"2": self.install_moonraker,
"3": self.install_mainsail,
"4": self.install_fluidd,
"5": self.install_klipperscreen,
"6": self.install_pretty_gcode,
"7": self.install_telegram_bot,
"8": self.install_obico,
"9": self.install_octoeverywhere,
"10": self.install_mobileraker,
"11": self.install_crowsnest,
"5": self.install_mainsail_config,
"6": self.install_fluidd_config,
"7": None,
"8": None,
"9": None,
},
footer_type=BACK_FOOTER,
)
@@ -51,16 +49,18 @@ class InstallMenu(BaseMenu):
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Firmware & API: | Other: |
| 1) [Klipper] | 6) [PrettyGCode] |
| 2) [Moonraker] | 7) [Telegram Bot] |
| | 8) $(obico_install_title) |
| Klipper Webinterface: | 9) [OctoEverywhere] |
| 3) [Mainsail] | 10) [Mobileraker] |
| 4) [Fluidd] | |
| | Webcam Streamer: |
| Touchscreen GUI: | 11) [Crowsnest] |
| 5) [KlipperScreen] | |
| Firmware & API: | Touchscreen GUI: |
| 1) [Klipper] | 7) [KlipperScreen] |
| 2) [Moonraker] | |
| | Android / iOS: |
| Webinterface: | 8) [Mobileraker] |
| 3) [Mainsail] | |
| 4) [Fluidd] | Webcam Streamer: |
| | 9) [Crowsnest] |
| Client-Config: | |
| 5) [Mainsail-Config] | |
| 6) [Fluidd-Config] | |
| | |
"""
)[1:]
print(menu, end="")
@@ -72,28 +72,13 @@ class InstallMenu(BaseMenu):
moonraker_setup.install_moonraker()
def install_mainsail(self, **kwargs):
mainsail_setup.install_mainsail()
client_setup.install_client(client_name="mainsail")
def install_mainsail_config(self, **kwargs):
client_config_setup.install_client_config(client_name="mainsail")
def install_fluidd(self, **kwargs):
fluidd_setup.install_fluidd()
client_setup.install_client(client_name="fluidd")
def install_klipperscreen(self, **kwargs):
print("install_klipperscreen")
def install_pretty_gcode(self, **kwargs):
print("install_pretty_gcode")
def install_telegram_bot(self, **kwargs):
print("install_telegram_bot")
def install_obico(self, **kwargs):
print("install_obico")
def install_octoeverywhere(self, **kwargs):
print("install_octoeverywhere")
def install_mobileraker(self, **kwargs):
print("install_mobileraker")
def install_crowsnest(self, **kwargs):
print("install_crowsnest")
def install_fluidd_config(self, **kwargs):
client_config_setup.install_client_config(client_name="fluidd")

View File

@@ -11,11 +11,14 @@
import textwrap
from components.fluidd.fluidd_utils import get_fluidd_status
from components.klipper.klipper_utils import get_klipper_status
from components.log_uploads.menus.log_upload_menu import LogUploadMenu
from components.mainsail.mainsail_utils import get_mainsail_status
from components.moonraker.moonraker_utils import get_moonraker_status
from components.webui_client.client_utils import (
get_client_status,
load_client_data,
get_current_client_config,
)
from core.menus import QUIT_FOOTER
from core.menus.advanced_menu import AdvancedMenu
from core.menus.backup_menu import BackupMenu
@@ -61,13 +64,11 @@ class MainMenu(BaseMenu):
self.ks_status = ""
self.mb_status = ""
self.cn_status = ""
self.tg_status = ""
self.ob_status = ""
self.oe_status = ""
self.cc_status = ""
self.init_status()
def init_status(self) -> None:
status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn", "tg", "ob", "oe"]
status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"]
for var in status_vars:
setattr(self, f"{var}_status", f"{COLOR_RED}Not installed!{RESET_FORMAT}")
@@ -87,9 +88,15 @@ class MainMenu(BaseMenu):
self.mr_status = self.format_status_by_code(mr_code, mr_status, mr_instances)
self.mr_repo = f"{COLOR_CYAN}{moonraker_status.get('repo')}{RESET_FORMAT}"
# mainsail
self.ms_status = get_mainsail_status()
mainsail_client_data = load_client_data("mainsail")
self.ms_status = get_client_status(mainsail_client_data)
# fluidd
self.fl_status = get_fluidd_status()
fluidd_client_data = load_client_data("fluidd")
self.fl_status = get_client_status(fluidd_client_data)
# client-config
self.cc_status = get_current_client_config(
[mainsail_client_data, fluidd_client_data]
)
def format_status_by_code(self, code: int, status: str, count: str) -> str:
if code == 1:
@@ -120,13 +127,11 @@ class MainMenu(BaseMenu):
| 4) [Advanced] |------------------------------------|
| 5) [Backup] | Mainsail: {self.ms_status:<26} |
| | Fluidd: {self.fl_status:<26} |
| E) [Extensions] | KlipperScreen: {self.ks_status:<26} |
| | Mobileraker: {self.mb_status:<26} |
| S) [Settings] | Client-Config: {self.cc_status:<26} |
| | |
| Community: | KlipperScreen: {self.ks_status:<26} |
| E) [Extensions] | Mobileraker: {self.mb_status:<26} |
| | Crowsnest: {self.cn_status:<26} |
| | Telegram Bot: {self.tg_status:<26} |
| | Obico: {self.ob_status:<26} |
| S) [Settings] | OctoEverywhere: {self.oe_status:<26} |
|-------------------------------------------------------|
| {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} |
"""

View File

@@ -11,10 +11,10 @@
import textwrap
from components.fluidd.menus.fluidd_remove_menu import FluiddRemoveMenu
from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu
from components.mainsail.menus.mainsail_remove_menu import MainsailRemoveMenu
from components.moonraker.menus.moonraker_remove_menu import MoonrakerRemoveMenu
from components.webui_client.client_utils import load_client_data
from components.webui_client.menus.client_remove_menu import ClientRemoveMenu
from core.menus import BACK_FOOTER
from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_RED, RESET_FORMAT
@@ -29,8 +29,8 @@ class RemoveMenu(BaseMenu):
options={
"1": KlipperRemoveMenu,
"2": MoonrakerRemoveMenu,
"3": MainsailRemoveMenu,
"4": FluiddRemoveMenu,
"3": ClientRemoveMenu(client=load_client_data("mainsail")),
"4": ClientRemoveMenu(client=load_client_data("fluidd")),
"5": None,
"6": None,
"7": None,

View File

@@ -11,22 +11,22 @@
import textwrap
from components.fluidd.fluidd_setup import update_fluidd
from components.fluidd.fluidd_utils import (
get_fluidd_local_version,
get_fluidd_remote_version,
)
from components.klipper.klipper_setup import update_klipper
from components.klipper.klipper_utils import (
get_klipper_status,
)
from components.mainsail.mainsail_setup import update_mainsail
from components.mainsail.mainsail_utils import (
get_mainsail_local_version,
get_mainsail_remote_version,
)
from components.moonraker.moonraker_setup import update_moonraker
from components.moonraker.moonraker_utils import get_moonraker_status
from components.webui_client.client_config.client_config_setup import (
update_client_config,
)
from components.webui_client.client_setup import update_client
from components.webui_client.client_utils import (
get_local_client_version,
get_remote_client_version,
load_client_data,
get_client_config_status,
)
from core.menus import BACK_FOOTER
from core.menus.base_menu import BaseMenu
from utils.constants import (
@@ -50,14 +50,12 @@ class UpdateMenu(BaseMenu):
"2": self.update_moonraker,
"3": self.update_mainsail,
"4": self.update_fluidd,
"5": self.update_klipperscreen,
"6": self.update_pgc_for_klipper,
"7": self.update_telegram_bot,
"8": self.update_moonraker_obico,
"9": self.update_octoeverywhere,
"10": self.update_mobileraker,
"11": self.update_crowsnest,
"12": self.upgrade_system_packages,
"5": self.update_mainsail_config,
"6": self.update_fluidd_config,
"7": self.update_klipperscreen,
"8": self.update_mobileraker,
"9": self.update_crowsnest,
"10": self.upgrade_system_packages,
},
footer_type=BACK_FOOTER,
)
@@ -69,6 +67,10 @@ class UpdateMenu(BaseMenu):
self.ms_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
self.fl_local = f"{COLOR_WHITE}{RESET_FORMAT}"
self.fl_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
self.mc_local = f"{COLOR_WHITE}{RESET_FORMAT}"
self.mc_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
self.fc_local = f"{COLOR_WHITE}{RESET_FORMAT}"
self.fc_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
def print_menu(self):
self.fetch_update_status()
@@ -87,22 +89,20 @@ class UpdateMenu(BaseMenu):
| 1) Klipper | {self.kl_local:<22} | {self.kl_remote:<22} |
| 2) Moonraker | {self.mr_local:<22} | {self.mr_remote:<22} |
| | | |
| Klipper Webinterface: |---------------|---------------|
| Webinterface: |---------------|---------------|
| 3) Mainsail | {self.ms_local:<22} | {self.ms_remote:<22} |
| 4) Fluidd | {self.fl_local:<22} | {self.fl_remote:<22} |
| | | |
| Touchscreen GUI: |---------------|---------------|
| 5) KlipperScreen | | |
| Client-Config: |---------------|---------------|
| 5) Mainsail-Config | {self.mc_local:<22} | {self.mc_remote:<22} |
| 6) Fluidd-Config | {self.fc_local:<22} | {self.fc_remote:<22} |
| | | |
| Other: |---------------|---------------|
| 6) PrettyGCode | | |
| 7) Telegram Bot | | |
| 8) Obico for Klipper | | |
| 9) OctoEverywhere | | |
| 10) Mobileraker | | |
| 11) Crowsnest | | |
| 7) KlipperScreen | | |
| 8) Mobileraker | | |
| 9) Crowsnest | | |
| |-------------------------------|
| 12) System | |
| 10) System | |
"""
)[1:]
print(menu, end="")
@@ -117,34 +117,24 @@ class UpdateMenu(BaseMenu):
update_moonraker()
def update_mainsail(self, **kwargs):
update_mainsail()
update_client(load_client_data("mainsail"))
def update_mainsail_config(self, **kwargs):
update_client_config(load_client_data("mainsail"))
def update_fluidd(self, **kwargs):
update_fluidd()
update_client(load_client_data("fluidd"))
def update_klipperscreen(self, **kwargs):
print("update_klipperscreen")
def update_fluidd_config(self, **kwargs):
update_client_config(load_client_data("fluidd"))
def update_pgc_for_klipper(self, **kwargs):
print("update_pgc_for_klipper")
def update_klipperscreen(self, **kwargs): ...
def update_telegram_bot(self, **kwargs):
print("update_telegram_bot")
def update_mobileraker(self, **kwargs): ...
def update_moonraker_obico(self, **kwargs):
print("update_moonraker_obico")
def update_crowsnest(self, **kwargs): ...
def update_octoeverywhere(self, **kwargs):
print("update_octoeverywhere")
def update_mobileraker(self, **kwargs):
print("update_mobileraker")
def update_crowsnest(self, **kwargs):
print("update_crowsnest")
def upgrade_system_packages(self, **kwargs):
print("upgrade_system_packages")
def upgrade_system_packages(self, **kwargs): ...
def fetch_update_status(self):
# klipper
@@ -166,18 +156,38 @@ class UpdateMenu(BaseMenu):
self.mr_local = f"{COLOR_YELLOW}{self.mr_local}{RESET_FORMAT}"
self.mr_remote = f"{COLOR_GREEN}{self.mr_remote}{RESET_FORMAT}"
# mainsail
self.ms_local = get_mainsail_local_version()
self.ms_remote = get_mainsail_remote_version()
mainsail_client_data = load_client_data("mainsail")
self.ms_local = get_local_client_version(mainsail_client_data)
self.ms_remote = get_remote_client_version(mainsail_client_data)
if self.ms_local == self.ms_remote:
self.ms_local = f"{COLOR_GREEN}{self.ms_local}{RESET_FORMAT}"
else:
self.ms_local = f"{COLOR_YELLOW}{self.ms_local}{RESET_FORMAT}"
self.ms_remote = f"{COLOR_GREEN if self.ms_remote != 'ERROR' else COLOR_RED}{self.ms_remote}{RESET_FORMAT}"
# fluidd
self.fl_local = get_fluidd_local_version()
self.fl_remote = get_fluidd_remote_version()
fluidd_client_data = load_client_data("fluidd")
self.fl_local = get_local_client_version(fluidd_client_data)
self.fl_remote = get_remote_client_version(fluidd_client_data)
if self.fl_local == self.fl_remote:
self.fl_local = f"{COLOR_GREEN}{self.fl_local}{RESET_FORMAT}"
else:
self.fl_local = f"{COLOR_YELLOW}{self.fl_local}{RESET_FORMAT}"
self.fl_remote = f"{COLOR_GREEN if self.fl_remote != 'ERROR' else COLOR_RED}{self.fl_remote}{RESET_FORMAT}"
# mainsail-config
mc_status = get_client_config_status(load_client_data("mainsail"))
self.mc_local = mc_status.get("local")
self.mc_remote = mc_status.get("remote")
if self.mc_local == self.mc_remote:
self.mc_local = f"{COLOR_GREEN}{self.mc_local}{RESET_FORMAT}"
else:
self.mc_local = f"{COLOR_YELLOW}{self.mc_local}{RESET_FORMAT}"
self.mc_remote = f"{COLOR_GREEN}{self.mc_remote}{RESET_FORMAT}"
# fluidd-config
fc_status = get_client_config_status(load_client_data("fluidd"))
self.fc_local = fc_status.get("local")
self.fc_remote = fc_status.get("remote")
if self.fc_local == self.mc_remote:
self.fc_local = f"{COLOR_GREEN}{self.fc_local}{RESET_FORMAT}"
else:
self.fc_local = f"{COLOR_YELLOW}{self.fc_local}{RESET_FORMAT}"
self.fc_remote = f"{COLOR_GREEN}{self.fc_remote}{RESET_FORMAT}"

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# ======================================================================= #
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
# #
@@ -7,14 +8,20 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
import re
import shutil
import subprocess
from pathlib import Path
from zipfile import ZipFile
from typing import List
from typing import List, Type, TypeVar, Union, Tuple
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from core.config_manager.config_manager import ConfigManager
from core.instance_manager.base_instance import BaseInstance
from core.instance_manager.instance_manager import InstanceManager
from utils import (
NGINX_SITES_AVAILABLE,
MODULE_PATH,
@@ -24,6 +31,10 @@ from utils import (
from utils.logger import Logger
B = TypeVar('B', bound='BaseInstance')
ConfigOption = Tuple[str, str]
def check_file_exist(file_path: Path, sudo=False) -> bool:
"""
Helper function for checking the existence of a file |
@@ -169,3 +180,102 @@ def get_next_free_port(ports_in_use: List[str]) -> str:
used_ports = set(map(int, ports_in_use))
return str(min(valid_ports - used_ports))
def add_config_section(section: str, instances: List[B], options: List[ConfigOption] = None) -> None:
for instance in instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Add section '[{section}]' to '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
continue
cm = ConfigManager(cfg_file)
if cm.config.has_section(section):
Logger.print_info("Section already exist. Skipped ...")
continue
cm.config.add_section(section)
if options is not None:
for option in options:
cm.config.set(section, option[0], option[1])
cm.write_config()
def remove_config_section(section: str, instances: List[B]) -> None:
for instance in instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Remove section '[{section}]' from '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
continue
cm = ConfigManager(cfg_file)
if not cm.config.has_section(section):
Logger.print_info("Section does not exist. Skipped ...")
continue
cm.config.remove_section(section)
cm.write_config()
def patch_moonraker_conf(
moonraker_instances: List[Moonraker],
name: str,
section_name: str,
template_file: str,
) -> None:
for instance in moonraker_instances:
cfg_file = instance.cfg_file
Logger.print_status(f"Add {name} update section to '{cfg_file}' ...")
if not Path(cfg_file).exists():
Logger.print_warn(f"'{cfg_file}' not found!")
return
cm = ConfigManager(cfg_file)
if cm.config.has_section(section_name):
Logger.print_info("Section already exist. Skipped ...")
return
template = MODULE_PATH.joinpath("assets", template_file)
with open(template, "r") as t:
template_content = "\n"
template_content += t.read()
with open(cfg_file, "a") as f:
f.write(template_content)
def remove_nginx_config(name: str) -> None:
Logger.print_status(f"Removing NGINX config for {name.capitalize()} ...")
try:
remove_file(NGINX_SITES_AVAILABLE.joinpath(name), True)
remove_file(NGINX_SITES_ENABLED.joinpath(name), True)
except subprocess.CalledProcessError as e:
log = f"Unable to remove NGINX config '{name}':\n{e.stderr.decode()}"
Logger.print_error(log)
def remove_nginx_logs(name: str) -> None:
Logger.print_status(f"Removing NGINX logs for {name.capitalize()} ...")
try:
remove_file(Path(f"/var/log/nginx/{name}-access.log"), True)
remove_file(Path(f"/var/log/nginx/{name}-error.log"), True)
im = InstanceManager(Klipper)
instances: List[Klipper] = im.instances
if not instances:
return
for instance in instances:
remove_file(instance.log_dir.joinpath(f"{name}-access.log"))
remove_file(instance.log_dir.joinpath(f"{name}-error.log"))
except (OSError, subprocess.CalledProcessError) as e:
Logger.print_error(f"Unable to remove NGINX logs:\n{e}")