Compare commits

...

11 Commits

Author SHA1 Message Date
dw-0
a63cf8c9d9 Release v6.0.0-alpha.9
Merge develop into master (Release v6.0.0-alpha.9)
2024-10-24 12:29:24 +02:00
dw-0
02ed3e7da0 feat: show actual current branch in settings menu (#588)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-24 12:26:18 +02:00
dw-0
4427ae94af fix: make sure all output is utf-8 encoded (#587)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-24 12:26:01 +02:00
dw-0
81b7b156b9 feat: implement port reconfiguration for webclients (#586)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-24 12:25:44 +02:00
dw-0
2df364512b fix: Path.rename() not working across devices (#584)
causes `[Errno 18] Invalid cross-device link` on tmpfs filesystems

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-24 12:25:22 +02:00
dw-0
dfa0036326 refactor: don't clear scrollback on clear
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-24 12:24:05 +02:00
dw-0
425d86a12f Release v6.0.0-alpha.8
Merge develop into master (Release v6.0.0-alpha.8)
2024-10-21 19:45:55 +02:00
dw-0
ff6162d799 refactor: do not silently configure Fluidd for port 81 (#582)
* refactor: use port 80 as default for fluidd

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* refactor: improve port selection logic, write last port selection for client to kiauh.cfg

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-21 19:30:03 +02:00
dw-0
674c174224 fix: correctly add Spoolman to moonraker.asvc (#581)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-20 18:11:30 +02:00
CODeRUS
a368331693 feat(flashing): Flash RP2040 in Boot Mode (#580)
* feat(flashing): Flash RP2040 in Boot Mode

* docs: add info about STM32 DFU and RP2040 boot modes
2024-10-18 18:56:21 +02:00
Pedro Lamas
406b64d1e5 refactor: add client name to Moonraker not found dialog (#574)
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
Co-authored-by: dw-0 <th33xitus@gmail.com>
2024-10-14 17:18:10 +02:00
20 changed files with 369 additions and 126 deletions

View File

@@ -14,5 +14,5 @@ port: 80
unstable_releases: False unstable_releases: False
[fluidd] [fluidd]
port: 81 port: 80
unstable_releases: False unstable_releases: False

View File

@@ -9,7 +9,13 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import io
import sys
from kiauh.main import main from kiauh.main import main
# ensure that all output is utf-8 encoded
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -10,7 +10,7 @@
#=======================================================================# #=======================================================================#
set -e set -e
clear clear -x
# make sure we have the correct permissions while running the script # make sure we have the correct permissions while running the script
umask 022 umask 022
@@ -110,7 +110,7 @@ function launch_kiauh_v6() {
export PYTHONPATH="${entrypoint}" export PYTHONPATH="${entrypoint}"
clear clear -x
python3 "${entrypoint}/kiauh.py" python3 "${entrypoint}/kiauh.py"
} }

View File

@@ -90,6 +90,21 @@ def find_usb_dfu_device() -> List[str]:
return [] return []
def find_usb_rp2_boot_device() -> List[str]:
try:
output = check_output("lsusb", shell=True, text=True, stderr=DEVNULL)
device_list = []
if output:
devices = output.splitlines()
device_list = [d.split(" ")[5] for d in devices if "RP2 Boot" in d]
return device_list
except CalledProcessError as e:
Logger.print_error("Unable to find a USB RP2 Boot device!")
Logger.print_error(e, prefix=False)
return []
def get_sd_flash_board_list() -> List[str]: def get_sd_flash_board_list() -> List[str]:
if not KLIPPER_DIR.exists() or not SD_FLASH_SCRIPT.exists(): if not KLIPPER_DIR.exists() or not SD_FLASH_SCRIPT.exists():
return [] return []

View File

@@ -26,6 +26,7 @@ class FlashCommand(Enum):
class ConnectionType(Enum): class ConnectionType(Enum):
USB = "USB" USB = "USB"
USB_DFU = "USB (DFU)" USB_DFU = "USB (DFU)"
USB_RP2040 = "USB (RP2040)"
UART = "UART" UART = "UART"

View File

@@ -143,6 +143,8 @@ class KlipperMcuConnectionHelpMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT) count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}USB:{RESET_FORMAT}" subheader1 = f"{COLOR_CYAN}USB:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}UART:{RESET_FORMAT}" subheader2 = f"{COLOR_CYAN}UART:{RESET_FORMAT}"
subheader3 = f"{COLOR_CYAN}USB DFU:{RESET_FORMAT}"
subheader4 = f"{COLOR_CYAN}USB RP2040 Boot:{RESET_FORMAT}"
menu = textwrap.dedent( menu = textwrap.dedent(
f""" f"""
╔═══════════════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════════════╗
@@ -164,6 +166,19 @@ class KlipperMcuConnectionHelpMenu(BaseMenu):
║ port your controller board is connected to when using ║ ║ port your controller board is connected to when using ║
║ this connection method. ║ ║ this connection method. ║
║ ║ ║ ║
{subheader3:<62}
║ Selecting USB DFU as the connection method will scan ║
║ the USB ports for connected controller boards in ║
║ STM32 DFU mode, which is usually done by holding down ║
║ the BOOT button or setting a special jumper on the ║
║ board before powering up. ║
║ ║
{subheader4:<62}
║ Selecting USB RP2 Boot as the connection method will ║
║ scan the USB ports for connected RP2040 controller ║
║ boards in Boot mode, which is usually done by holding ║
║ down the BOOT button before powering up. ║
║ ║
╟───────────────────────────────────────────────────────╢ ╟───────────────────────────────────────────────────────╢
""" """
)[1:] )[1:]

View File

@@ -17,6 +17,7 @@ from components.klipper_firmware.firmware_utils import (
find_uart_device, find_uart_device,
find_usb_device_by_id, find_usb_device_by_id,
find_usb_dfu_device, find_usb_dfu_device,
find_usb_rp2_boot_device,
get_sd_flash_board_list, get_sd_flash_board_list,
start_flash_process, start_flash_process,
) )
@@ -177,6 +178,7 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
"1": Option(method=self.select_usb), "1": Option(method=self.select_usb),
"2": Option(method=self.select_dfu), "2": Option(method=self.select_dfu),
"3": Option(method=self.select_usb_dfu), "3": Option(method=self.select_usb_dfu),
"4": Option(method=self.select_usb_rp2040),
} }
def print_menu(self) -> None: def print_menu(self) -> None:
@@ -193,6 +195,7 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
║ 1) USB ║ ║ 1) USB ║
║ 2) UART ║ ║ 2) UART ║
║ 3) USB (DFU mode) ║ ║ 3) USB (DFU mode) ║
║ 4) USB (RP2040 mode) ║
╟───────────────────────────┬───────────────────────────╢ ╟───────────────────────────┬───────────────────────────╢
""" """
)[1:] )[1:]
@@ -210,6 +213,10 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
self.flash_options.connection_type = ConnectionType.USB_DFU self.flash_options.connection_type = ConnectionType.USB_DFU
self.get_mcu_list() self.get_mcu_list()
def select_usb_rp2040(self, **kwargs):
self.flash_options.connection_type = ConnectionType.USB_RP2040
self.get_mcu_list()
def get_mcu_list(self, **kwargs): def get_mcu_list(self, **kwargs):
conn_type = self.flash_options.connection_type conn_type = self.flash_options.connection_type
@@ -222,6 +229,9 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
elif conn_type is ConnectionType.USB_DFU: elif conn_type is ConnectionType.USB_DFU:
Logger.print_status("Identifying MCU connected via USB in DFU mode ...") Logger.print_status("Identifying MCU connected via USB in DFU mode ...")
self.flash_options.mcu_list = find_usb_dfu_device() self.flash_options.mcu_list = find_usb_dfu_device()
elif conn_type is ConnectionType.USB_RP2040:
Logger.print_status("Identifying MCU connected via USB in RP2 Boot mode ...")
self.flash_options.mcu_list = find_usb_rp2_boot_device()
if len(self.flash_options.mcu_list) < 1: if len(self.flash_options.mcu_list) < 1:
Logger.print_warn("No MCUs found!") Logger.print_warn("No MCUs found!")

