Compare commits

...

13 Commits

Author SHA1 Message Date
dw-0
348f6a2794 Merge 1cd9414cae into a929c6983d 2024-07-01 21:17:25 +02:00
dw-0
1cd9414cae refactor: extract redundant code into shared methods
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-07-01 21:04:15 +02:00
dw-0
2391f491bb refactor: implement constants for klipper
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-07-01 20:42:22 +02:00
dw-0
92ed67ddd2 fix(mobileraker): fix typo and add more constants
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-07-01 20:41:56 +02:00
dw-0
0cb1e35b06 refactor: improve klipper class structure
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-07-01 20:41:56 +02:00
dw-0
7632c3c980 refactor: implement constants for klipper
use ubuntu 22.04 install script

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-07-01 20:41:54 +02:00
dw-0
c1f600f539 refactor: replace glob with iterdir
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-30 21:25:55 +02:00
dw-0
01deab7c64 fix: disallow installing client config if another client config is installed
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-30 20:51:04 +02:00
Beans Baxter
a929c6983d refactor: don't check spoolman version if not installed (#487) 2024-06-28 23:07:20 +02:00
breakheart
bce92001a6 fix: use correct Spoolman directory name (#486) 2024-06-28 17:46:19 +02:00
Alessandro Maggi
7993b98ee1 fix: replace jq by grep to check Spoolman update (#482)
* fix: remove extra space in remove menu

* fix(spoolman): replace jq with grep
jq isn't included in some minimal installations
2024-06-26 07:04:09 +02:00
Alessandro Maggi
62296e112e feat: add Spoolman support (#477) 2024-06-25 20:10:14 +02:00
Justin Otherguy
a374ac8fac fix: add unzip to dependencies for Mainsail and Fluidd 2024-05-09 20:56:16 +02:00
25 changed files with 617 additions and 248 deletions

View File

@@ -13,9 +13,24 @@ from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent MODULE_PATH = Path(__file__).resolve().parent
# names
KLIPPER_LOG_NAME = "klippy.log"
KLIPPER_CFG_NAME = "printer.cfg"
KLIPPER_SERIAL_NAME = "klippy.serial"
KLIPPER_UDS_NAME = "klippy.sock"
KLIPPER_ENV_FILE_NAME = "klipper.env"
KLIPPER_SERVICE_NAME = "klipper.service"
# directories
KLIPPER_DIR = Path.home().joinpath("klipper") KLIPPER_DIR = Path.home().joinpath("klipper")
KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env")
KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups")
KLIPPER_REQUIREMENTS_TXT = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt")
# files
KLIPPER_REQ_FILE = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt")
KLIPPER_INSTALL_SCRIPT = KLIPPER_DIR.joinpath("scripts/install-ubuntu-22.04.sh")
KLIPPER_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{KLIPPER_SERVICE_NAME}")
KLIPPER_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{KLIPPER_ENV_FILE_NAME}")
EXIT_KLIPPER_SETUP = "Exiting Klipper setup ..." EXIT_KLIPPER_SETUP = "Exiting Klipper setup ..."

View File

@@ -8,12 +8,21 @@
# ======================================================================= # # ======================================================================= #
from pathlib import Path from pathlib import Path
from subprocess import DEVNULL, CalledProcessError, run from subprocess import CalledProcessError, run
from typing import List from typing import List
from components.klipper import KLIPPER_DIR, KLIPPER_ENV_DIR, MODULE_PATH from components.klipper import (
KLIPPER_CFG_NAME,
KLIPPER_DIR,
KLIPPER_ENV_DIR,
KLIPPER_ENV_FILE_NAME,
KLIPPER_ENV_FILE_TEMPLATE,
KLIPPER_LOG_NAME,
KLIPPER_SERIAL_NAME,
KLIPPER_SERVICE_TEMPLATE,
KLIPPER_UDS_NAME,
)
from core.instance_manager.base_instance import BaseInstance from core.instance_manager.base_instance import BaseInstance
from utils.constants import SYSTEMD
from utils.logger import Logger from utils.logger import Logger
@@ -27,10 +36,10 @@ class Klipper(BaseInstance):
super().__init__(instance_type=self, suffix=suffix) super().__init__(instance_type=self, suffix=suffix)
self.klipper_dir: Path = KLIPPER_DIR self.klipper_dir: Path = KLIPPER_DIR
self.env_dir: Path = KLIPPER_ENV_DIR self.env_dir: Path = KLIPPER_ENV_DIR
self._cfg_file = self.cfg_dir.joinpath("printer.cfg") self._cfg_file = self.cfg_dir.joinpath(KLIPPER_CFG_NAME)
self._log = self.log_dir.joinpath("klippy.log") self._log = self.log_dir.joinpath(KLIPPER_LOG_NAME)
self._serial = self.comms_dir.joinpath("klippy.serial") self._serial = self.comms_dir.joinpath(KLIPPER_SERIAL_NAME)
self._uds = self.comms_dir.joinpath("klippy.sock") self._uds = self.comms_dir.joinpath(KLIPPER_UDS_NAME)
@property @property
def cfg_file(self) -> Path: def cfg_file(self) -> Path:
@@ -49,23 +58,22 @@ class Klipper(BaseInstance):
return self._uds return self._uds
def create(self) -> None: def create(self) -> None:
from utils.sys_utils import create_env_file, create_service_file
Logger.print_status("Creating new Klipper Instance ...") Logger.print_status("Creating new Klipper Instance ...")
service_template_path = MODULE_PATH.joinpath("assets/klipper.service")
service_file_name = self.get_service_file_name(extension=True)
service_file_target = SYSTEMD.joinpath(service_file_name)
env_template_file_path = MODULE_PATH.joinpath("assets/klipper.env")
env_file_target = self.sysd_dir.joinpath("klipper.env")
try: try:
self.create_folders() self.create_folders()
self._write_service_file(
service_template_path, create_service_file(
service_file_target, name=self.get_service_file_name(extension=True),
env_file_target, content=self._prep_service_file_content(),
)
create_env_file(
path=self.sysd_dir.joinpath(KLIPPER_ENV_FILE_NAME),
content=self._prep_env_file_content(),
) )
self._write_env_file(env_template_file_path, env_file_target)
except CalledProcessError as e: except CalledProcessError as e:
Logger.print_error(f"Error creating instance: {e}") Logger.print_error(f"Error creating instance: {e}")
@@ -83,80 +91,68 @@ class Klipper(BaseInstance):
try: try:
command = ["sudo", "rm", "-f", service_file_path] command = ["sudo", "rm", "-f", service_file_path]
run(command, check=True) run(command, check=True)
self._delete_logfiles() self.delete_logfiles(KLIPPER_LOG_NAME)
Logger.print_ok("Instance successfully removed!") Logger.print_ok("Instance successfully removed!")
except CalledProcessError as e: except CalledProcessError as e:
Logger.print_error(f"Error removing instance: {e}") Logger.print_error(f"Error removing instance: {e}")
raise raise
def _write_service_file( def _prep_service_file_content(self) -> str:
self, template = KLIPPER_SERVICE_TEMPLATE
service_template_path: Path,
service_file_target: Path,
env_file_target: Path,
) -> None:
service_content = self._prep_service_file(
service_template_path, env_file_target
)
command = ["sudo", "tee", service_file_target]
run(
command,
input=service_content.encode(),
stdout=DEVNULL,
check=True,
)
Logger.print_ok(f"Service file created: {service_file_target}")
def _write_env_file(
self, env_template_file_path: Path, env_file_target: Path
) -> None:
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}")
def _prep_service_file(
self, service_template_path: Path, env_file_path: Path
) -> str:
try: try:
with open(service_template_path, "r") as template_file: with open(template, "r") as template_file:
template_content = template_file.read() template_content = template_file.read()
except FileNotFoundError: except FileNotFoundError:
Logger.print_error( Logger.print_error(f"Unable to open {template} - File not found")
f"Unable to open {service_template_path} - File not found"
)
raise raise
service_content = template_content.replace("%USER%", self.user)
service_content = service_content.replace( service_content = template_content.replace(
"%KLIPPER_DIR%", str(self.klipper_dir) "%USER%",
self.user,
)
service_content = service_content.replace(
"%KLIPPER_DIR%",
self.klipper_dir.as_posix(),
)
service_content = service_content.replace(
"%ENV%",
self.env_dir.as_posix(),
)
service_content = service_content.replace(
"%ENV_FILE%",
self.sysd_dir.joinpath(KLIPPER_ENV_FILE_NAME).as_posix(),
) )
service_content = service_content.replace("%ENV%", str(self.env_dir))
service_content = service_content.replace("%ENV_FILE%", str(env_file_path))
return service_content return service_content
def _prep_env_file(self, env_template_file_path: Path) -> str: def _prep_env_file_content(self) -> str:
template = KLIPPER_ENV_FILE_TEMPLATE
try: try:
with open(env_template_file_path, "r") as env_file: with open(template, "r") as env_file:
env_template_file_content = env_file.read() env_template_file_content = env_file.read()
except FileNotFoundError: except FileNotFoundError:
Logger.print_error( Logger.print_error(f"Unable to open {template} - File not found")
f"Unable to open {env_template_file_path} - File not found"
)
raise raise
env_file_content = env_template_file_content.replace( env_file_content = env_template_file_content.replace(
"%KLIPPER_DIR%", str(self.klipper_dir) "%KLIPPER_DIR%", self.klipper_dir.as_posix()
) )
env_file_content = env_file_content.replace( env_file_content = env_file_content.replace(
"%CFG%", f"{self.cfg_dir}/printer.cfg" "%CFG%",
f"{self.cfg_dir}/{KLIPPER_CFG_NAME}",
) )
env_file_content = env_file_content.replace("%SERIAL%", str(self.serial)) env_file_content = env_file_content.replace(
env_file_content = env_file_content.replace("%LOG%", str(self.log)) "%SERIAL%",
env_file_content = env_file_content.replace("%UDS%", str(self.uds)) self.serial.as_posix(),
)
env_file_content = env_file_content.replace(
"%LOG%",
self.log.as_posix(),
)
env_file_content = env_file_content.replace(
"%UDS%",
self.uds.as_posix(),
)
return env_file_content return env_file_content
def _delete_logfiles(self) -> None:
from utils.fs_utils import run_remove_routines
for log in list(self.log_dir.glob("klippy.log*")):
Logger.print_status(f"Remove '{log}'")
run_remove_routines(log)

View File

@@ -13,7 +13,8 @@ from components.klipper import (
EXIT_KLIPPER_SETUP, EXIT_KLIPPER_SETUP,
KLIPPER_DIR, KLIPPER_DIR,
KLIPPER_ENV_DIR, KLIPPER_ENV_DIR,
KLIPPER_REQUIREMENTS_TXT, KLIPPER_INSTALL_SCRIPT,
KLIPPER_REQ_FILE,
) )
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.klipper.klipper_utils import ( from components.klipper.klipper_utils import (
@@ -115,21 +116,19 @@ def setup_klipper_prerequesites() -> None:
# install klipper dependencies and create python virtualenv # install klipper dependencies and create python virtualenv
try: try:
install_klipper_packages(KLIPPER_DIR) install_klipper_packages()
create_python_venv(KLIPPER_ENV_DIR) create_python_venv(KLIPPER_ENV_DIR)
install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT) install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQ_FILE)
except Exception: except Exception:
Logger.print_error("Error during installation of Klipper requirements!") Logger.print_error("Error during installation of Klipper requirements!")
raise raise
def install_klipper_packages(klipper_dir: Path) -> None: def install_klipper_packages() -> None:
script = klipper_dir.joinpath("scripts/install-debian.sh") script = KLIPPER_INSTALL_SCRIPT
packages = parse_packages_from_file(script) packages = parse_packages_from_file(script)
packages = [pkg.replace("python-dev", "python3-dev") for pkg in packages] packages.append("python3-venv") # todo: remove once switched to virtualenv
packages.append("python3-venv")
# Add dfu-util for octopi-images
packages.append("dfu-util")
# Add dbus requirement for DietPi distro # Add dbus requirement for DietPi distro
if Path("/boot/dietpi/.version").exists(): if Path("/boot/dietpi/.version").exists():
packages.append("dbus") packages.append("dbus")
@@ -160,9 +159,9 @@ def update_klipper() -> None:
git_pull_wrapper(repo=settings.klipper.repo_url, target_dir=KLIPPER_DIR) git_pull_wrapper(repo=settings.klipper.repo_url, target_dir=KLIPPER_DIR)
# install possible new system packages # install possible new system packages
install_klipper_packages(KLIPPER_DIR) install_klipper_packages()
# install possible new python dependencies # install possible new python dependencies
install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQUIREMENTS_TXT) install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQ_FILE)
instance_manager.start_all_instance() instance_manager.start_all_instance()

