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