View File

@@ -34,7 +34,7 @@ from utils.input_utils import get_confirm
from utils.instance_utils import get_instances from utils.instance_utils import get_instances
def install_client_config(client_data: BaseWebClient) -> None: def install_client_config(client_data: BaseWebClient, cfg_backup=True) -> None:
client_config: BaseWebClientConfig = client_data.client_config client_config: BaseWebClientConfig = client_data.client_config
display_name = client_config.display_name display_name = client_config.display_name
@@ -56,7 +56,8 @@ def install_client_config(client_data: BaseWebClient) -> None:
download_client_config(client_config) download_client_config(client_config)
create_client_config_symlink(client_config, kl_instances) create_client_config_symlink(client_config, kl_instances)
backup_printer_config_dir() if cfg_backup:
backup_printer_config_dir()
add_config_section( add_config_section(
section=f"update_manager {client_config.name}", section=f"update_manager {client_config.name}",

View File

@@ -13,15 +13,15 @@ from components.webui_client.base_data import BaseWebClient
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
def print_moonraker_not_found_dialog() -> None: def print_moonraker_not_found_dialog(name: str) -> None:
Logger.print_dialog( Logger.print_dialog(
DialogType.WARNING, DialogType.WARNING,
[ [
"No local Moonraker installation was found!", "No local Moonraker installation was found!",
"\n\n", "\n\n",
"It is possible to install Mainsail without a local Moonraker installation. " f"It is possible to install {name} without a local Moonraker installation. "
"If you continue, you need to make sure, that Moonraker is installed on " "If you continue, you need to make sure, that Moonraker is installed on "
"another machine in your network. Otherwise Mainsail will NOT work " f"another machine in your network. Otherwise {name} will NOT work "
"correctly.", "correctly.",
], ],
) )
@@ -40,20 +40,25 @@ def print_client_already_installed_dialog(name: str) -> None:
def print_client_port_select_dialog( def print_client_port_select_dialog(
name: str, port: int, ports_in_use: List[int] name: str, port: int, ports_in_use: List[int]
) -> None: ) -> None:
Logger.print_dialog( dialog_content: List[str] = [
DialogType.CUSTOM, f"Please select the port, {name} should be served on. If your are unsure "
[ f"what to select, hit Enter to apply the suggested value of: {port}",
f"Please select the port, {name} should be served on. If your are unsure " "\n\n",
f"what to select, hit Enter to apply the suggested value of: {port}", f"In case you need {name} to be served on a specific port, you can set it "
"\n\n", f"now. Make sure that the port is not already used by another application "
f"In case you need {name} to be served on a specific port, you can set it " f"on your system!",
f"now. Make sure that the port is not already used by another application " ]
f"on your system!",
"\n\n", if ports_in_use:
"The following ports were found to be in use already:", dialog_content.extend(
*[f"{port}" for port in ports_in_use], [
], "\n\n",
) "The following ports were found to be already in use:",
*[f"{p}" for p in ports_in_use if p != port],
]
)
Logger.print_dialog(DialogType.CUSTOM, dialog_content)
def print_install_client_config_dialog(client: BaseWebClient) -> None: def print_install_client_config_dialog(client: BaseWebClient) -> None:

View File

@@ -23,7 +23,6 @@ from components.webui_client.client_config.client_config_setup import (
install_client_config, install_client_config,
) )
from components.webui_client.client_dialogs import ( from components.webui_client.client_dialogs import (
print_client_port_select_dialog,
print_install_client_config_dialog, print_install_client_config_dialog,
print_moonraker_not_found_dialog, print_moonraker_not_found_dialog,
) )
@@ -33,18 +32,16 @@ from components.webui_client.client_utils import (
create_nginx_cfg, create_nginx_cfg,
detect_client_cfg_conflict, detect_client_cfg_conflict,
enable_mainsail_remotemode, enable_mainsail_remotemode,
get_next_free_port, get_client_port_selection,
is_valid_port,
read_ports_from_nginx_configs,
symlink_webui_nginx_log, symlink_webui_nginx_log,
) )
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.logger import Logger from core.logger import DialogCustomColor, DialogType, Logger
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from utils.common import check_install_dependencies from utils.common import backup_printer_config_dir, check_install_dependencies
from utils.config_utils import add_config_section from utils.config_utils import add_config_section
from utils.fs_utils import unzip from utils.fs_utils import unzip
from utils.input_utils import get_confirm, get_number_input from utils.input_utils import get_confirm
from utils.instance_utils import get_instances from utils.instance_utils import get_instances
from utils.sys_utils import ( from utils.sys_utils import (
cmd_sysctl_service, cmd_sysctl_service,
@@ -53,21 +50,16 @@ from utils.sys_utils import (
) )
def install_client(client: BaseWebClient) -> None: def install_client(
if client is None: client: BaseWebClient,
raise ValueError("Missing parameter client_data!") settings: KiauhSettings,
reinstall: bool = False,
if client.client_dir.exists(): ) -> None:
Logger.print_info(
f"{client.display_name} seems to be already installed! Skipped ..."
)
return
mr_instances: List[Moonraker] = get_instances(Moonraker) mr_instances: List[Moonraker] = get_instances(Moonraker)
enable_remotemode = False enable_remotemode = False
if not mr_instances: if not mr_instances:
print_moonraker_not_found_dialog() print_moonraker_not_found_dialog(client.display_name)
if not get_confirm(f"Continue {client.display_name} installation?"): if not get_confirm(f"Continue {client.display_name} installation?"):
return return
@@ -92,21 +84,10 @@ def install_client(client: BaseWebClient) -> None:
question = f"Download the recommended {client_config.display_name}?" question = f"Download the recommended {client_config.display_name}?"
install_client_cfg = get_confirm(question, allow_go_back=False) install_client_cfg = get_confirm(question, allow_go_back=False)
settings = KiauhSettings() default_port: int = int(settings.get(client.name, "port"))
port: int = settings.get(client.name, "port") port: int = (
ports_in_use: List[int] = read_ports_from_nginx_configs() default_port if reinstall else get_client_port_selection(client, settings)
)
# check if configured port is a valid number and not in use already
valid_port = is_valid_port(port, ports_in_use)
while not valid_port:
next_port = get_next_free_port(ports_in_use)
print_client_port_select_dialog(client.display_name, next_port, ports_in_use)
port = get_number_input(
f"Configure {client.display_name} for port",
min_count=int(next_port),
default=next_port,
)
valid_port = is_valid_port(port, ports_in_use)
check_install_dependencies({"nginx"}) check_install_dependencies({"nginx"})
@@ -114,20 +95,22 @@ def install_client(client: BaseWebClient) -> None:
download_client(client) download_client(client)
if enable_remotemode and client.client == WebClientType.MAINSAIL: if enable_remotemode and client.client == WebClientType.MAINSAIL:
enable_mainsail_remotemode() enable_mainsail_remotemode()
if mr_instances:
add_config_section( backup_printer_config_dir()
section=f"update_manager {client.name}", add_config_section(
instances=mr_instances, section=f"update_manager {client.name}",
options=[ instances=mr_instances,
("type", "web"), options=[
("channel", "stable"), ("type", "web"),
("repo", str(client.repo_path)), ("channel", "stable"),
("path", str(client.client_dir)), ("repo", str(client.repo_path)),
], ("path", str(client.client_dir)),
) ],
InstanceManager.restart_all(mr_instances) )
InstanceManager.restart_all(mr_instances)
if install_client_cfg and kl_instances: if install_client_cfg and kl_instances:
install_client_config(client) install_client_config(client, False)
copy_upstream_nginx_cfg() copy_upstream_nginx_cfg()
copy_common_vars_nginx_cfg() copy_common_vars_nginx_cfg()
@@ -145,12 +128,24 @@ def install_client(client: BaseWebClient) -> None:
cmd_sysctl_service("nginx", "restart") cmd_sysctl_service("nginx", "restart")
except Exception as e: except Exception as e:
Logger.print_error(f"{client.display_name} installation failed!\n{e}") Logger.print_error(e)
Logger.print_dialog(
DialogType.ERROR,
center_content=True,
content=[f"{client.display_name} installation failed!"],
)
return return
log = f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{port}" # noinspection HttpUrlsUsage
Logger.print_ok(f"{client.display_name} installation complete!", start="\n") Logger.print_dialog(
Logger.print_ok(log, prefix=False, end="\n\n") DialogType.CUSTOM,
custom_title=f"{client.display_name} installation complete!",
custom_color=DialogCustomColor.GREEN,
center_content=True,
content=[
f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{port}",
],
)
def download_client(client: BaseWebClient) -> None: def download_client(client: BaseWebClient) -> None:

View File

@@ -21,6 +21,7 @@ from components.webui_client.base_data import (
BaseWebClient, BaseWebClient,
WebClientType, WebClientType,
) )
from components.webui_client.client_dialogs import print_client_port_select_dialog
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
@@ -33,7 +34,7 @@ from core.constants import (
RESET_FORMAT, RESET_FORMAT,
) )
from core.logger import Logger from core.logger import Logger
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings, WebUiSettings
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser, SimpleConfigParser,
) )
@@ -44,6 +45,7 @@ from utils.git_utils import (
get_latest_remote_tag, get_latest_remote_tag,
get_latest_unstable_tag, get_latest_unstable_tag,
) )
from utils.input_utils import get_number_input
from utils.instance_utils import get_instances from utils.instance_utils import get_instances
@@ -368,8 +370,36 @@ def read_ports_from_nginx_configs() -> List[int]:
return sorted(ports_to_ints_list, key=lambda x: int(x)) return sorted(ports_to_ints_list, key=lambda x: int(x))
def is_valid_port(port: int, ports_in_use: List[int]) -> bool: def get_client_port_selection(
return port not in ports_in_use client: BaseWebClient,
settings: KiauhSettings,
reconfigure=False,
) -> int:
default_port: int = int(settings.get(client.name, "port"))
ports_in_use: List[int] = read_ports_from_nginx_configs()
next_free_port: int = get_next_free_port(ports_in_use)
port: int = (
next_free_port
if not reconfigure and default_port in ports_in_use
else default_port
)
print_client_port_select_dialog(client.display_name, port, ports_in_use)
while True:
_type = "Reconfigure" if reconfigure else "Configure"
question = f"{_type} {client.display_name} for port"
port_input = get_number_input(question, min_count=80, default=port)
if port_input not in ports_in_use:
client_settings: WebUiSettings = settings[client.name]
client_settings.port = port_input
settings.save()
return port_input
Logger.print_error("This port is already in use. Please select another one.")
def get_next_free_port(ports_in_use: List[int]) -> int: def get_next_free_port(ports_in_use: List[int]) -> int:
@@ -377,3 +407,23 @@ def get_next_free_port(ports_in_use: List[int]) -> int:
used_ports = set(map(int, ports_in_use)) used_ports = set(map(int, ports_in_use))
return min(valid_ports - used_ports) return min(valid_ports - used_ports)
def set_listen_port(client: BaseWebClient, curr_port: int, new_port: int) -> None:
"""
Set the port the client should listen on in the NGINX config
:param curr_port: The current port the client listens on
:param new_port: The new port to set
:param client: The client to set the port for
:return: None
"""
config = NGINX_SITES_AVAILABLE.joinpath(client.name)
with open(config, "r") as f:
lines = f.readlines()
for i, line in enumerate(lines):
if "listen" in line:
lines[i] = line.replace(str(curr_port), str(new_port))
with open(config, "w") as f:
f.writelines(lines)