View File

@@ -9,8 +9,19 @@
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR from core.backup_manager import BACKUP_ROOT_DIR
from utils.constants import SYSTEMD
# names
MOBILERAKER_REPO = "https://github.com/Clon1998/mobileraker_companion.git" MOBILERAKER_REPO = "https://github.com/Clon1998/mobileraker_companion.git"
MOBILERAKER_UPDATER_SECTION_NAME = "update_manager mobileraker"
MOBILERAKER_LOG_NAME = "mobileraker.log"
# directories
MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion") MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion")
MOBILERAKER_ENV = Path.home().joinpath("mobileraker-env")
MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups") MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups")
# files
MOBILERAKER_ENV = Path.home().joinpath("mobileraker-env")
MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh")
MOBILERAKER_REQ_FILE = MOBILERAKER_DIR.joinpath("scripts/mobileraker-requirements.txt")
MOBILERAKER_SERVICE_FILE = SYSTEMD.joinpath("mobileraker.service")

View File

@@ -16,7 +16,12 @@ from components.mobileraker import (
MOBILERAKER_BACKUP_DIR, MOBILERAKER_BACKUP_DIR,
MOBILERAKER_DIR, MOBILERAKER_DIR,
MOBILERAKER_ENV, MOBILERAKER_ENV,
MOBILERAKER_INSTALL_SCRIPT,
MOBILERAKER_LOG_NAME,
MOBILERAKER_REPO, MOBILERAKER_REPO,
MOBILERAKER_REQ_FILE,
MOBILERAKER_SERVICE_FILE,
MOBILERAKER_UPDATER_SECTION_NAME,
) )
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
@@ -24,7 +29,6 @@ from core.instance_manager.instance_manager import InstanceManager
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from utils.common import check_install_dependencies, get_install_status from utils.common import check_install_dependencies, get_install_status
from utils.config_utils import add_config_section, remove_config_section from utils.config_utils import add_config_section, remove_config_section
from utils.constants import SYSTEMD
from utils.fs_utils import remove_with_sudo from utils.fs_utils import remove_with_sudo
from utils.git_utils import ( from utils.git_utils import (
git_clone_wrapper, git_clone_wrapper,
@@ -72,8 +76,7 @@ def install_mobileraker() -> None:
git_clone_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR) git_clone_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR)
try: try:
script = f"{MOBILERAKER_DIR}/scripts/install.sh" run(MOBILERAKER_INSTALL_SCRIPT.as_posix(), shell=True, check=True)
run(script, shell=True, check=True)
if mr_instances: if mr_instances:
patch_mobileraker_update_manager(mr_instances) patch_mobileraker_update_manager(mr_instances)
mr_im.restart_all_instance() mr_im.restart_all_instance()
@@ -89,19 +92,18 @@ def install_mobileraker() -> None:
def patch_mobileraker_update_manager(instances: List[Moonraker]) -> None: def patch_mobileraker_update_manager(instances: List[Moonraker]) -> None:
env_py = f"{MOBILERAKER_ENV}/bin/python"
add_config_section( add_config_section(
section="update_manager mobileraker", section=MOBILERAKER_UPDATER_SECTION_NAME,
instances=instances, instances=instances,
options=[ options=[
("type", "git_repo"), ("type", "git_repo"),
("path", "mobileraker_companion"), ("path", MOBILERAKER_DIR.as_posix()),
("orgin", MOBILERAKER_REPO), ("origin", MOBILERAKER_REPO),
("primary_branch", "main"), ("primary_branch", "main"),
("managed_services", "mobileraker"), ("managed_services", "mobileraker"),
("env", env_py), ("env", f"{MOBILERAKER_ENV}/bin/python"),
("requirements", "scripts/mobileraker-requirements.txt"), ("requirements", MOBILERAKER_REQ_FILE.as_posix()),
("install_script", "scripts/install.sh"), ("install_script", MOBILERAKER_INSTALL_SCRIPT.as_posix()),
], ],
) )
@@ -124,8 +126,7 @@ def update_mobileraker() -> None:
git_pull_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR) git_pull_wrapper(MOBILERAKER_REPO, MOBILERAKER_DIR)
requirements = MOBILERAKER_DIR.joinpath("/scripts/mobileraker-requirements.txt") install_python_requirements(MOBILERAKER_ENV, MOBILERAKER_REQ_FILE)
install_python_requirements(MOBILERAKER_ENV, requirements)
cmd_sysctl_service("mobileraker", "start") cmd_sysctl_service("mobileraker", "start")
@@ -139,7 +140,7 @@ def get_mobileraker_status() -> ComponentStatus:
return get_install_status( return get_install_status(
MOBILERAKER_DIR, MOBILERAKER_DIR,
MOBILERAKER_ENV, MOBILERAKER_ENV,
files=[SYSTEMD.joinpath("mobileraker.service")], files=[MOBILERAKER_SERVICE_FILE],
) )
@@ -160,12 +161,11 @@ def remove_mobileraker() -> None:
else: else:
Logger.print_warn("Mobileraker's companion environment not found!") Logger.print_warn("Mobileraker's companion environment not found!")
service = SYSTEMD.joinpath("mobileraker.service") if MOBILERAKER_SERVICE_FILE.exists():
if service.exists():
Logger.print_status("Removing mobileraker service ...") Logger.print_status("Removing mobileraker service ...")
cmd_sysctl_service(service, "stop") cmd_sysctl_service(MOBILERAKER_SERVICE_FILE, "stop")
cmd_sysctl_service(service, "disable") cmd_sysctl_service(MOBILERAKER_SERVICE_FILE, "disable")
remove_with_sudo(service) remove_with_sudo(MOBILERAKER_SERVICE_FILE)
cmd_sysctl_manage("daemon-reload") cmd_sysctl_manage("daemon-reload")
cmd_sysctl_manage("reset-failed") cmd_sysctl_manage("reset-failed")
Logger.print_ok("Mobileraker's companion service successfully removed!") Logger.print_ok("Mobileraker's companion service successfully removed!")
@@ -173,7 +173,7 @@ def remove_mobileraker() -> None:
kl_im = InstanceManager(Klipper) kl_im = InstanceManager(Klipper)
kl_instances: List[Klipper] = kl_im.instances kl_instances: List[Klipper] = kl_im.instances
for instance in kl_instances: for instance in kl_instances:
logfile = instance.log_dir.joinpath("mobileraker.log") logfile = instance.log_dir.joinpath(MOBILERAKER_LOG_NAME)
if logfile.exists(): if logfile.exists():
Logger.print_status(f"Removing {logfile} ...") Logger.print_status(f"Removing {logfile} ...")
Path(logfile).unlink() Path(logfile).unlink()
@@ -185,7 +185,7 @@ def remove_mobileraker() -> None:
Logger.print_status( Logger.print_status(
"Removing Mobileraker's companion from update manager ..." "Removing Mobileraker's companion from update manager ..."
) )
remove_config_section("update_manager mobileraker", mr_instances) remove_config_section(MOBILERAKER_UPDATER_SECTION_NAME, mr_instances)
Logger.print_ok( Logger.print_ok(
"Mobileraker's companion successfully removed from update manager!" "Mobileraker's companion successfully removed from update manager!"
) )
@@ -199,12 +199,12 @@ def remove_mobileraker() -> None:
def backup_mobileraker_dir() -> None: def backup_mobileraker_dir() -> None:
bm = BackupManager() bm = BackupManager()
bm.backup_directory( bm.backup_directory(
"mobileraker_companion", MOBILERAKER_DIR.name,
source=MOBILERAKER_DIR, source=MOBILERAKER_DIR,
target=MOBILERAKER_BACKUP_DIR, target=MOBILERAKER_BACKUP_DIR,
) )
bm.backup_directory( bm.backup_directory(
"mobileraker-env", MOBILERAKER_ENV.name,
source=MOBILERAKER_ENV, source=MOBILERAKER_ENV,
target=MOBILERAKER_BACKUP_DIR, target=MOBILERAKER_BACKUP_DIR,
) )

