mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-23 07:43:36 +05:00
feat(klipper): implement instance manager and klipper installer in python
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
.vscode
|
||||
.idea
|
||||
.pytest_cache
|
||||
.kiauh-env
|
||||
*.code-workspace
|
||||
klipper_repos.txt
|
||||
klipper_repos.json
|
||||
|
||||
|
||||
15
kiauh.py
Normal file
15
kiauh.py
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.main import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
158
kiauh.sh
158
kiauh.sh
@@ -12,77 +12,97 @@
|
||||
set -e
|
||||
clear
|
||||
|
||||
### sourcing all additional scripts
|
||||
KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done
|
||||
for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done
|
||||
function main() {
|
||||
local python_command
|
||||
local entrypoint
|
||||
|
||||
#===================================================#
|
||||
#=================== UPDATE KIAUH ==================#
|
||||
#===================================================#
|
||||
|
||||
function update_kiauh() {
|
||||
status_msg "Updating KIAUH ..."
|
||||
|
||||
cd "${KIAUH_SRCDIR}"
|
||||
git reset --hard && git pull
|
||||
|
||||
ok_msg "Update complete! Please restart KIAUH."
|
||||
exit 0
|
||||
}
|
||||
|
||||
#===================================================#
|
||||
#=================== KIAUH STATUS ==================#
|
||||
#===================================================#
|
||||
|
||||
function kiauh_update_avail() {
|
||||
[[ ! -d "${KIAUH_SRCDIR}/.git" ]] && return
|
||||
local origin head
|
||||
|
||||
cd "${KIAUH_SRCDIR}"
|
||||
|
||||
### abort if not on master branch
|
||||
! git branch -a | grep -q "\* master" && return
|
||||
|
||||
### compare commit hash
|
||||
git fetch -q
|
||||
origin=$(git rev-parse --short=8 origin/master)
|
||||
head=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
if [[ ${origin} != "${head}" ]]; then
|
||||
echo "true"
|
||||
if command -v python3 &>/dev/null; then
|
||||
python_command="python3"
|
||||
elif command -v python &>/dev/null; then
|
||||
python_command="python"
|
||||
else
|
||||
echo "Python is not installed. Please install Python and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
entrypoint=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
|
||||
|
||||
${python_command} "${entrypoint}/kiauh.py"
|
||||
}
|
||||
|
||||
function kiauh_update_dialog() {
|
||||
[[ ! $(kiauh_update_avail) == "true" ]] && return
|
||||
top_border
|
||||
echo -e "|${green} New KIAUH update available! ${white}|"
|
||||
hr
|
||||
echo -e "|${green} View Changelog: https://git.io/JnmlX ${white}|"
|
||||
blank_line
|
||||
echo -e "|${yellow} It is recommended to keep KIAUH up to date. Updates ${white}|"
|
||||
echo -e "|${yellow} usually contain bugfixes, important changes or new ${white}|"
|
||||
echo -e "|${yellow} features. Please consider updating! ${white}|"
|
||||
bottom_border
|
||||
main
|
||||
|
||||
local yn
|
||||
read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn
|
||||
while true; do
|
||||
case "${yn}" in
|
||||
Y|y|Yes|yes|"")
|
||||
do_action "update_kiauh"
|
||||
break;;
|
||||
N|n|No|no)
|
||||
break;;
|
||||
*)
|
||||
deny_action "kiauh_update_dialog";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
check_euid
|
||||
init_logfile
|
||||
set_globals
|
||||
kiauh_update_dialog
|
||||
main_menu
|
||||
#### sourcing all additional scripts
|
||||
#KIAUH_SRCDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
#for script in "${KIAUH_SRCDIR}/scripts/"*.sh; do . "${script}"; done
|
||||
#for script in "${KIAUH_SRCDIR}/scripts/ui/"*.sh; do . "${script}"; done
|
||||
#
|
||||
##===================================================#
|
||||
##=================== UPDATE KIAUH ==================#
|
||||
##===================================================#
|
||||
#
|
||||
#function update_kiauh() {
|
||||
# status_msg "Updating KIAUH ..."
|
||||
#
|
||||
# cd "${KIAUH_SRCDIR}"
|
||||
# git reset --hard && git pull
|
||||
#
|
||||
# ok_msg "Update complete! Please restart KIAUH."
|
||||
# exit 0
|
||||
#}
|
||||
#
|
||||
##===================================================#
|
||||
##=================== KIAUH STATUS ==================#
|
||||
##===================================================#
|
||||
#
|
||||
#function kiauh_update_avail() {
|
||||
# [[ ! -d "${KIAUH_SRCDIR}/.git" ]] && return
|
||||
# local origin head
|
||||
#
|
||||
# cd "${KIAUH_SRCDIR}"
|
||||
#
|
||||
# ### abort if not on master branch
|
||||
# ! git branch -a | grep -q "\* master" && return
|
||||
#
|
||||
# ### compare commit hash
|
||||
# git fetch -q
|
||||
# origin=$(git rev-parse --short=8 origin/master)
|
||||
# head=$(git rev-parse --short=8 HEAD)
|
||||
#
|
||||
# if [[ ${origin} != "${head}" ]]; then
|
||||
# echo "true"
|
||||
# fi
|
||||
#}
|
||||
#
|
||||
#function kiauh_update_dialog() {
|
||||
# [[ ! $(kiauh_update_avail) == "true" ]] && return
|
||||
# top_border
|
||||
# echo -e "|${green} New KIAUH update available! ${white}|"
|
||||
# hr
|
||||
# echo -e "|${green} View Changelog: https://git.io/JnmlX ${white}|"
|
||||
# blank_line
|
||||
# echo -e "|${yellow} It is recommended to keep KIAUH up to date. Updates ${white}|"
|
||||
# echo -e "|${yellow} usually contain bugfixes, important changes or new ${white}|"
|
||||
# echo -e "|${yellow} features. Please consider updating! ${white}|"
|
||||
# bottom_border
|
||||
#
|
||||
# local yn
|
||||
# read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn
|
||||
# while true; do
|
||||
# case "${yn}" in
|
||||
# Y|y|Yes|yes|"")
|
||||
# do_action "update_kiauh"
|
||||
# break;;
|
||||
# N|n|No|no)
|
||||
# break;;
|
||||
# *)
|
||||
# deny_action "kiauh_update_dialog";;
|
||||
# esac
|
||||
# done
|
||||
#}
|
||||
#
|
||||
#check_euid
|
||||
#init_logfile
|
||||
#set_globals
|
||||
#kiauh_update_dialog
|
||||
#main_menu
|
||||
|
||||
0
kiauh/__init__.py
Normal file
0
kiauh/__init__.py
Normal file
0
kiauh/instance_manager/__init__.py
Normal file
0
kiauh/instance_manager/__init__.py
Normal file
85
kiauh/instance_manager/base_instance.py
Normal file
85
kiauh/instance_manager/base_instance.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 abc import abstractmethod, ABC
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class BaseInstance(ABC):
|
||||
@classmethod
|
||||
def blacklist(cls) -> List[str]:
|
||||
return []
|
||||
|
||||
def __init__(self, prefix: Optional[str], name: Optional[str],
|
||||
user: Optional[str], data_dir_name: Optional[str]):
|
||||
self._prefix = prefix
|
||||
self._name = name
|
||||
self._user = user
|
||||
self._data_dir_name = data_dir_name
|
||||
self.data_dir = f"{Path.home()}/{self._data_dir_name}_data"
|
||||
self.cfg_dir = f"{self.data_dir}/config"
|
||||
self.log_dir = f"{self.data_dir}/logs"
|
||||
self.comms_dir = f"{self.data_dir}/comms"
|
||||
self.sysd_dir = f"{self.data_dir}/systemd"
|
||||
|
||||
@property
|
||||
def prefix(self) -> str:
|
||||
return self._prefix
|
||||
|
||||
@prefix.setter
|
||||
def prefix(self, value) -> None:
|
||||
self._prefix = value
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value) -> None:
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def user(self) -> str:
|
||||
return self._user
|
||||
|
||||
@user.setter
|
||||
def user(self, value) -> None:
|
||||
self._user = value
|
||||
|
||||
@property
|
||||
def data_dir_name(self) -> str:
|
||||
return self._data_dir_name
|
||||
|
||||
@data_dir_name.setter
|
||||
def data_dir_name(self, value) -> None:
|
||||
self._data_dir_name = value
|
||||
|
||||
@abstractmethod
|
||||
def create(self) -> None:
|
||||
raise NotImplementedError("Subclasses must implement the create method")
|
||||
|
||||
@abstractmethod
|
||||
def read(self) -> None:
|
||||
raise NotImplementedError("Subclasses must implement the read method")
|
||||
|
||||
@abstractmethod
|
||||
def update(self) -> None:
|
||||
raise NotImplementedError("Subclasses must implement the update method")
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, del_remnants: bool) -> None:
|
||||
raise NotImplementedError("Subclasses must implement the delete method")
|
||||
|
||||
@abstractmethod
|
||||
def get_service_file_name(self) -> str:
|
||||
raise NotImplementedError(
|
||||
"Subclasses must implement the get_service_file_name method")
|
||||
151
kiauh/instance_manager/instance_manager.py
Normal file
151
kiauh/instance_manager/instance_manager.py
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Type, Union
|
||||
|
||||
from kiauh.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.utils.constants import SYSTEMD
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class InstanceManager:
|
||||
def __init__(self, instance_type: Type[BaseInstance],
|
||||
current_instance: Optional[BaseInstance] = None) -> None:
|
||||
self.instance_type = instance_type
|
||||
self.current_instance = current_instance
|
||||
self.instance_name = current_instance.name if current_instance is not None else None
|
||||
self.instances = []
|
||||
|
||||
def get_current_instance(self) -> BaseInstance:
|
||||
return self.current_instance
|
||||
|
||||
def set_current_instance(self, instance: BaseInstance) -> None:
|
||||
self.current_instance = instance
|
||||
self.instance_name = f"{instance.prefix}-{instance.name}" if instance.name else instance.prefix
|
||||
|
||||
def create_instance(self) -> None:
|
||||
if self.current_instance is not None:
|
||||
try:
|
||||
self.current_instance.create()
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
Logger.print_error(f"Creating instance failed: {e}")
|
||||
raise
|
||||
else:
|
||||
raise ValueError("current_instance cannot be None")
|
||||
|
||||
def delete_instance(self, del_remnants=False) -> None:
|
||||
if self.current_instance is not None:
|
||||
try:
|
||||
self.current_instance.delete(del_remnants)
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
Logger.print_error(f"Removing instance failed: {e}")
|
||||
raise
|
||||
else:
|
||||
raise ValueError("current_instance cannot be None")
|
||||
|
||||
def enable_instance(self) -> None:
|
||||
Logger.print_info(f"Enabling {self.instance_name}.service ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "enable",
|
||||
f"{self.instance_name}.service"]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_name}.service enabled.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error enabling service {self.instance_name}.service:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def disable_instance(self) -> None:
|
||||
Logger.print_info(f"Disabling {self.instance_name}.service ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "disable",
|
||||
f"{self.instance_name}.service"]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_name}.service disabled.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error disabling service {self.instance_name}.service:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def start_instance(self) -> None:
|
||||
Logger.print_info(f"Starting {self.instance_name}.service ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "start",
|
||||
f"{self.instance_name}.service"]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_name}.service started.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error starting service {self.instance_name}.service:")
|
||||
Logger.print_error(f"{e}")
|
||||
|
||||
def stop_instance(self) -> None:
|
||||
Logger.print_info(f"Stopping {self.instance_name}.service ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "stop",
|
||||
f"{self.instance_name}.service"]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok(f"{self.instance_name}.service stopped.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error stopping service {self.instance_name}.service:")
|
||||
Logger.print_error(f"{e}")
|
||||
raise
|
||||
|
||||
def reload_daemon(self) -> None:
|
||||
Logger.print_info("Reloading systemd manager configuration ...")
|
||||
try:
|
||||
command = ["sudo", "systemctl", "daemon-reload"]
|
||||
if subprocess.run(command, check=True):
|
||||
Logger.print_ok("Systemd manager configuration reloaded")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error("Error reloading systemd manager configuration:")
|
||||
Logger.print_error(f"{e}")
|
||||
raise
|
||||
|
||||
def get_instances(self) -> List[BaseInstance]:
|
||||
if not self.instances:
|
||||
self._find_instances()
|
||||
|
||||
return sorted(self.instances,
|
||||
key=lambda x: self._sort_instance_list(x.name))
|
||||
|
||||
def _find_instances(self) -> None:
|
||||
prefix = self.instance_type.__name__.lower()
|
||||
pattern = re.compile(f"{prefix}(-[0-9a-zA-Z]+)?.service")
|
||||
|
||||
excluded = self.instance_type.blacklist()
|
||||
service_list = [
|
||||
os.path.join(SYSTEMD, service)
|
||||
for service in os.listdir(SYSTEMD)
|
||||
if pattern.search(service)
|
||||
and not any(s in service for s in excluded)]
|
||||
|
||||
instance_list = [
|
||||
self.instance_type(name=self._get_instance_name(Path(service)))
|
||||
for service in service_list]
|
||||
|
||||
self.instances = instance_list
|
||||
|
||||
def _get_instance_name(self, file_path: Path) -> Union[str, None]:
|
||||
full_name = str(file_path).split("/")[-1].split(".")[0]
|
||||
if full_name.isalnum():
|
||||
return None
|
||||
|
||||
return full_name.split("-")[-1]
|
||||
|
||||
def _sort_instance_list(self, s):
|
||||
return int(s) if s.isdigit() else s
|
||||
20
kiauh/main.py
Normal file
20
kiauh/main.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.main_menu import MainMenu
|
||||
from kiauh.utils.logger import Logger
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
MainMenu().start()
|
||||
except KeyboardInterrupt:
|
||||
Logger.print_ok("\nHappy printing!\n", prefix=False)
|
||||
0
kiauh/menus/__init__.py
Normal file
0
kiauh/menus/__init__.py
Normal file
40
kiauh/menus/advanced_menu.py
Normal file
40
kiauh/menus/advanced_menu.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.base_menu import BaseMenu
|
||||
from kiauh.utils.constants import COLOR_YELLOW, RESET_FORMAT
|
||||
|
||||
|
||||
class AdvancedMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={},
|
||||
footer_type="back"
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
menu = textwrap.dedent(f"""
|
||||
/=======================================================\\
|
||||
| {COLOR_YELLOW}~~~~~~~~~~~~~ [ Advanced Menu ] ~~~~~~~~~~~~~{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| Klipper & API: | Mainsail: |
|
||||
| 0) [Rollback] | 5) [Theme installer] |
|
||||
| | |
|
||||
| Firmware: | System: |
|
||||
| 1) [Build only] | 6) [Change hostname] |
|
||||
| 2) [Flash only] | |
|
||||
| 3) [Build + Flash] | Extras: |
|
||||
| 4) [Get MCU ID] | 7) [G-Code Shell Command] |
|
||||
""")[1:]
|
||||
print(menu, end="")
|
||||
177
kiauh/menus/base_menu.py
Normal file
177
kiauh/menus/base_menu.py
Normal file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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
|
||||
import sys
|
||||
import textwrap
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Dict, Any
|
||||
|
||||
from kiauh.utils.constants import COLOR_GREEN, COLOR_YELLOW, COLOR_RED, \
|
||||
COLOR_CYAN, RESET_FORMAT
|
||||
|
||||
|
||||
def clear():
|
||||
subprocess.call("clear", shell=True)
|
||||
|
||||
|
||||
def print_header():
|
||||
header = textwrap.dedent(f"""
|
||||
/=======================================================\\
|
||||
| {COLOR_CYAN}~~~~~~~~~~~~~~~~~ [ KIAUH ] ~~~~~~~~~~~~~~~~~{RESET_FORMAT} |
|
||||
| {COLOR_CYAN} Klipper Installation And Update Helper {RESET_FORMAT} |
|
||||
| {COLOR_CYAN}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
""")[1:]
|
||||
print(header, end="")
|
||||
|
||||
|
||||
def print_quit_footer():
|
||||
footer = textwrap.dedent(f"""
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_RED}Q) Quit{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
""")[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
def print_back_footer():
|
||||
footer = textwrap.dedent(f"""
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_GREEN}B) « Back{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
""")[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
def print_back_help_footer():
|
||||
footer = textwrap.dedent(f"""
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
""")[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
def print_back_quit_footer():
|
||||
footer = textwrap.dedent(f"""
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
""")[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
def print_back_quit_help_footer():
|
||||
footer = textwrap.dedent(f"""
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_GREEN}B) « Back{RESET_FORMAT} | {COLOR_RED}Q) Quit{RESET_FORMAT} | {COLOR_YELLOW}H) Help [?]{RESET_FORMAT} |
|
||||
\=======================================================/
|
||||
""")[1:]
|
||||
print(footer, end="")
|
||||
|
||||
|
||||
class BaseMenu(ABC):
|
||||
QUIT_FOOTER = "quit"
|
||||
BACK_FOOTER = "back"
|
||||
BACK_HELP_FOOTER = "back_help"
|
||||
BACK_QUIT_FOOTER = "back_quit"
|
||||
BACK_QUIT_HELP_FOOTER = "back_quit_help"
|
||||
|
||||
def __init__(self, options: Dict[int, Any], options_offset=0, header=True,
|
||||
footer_type="quit"):
|
||||
self.options = options
|
||||
self.options_offset = options_offset
|
||||
self.header = header
|
||||
self.footer_type = footer_type
|
||||
|
||||
@abstractmethod
|
||||
def print_menu(self):
|
||||
raise NotImplementedError(
|
||||
"Subclasses must implement the print_menu method")
|
||||
|
||||
def print_footer(self):
|
||||
footer_type_map = {
|
||||
self.QUIT_FOOTER: print_quit_footer,
|
||||
self.BACK_FOOTER: print_back_footer,
|
||||
self.BACK_HELP_FOOTER: print_back_help_footer,
|
||||
self.BACK_QUIT_FOOTER: print_back_quit_footer,
|
||||
self.BACK_QUIT_HELP_FOOTER: print_back_quit_help_footer
|
||||
}
|
||||
footer_function = footer_type_map.get(self.footer_type,
|
||||
print_quit_footer)
|
||||
footer_function()
|
||||
|
||||
def display(self):
|
||||
# clear()
|
||||
if self.header:
|
||||
print_header()
|
||||
self.print_menu()
|
||||
self.print_footer()
|
||||
|
||||
def handle_user_input(self):
|
||||
while True:
|
||||
choice = input(f"{COLOR_CYAN}###### Perform action: {RESET_FORMAT}")
|
||||
|
||||
error_msg = f"{COLOR_RED}Invalid input.{RESET_FORMAT}" \
|
||||
if choice.isalpha() \
|
||||
else f"{COLOR_RED}Invalid input. Select a number between {min(self.options)} and {max(self.options)}.{RESET_FORMAT}"
|
||||
|
||||
if choice.isdigit() and 0 <= int(choice) < len(self.options):
|
||||
return choice
|
||||
elif choice.isalpha():
|
||||
allowed_input = {
|
||||
"quit": ["q"],
|
||||
"back": ["b"],
|
||||
"back_help": ["b", "h"],
|
||||
"back_quit": ["b", "q"],
|
||||
"back_quit_help": ["b", "q", "h"]
|
||||
}
|
||||
if self.footer_type in allowed_input and choice.lower() in \
|
||||
allowed_input[self.footer_type]:
|
||||
return choice
|
||||
else:
|
||||
print(error_msg)
|
||||
else:
|
||||
print(error_msg)
|
||||
|
||||
def start(self):
|
||||
while True:
|
||||
self.display()
|
||||
choice = self.handle_user_input()
|
||||
|
||||
if choice == "q":
|
||||
print(f"{COLOR_GREEN}###### Happy printing!{RESET_FORMAT}")
|
||||
sys.exit(0)
|
||||
elif choice == "b":
|
||||
return
|
||||
elif choice == "p":
|
||||
print("help!")
|
||||
else:
|
||||
self.execute_option(int(choice))
|
||||
|
||||
def execute_option(self, choice):
|
||||
option = self.options.get(choice, None)
|
||||
|
||||
if isinstance(option, type) and issubclass(option, BaseMenu):
|
||||
self.navigate_to_submenu(option)
|
||||
elif callable(option):
|
||||
option()
|
||||
elif option is None:
|
||||
raise NotImplementedError(f"No implementation for option {choice}")
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Type {type(option)} of option {choice} not of type BaseMenu or Method")
|
||||
|
||||
def navigate_to_submenu(self, submenu_class):
|
||||
submenu = submenu_class()
|
||||
submenu.previous_menu = self
|
||||
submenu.start()
|
||||
93
kiauh/menus/install_menu.py
Normal file
93
kiauh/menus/install_menu.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.base_menu import BaseMenu
|
||||
from kiauh.modules.klipper import klipper_setup
|
||||
from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class InstallMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
1: self.install_klipper,
|
||||
2: self.install_moonraker,
|
||||
3: self.install_mainsail,
|
||||
4: self.install_fluidd,
|
||||
5: self.install_klipperscreen,
|
||||
6: self.install_pretty_gcode,
|
||||
7: self.install_telegram_bot,
|
||||
8: self.install_obico,
|
||||
9: self.install_octoeverywhere,
|
||||
10: self.install_mobileraker,
|
||||
11: self.install_crowsnest
|
||||
},
|
||||
footer_type="back"
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
menu = textwrap.dedent(f"""
|
||||
/=======================================================\\
|
||||
| {COLOR_GREEN}~~~~~~~~~~~ [ Installation Menu ] ~~~~~~~~~~~{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| You need this menu usually only for installing |
|
||||
| all necessary dependencies for the various |
|
||||
| functions on a completely fresh system. |
|
||||
|-------------------------------------------------------|
|
||||
| Firmware & API: | Other: |
|
||||
| 1) [Klipper] | 6) [PrettyGCode] |
|
||||
| 2) [Moonraker] | 7) [Telegram Bot] |
|
||||
| | 8) $(obico_install_title) |
|
||||
| Klipper Webinterface: | 9) [OctoEverywhere] |
|
||||
| 3) [Mainsail] | 10) [Mobileraker] |
|
||||
| 4) [Fluidd] | |
|
||||
| | Webcam Streamer: |
|
||||
| Touchscreen GUI: | 11) [Crowsnest] |
|
||||
| 5) [KlipperScreen] | |
|
||||
""")[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def install_klipper(self):
|
||||
klipper_setup.run_klipper_setup(install=True)
|
||||
|
||||
def install_moonraker(self):
|
||||
print("install_moonraker")
|
||||
|
||||
def install_mainsail(self):
|
||||
print("install_mainsail")
|
||||
|
||||
def install_fluidd(self):
|
||||
print("install_fluidd")
|
||||
|
||||
def install_klipperscreen(self):
|
||||
print("install_klipperscreen")
|
||||
|
||||
def install_pretty_gcode(self):
|
||||
print("install_pretty_gcode")
|
||||
|
||||
def install_telegram_bot(self):
|
||||
print("install_telegram_bot")
|
||||
|
||||
def install_obico(self):
|
||||
print("install_obico")
|
||||
|
||||
def install_octoeverywhere(self):
|
||||
print("install_octoeverywhere")
|
||||
|
||||
def install_mobileraker(self):
|
||||
print("install_mobileraker")
|
||||
|
||||
def install_crowsnest(self):
|
||||
print("install_crowsnest")
|
||||
65
kiauh/menus/main_menu.py
Normal file
65
kiauh/menus/main_menu.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.advanced_menu import AdvancedMenu
|
||||
from kiauh.menus.base_menu import BaseMenu
|
||||
from kiauh.menus.install_menu import InstallMenu
|
||||
from kiauh.menus.remove_menu import RemoveMenu
|
||||
from kiauh.menus.settings_menu import SettingsMenu
|
||||
from kiauh.menus.update_menu import UpdateMenu
|
||||
from kiauh.utils.constants import COLOR_MAGENTA, COLOR_CYAN, RESET_FORMAT
|
||||
|
||||
|
||||
class MainMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
0: self.test,
|
||||
1: InstallMenu,
|
||||
2: UpdateMenu,
|
||||
3: RemoveMenu,
|
||||
4: AdvancedMenu,
|
||||
5: None,
|
||||
6: SettingsMenu
|
||||
},
|
||||
footer_type="quit"
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
menu = textwrap.dedent(f"""
|
||||
/=======================================================\\
|
||||
| {COLOR_CYAN}~~~~~~~~~~~~~~~ [ Main Menu ] ~~~~~~~~~~~~~~~{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| 0) [Log-Upload] | Klipper: <TODO> |
|
||||
| | Repo: <TODO> |
|
||||
| 1) [Install] | |
|
||||
| 2) [Update] | Moonraker: <TODO> |
|
||||
| 3) [Remove] | Repo: <TODO> |
|
||||
| 4) [Advanced] | |
|
||||
| 5) [Backup] | Mainsail: <TODO> |
|
||||
| | Fluidd: <TODO> |
|
||||
| 6) [Settings] | KlipperScreen: <TODO> |
|
||||
| | Mobileraker: <TODO> |
|
||||
| | |
|
||||
| | Crowsnest: <TODO> |
|
||||
| | Telegram Bot: <TODO> |
|
||||
| | Obico: <TODO> |
|
||||
| | OctoEverywhere: <TODO> |
|
||||
|-------------------------------------------------------|
|
||||
| {COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT} | Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT} |
|
||||
""")[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def test(self):
|
||||
print("blub")
|
||||
109
kiauh/menus/remove_menu.py
Normal file
109
kiauh/menus/remove_menu.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.base_menu import BaseMenu
|
||||
from kiauh.modules.klipper import klipper_setup
|
||||
from kiauh.utils.constants import COLOR_RED, RESET_FORMAT
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class RemoveMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
1: self.remove_klipper,
|
||||
2: self.remove_moonraker,
|
||||
3: self.remove_mainsail,
|
||||
4: self.remove_mainsail_config,
|
||||
5: self.remove_fluidd,
|
||||
6: self.remove_fluidd_config,
|
||||
7: self.remove_klipperscreen,
|
||||
8: self.remove_crowsnest,
|
||||
9: self.remove_mjpgstreamer,
|
||||
10: self.remove_pretty_gcode,
|
||||
11: self.remove_telegram_bot,
|
||||
12: self.remove_obico,
|
||||
13: self.remove_octoeverywhere,
|
||||
14: self.remove_mobileraker,
|
||||
15: self.remove_nginx,
|
||||
},
|
||||
footer_type="back"
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
menu = textwrap.dedent(f"""
|
||||
/=======================================================\\
|
||||
| {COLOR_RED}~~~~~~~~~~~~~~ [ Remove Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| INFO: Configurations and/or any backups will be kept! |
|
||||
|-------------------------------------------------------|
|
||||
| Firmware & API: | Webcam Streamer: |
|
||||
| 1) [Klipper] | 8) [Crowsnest] |
|
||||
| 2) [Moonraker] | 9) [MJPG-Streamer] |
|
||||
| | |
|
||||
| Klipper Webinterface: | Other: |
|
||||
| 3) [Mainsail] | 10) [PrettyGCode] |
|
||||
| 4) [Mainsail-Config] | 11) [Telegram Bot] |
|
||||
| 5) [Fluidd] | 12) [Obico for Klipper] |
|
||||
| 6) [Fluidd-Config] | 13) [OctoEverywhere] |
|
||||
| | 14) [Mobileraker] |
|
||||
| Touchscreen GUI: | 15) [NGINX] |
|
||||
| 7) [KlipperScreen] | |
|
||||
""")[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def remove_klipper(self):
|
||||
klipper_setup.run_klipper_setup(install=False)
|
||||
|
||||
def remove_moonraker(self):
|
||||
print("remove_moonraker")
|
||||
|
||||
def remove_mainsail(self):
|
||||
print("remove_mainsail")
|
||||
|
||||
def remove_mainsail_config(self):
|
||||
print("remove_mainsail_config")
|
||||
|
||||
def remove_fluidd(self):
|
||||
print("remove_fluidd")
|
||||
|
||||
def remove_fluidd_config(self):
|
||||
print("remove_fluidd_config")
|
||||
|
||||
def remove_klipperscreen(self):
|
||||
print("remove_klipperscreen")
|
||||
|
||||
def remove_crowsnest(self):
|
||||
print("remove_crowsnest")
|
||||
|
||||
def remove_mjpgstreamer(self):
|
||||
print("remove_mjpgstreamer")
|
||||
|
||||
def remove_pretty_gcode(self):
|
||||
print("remove_pretty_gcode")
|
||||
|
||||
def remove_telegram_bot(self):
|
||||
print("remove_telegram_bot")
|
||||
|
||||
def remove_obico(self):
|
||||
print("remove_obico")
|
||||
|
||||
def remove_octoeverywhere(self):
|
||||
print("remove_octoeverywhere")
|
||||
|
||||
def remove_mobileraker(self):
|
||||
print("remove_mobileraker")
|
||||
|
||||
def remove_nginx(self):
|
||||
print("remove_nginx")
|
||||
36
kiauh/menus/settings_menu.py
Normal file
36
kiauh/menus/settings_menu.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.base_menu import BaseMenu
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class SettingsMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={}
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
print("self")
|
||||
|
||||
def execute_option_p(self):
|
||||
# Implement the functionality for Option P
|
||||
print("Executing Option P")
|
||||
|
||||
def execute_option_q(self):
|
||||
# Implement the functionality for Option Q
|
||||
print("Executing Option Q")
|
||||
|
||||
def execute_option_r(self):
|
||||
# Implement the functionality for Option R
|
||||
print("Executing Option R")
|
||||
108
kiauh/menus/update_menu.py
Normal file
108
kiauh/menus/update_menu.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.menus.base_menu import BaseMenu
|
||||
from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class UpdateMenu(BaseMenu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
header=True,
|
||||
options={
|
||||
0: self.update_all,
|
||||
1: self.update_klipper,
|
||||
2: self.update_moonraker,
|
||||
3: self.update_mainsail,
|
||||
4: self.update_fluidd,
|
||||
5: self.update_klipperscreen,
|
||||
6: self.update_pgc_for_klipper,
|
||||
7: self.update_telegram_bot,
|
||||
8: self.update_moonraker_obico,
|
||||
9: self.update_octoeverywhere,
|
||||
10: self.update_mobileraker,
|
||||
11: self.update_crowsnest,
|
||||
12: self.upgrade_system_packages,
|
||||
},
|
||||
footer_type="back"
|
||||
)
|
||||
|
||||
def print_menu(self):
|
||||
menu = textwrap.dedent(f"""
|
||||
/=======================================================\\
|
||||
| {COLOR_GREEN}~~~~~~~~~~~~~~ [ Update Menu ] ~~~~~~~~~~~~~~{RESET_FORMAT} |
|
||||
|-------------------------------------------------------|
|
||||
| 0) [Update all] | | |
|
||||
| | Current: | Latest: |
|
||||
| Klipper & API: |--------------|--------------|
|
||||
| 1) [Klipper] | | |
|
||||
| 2) [Moonraker] | | |
|
||||
| | | |
|
||||
| Klipper Webinterface: |--------------|--------------|
|
||||
| 3) [Mainsail] | | |
|
||||
| 4) [Fluidd] | | |
|
||||
| | | |
|
||||
| Touchscreen GUI: |--------------|--------------|
|
||||
| 5) [KlipperScreen] | | |
|
||||
| | | |
|
||||
| Other: |--------------|--------------|
|
||||
| 6) [PrettyGCode] | | |
|
||||
| 7) [Telegram Bot] | | |
|
||||
| 8) [Obico for Klipper] | | |
|
||||
| 9) [OctoEverywhere] | | |
|
||||
| 10) [Mobileraker] | | |
|
||||
| 11) [Crowsnest] | | |
|
||||
| |-----------------------------|
|
||||
| 12) [System] | | |
|
||||
""")[1:]
|
||||
print(menu, end="")
|
||||
|
||||
def update_all(self):
|
||||
print("update_all")
|
||||
|
||||
def update_klipper(self):
|
||||
print("update_klipper")
|
||||
|
||||
def update_moonraker(self):
|
||||
print("update_moonraker")
|
||||
|
||||
def update_mainsail(self):
|
||||
print("update_mainsail")
|
||||
|
||||
def update_fluidd(self):
|
||||
print("update_fluidd")
|
||||
|
||||
def update_klipperscreen(self):
|
||||
print("update_klipperscreen")
|
||||
|
||||
def update_pgc_for_klipper(self):
|
||||
print("update_pgc_for_klipper")
|
||||
|
||||
def update_telegram_bot(self):
|
||||
print("update_telegram_bot")
|
||||
|
||||
def update_moonraker_obico(self):
|
||||
print("update_moonraker_obico")
|
||||
|
||||
def update_octoeverywhere(self):
|
||||
print("update_octoeverywhere")
|
||||
|
||||
def update_mobileraker(self):
|
||||
print("update_mobileraker")
|
||||
|
||||
def update_crowsnest(self):
|
||||
print("update_crowsnest")
|
||||
|
||||
def upgrade_system_packages(self):
|
||||
print("upgrade_system_packages")
|
||||
0
kiauh/modules/__init__.py
Normal file
0
kiauh/modules/__init__.py
Normal file
0
kiauh/modules/klipper/__init__.py
Normal file
0
kiauh/modules/klipper/__init__.py
Normal file
164
kiauh/modules/klipper/klipper.py
Normal file
164
kiauh/modules/klipper/klipper.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# !/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 os
|
||||
import pwd
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from kiauh.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.utils.constants import SYSTEMD, KLIPPER_DIR, KLIPPER_ENV_DIR
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import create_directory
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class Klipper(BaseInstance):
|
||||
@classmethod
|
||||
def blacklist(cls) -> List[str]:
|
||||
return ["None", "mcu"]
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__(name=name,
|
||||
prefix="klipper",
|
||||
user=pwd.getpwuid(os.getuid())[0],
|
||||
data_dir_name=self._get_data_dir_from_name(name))
|
||||
self.klipper_dir = KLIPPER_DIR
|
||||
self.env_dir = KLIPPER_ENV_DIR
|
||||
self.cfg_file = f"{self.cfg_dir}/printer.cfg"
|
||||
self.log = f"{self.log_dir}/klippy.log"
|
||||
self.serial = f"{self.comms_dir}/klippy.serial"
|
||||
self.uds = f"{self.comms_dir}/klippy.sock"
|
||||
|
||||
def create(self) -> None:
|
||||
Logger.print_info("Creating Klipper Instance")
|
||||
module_path = os.path.dirname(os.path.abspath(__file__))
|
||||
service_template_path = os.path.join(module_path, "res",
|
||||
"klipper.service")
|
||||
env_template_file_path = os.path.join(module_path, "res", "klipper.env")
|
||||
service_file_name = self.get_service_file_name(extension=True)
|
||||
service_file_target = f"{SYSTEMD}/{service_file_name}"
|
||||
env_file_target = os.path.abspath(f"{self.sysd_dir}/klipper.env")
|
||||
|
||||
# create folder structure
|
||||
dirs = [self.data_dir, self.cfg_dir, self.log_dir,
|
||||
self.comms_dir, self.sysd_dir]
|
||||
for _dir in dirs:
|
||||
create_directory(Path(_dir))
|
||||
|
||||
try:
|
||||
# writing the klipper service file (requires sudo!)
|
||||
service_content = self._prep_service_file(service_template_path,
|
||||
env_file_target)
|
||||
command = ["sudo", "tee", service_file_target]
|
||||
subprocess.run(command, input=service_content.encode(),
|
||||
stdout=subprocess.DEVNULL, check=True)
|
||||
Logger.print_ok(f"Service file created: {service_file_target}")
|
||||
|
||||
# writing the klipper.env file
|
||||
env_file_content = self._prep_env_file(env_template_file_path)
|
||||
with open(env_file_target, "w") as env_file:
|
||||
env_file.write(env_file_content)
|
||||
Logger.print_ok(f"Env file created: {env_file_target}")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(
|
||||
f"Error creating service file {service_file_target}: {e}")
|
||||
raise
|
||||
except OSError as e:
|
||||
Logger.print_error(
|
||||
f"Error creating env file {env_file_target}: {e}")
|
||||
raise
|
||||
|
||||
def read(self) -> None:
|
||||
print("Reading Klipper Instance")
|
||||
|
||||
def update(self) -> None:
|
||||
print("Updating Klipper Instance")
|
||||
|
||||
def delete(self, del_remnants: bool) -> None:
|
||||
service_file = self.get_service_file_name(extension=True)
|
||||
service_file_path = self._get_service_file_path()
|
||||
|
||||
Logger.print_info(f"Deleting Klipper Instance: {service_file}")
|
||||
|
||||
try:
|
||||
command = ["sudo", "rm", "-f", service_file_path]
|
||||
subprocess.run(command, check=True)
|
||||
Logger.print_ok(f"Service file deleted: {service_file_path}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.print_error(f"Error deleting service file: {e}")
|
||||
raise
|
||||
|
||||
if del_remnants:
|
||||
self._delete_klipper_remnants()
|
||||
|
||||
def _delete_klipper_remnants(self) -> None:
|
||||
try:
|
||||
Logger.print_info(f"Delete {self.klipper_dir} ...")
|
||||
shutil.rmtree(Path(self.klipper_dir))
|
||||
Logger.print_info(f"Delete {self.env_dir} ...")
|
||||
shutil.rmtree(Path(self.env_dir))
|
||||
except FileNotFoundError:
|
||||
Logger.print_info("Cannot delete Klipper directories. Not found.")
|
||||
except PermissionError as e:
|
||||
Logger.print_error(f"Error deleting Klipper directories: {e}")
|
||||
raise
|
||||
|
||||
Logger.print_ok("Directories successfully deleted.")
|
||||
|
||||
def get_service_file_name(self, extension=False) -> str:
|
||||
name = self.prefix if self.name is None else self.prefix + '-' + self.name
|
||||
return name if not extension else f"{name}.service"
|
||||
|
||||
def _get_service_file_path(self):
|
||||
return f"{SYSTEMD}/{self.get_service_file_name(extension=True)}"
|
||||
|
||||
def _get_data_dir_from_name(self, name: str) -> str:
|
||||
if name is None:
|
||||
return "printer"
|
||||
elif int(name.isdigit()):
|
||||
return f"printer_{name}"
|
||||
else:
|
||||
return name
|
||||
|
||||
def _prep_service_file(self, service_template_path, env_file_path):
|
||||
try:
|
||||
with open(service_template_path, "r") as template_file:
|
||||
template_content = template_file.read()
|
||||
except FileNotFoundError:
|
||||
Logger.print_error(
|
||||
f"Unable to open {service_template_path} - File not found")
|
||||
raise
|
||||
service_content = template_content.replace("%USER%", self.user)
|
||||
service_content = service_content.replace("%KLIPPER_DIR%",
|
||||
self.klipper_dir)
|
||||
service_content = service_content.replace("%ENV%", self.env_dir)
|
||||
service_content = service_content.replace("%ENV_FILE%", env_file_path)
|
||||
return service_content
|
||||
|
||||
def _prep_env_file(self, env_template_file_path):
|
||||
try:
|
||||
with open(env_template_file_path, "r") as env_file:
|
||||
env_template_file_content = env_file.read()
|
||||
except FileNotFoundError:
|
||||
Logger.print_error(
|
||||
f"Unable to open {env_template_file_path} - File not found")
|
||||
raise
|
||||
env_file_content = env_template_file_content.replace("%KLIPPER_DIR%",
|
||||
self.klipper_dir)
|
||||
env_file_content = env_file_content.replace("%CFG%", self.cfg_file)
|
||||
env_file_content = env_file_content.replace("%SERIAL%", self.serial)
|
||||
env_file_content = env_file_content.replace("%LOG%", self.log)
|
||||
env_file_content = env_file_content.replace("%UDS%", self.uds)
|
||||
return env_file_content
|
||||
236
kiauh/modules/klipper/klipper_setup.py
Normal file
236
kiauh/modules/klipper/klipper_setup.py
Normal file
@@ -0,0 +1,236 @@
|
||||
# !/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Union
|
||||
|
||||
from kiauh.instance_manager.instance_manager import InstanceManager
|
||||
from kiauh.modules.klipper.klipper import Klipper
|
||||
from kiauh.modules.klipper.klipper_utils import print_instance_overview
|
||||
from kiauh.utils.constants import KLIPPER_DIR, KLIPPER_ENV_DIR
|
||||
from kiauh.utils.input_utils import get_user_confirm, get_user_number_input, \
|
||||
get_user_string_input, get_user_selection_input
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.system_utils import parse_packages_from_file, \
|
||||
clone_repo, create_python_venv, \
|
||||
install_python_requirements, update_system_package_lists, \
|
||||
install_system_packages
|
||||
|
||||
|
||||
def run_klipper_setup(install: bool) -> None:
|
||||
instance_manager = InstanceManager(Klipper)
|
||||
instance_list = instance_manager.get_instances()
|
||||
instances_installed = len(instance_list)
|
||||
|
||||
is_klipper_installed = check_klipper_installation(instance_manager)
|
||||
if not install and not is_klipper_installed:
|
||||
Logger.print_warn("Klipper not installed!")
|
||||
return
|
||||
|
||||
if install:
|
||||
add_additional = handle_existing_instances(instance_manager)
|
||||
if is_klipper_installed and not add_additional:
|
||||
Logger.print_info("Exiting Klipper setup ...")
|
||||
return
|
||||
|
||||
install_klipper(instance_manager)
|
||||
|
||||
if not install:
|
||||
if instances_installed == 1:
|
||||
remove_single_instance(instance_manager)
|
||||
else:
|
||||
remove_multi_instance(instance_manager)
|
||||
|
||||
|
||||
def check_klipper_installation(instance_manager: InstanceManager) -> bool:
|
||||
instance_list = instance_manager.get_instances()
|
||||
instances_installed = len(instance_list)
|
||||
|
||||
if instances_installed < 1:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def handle_existing_instances(instance_manager: InstanceManager) -> bool:
|
||||
instance_list = instance_manager.get_instances()
|
||||
instance_count = len(instance_list)
|
||||
|
||||
if instance_count > 0:
|
||||
print_instance_overview(instance_list)
|
||||
if not get_user_confirm("Add new instances?"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def install_klipper(instance_manager: InstanceManager) -> None:
|
||||
instance_list = instance_manager.get_instances()
|
||||
if_adding = " additional" if len(instance_list) > 0 else ""
|
||||
install_count = get_user_number_input(
|
||||
f"Number of{if_adding} Klipper instances to set up",
|
||||
1, default=1)
|
||||
|
||||
instance_names = set_instance_names(instance_list, install_count)
|
||||
|
||||
if len(instance_list) < 1:
|
||||
setup_klipper_prerequesites()
|
||||
|
||||
for name in instance_names:
|
||||
current_instance = Klipper(name=name)
|
||||
instance_manager.set_current_instance(current_instance)
|
||||
instance_manager.create_instance()
|
||||
instance_manager.enable_instance()
|
||||
instance_manager.start_instance()
|
||||
|
||||
instance_manager.reload_daemon()
|
||||
|
||||
# step 4: check/handle conflicting packages/services
|
||||
|
||||
# step 5: check for required group membership
|
||||
|
||||
|
||||
def setup_klipper_prerequesites() -> None:
|
||||
# clone klipper TODO: read branch and url from json to allow forks
|
||||
url = "https://github.com/Klipper3D/klipper"
|
||||
branch = "master"
|
||||
clone_repo(Path(KLIPPER_DIR), url, branch)
|
||||
|
||||
# install klipper dependencies and create python virtualenv
|
||||
install_klipper_packages(Path(KLIPPER_DIR))
|
||||
create_python_venv(Path(KLIPPER_ENV_DIR))
|
||||
klipper_py_req = Path(f"{KLIPPER_DIR}/scripts/klippy-requirements.txt")
|
||||
install_python_requirements(Path(KLIPPER_ENV_DIR), klipper_py_req)
|
||||
|
||||
|
||||
def install_klipper_packages(klipper_dir: Path) -> None:
|
||||
script = f"{klipper_dir}/scripts/install-debian.sh"
|
||||
packages = parse_packages_from_file(script)
|
||||
packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages]
|
||||
# Add dfu-util for octopi-images
|
||||
packages.append("dfu-util")
|
||||
# Add dbus requirement for DietPi distro
|
||||
if os.path.exists("/boot/dietpi/.version"):
|
||||
packages.append("dbus")
|
||||
|
||||
update_system_package_lists(silent=False)
|
||||
install_system_packages(packages)
|
||||
|
||||
|
||||
def set_instance_names(instance_list, install_count: int) -> List[
|
||||
Union[str, None]]:
|
||||
instance_count = len(instance_list)
|
||||
|
||||
# default single instance install
|
||||
if instance_count == 0 and install_count == 1:
|
||||
return [None]
|
||||
|
||||
# new multi instance install
|
||||
elif ((instance_count == 0 and install_count > 1)
|
||||
# or convert single instance install to multi instance install
|
||||
or (instance_count == 1 and install_count >= 1)):
|
||||
if get_user_confirm("Assign custom names?", False):
|
||||
return assign_custom_names(instance_count, install_count, None)
|
||||
else:
|
||||
_range = range(1, install_count + 1)
|
||||
return [str(i) for i in _range]
|
||||
|
||||
# existing multi instance install
|
||||
elif instance_count > 1:
|
||||
if has_custom_names(instance_list):
|
||||
return assign_custom_names(instance_count, install_count,
|
||||
instance_list)
|
||||
else:
|
||||
start = get_highest_index(instance_list) + 1
|
||||
_range = range(start, start + install_count)
|
||||
return [str(i) for i in _range]
|
||||
|
||||
|
||||
def has_custom_names(instance_list: List[Klipper]) -> bool:
|
||||
pattern = re.compile("^\d+$")
|
||||
for instance in instance_list:
|
||||
if not pattern.match(instance.name):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def assign_custom_names(instance_count: int, install_count: int,
|
||||
instance_list: Optional[List[Klipper]]) -> List[str]:
|
||||
instance_names = []
|
||||
exclude = Klipper.blacklist()
|
||||
|
||||
# if an instance_list is provided, exclude all existing instance names
|
||||
if instance_list is not None:
|
||||
for instance in instance_list:
|
||||
exclude.append(instance.name)
|
||||
|
||||
for i in range(instance_count + install_count):
|
||||
question = f"Enter name for instance {i + 1}"
|
||||
name = get_user_string_input(question, exclude=exclude)
|
||||
instance_names.append(name)
|
||||
exclude.append(name)
|
||||
|
||||
return instance_names
|
||||
|
||||
|
||||
def get_highest_index(instance_list: List[Klipper]) -> int:
|
||||
indices = [int(instance.name.split('-')[-1]) for instance in instance_list]
|
||||
return max(indices)
|
||||
|
||||
|
||||
def remove_single_instance(instance_manager: InstanceManager) -> None:
|
||||
instance_list = instance_manager.get_instances()
|
||||
try:
|
||||
instance_manager.set_current_instance(instance_list[0])
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=True)
|
||||
instance_manager.reload_daemon()
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
Logger.print_error("Removing instance failed!")
|
||||
return
|
||||
|
||||
|
||||
def remove_multi_instance(instance_manager: InstanceManager) -> None:
|
||||
instance_list = instance_manager.get_instances()
|
||||
print_instance_overview(instance_list, show_index=True,
|
||||
show_select_all=True)
|
||||
|
||||
options = [str(i) for i in range(len(instance_list))]
|
||||
options.extend(["a", "A", "b", "B"])
|
||||
|
||||
selection = get_user_selection_input(
|
||||
"Select Klipper instance to remove", options)
|
||||
print(selection)
|
||||
|
||||
if selection == "b".lower():
|
||||
return
|
||||
elif selection == "a".lower():
|
||||
Logger.print_info("Removing all Klipper instances ...")
|
||||
for instance in instance_list:
|
||||
instance_manager.set_current_instance(instance)
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=True)
|
||||
else:
|
||||
instance = instance_list[int(selection)]
|
||||
Logger.print_info(
|
||||
f"Removing Klipper instance: {instance.get_service_file_name()}")
|
||||
instance_manager.set_current_instance(instance)
|
||||
instance_manager.stop_instance()
|
||||
instance_manager.disable_instance()
|
||||
instance_manager.delete_instance(del_remnants=False)
|
||||
|
||||
instance_manager.reload_daemon()
|
||||
39
kiauh/modules/klipper/klipper_utils.py
Normal file
39
kiauh/modules/klipper/klipper_utils.py
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 typing import List
|
||||
|
||||
from kiauh.instance_manager.base_instance import BaseInstance
|
||||
from kiauh.menus.base_menu import print_back_footer
|
||||
from kiauh.utils.constants import COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, \
|
||||
RESET_FORMAT
|
||||
|
||||
|
||||
def print_instance_overview(instances: List[BaseInstance], show_index=False,
|
||||
show_select_all=False):
|
||||
headline = f"{COLOR_GREEN}The following Klipper instances were found:{RESET_FORMAT}"
|
||||
|
||||
print("/=======================================================\\")
|
||||
print(f"|{'{:^64}'.format(headline)}|")
|
||||
print("|-------------------------------------------------------|")
|
||||
|
||||
if show_select_all:
|
||||
select_all = f" {COLOR_YELLOW}a) Select all{RESET_FORMAT}"
|
||||
print(f"|{'{:64}'.format(select_all)}|")
|
||||
print("| |")
|
||||
|
||||
for i, s in enumerate(instances):
|
||||
index = f"{i})" if show_index else "●"
|
||||
instance = s.get_service_file_name()
|
||||
line = f"{'{:53}'.format(f'{index} {instance}')}"
|
||||
print(f"| {COLOR_CYAN}{line}{RESET_FORMAT}|")
|
||||
|
||||
print_back_footer()
|
||||
1
kiauh/modules/klipper/res/klipper.env
Normal file
1
kiauh/modules/klipper/res/klipper.env
Normal file
@@ -0,0 +1 @@
|
||||
KLIPPER_ARGS="%KLIPPER_DIR%/klippy/klippy.py %CFG% -I %SERIAL% -l %LOG% -a %UDS%"
|
||||
18
kiauh/modules/klipper/res/klipper.service
Normal file
18
kiauh/modules/klipper/res/klipper.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Klipper 3D Printer Firmware SV1
|
||||
Documentation=https://www.klipper3d.org/
|
||||
After=network-online.target
|
||||
Wants=udev.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=%USER%
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=%KLIPPER_DIR%
|
||||
EnvironmentFile=%ENV_FILE%
|
||||
ExecStart=%ENV%/bin/python $KLIPPER_ARGS
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
0
kiauh/utils/__init__.py
Normal file
0
kiauh/utils/__init__.py
Normal file
29
kiauh/utils/constants.py
Normal file
29
kiauh/utils/constants.py
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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
|
||||
|
||||
# text colors and formats
|
||||
COLOR_MAGENTA = "\033[35m" # magenta
|
||||
COLOR_GREEN = "\033[92m" # bright green
|
||||
COLOR_YELLOW = "\033[93m" # bright yellow
|
||||
COLOR_RED = "\033[91m" # bright red
|
||||
COLOR_CYAN = "\033[96m" # bright cyan
|
||||
RESET_FORMAT = "\033[0m" # reset format
|
||||
|
||||
SYSTEMD = "/etc/systemd/system"
|
||||
|
||||
KLIPPER_DIR = f"{Path.home()}/klipper"
|
||||
KLIPPER_ENV_DIR = f"{Path.home()}/klippy-env"
|
||||
MOONRAKER_DIR = f"{Path.home()}/moonraker"
|
||||
MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env"
|
||||
MAINSAIL_DIR = f"{Path.home()}/mainsail"
|
||||
FLUIDD_DIR = f"{Path.home()}/fluidd"
|
||||
87
kiauh/utils/input_utils.py
Normal file
87
kiauh/utils/input_utils.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 typing import Optional, List
|
||||
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.constants import COLOR_CYAN, RESET_FORMAT
|
||||
|
||||
|
||||
def get_user_confirm(question: str, default_choice=True) -> bool:
|
||||
options_confirm = ["y", "yes"]
|
||||
options_decline = ["n", "no"]
|
||||
|
||||
if default_choice:
|
||||
def_choice = "(Y/n)"
|
||||
options_confirm.append("")
|
||||
else:
|
||||
def_choice = "(y/N)"
|
||||
options_decline.append("")
|
||||
|
||||
while True:
|
||||
choice = (
|
||||
input(f"{COLOR_CYAN}###### {question} {def_choice} {RESET_FORMAT}")
|
||||
.strip()
|
||||
.lower())
|
||||
|
||||
if choice in options_confirm:
|
||||
return True
|
||||
elif choice in options_decline:
|
||||
return False
|
||||
else:
|
||||
Logger.print_error("Invalid choice. Please select 'y' or 'n'.")
|
||||
|
||||
|
||||
def get_user_number_input(question: str, min_count: int, max_count=None,
|
||||
default=None) -> int:
|
||||
_question = question + f" (default={default})" if default else question
|
||||
_question = f"{COLOR_CYAN}###### {_question}: {RESET_FORMAT}"
|
||||
while True:
|
||||
try:
|
||||
num = input(_question)
|
||||
if num == "":
|
||||
return default
|
||||
|
||||
if max_count is not None:
|
||||
if min_count <= int(num) <= max_count:
|
||||
return int(num)
|
||||
else:
|
||||
raise ValueError
|
||||
elif int(num) >= min_count:
|
||||
return int(num)
|
||||
else:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
Logger.print_error("Invalid choice. Please select a valid number.")
|
||||
|
||||
|
||||
def get_user_string_input(question: str, exclude=Optional[List]) -> str:
|
||||
while True:
|
||||
_input = (input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}")
|
||||
.strip())
|
||||
|
||||
if _input.isalnum() and _input not in exclude:
|
||||
return _input
|
||||
|
||||
Logger.print_error("Invalid choice. Please enter a valid value.")
|
||||
if _input in exclude:
|
||||
Logger.print_error("This value is already in use/reserved.")
|
||||
|
||||
|
||||
def get_user_selection_input(question: str, option_list: List) -> str:
|
||||
while True:
|
||||
_input = (input(f"{COLOR_CYAN}###### {question}: {RESET_FORMAT}")
|
||||
.strip())
|
||||
|
||||
if _input in option_list:
|
||||
return _input
|
||||
|
||||
Logger.print_error("Invalid choice. Please enter a valid value.")
|
||||
51
kiauh/utils/logger.py
Normal file
51
kiauh/utils/logger.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 kiauh.utils.constants import COLOR_GREEN, COLOR_YELLOW, COLOR_RED, \
|
||||
COLOR_MAGENTA, RESET_FORMAT
|
||||
|
||||
|
||||
class Logger:
|
||||
|
||||
@staticmethod
|
||||
def info(msg):
|
||||
# log to kiauh.log
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def warn(msg):
|
||||
# log to kiauh.log
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def error(msg):
|
||||
# log to kiauh.log
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def print_ok(msg, prefix=True, end="\n") -> None:
|
||||
message = f"[OK] {msg}" if prefix else msg
|
||||
print(f"{COLOR_GREEN}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_warn(msg, prefix=True, end="\n") -> None:
|
||||
message = f"[WARN] {msg}" if prefix else msg
|
||||
print(f"{COLOR_YELLOW}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_error(msg, prefix=True, end="\n") -> None:
|
||||
message = f"[ERROR] {msg}" if prefix else msg
|
||||
print(f"{COLOR_RED}{message}{RESET_FORMAT}", end=end)
|
||||
|
||||
@staticmethod
|
||||
def print_info(msg, prefix=True, end="\n") -> None:
|
||||
message = f"###### {msg}" if prefix else msg
|
||||
print(f"{COLOR_MAGENTA}{message}{RESET_FORMAT}", end=end)
|
||||
203
kiauh/utils/system_utils.py
Normal file
203
kiauh/utils/system_utils.py
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# ======================================================================= #
|
||||
# Copyright (C) 2020 - 2023 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 os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from kiauh.utils.constants import COLOR_RED, RESET_FORMAT
|
||||
from kiauh.utils.logger import Logger
|
||||
from kiauh.utils.input_utils import get_user_confirm
|
||||
|
||||
|
||||
def kill(opt_err_msg=None) -> None:
|
||||
"""
|
||||
Kill the application.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
opt_err_msg : str
|
||||
optional, additional error message to display
|
||||
|
||||
Returns
|
||||
----------
|
||||
None
|
||||
"""
|
||||
|
||||
if opt_err_msg:
|
||||
Logger.print_error(opt_err_msg)
|
||||
Logger.print_error("A critical error has occured. KIAUH was terminated.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def clone_repo(target_dir: Path, url: str, branch: str) -> None:
|
||||
Logger.print_info(f"Cloning repository from {url}")
|
||||
if not target_dir.exists():
|
||||
try:
|
||||
command = ["git", "clone", f"{url}"]
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
command = ["git", "checkout", f"{branch}"]
|
||||
subprocess.run(command, cwd=target_dir, check=True)
|
||||
|
||||
Logger.print_ok("Clone successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error cloning repository:", e.output.decode())
|
||||
else:
|
||||
overwrite_target = get_user_confirm(
|
||||
"Target directory already exists. Overwrite?")
|
||||
if overwrite_target:
|
||||
try:
|
||||
shutil.rmtree(target_dir)
|
||||
clone_repo(target_dir, url, branch)
|
||||
except OSError as e:
|
||||
print("Error removing existing repository:", e.strerror)
|
||||
else:
|
||||
print("Skipping re-clone of repository ...")
|
||||
|
||||
|
||||
def parse_packages_from_file(source_file) -> List[str]:
|
||||
packages = []
|
||||
print("Reading dependencies...")
|
||||
with open(source_file, 'r') as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
if line.startswith("PKGLIST="):
|
||||
line = line.replace("\"", "")
|
||||
line = line.replace("PKGLIST=", "")
|
||||
line = line.replace('${PKGLIST}', '')
|
||||
packages.extend(line.split())
|
||||
return packages
|
||||
|
||||
|
||||
def create_python_venv(target: Path) -> None:
|
||||
Logger.print_info("Set up Python virtual environment ...")
|
||||
if not target.exists():
|
||||
try:
|
||||
command = ["python3", "-m", "venv", f"{target}"]
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
print(f"{COLOR_RED}{result.stderr}{RESET_FORMAT}")
|
||||
Logger.print_error("Setup of virtualenv failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Setup of virtualenv successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error setting up virtualenv:", e.output.decode())
|
||||
else:
|
||||
overwrite_venv = get_user_confirm(
|
||||
"Virtualenv already exists. Re-create?")
|
||||
if overwrite_venv:
|
||||
try:
|
||||
shutil.rmtree(target)
|
||||
create_python_venv(target)
|
||||
except OSError as e:
|
||||
Logger.print_error(
|
||||
f"Error removing existing virtualenv: {e.strerror}",
|
||||
False)
|
||||
else:
|
||||
print("Skipping re-creation of virtualenv ...")
|
||||
|
||||
|
||||
def update_python_pip(target: Path) -> None:
|
||||
Logger.print_info("Updating pip ...")
|
||||
try:
|
||||
command = [f"{target}/bin/pip", "install", "-U", "pip"]
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Updating pip failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Updating pip successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error updating pip:", e.output.decode())
|
||||
|
||||
|
||||
def install_python_requirements(target: Path, requirements: Path) -> None:
|
||||
update_python_pip(target)
|
||||
Logger.print_info("Installing Python requirements ...")
|
||||
try:
|
||||
command = [f"{target}/bin/pip", "install", "-r", f"{requirements}"]
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Installing Python requirements failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("Installing Python requirements successfull!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error installing Python requirements:", e.output.decode())
|
||||
|
||||
|
||||
def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
|
||||
cache_mtime = 0
|
||||
cache_files = [
|
||||
"/var/lib/apt/periodic/update-success-stamp",
|
||||
"/var/lib/apt/lists"
|
||||
]
|
||||
for cache_file in cache_files:
|
||||
if Path(cache_file).exists():
|
||||
cache_mtime = max(cache_mtime, os.path.getmtime(cache_file))
|
||||
|
||||
update_age = int(time.time() - cache_mtime)
|
||||
update_interval = 6 * 3600 # 48hrs
|
||||
|
||||
if update_age <= update_interval:
|
||||
return
|
||||
|
||||
if not silent:
|
||||
print("Updating package list...")
|
||||
|
||||
try:
|
||||
command = ["sudo", "apt-get", "update"]
|
||||
if rls_info_change:
|
||||
command.append("--allow-releaseinfo-change")
|
||||
|
||||
result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0 or result.stderr:
|
||||
Logger.print_error(f"{result.stderr}", False)
|
||||
Logger.print_error("Updating system package list failed!")
|
||||
return
|
||||
|
||||
Logger.print_ok("System package list updated successfully!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
kill(f"Error updating system package list:\n{e.stderr.decode()}")
|
||||
|
||||
|
||||
def install_system_packages(packages: List) -> None:
|
||||
try:
|
||||
command = ["sudo", "apt-get", "install", "-y"]
|
||||
for pkg in packages:
|
||||
command.append(pkg)
|
||||
subprocess.run(command, stderr=subprocess.PIPE, check=True)
|
||||
|
||||
Logger.print_ok("Packages installed successfully.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
kill(f"Error installing packages:\n{e.stderr.decode()}")
|
||||
|
||||
|
||||
def create_directory(_dir: Path) -> None:
|
||||
try:
|
||||
if not os.path.isdir(_dir):
|
||||
Logger.print_info(f"Create directory: {_dir}")
|
||||
os.makedirs(_dir, exist_ok=True)
|
||||
Logger.print_ok("Directory created!")
|
||||
else:
|
||||
Logger.print_info(
|
||||
f"Directory already exists: {_dir}\nSkip creation ...")
|
||||
except OSError as e:
|
||||
Logger.print_error(f"Error creating folder: {e}")
|
||||
raise
|
||||
Reference in New Issue
Block a user