View File

@@ -0,0 +1,104 @@
# ======================================================================= #
# 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 __future__ import annotations
import textwrap
from typing import Type
from components.webui_client.base_data import BaseWebClient
from components.webui_client.client_setup import install_client
from components.webui_client.client_utils import (
get_client_port_selection,
set_listen_port,
)
from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT
from core.logger import DialogCustomColor, DialogType, Logger
from core.menus import Option
from core.menus.base_menu import BaseMenu
from core.settings.kiauh_settings import KiauhSettings, WebUiSettings
from utils.sys_utils import cmd_sysctl_service, get_ipv4_addr
# noinspection PyUnusedLocal
class ClientInstallMenu(BaseMenu):
def __init__(
self, client: BaseWebClient, previous_menu: Type[BaseMenu] | None = None
):
super().__init__()
self.previous_menu: Type[BaseMenu] | None = previous_menu
self.client: BaseWebClient = client
self.settings = KiauhSettings()
self.client_settings: WebUiSettings = self.settings[client.name]
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
from core.menus.install_menu import InstallMenu
self.previous_menu = previous_menu if previous_menu is not None else InstallMenu
def set_options(self) -> None:
self.options = {
"1": Option(method=self.reinstall_client),
"2": Option(method=self.change_listen_port),
}
def print_menu(self) -> None:
client_name = self.client.display_name
header = f" [ Installation Menu > {client_name} ] "
color = COLOR_GREEN
count = 62 - len(color) - len(RESET_FORMAT)
port = f"(Current: {COLOR_CYAN}{int(self.client_settings.port)}{RESET_FORMAT})"
menu = textwrap.dedent(
f"""
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
║ 1) Reinstall {client_name:16}
║ 2) Reconfigure Listen Port {port:<34}
╟───────────────────────────────────────────────────────╢
"""
)[1:]
print(menu, end="")
def reinstall_client(self, **kwargs) -> None:
install_client(self.client, settings=self.settings, reinstall=True)
def change_listen_port(self, **kwargs) -> None:
curr_port = int(self.client_settings.port)
new_port = get_client_port_selection(
self.client,
self.settings,
reconfigure=True,
)
cmd_sysctl_service("nginx", "stop")
set_listen_port(self.client, curr_port, new_port)
Logger.print_status("Saving new port configuration ...")
self.client_settings.port = new_port
self.settings.save()
Logger.print_ok("Port configuration saved!")
cmd_sysctl_service("nginx", "start")
# noinspection HttpUrlsUsage
Logger.print_dialog(
DialogType.CUSTOM,
custom_title="Port reconfiguration complete!",
custom_color=DialogCustomColor.GREEN,
center_content=True,
content=[
f"Open {self.client.display_name} now on: "
f"http://{get_ipv4_addr()}:{new_port}",
],
)
def _go_back(self, **kwargs) -> None:
if self.previous_menu is not None:
self.previous_menu().run()