View File

@@ -13,15 +13,21 @@ from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent MODULE_PATH = Path(__file__).resolve().parent
# names
MOONRAKER_CFG_NAME = "moonraker.conf"
MOONRAKER_LOG_NAME = "moonraker.log"
MOONRAKER_SERVICE_NAME = "moonraker.service"
MOONRAKER_DEFAULT_PORT = 7125
MOONRAKER_ENV_FILE_NAME = "moonraker.env"
# directories
MOONRAKER_DIR = Path.home().joinpath("moonraker") MOONRAKER_DIR = Path.home().joinpath("moonraker")
MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env") MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env")
MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups") MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups")
MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups") MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups")
MOONRAKER_REQUIREMENTS_TXT = MOONRAKER_DIR.joinpath(
"scripts/moonraker-requirements.txt"
)
DEFAULT_MOONRAKER_PORT = 7125
# files
MOONRAKER_REQ_FILE = MOONRAKER_DIR.joinpath("scripts/moonraker-requirements.txt")
# introduced due to # introduced due to
# https://github.com/Arksine/moonraker/issues/349 # https://github.com/Arksine/moonraker/issues/349
# https://github.com/Arksine/moonraker/pull/346 # https://github.com/Arksine/moonraker/pull/346
@@ -29,5 +35,8 @@ POLKIT_LEGACY_FILE = Path("/etc/polkit-1/localauthority/50-local.d/10-moonraker.
POLKIT_FILE = Path("/etc/polkit-1/rules.d/moonraker.rules") POLKIT_FILE = Path("/etc/polkit-1/rules.d/moonraker.rules")
POLKIT_USR_FILE = Path("/usr/share/polkit-1/rules.d/moonraker.rules") POLKIT_USR_FILE = Path("/usr/share/polkit-1/rules.d/moonraker.rules")
POLKIT_SCRIPT = Path.home().joinpath("moonraker/scripts/set-policykit-rules.sh") POLKIT_SCRIPT = Path.home().joinpath("moonraker/scripts/set-policykit-rules.sh")
MOONRAKER_SERVICE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_SERVICE_NAME}")
MOONRAKER_ENV_FILE_TEMPLATE = MODULE_PATH.joinpath(f"assets/{MOONRAKER_ENV_FILE_NAME}")
EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..." EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..."