View File

@@ -29,7 +29,7 @@ from utils.input_utils import get_selection_input
def clear() -> None: def clear() -> None:
subprocess.call("clear", shell=True) subprocess.call("clear -x", shell=True)
def print_header() -> None: def print_header() -> None:

View File

@@ -15,13 +15,17 @@ from components.crowsnest.crowsnest import install_crowsnest
from components.klipper import klipper_setup from components.klipper import klipper_setup
from components.klipperscreen.klipperscreen import install_klipperscreen from components.klipperscreen.klipperscreen import install_klipperscreen
from components.moonraker import moonraker_setup from components.moonraker import moonraker_setup
from components.webui_client import client_setup from components.webui_client.client_config.client_config_setup import (
from components.webui_client.client_config import client_config_setup install_client_config,
)
from components.webui_client.client_setup import install_client
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from components.webui_client.menus.client_install_menu import ClientInstallMenu
from core.constants import COLOR_GREEN, RESET_FORMAT from core.constants import COLOR_GREEN, RESET_FORMAT
from core.menus import Option from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from core.settings.kiauh_settings import KiauhSettings
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@@ -80,16 +84,24 @@ class InstallMenu(BaseMenu):
moonraker_setup.install_moonraker() moonraker_setup.install_moonraker()
def install_mainsail(self, **kwargs) -> None: def install_mainsail(self, **kwargs) -> None:
client_setup.install_client(MainsailData()) client: MainsailData = MainsailData()
if client.client_dir.exists():
ClientInstallMenu(client, self.__class__).run()
else:
install_client(client, settings=KiauhSettings())
def install_mainsail_config(self, **kwargs) -> None: def install_mainsail_config(self, **kwargs) -> None:
client_config_setup.install_client_config(MainsailData()) install_client_config(MainsailData())
def install_fluidd(self, **kwargs) -> None: def install_fluidd(self, **kwargs) -> None:
client_setup.install_client(FluiddData()) client: FluiddData = FluiddData()
if client.client_dir.exists():
ClientInstallMenu(client, self.__class__).run()
else:
install_client(client, settings=KiauhSettings())
def install_fluidd_config(self, **kwargs) -> None: def install_fluidd_config(self, **kwargs) -> None:
client_config_setup.install_client_config(FluiddData()) install_client_config(FluiddData())
def install_klipperscreen(self, **kwargs) -> None: def install_klipperscreen(self, **kwargs) -> None:
install_klipperscreen() install_klipperscreen()