View File

@@ -9,15 +9,22 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from subprocess import DEVNULL, CalledProcessError, run from subprocess import CalledProcessError, run
from typing import List from typing import List
from components.moonraker import MODULE_PATH, MOONRAKER_DIR, MOONRAKER_ENV_DIR from components.moonraker import (
MOONRAKER_CFG_NAME,
MOONRAKER_DIR,
MOONRAKER_ENV_DIR,
MOONRAKER_ENV_FILE_NAME,
MOONRAKER_ENV_FILE_TEMPLATE,
MOONRAKER_LOG_NAME,
MOONRAKER_SERVICE_TEMPLATE,
)
from core.instance_manager.base_instance import BaseInstance from core.instance_manager.base_instance import BaseInstance
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser, SimpleConfigParser,
) )
from utils.constants import SYSTEMD
from utils.logger import Logger from utils.logger import Logger
@@ -31,13 +38,13 @@ class Moonraker(BaseInstance):
super().__init__(instance_type=self, suffix=suffix) super().__init__(instance_type=self, suffix=suffix)
self.moonraker_dir: Path = MOONRAKER_DIR self.moonraker_dir: Path = MOONRAKER_DIR
self.env_dir: Path = MOONRAKER_ENV_DIR self.env_dir: Path = MOONRAKER_ENV_DIR
self.cfg_file = self.cfg_dir.joinpath("moonraker.conf") self.cfg_file = self.cfg_dir.joinpath(MOONRAKER_CFG_NAME)
self.port = self._get_port() self.port = self._get_port()
self.backup_dir = self.data_dir.joinpath("backup") self.backup_dir = self.data_dir.joinpath("backup")
self.certs_dir = self.data_dir.joinpath("certs") self.certs_dir = self.data_dir.joinpath("certs")
self._db_dir = self.data_dir.joinpath("database") self._db_dir = self.data_dir.joinpath("database")
self._comms_dir = self.data_dir.joinpath("comms") self._comms_dir = self.data_dir.joinpath("comms")
self.log = self.log_dir.joinpath("moonraker.log") self.log = self.log_dir.joinpath(MOONRAKER_LOG_NAME)
@property @property
def db_dir(self) -> Path: def db_dir(self) -> Path:
@@ -48,23 +55,20 @@ class Moonraker(BaseInstance):
return self._comms_dir return self._comms_dir
def create(self, create_example_cfg: bool = False) -> None: def create(self, create_example_cfg: bool = False) -> None:
from utils.sys_utils import create_env_file, create_service_file
Logger.print_status("Creating new Moonraker Instance ...") Logger.print_status("Creating new Moonraker Instance ...")
service_template_path = MODULE_PATH.joinpath("assets/moonraker.service")
service_file_name = self.get_service_file_name(extension=True)
service_file_target = SYSTEMD.joinpath(service_file_name)
env_template_file_path = MODULE_PATH.joinpath("assets/moonraker.env")
env_file_target = self.sysd_dir.joinpath("moonraker.env")
try: try:
self.create_folders([self.backup_dir, self.certs_dir, self._db_dir]) self.create_folders([self.backup_dir, self.certs_dir, self._db_dir])
self._write_service_file( create_service_file(
service_template_path, name=self.get_service_file_name(extension=True),
service_file_target, content=self._prep_service_file_content(),
env_file_target, )
create_env_file(
path=self.sysd_dir.joinpath(MOONRAKER_ENV_FILE_NAME),
content=self._prep_env_file_content(),
) )
self._write_env_file(env_template_file_path, env_file_target)
except CalledProcessError as e: except CalledProcessError as e:
Logger.print_error(f"Error creating instance: {e}") Logger.print_error(f"Error creating instance: {e}")
@@ -82,72 +86,59 @@ class Moonraker(BaseInstance):
try: try:
command = ["sudo", "rm", "-f", service_file_path] command = ["sudo", "rm", "-f", service_file_path]
run(command, check=True) run(command, check=True)
self._delete_logfiles() self.delete_logfiles(MOONRAKER_LOG_NAME)
Logger.print_ok("Instance successfully removed!") Logger.print_ok("Instance successfully removed!")
except CalledProcessError as e: except CalledProcessError as e:
Logger.print_error(f"Error removing instance: {e}") Logger.print_error(f"Error removing instance: {e}")
raise raise
def _write_service_file( def _prep_service_file_content(self) -> str:
self, template = MOONRAKER_SERVICE_TEMPLATE
service_template_path: Path,
service_file_target: Path,
env_file_target: Path,
) -> None:
service_content = self._prep_service_file(
service_template_path, env_file_target
)
command = ["sudo", "tee", service_file_target]
run(
command,
input=service_content.encode(),
stdout=DEVNULL,
check=True,
)
Logger.print_ok(f"Service file created: {service_file_target}")
def _write_env_file(
self, env_template_file_path: Path, env_file_target: Path
) -> None:
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}")
def _prep_service_file(
self, service_template_path: Path, env_file_path: Path
) -> str:
try: try:
with open(service_template_path, "r") as template_file: with open(template, "r") as template_file:
template_content = template_file.read() template_content = template_file.read()
except FileNotFoundError: except FileNotFoundError:
Logger.print_error( Logger.print_error(f"Unable to open {template} - File not found")
f"Unable to open {service_template_path} - File not found"
)
raise raise
service_content = template_content.replace("%USER%", self.user)
service_content = service_content.replace( service_content = template_content.replace(
"%MOONRAKER_DIR%", str(self.moonraker_dir) "%USER%",
self.user,
)
service_content = service_content.replace(
"%MOONRAKER_DIR%",
self.moonraker_dir.as_posix(),
)
service_content = service_content.replace(
"%ENV%",
self.env_dir.as_posix(),
)
service_content = service_content.replace(
"%ENV_FILE%",
self.sysd_dir.joinpath(MOONRAKER_ENV_FILE_NAME).as_posix(),
) )
service_content = service_content.replace("%ENV%", str(self.env_dir))
service_content = service_content.replace("%ENV_FILE%", str(env_file_path))
return service_content return service_content
def _prep_env_file(self, env_template_file_path: Path) -> str: def _prep_env_file_content(self) -> str:
template = MOONRAKER_ENV_FILE_TEMPLATE
try: try:
with open(env_template_file_path, "r") as env_file: with open(template, "r") as env_file:
env_template_file_content = env_file.read() env_template_file_content = env_file.read()
except FileNotFoundError: except FileNotFoundError:
Logger.print_error( Logger.print_error(f"Unable to open {template} - File not found")
f"Unable to open {env_template_file_path} - File not found"
)
raise raise
env_file_content = env_template_file_content.replace( env_file_content = env_template_file_content.replace(
"%MOONRAKER_DIR%", str(self.moonraker_dir) "%MOONRAKER_DIR%",
self.moonraker_dir.as_posix(),
) )
env_file_content = env_file_content.replace( env_file_content = env_file_content.replace(
"%PRINTER_DATA%", str(self.data_dir) "%PRINTER_DATA%",
self.data_dir.as_posix(),
) )
return env_file_content return env_file_content
def _get_port(self) -> int | None: def _get_port(self) -> int | None:
@@ -159,10 +150,3 @@ class Moonraker(BaseInstance):
port = scp.getint("server", "port", fallback=None) port = scp.getint("server", "port", fallback=None)
return port return port
def _delete_logfiles(self) -> None:
from utils.fs_utils import run_remove_routines
for log in list(self.log_dir.glob("moonraker.log*")):
Logger.print_status(f"Remove '{log}'")
run_remove_routines(log)

View File

@@ -15,7 +15,7 @@ from components.moonraker import (
EXIT_MOONRAKER_SETUP, EXIT_MOONRAKER_SETUP,
MOONRAKER_DIR, MOONRAKER_DIR,
MOONRAKER_ENV_DIR, MOONRAKER_ENV_DIR,
MOONRAKER_REQUIREMENTS_TXT, MOONRAKER_REQ_FILE,
POLKIT_FILE, POLKIT_FILE,
POLKIT_LEGACY_FILE, POLKIT_LEGACY_FILE,
POLKIT_SCRIPT, POLKIT_SCRIPT,
@@ -145,7 +145,7 @@ def setup_moonraker_prerequesites() -> None:
# install moonraker dependencies and create python virtualenv # install moonraker dependencies and create python virtualenv
install_moonraker_packages(MOONRAKER_DIR) install_moonraker_packages(MOONRAKER_DIR)
create_python_venv(MOONRAKER_ENV_DIR) create_python_venv(MOONRAKER_ENV_DIR)
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQUIREMENTS_TXT) install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
def install_moonraker_packages(moonraker_dir: Path) -> None: def install_moonraker_packages(moonraker_dir: Path) -> None:
@@ -206,13 +206,11 @@ def update_moonraker() -> None:
instance_manager = InstanceManager(Moonraker) instance_manager = InstanceManager(Moonraker)
instance_manager.stop_all_instance() instance_manager.stop_all_instance()
git_pull_wrapper( git_pull_wrapper(repo=settings.moonraker.repo_url, target_dir=MOONRAKER_DIR)
repo=settings.moonraker.repo_url, target_dir=MOONRAKER_DIR
)
# install possible new system packages # install possible new system packages
install_moonraker_packages(MOONRAKER_DIR) install_moonraker_packages(MOONRAKER_DIR)
# install possible new python dependencies # install possible new python dependencies
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQUIREMENTS_TXT) install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
instance_manager.start_all_instance() instance_manager.start_all_instance()

View File

@@ -11,10 +11,10 @@ import shutil
from typing import Dict, List, Optional from typing import Dict, List, Optional
from components.moonraker import ( from components.moonraker import (
DEFAULT_MOONRAKER_PORT,
MODULE_PATH, MODULE_PATH,
MOONRAKER_BACKUP_DIR, MOONRAKER_BACKUP_DIR,
MOONRAKER_DB_BACKUP_DIR, MOONRAKER_DB_BACKUP_DIR,
MOONRAKER_DEFAULT_PORT,
MOONRAKER_DIR, MOONRAKER_DIR,
MOONRAKER_ENV_DIR, MOONRAKER_ENV_DIR,
) )
@@ -68,7 +68,7 @@ def create_example_moonraker_conf(
# of moonraker-1 is 7125 and moonraker-3 is 7127 and there are moonraker.conf files for moonraker-1 # of moonraker-1 is 7125 and moonraker-3 is 7127 and there are moonraker.conf files for moonraker-1
# and moonraker-3 already. though, there does not seem to be a very reliable way of always assigning # and moonraker-3 already. though, there does not seem to be a very reliable way of always assigning
# the correct port to each instance and the user will likely be required to correct the value manually. # the correct port to each instance and the user will likely be required to correct the value manually.
port = max(ports) + 1 if ports else DEFAULT_MOONRAKER_PORT port = max(ports) + 1 if ports else MOONRAKER_DEFAULT_PORT
else: else:
port = ports_map.get(instance.suffix) port = ports_map.get(instance.suffix)

View File

@@ -20,7 +20,7 @@ from components.webui_client.client_dialogs import (
) )
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
backup_client_config_data, backup_client_config_data,
config_for_other_client_exist, detect_client_cfg_conflict,
) )
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
@@ -36,7 +36,7 @@ def install_client_config(client_data: BaseWebClient) -> 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
if config_for_other_client_exist(client_data.client): if detect_client_cfg_conflict(client_data):
Logger.print_info("Another Client-Config is already installed! Skipped ...") Logger.print_info("Another Client-Config is already installed! Skipped ...")
return return

View File

@@ -28,7 +28,7 @@ from components.webui_client.client_dialogs import (
) )
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
backup_mainsail_config_json, backup_mainsail_config_json,
config_for_other_client_exist, detect_client_cfg_conflict,
enable_mainsail_remotemode, enable_mainsail_remotemode,
restore_mainsail_config_json, restore_mainsail_config_json,
symlink_webui_nginx_log, symlink_webui_nginx_log,
@@ -90,7 +90,7 @@ def install_client(client: BaseWebClient) -> None:
if ( if (
kl_instances kl_instances
and not client_config.config_dir.exists() and not client_config.config_dir.exists()
and not config_for_other_client_exist(client_to_ignore=client.client) and not detect_client_cfg_conflict(client)
): ):
print_install_client_config_dialog(client) print_install_client_config_dialog(client)
question = f"Download the recommended {client_config.display_name}?" question = f"Download the recommended {client_config.display_name}?"

View File

@@ -8,7 +8,7 @@
# ======================================================================= # # ======================================================================= #
from __future__ import annotations from __future__ import annotations
import json # noqa: I001 import json
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import List, get_args from typing import List, get_args
@@ -16,9 +16,9 @@ from typing import List, get_args
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.webui_client.base_data import ( from components.webui_client.base_data import (
BaseWebClient, BaseWebClient,
BaseWebClientConfig,
WebClientType, WebClientType,
) )
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
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
@@ -187,30 +187,24 @@ def get_existing_clients() -> List[BaseWebClient]:
return installed_clients return installed_clients
def get_existing_client_config() -> List[BaseWebClient]: def detect_client_cfg_conflict(curr_client: BaseWebClient) -> bool:
clients = list(get_args(WebClientType))
installed_client_configs: List[BaseWebClient] = []
for client in clients:
c_config_data: BaseWebClientConfig = client.client_config
if c_config_data.config_dir.exists():
installed_client_configs.append(client)
return installed_client_configs
def config_for_other_client_exist(client_to_ignore: WebClientType) -> bool:
""" """
Check if any other client configs are present on the system. Check if any other client configs are present on the system.
It is usually not harmful, but chances are they can conflict each other. It is usually not harmful, but chances are they can conflict each other.
Multiple client configs are, at least, redundant to have them installed Multiple client configs are, at least, redundant to have them installed
:param client_to_ignore: The client name to ignore for the check :param curr_client: The client name to check for the conflict
:return: True, if other client configs were found, else False :return: True, if other client configs were found, else False
""" """
clients = set([c.name for c in get_existing_client_config()]) mainsail_cfg_status: ComponentStatus = get_client_config_status(MainsailData())
clients = clients - {client_to_ignore.value} fluidd_cfg_status: ComponentStatus = get_client_config_status(FluiddData())
return True if len(clients) > 0 else False if curr_client.client == WebClientType.MAINSAIL and fluidd_cfg_status.status == 2:
return True
if curr_client.client == WebClientType.FLUIDD and mainsail_cfg_status.status == 2:
return True
return False
def get_download_url(base_url: str, client: BaseWebClient) -> str: def get_download_url(base_url: str, client: BaseWebClient) -> str:

View File

@@ -18,7 +18,6 @@ from components.webui_client.base_data import (
WebClientConfigType, WebClientConfigType,
WebClientType, WebClientType,
) )
from components.webui_client.client_utils import get_download_url
from core.backup_manager import BACKUP_ROOT_DIR from core.backup_manager import BACKUP_ROOT_DIR
@@ -47,6 +46,8 @@ class FluiddData(BaseWebClient):
@property @property
def download_url(self) -> str: def download_url(self) -> str:
from components.webui_client.client_utils import get_download_url
return get_download_url(self.BASE_DL_URL, self) return get_download_url(self.BASE_DL_URL, self)
@property @property

View File