View File

@@ -11,6 +11,8 @@ from __future__ import annotations
import textwrap import textwrap
from typing import Literal, Tuple, Type from typing import Literal, Tuple, Type
from components.klipper.klipper_utils import get_klipper_status
from components.moonraker.moonraker_utils import get_moonraker_status
from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.menus import Option from core.menus import Option
@@ -26,8 +28,8 @@ class SettingsMenu(BaseMenu):
def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None:
super().__init__() super().__init__()
self.previous_menu: Type[BaseMenu] | None = previous_menu self.previous_menu: Type[BaseMenu] | None = previous_menu
self.klipper_repo: str | None = None self.klipper_status = get_klipper_status()
self.moonraker_repo: str | None = None self.moonraker_status = get_moonraker_status()
self.mainsail_unstable: bool | None = None self.mainsail_unstable: bool | None = None
self.fluidd_unstable: bool | None = None self.fluidd_unstable: bool | None = None
self.auto_backups_enabled: bool | None = None self.auto_backups_enabled: bool | None = None
@@ -49,31 +51,41 @@ class SettingsMenu(BaseMenu):
def print_menu(self) -> None: def print_menu(self) -> None:
header = " [ KIAUH Settings ] " header = " [ KIAUH Settings ] "
color = COLOR_CYAN color, rst = COLOR_CYAN, RESET_FORMAT
count = 62 - len(color) - len(RESET_FORMAT) count = 62 - len(color) - len(rst)
checked = f"[{COLOR_GREEN}x{RESET_FORMAT}]" checked = f"[{COLOR_GREEN}x{rst}]"
unchecked = "[ ]" unchecked = "[ ]"
kl_repo: str = f"{color}{self.klipper_status.repo}{rst}"
kl_branch: str = f"{color}{self.klipper_status.branch}{rst}"
kl_owner: str = f"{color}{self.klipper_status.owner}{rst}"
mr_repo: str = f"{color}{self.moonraker_status.repo}{rst}"
mr_branch: str = f"{color}{self.moonraker_status.branch}{rst}"
mr_owner: str = f"{color}{self.moonraker_status.owner}{rst}"
o1 = checked if self.mainsail_unstable else unchecked o1 = checked if self.mainsail_unstable else unchecked
o2 = checked if self.fluidd_unstable else unchecked o2 = checked if self.fluidd_unstable else unchecked
o3 = checked if self.auto_backups_enabled else unchecked o3 = checked if self.auto_backups_enabled else unchecked
menu = textwrap.dedent( menu = textwrap.dedent(
f""" f"""
╔═══════════════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT} {color}{header:~^{count}}{rst}
╟───────────────────────────────────────────────────────╢ ╟───────────────────────────────────────────────────────╢
║ Klipper source repository: ║ Klipper:
{self.klipper_repo:<67} ● Repo: {kl_repo:51}
● Owner: {kl_owner:51}
Moonraker source repository: ● Branch: {kl_branch:51}
║ ● {self.moonraker_repo:<67} ╟───────────────────────────────────────────────────────╢
Moonraker:
Install unstable Webinterface releases: ● Repo: {mr_repo:51}
║ ● Owner: {mr_owner:51}
║ ● Branch: {mr_branch:51}
╟───────────────────────────────────────────────────────╢
║ Install unstable releases: ║
{o1} Mainsail ║ {o1} Mainsail ║
{o2} Fluidd ║ {o2} Fluidd ║
║ ║ ╟───────────────────────────────────────────────────────╢
║ Auto-Backup: ║ ║ Auto-Backup: ║
{o3} Automatic backup before update ║ {o3} Automatic backup before update ║
║ ║
╟───────────────────────────────────────────────────────╢ ╟───────────────────────────────────────────────────────╢
║ 1) Set Klipper source repository ║ ║ 1) Set Klipper source repository ║
║ 2) Set Moonraker source repository ║ ║ 2) Set Moonraker source repository ║
@@ -89,25 +101,10 @@ class SettingsMenu(BaseMenu):
def _load_settings(self) -> None: def _load_settings(self) -> None:
self.settings = KiauhSettings() self.settings = KiauhSettings()
self._format_repo_str("klipper")
self._format_repo_str("moonraker")
self.auto_backups_enabled = self.settings.kiauh.backup_before_update self.auto_backups_enabled = self.settings.kiauh.backup_before_update
self.mainsail_unstable = self.settings.mainsail.unstable_releases self.mainsail_unstable = self.settings.mainsail.unstable_releases
self.fluidd_unstable = self.settings.fluidd.unstable_releases self.fluidd_unstable = self.settings.fluidd.unstable_releases
def _format_repo_str(self, repo_name: Literal["klipper", "moonraker"]) -> None:
repo: RepoSettings = self.settings[repo_name]
repo_str = f"{'/'.join(repo.repo_url.rsplit('/', 2)[-2:])}"
branch_str = f"({COLOR_CYAN}@ {repo.branch}{RESET_FORMAT})"
setattr(
self,
f"{repo_name}_repo",
f"{COLOR_CYAN}{repo_str}{RESET_FORMAT} {branch_str}",
)
def _gather_input(self) -> Tuple[str, str]: def _gather_input(self) -> Tuple[str, str]:
Logger.print_dialog( Logger.print_dialog(
DialogType.ATTENTION, DialogType.ATTENTION,

View File

@@ -25,6 +25,7 @@ class ComponentStatus:
status: StatusCode status: StatusCode
owner: str | None = None owner: str | None = None
repo: str | None = None repo: str | None = None
branch: str = ""
local: str | None = None local: str | None = None
remote: str | None = None remote: str | None = None
instances: int | None = None instances: int | None = None

View File

@@ -24,6 +24,7 @@ from core.constants import (
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.types import ComponentStatus, StatusCode from core.types import ComponentStatus, StatusCode
from utils.git_utils import ( from utils.git_utils import (
get_current_branch,
get_local_commit, get_local_commit,
get_local_tags, get_local_tags,
get_remote_commit, get_remote_commit,
@@ -103,7 +104,12 @@ def get_install_status(
""" """
from utils.instance_utils import get_instances from utils.instance_utils import get_instances
checks = [repo_dir.exists()] checks = []
branch: str = ""
if repo_dir.exists():
checks.append(True)
branch = get_current_branch(repo_dir)
if env_dir is not None: if env_dir is not None:
checks.append(env_dir.exists()) checks.append(env_dir.exists())
@@ -131,6 +137,7 @@ def get_install_status(
instances=instances, instances=instances,
owner=org, owner=org,
repo=repo, repo=repo,
branch=branch,
local=get_local_commit(repo_dir), local=get_local_commit(repo_dir),
remote=get_remote_commit(repo_dir), remote=get_remote_commit(repo_dir),
) )

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
from __future__ import annotations from __future__ import annotations
import shutil
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Tuple
@@ -26,6 +27,9 @@ def add_config_section(
instances: List[InstanceType], instances: List[InstanceType],
options: List[ConfigOption] | None = None, options: List[ConfigOption] | None = None,
) -> None: ) -> None:
if not instances:
return
for instance in instances: for instance in instances:
cfg_file = instance.cfg_file cfg_file = instance.cfg_file
Logger.print_status(f"Add section '[{section}]' to '{cfg_file}' ...") Logger.print_status(f"Add section '[{section}]' to '{cfg_file}' ...")
@@ -69,7 +73,7 @@ def add_config_section_at_top(section: str, instances: List[InstanceType]) -> No
tmp.writelines(org_content) tmp.writelines(org_content)
cfg_file.unlink() cfg_file.unlink()
tmp_cfg_path.rename(cfg_file) shutil.move(tmp_cfg_path, cfg_file)
Logger.print_ok("OK!") Logger.print_ok("OK!")

View File

@@ -87,12 +87,29 @@ def get_repo_name(repo: Path) -> Tuple[str, str]:
orga: str = substrings[0] if substrings[0] else "-" orga: str = substrings[0] if substrings[0] else "-"
name: str = substrings[1] if substrings[1] else "-" name: str = substrings[1] if substrings[1] else "-"
return orga, name return orga, name.replace(".git", "")
except CalledProcessError: except CalledProcessError:
return "-", "-" return "-", "-"
def get_current_branch(repo: Path) -> str:
"""
Get the current branch of a local Git repository
:param repo: Path to the local Git repository
:return: Current branch
"""
try:
cmd = ["git", "branch", "--show-current"]
result: str = check_output(cmd, stderr=DEVNULL, cwd=repo).decode(
encoding="utf-8"
)
return result.strip()
except CalledProcessError:
return ""
def get_local_tags(repo_path: Path, _filter: str | None = None) -> List[str]: def get_local_tags(repo_path: Path, _filter: str | None = None) -> List[str]:
""" """
Get all tags of a local Git repository Get all tags of a local Git repository
@@ -209,8 +226,8 @@ def get_local_commit(repo: Path) -> str | None:
return None return None
try: try:
cmd = f"cd {repo} && git describe HEAD --always --tags | cut -d '-' -f 1,2" cmd = "git describe HEAD --always --tags | cut -d '-' -f 1,2"
return check_output(cmd, shell=True, text=True).strip() return check_output(cmd, shell=True, text=True, cwd=repo).strip()
except CalledProcessError: except CalledProcessError:
return None return None
@@ -220,12 +237,15 @@ def get_remote_commit(repo: Path) -> str | None:
return None return None
try: try:
# get locally checked out branch branch = get_current_branch(repo)
branch_cmd = f"cd {repo} && git branch | grep -E '\*'" cmd = f"git describe 'origin/{branch}' --always --tags | cut -d '-' -f 1,2"
branch = check_output(branch_cmd, shell=True, text=True) return check_output(
branch = branch.split("*")[-1].strip() cmd,
cmd = f"cd {repo} && git describe 'origin/{branch}' --always --tags | cut -d '-' -f 1,2" shell=True,
return check_output(cmd, shell=True, text=True).strip() text=True,
cwd=repo,
stderr=DEVNULL,
).strip()
except CalledProcessError: except CalledProcessError:
return None return None

View File

@@ -127,9 +127,9 @@ managed_services: Spoolman
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/moonraker\.asvc" regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/moonraker\.asvc"
moonraker_asvc=$(find "${HOME}" -maxdepth 2 -type f -regextype posix-extended -regex "${regex}" | sort) moonraker_asvc=$(find "${HOME}" -maxdepth 2 -type f -regextype posix-extended -regex "${regex}" | sort)
if [[ -n ${moonraker_asvc} ]]; then if ! grep -q "^Spoolman$" "${moonraker_asvc}"; then
status_msg "Adding Spoolman service to moonraker.asvc..." status_msg "Adding Spoolman service to moonraker.asvc..."
/bin/sh -c "echo 'Spoolman' >> ${moonraker_asvc}" sed -i '$a''Spoolman' "${moonraker_asvc}"
fi fi
} }
@@ -248,7 +248,7 @@ function get_spoolman_status() {
function get_local_spoolman_version() { function get_local_spoolman_version() {
[[ ! -d "${SPOOLMAN_DIR}" ]] && return [[ ! -d "${SPOOLMAN_DIR}" ]] && return
local version local version
version=$(grep -o '"version":\s*"[^"]*' "${SPOOLMAN_DIR}"/release_info.json | cut -d'"' -f4) version=$(grep -o '"version":\s*"[^"]*' "${SPOOLMAN_DIR}"/release_info.json | cut -d'"' -f4)
echo "${version}" echo "${version}"