@@ -14,6 +14,7 @@ from pathlib import Path
from typing import List, Optional from typing import List, Optional
from utils.constants import CURRENT_USER, SYSTEMD from utils.constants import CURRENT_USER, SYSTEMD
from utils.logger import Logger
class BaseInstance(ABC): class BaseInstance(ABC):
@@ -159,3 +160,12 @@ class BaseInstance(ABC):
return f"printer_{self._suffix}" return f"printer_{self._suffix}"
else: else:
return self._suffix return self._suffix
def delete_logfiles(self, log_name: str) -> None:
from utils.fs_utils import run_remove_routines
files = self.log_dir.iterdir()
logs = [f for f in files if f.name.startswith(log_name)]
for log in logs:
Logger.print_status(f"Remove '{log}'")
run_remove_routines(log)

View File

@@ -402,3 +402,39 @@ def log_process(process: Popen) -> None:
if process.poll() is not None: if process.poll() is not None:
break break
def create_service_file(name: str, content: str) -> None:
"""
Creates a service file at the provided path with the provided content.
:param name: the name of the service file
:param content: the content of the service file
:return: None
"""
try:
run(
["sudo", "tee", SYSTEMD.joinpath(name)],
input=content.encode(),
stdout=DEVNULL,
check=True,
)
Logger.print_ok(f"Service file created: {SYSTEMD.joinpath(name)}")
except CalledProcessError as e:
Logger.print_error(f"Error creating service file: {e}")
raise
def create_env_file(path: Path, content: str) -> None:
"""
Creates an env file at the provided path with the provided content.
:param path: the path of the env file
:param content: the content of the env file
:return: None
"""
try:
with open(path, "w") as env_file:
env_file.write(content)
Logger.print_ok(f"Env file created: {path}")
except OSError as e:
Logger.print_error(f"Error creating env file: {e}")
raise

View File

@@ -45,13 +45,13 @@ function backup_config_dir() {
for folder in ${config_pathes}; do for folder in ${config_pathes}; do
if [[ -d ${folder} ]]; then if [[ -d ${folder} ]]; then
status_msg "Create backup of ${folder} ..." status_msg "Create backup of ${folder} ..."
folder_name=$(echo "${folder}" | rev | cut -d"/" -f2 | rev) folder_name=$(echo "${folder}" | rev | cut -d"/" -f2 | rev)
target_dir="${BACKUP_DIR}/configs/${current_date}/${folder_name}" target_dir="${BACKUP_DIR}/configs/${current_date}/${folder_name}"
mkdir -p "${target_dir}" mkdir -p "${target_dir}"
cp -r "${folder}" "${target_dir}" cp -r "${folder}" "${target_dir}"
i=$(( i + 1 )) i=$(( i + 1 ))
ok_msg "Backup created in:\n${target_dir}" ok_msg "Backup created in:\n${target_dir}"
fi fi
done done
@@ -213,3 +213,19 @@ function backup_octoeverywhere() {
print_error "Can't back up OctoEverywhere directory!\n Not found!" print_error "Can't back up OctoEverywhere directory!\n Not found!"
fi fi
} }
function backup_spoolman() {
local current_date
if [[ -d ${SPOOLMAN_DIR} ]] ; then
status_msg "Creating Spoolman backup ..."
check_for_backup_dir
current_date=$(get_date)
status_msg "Timestamp: ${current_date}"
mkdir -p "${BACKUP_DIR}/Spoolman-backups/${current_date}"
cp -r "${SPOOLMAN_DIR}" "${_}" && cp -r "${SPOOLMAN_DB_DIR}/spoolman.db" "${_}"
print_confirm "Spoolman backup complete!"
else
print_error "Can't back up Spoolman directory!\n Not found!"
fi
}

View File

@@ -37,7 +37,7 @@ function install_fluidd() {
fi fi
### checking dependencies ### checking dependencies
local dep=(wget nginx) local dep=(wget nginx unzip)
dependency_check "${dep[@]}" dependency_check "${dep[@]}"
### detect conflicting Haproxy and Apache2 installations ### detect conflicting Haproxy and Apache2 installations
detect_conflicting_packages detect_conflicting_packages

View File

@@ -87,4 +87,9 @@ function set_globals() {
OCTOAPP_ENV="${HOME}/octoapp-env" OCTOAPP_ENV="${HOME}/octoapp-env"
OCTOAPP_DIR="${HOME}/octoapp" OCTOAPP_DIR="${HOME}/octoapp"
OCTOAPP_REPO="https://github.com/crysxd/OctoApp-Plugin.git" OCTOAPP_REPO="https://github.com/crysxd/OctoApp-Plugin.git"
#=============== Spoolman ================#
SPOOLMAN_DIR="${HOME}/Spoolman"
SPOOLMAN_DB_DIR="${HOME}/.local/share/spoolman"
SPOOLMAN_REPO="https://api.github.com/repos/Donkie/Spoolman/releases/latest"
} }

View File

@@ -37,7 +37,7 @@ function install_mainsail() {
fi fi
### checking dependencies ### checking dependencies
local dep=(wget nginx) local dep=(wget nginx unzip)
dependency_check "${dep[@]}" dependency_check "${dep[@]}"
### detect conflicting Haproxy and Apache2 installations ### detect conflicting Haproxy and Apache2 installations
detect_conflicting_packages detect_conflicting_packages

281
scripts/spoolman.sh Normal file
View File

@@ -0,0 +1,281 @@
#!/usr/bin/env bash
#=======================================================================#
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================#
# Error Handling
set -e
function install_spoolman() {
pushd "${HOME}" &> /dev/null || exit 1
dependency_check curl jq
if [[ ! -d "${SPOOLMAN_DIR}" && -z "$(ls -A "${SPOOLMAN_DIR}" 2> /dev/null)" ]]; then
status_msg "Downloading spoolman..."
setup_spoolman_folder
status_msg "Downloading complete"
start_install_script
advanced_config_prompt
else
### In case spoolman is "incomplete" rerun install script
if get_spoolman_status | grep -q "Incomplete!"; then
start_install_script
exit 1
fi
ok_msg "Spoolman already installed"
exit 1
fi
enable_moonraker_integration_prompt
patch_spoolman_update_manager
do_action_service "restart" "moonraker"
}
function update_spoolman() {
### stop and disable old spoolman service
do_action_service "stop" "Spoolman"
do_action_service "disable" "Spoolman"
mv "${SPOOLMAN_DIR}" "${SPOOLMAN_DIR}_old"
setup_spoolman_folder
cp "${SPOOLMAN_DIR}_old/.env" "${SPOOLMAN_DIR}/.env"
start_install_script
rm -rf "${SPOOLMAN_DIR}_old"
}
function remove_spoolman(){
if [[ -d "${SPOOLMAN_DIR}" ]]; then
status_msg "Removing spoolman service..."
do_action_service "stop" "Spoolman"
do_action_service "disable" "Spoolman"
sudo rm -f "${SYSTEMD}/Spoolman.service"
sudo systemctl daemon-reload
sudo systemctl reset-failed
ok_msg "Spoolman service removed!"
status_msg "Removing spoolman directory..."
rm -rf "${SPOOLMAN_DIR}"
ok_msg "Spoolman directory removed!"
fi
print_confirm "Spoolman successfully removed!"
}
function update_moonraker_configs() {
local moonraker_configs regex
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/config\/moonraker\.conf"
moonraker_configs=$(find "${HOME}" -maxdepth 3 -type f -regextype posix-extended -regex "${regex}" | sort)
for conf in ${moonraker_configs}; do
if ! grep -Eq "^\[update_manager Spoolman\]\s*$" "${conf}"; then
### add new line to conf if it doesn't end with one
[[ $(tail -c1 "${conf}" | wc -l) -eq 0 ]] && echo "" >> "${conf}"
/bin/sh -c "cat >> ${conf}" << MOONRAKER_CONF
${1}
MOONRAKER_CONF
fi
done
}
function enable_moonraker_integration() {
local integration_str env_port
# get spoolman port from .env
env_port=$(grep "^SPOOLMAN_PORT=" "${SPOOLMAN_DIR}/.env" | cut -d"=" -f2)
integration_str="
[spoolman]
server: http://$(hostname -I | cut -d" " -f1):${env_port}
"
status_msg "Adding Spoolman integration..."
update_moonraker_configs "${integration_str}"
}
function patch_spoolman_update_manager() {
local updater_str
updater_str="
[update_manager Spoolman]
type: zip
channel: stable
repo: Donkie/Spoolman
path: ${SPOOLMAN_DIR}
virtualenv: .venv
requirements: requirements.txt
persistent_files:
.venv
.env
managed_services: Spoolman
"
update_moonraker_configs "${updater_str}"
# add spoolman service to moonraker.asvc
local moonraker_asvc regex
regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/moonraker\.asvc"
moonraker_asvc=$(find "${HOME}" -maxdepth 2 -type f -regextype posix-extended -regex "${regex}" | sort)
if [[ -n ${moonraker_asvc} ]]; then
status_msg "Adding Spoolman service to moonraker.asvc..."
/bin/sh -c "echo 'Spoolman' >> ${moonraker_asvc}"
fi
}
function advanced_config_prompt() {
local reply
while true; do
read -erp "${cyan}###### Continue with default configuration? (Y/n):${white} " reply
case "${reply}" in
Y|y|Yes|yes|"")
select_msg "Yes"
break;;
N|n|No|no)
select_msg "No"
advanced_config
break;;
*)
error_msg "Invalid Input!\n";;
esac
done
return 0
}
function enable_moonraker_integration_prompt() {
local reply
while true; do
read -erp "${cyan}###### Enable Moonraker integration? (Y/n):${white} " reply
case "${reply}" in
Y|y|Yes|yes|"")
select_msg "Yes"
enable_moonraker_integration
break;;
N|n|No|no)
select_msg "No"
break;;
*)
error_msg "Invalid Input!\n";;
esac
done
return 0
}
function advanced_config() {
status_msg "###### Advanced configuration"
local reply
while true; do
read -erp "${cyan}###### Select spoolman port (7912):${white} " reply
### set default
if [[ -z "${reply}" ]]; then
reply="7912"
fi
select_msg "${reply}"
### check if port is valid
if ! [[ "${reply}" =~ ^[0-9]+$ && "${reply}" -ge 1024 && "${reply}" -le 65535 ]]; then
error_msg "Invalid port number!\n"
continue
fi
### update .env
sed -i "s/^SPOOLMAN_PORT=.*$/SPOOLMAN_PORT=${reply}/" "${SPOOLMAN_DIR}/.env"
do_action_service "restart" "Spoolman"
break
done
return 0
}
function setup_spoolman_folder() {
local source_url
### get latest spoolman release url
source_url="$(curl -s "${SPOOLMAN_REPO}" | jq -r '.assets[] | select(.name == "spoolman.zip").browser_download_url')"
mkdir -p "${SPOOLMAN_DIR}"
curl -sSL "${source_url}" -o /tmp/temp.zip
unzip /tmp/temp.zip -d "${SPOOLMAN_DIR}" &> /dev/null
rm /tmp/temp.zip
chmod +x "${SPOOLMAN_DIR}"/scripts/install.sh
}
function start_install_script() {
pushd "${SPOOLMAN_DIR}" &> /dev/null || exit 1
if bash ./scripts/install.sh; then
ok_msg "Spoolman successfully installed!"
else
print_error "Spoolman installation failed!"
exit 1
fi
}
function get_spoolman_status() {
local -a files
files=(
"${SPOOLMAN_DIR}"
"${SYSTEMD}/Spoolman.service"
"${SPOOLMAN_DB_DIR}"
)
local count
count=0
for file in "${files[@]}"; do
[[ -e "${file}" ]] && count=$(( count +1 ))
done
if [[ "${count}" -eq "${#files[*]}" ]]; then
echo "Installed"
elif [[ "${count}" -gt 0 ]]; then
echo "Incomplete!"
else
echo "Not installed!"
fi
}
function get_local_spoolman_version() {
[[ ! -d "${SPOOLMAN_DIR}" ]] && return
local version
version=$(grep -o '"version":\s*"[^"]*' "${SPOOLMAN_DIR}"/release_info.json | cut -d'"' -f4)
echo "${version}"
}
function get_remote_spoolman_version() {
[[ ! -d "${SPOOLMAN_DIR}" ]] && return
local version
version=$(curl -s "${SPOOLMAN_REPO}" | grep -o '"tag_name":\s*"v[^"]*"' | cut -d'"' -f4)
echo "${version}"
}
function compare_spoolman_versions() {
local local_ver remote_ver
local_ver="$(get_local_spoolman_version)"
remote_ver="$(get_remote_spoolman_version)"
if [[ ${local_ver} != "${remote_ver}" ]]; then
versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
# add spoolman to application_updates_available in kiauh.ini
add_to_application_updates "spoolman"
else
versions="${green}$(printf " %-14s" "${local_ver}")${white}"
versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
fi
echo "${versions}"
}

View File

@@ -26,6 +26,7 @@ function backup_ui() {
echo -e "| Klipper Webinterface: | Other: |" echo -e "| Klipper Webinterface: | Other: |"
echo -e "| 5) [Mainsail] | 9) [Telegram Bot] |" echo -e "| 5) [Mainsail] | 9) [Telegram Bot] |"
echo -e "| 6) [Fluidd] | 10) [OctoEverywhere] |" echo -e "| 6) [Fluidd] | 10) [OctoEverywhere] |"
echo -e "| | 11) [Spoolman] |"
back_footer back_footer
} }
@@ -56,6 +57,8 @@ function backup_menu() {
do_action "backup_telegram_bot" "backup_ui";; do_action "backup_telegram_bot" "backup_ui";;
10) 10)
do_action "backup_octoeverywhere" "backup_ui";; do_action "backup_octoeverywhere" "backup_ui";;
11)
do_action "backup_spoolman" "backup_ui";;
B|b) B|b)
clear; main_menu; break;; clear; main_menu; break;;
*) *)

View File

@@ -19,19 +19,19 @@ function install_ui() {
echo -e "| all necessary dependencies for the various |" echo -e "| all necessary dependencies for the various |"
echo -e "| functions on a completely fresh system. |" echo -e "| functions on a completely fresh system. |"
hr hr
echo -e "| Firmware & API: | 3rd Party Webinterface: |" echo -e "| Firmware & API: | Other: |"
echo -e "| 1) [Klipper] | 6) [OctoPrint] |" echo -e "| 1) [Klipper] | 7) [PrettyGCode] |"
echo -e "| 2) [Moonraker] | |" echo -e "| 2) [Moonraker] | 8) [Telegram Bot] |"
echo -e "| | Other: |" echo -e "| | 9) $(obico_install_title) |"
echo -e "| Klipper Webinterface: | 7) [PrettyGCode] |" echo -e "| Klipper Webinterface: | 10) [OctoEverywhere] |"
echo -e "| 3) [Mainsail] | 8) [Telegram Bot] |" echo -e "| 3) [Mainsail] | 11) [Mobileraker] |"
echo -e "| 4) [Fluidd] | 9) $(obico_install_title) |" echo -e "| 4) [Fluidd] | 12) [OctoApp for Klipper] |"
echo -e "| | 10) [OctoEverywhere] |" echo -e "| | 13) [Spoolman] |"
echo -e "| | 11) [Mobileraker] |" echo -e "| Touchscreen GUI: | |"
echo -e "| Touchscreen GUI: | 12) [OctoApp for Klipper] |" echo -e "| 5) [KlipperScreen] | Webcam Streamer: |"
echo -e "| 5) [KlipperScreen] | |" echo -e "| | 14) [Crowsnest] |"
echo -e "| | Webcam Streamer: |" echo -e "| 3rd Party Webinterface: | |"
echo -e "| | 13) [Crowsnest] |" echo -e "| 6) [OctoPrint] | |"
back_footer back_footer
} }
@@ -75,6 +75,8 @@ function install_menu() {
12) 12)
do_action "octoapp_setup_dialog" "install_ui";; do_action "octoapp_setup_dialog" "install_ui";;
13) 13)
do_action "install_spoolman" "install_ui";;
14)
do_action "install_crowsnest" "install_ui";; do_action "install_crowsnest" "install_ui";;
B|b) B|b)
clear; main_menu; break;; clear; main_menu; break;;

View File

@@ -29,6 +29,7 @@ function main_ui() {
echo -e "| | OctoEverywhere: $(print_status "octoeverywhere")|" echo -e "| | OctoEverywhere: $(print_status "octoeverywhere")|"
echo -e "| | Mobileraker: $(print_status "mobileraker")|" echo -e "| | Mobileraker: $(print_status "mobileraker")|"
echo -e "| | OctoApp: $(print_status "octoapp")|" echo -e "| | OctoApp: $(print_status "octoapp")|"
echo -e "| | Spoolman: $(print_status "spoolman")|"
echo -e "| | |" echo -e "| | |"
echo -e "| | Octoprint: $(print_status "octoprint")|" echo -e "| | Octoprint: $(print_status "octoprint")|"
hr hr

View File

@@ -17,21 +17,21 @@ function remove_ui() {
hr hr
echo -e "| ${yellow}INFO: Configurations and/or any backups will be kept!${white} |" echo -e "| ${yellow}INFO: Configurations and/or any backups will be kept!${white} |"
hr hr
echo -e "| Firmware & API: | 3rd Party Webinterface: |" echo -e "| Firmware & API: | Webcam Streamer: |"
echo -e "| 1) [Klipper] | 8) [OctoPrint] |" echo -e "| 1) [Klipper] | 9) [Crowsnest] |"
echo -e "| 2) [Moonraker] | |" echo -e "| 2) [Moonraker] | 10) [MJPG-Streamer] |"
echo -e "| | Webcam Streamer: |" echo -e "| | |"
echo -e "| Klipper Webinterface: | 9) [Crowsnest] |" echo -e "| Klipper Webinterface: | Other: |"
echo -e "| 3) [Mainsail] | 10) [MJPG-Streamer] |" echo -e "| 3) [Mainsail] | 11) [PrettyGCode] |"
echo -e "| 4) [Mainsail-Config] | |" echo -e "| 4) [Mainsail-Config] | 12) [Telegram Bot] |"
echo -e "| 5) [Fluidd] | Other: |" echo -e "| 5) [Fluidd] | 13) [Obico for Klipper] |"
echo -e "| 6) [Fluidd-Config] | 11) [PrettyGCode] |" echo -e "| 6) [Fluidd-Config] | 14) [OctoEverywhere] |"
echo -e "| | 12) [Telegram Bot] |"
echo -e "| Touchscreen GUI: | 13) [Obico for Klipper] |"
echo -e "| 7) [KlipperScreen] | 14) [OctoEverywhere] |"
echo -e "| | 15) [Mobileraker] |" echo -e "| | 15) [Mobileraker] |"
echo -e "| | 16) [NGINX] |" echo -e "| Touchscreen GUI: | 16) [NGINX] |"
echo -e "| | 17) [OctoApp] |" echo -e "| 7) [KlipperScreen] | 17) [OctoApp] |"
echo -e "| | 18) [Spoolman] |"
echo -e "| 3rd Party Webinterface: | |"
echo -e "| 8) [OctoPrint] | |"
back_footer back_footer
} }
@@ -76,6 +76,8 @@ function remove_menu() {
do_action "remove_nginx" "remove_ui";; do_action "remove_nginx" "remove_ui";;
17) 17)
do_action "remove_octoapp" "remove_ui";; do_action "remove_octoapp" "remove_ui";;
18)
do_action "remove_spoolman" "remove_ui";;
B|b) B|b)
clear; main_menu; break;; clear; main_menu; break;;
*) *)

View File

@@ -36,15 +36,16 @@ function update_ui() {
echo -e "| 10) [Mobileraker] |$(compare_mobileraker_versions)|" echo -e "| 10) [Mobileraker] |$(compare_mobileraker_versions)|"
echo -e "| 11) [Crowsnest] |$(compare_crowsnest_versions)|" echo -e "| 11) [Crowsnest] |$(compare_crowsnest_versions)|"
echo -e "| 12) [OctoApp] |$(compare_octoapp_versions)|" echo -e "| 12) [OctoApp] |$(compare_octoapp_versions)|"
echo -e "| 13) [Spoolman] |$(compare_spoolman_versions)|"
echo -e "| |------------------------------|" echo -e "| |------------------------------|"
echo -e "| 13) [System] | $(check_system_updates) |" echo -e "| 14) [System] | $(check_system_updates) |"
back_footer back_footer
} }
function update_menu() { function update_menu() {
clear -x && sudo true && clear -x # (re)cache sudo credentials so password prompt doesn't bork ui clear -x && sudo true && clear -x # (re)cache sudo credentials so password prompt doesn't bork ui
do_action "" "update_ui" do_action "" "update_ui"
local action local action
while true; do while true; do
read -p "${cyan}####### Perform action:${white} " action read -p "${cyan}####### Perform action:${white} " action
@@ -76,6 +77,8 @@ function update_menu() {
12) 12)
do_action "update_octoapp" "update_ui";; do_action "update_octoapp" "update_ui";;
13) 13)
do_action "update_spoolman" "update_ui";;
14)
do_action "upgrade_system_packages" "update_ui";; do_action "upgrade_system_packages" "update_ui";;
a) a)
do_action "update_all" "update_ui";; do_action "update_all" "update_ui";;
@@ -101,7 +104,7 @@ function update_all() {
print_confirm "Everything is already up-to-date!" print_confirm "Everything is already up-to-date!"
echo; break echo; break
fi fi
echo echo
top_border top_border
echo -e "| The following installations will be updated: |" echo -e "| The following installations will be updated: |"
@@ -121,6 +124,9 @@ function update_all() {
[[ "${update_arr[*]}" =~ "klipperscreen" ]] && \ [[ "${update_arr[*]}" =~ "klipperscreen" ]] && \
echo -e "| ${cyan}● KlipperScreen${white} |" echo -e "| ${cyan}● KlipperScreen${white} |"
[[ "${update_arr[*]}" =~ "spoolman" ]] && \
echo -e "| ${cyan}● SpoolMan${white} |"
[[ "${update_arr[*]}" =~ "pgc_for_klipper" ]] && \ [[ "${update_arr[*]}" =~ "pgc_for_klipper" ]] && \
echo -e "| ${cyan}● PrettyGCode for Klipper${white} |" echo -e "| ${cyan}● PrettyGCode for Klipper${white} |"
@@ -140,7 +146,7 @@ function update_all() {
echo -e "| ${cyan}● System${white} |" echo -e "| ${cyan}● System${white} |"
bottom_border bottom_border
local yn local yn
read -p "${cyan}###### Do you want to proceed? (Y/n):${white} " yn read -p "${cyan}###### Do you want to proceed? (Y/n):${white} " yn
case "${yn}" in case "${yn}" in