diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index c1e2646..03c82f1 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -14,6 +14,7 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper import klipper_setup +from kiauh.modules.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT @@ -65,7 +66,7 @@ class InstallMenu(BaseMenu): klipper_setup.run_klipper_setup(install=True) def install_moonraker(self): - print("install_moonraker") + moonraker_setup.run_moonraker_setup(install=True) def install_mainsail(self): print("install_mainsail") diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 51b3d44..1271557 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -14,6 +14,7 @@ import textwrap from kiauh.core.menus import BACK_FOOTER from kiauh.core.menus.base_menu import BaseMenu from kiauh.modules.klipper import klipper_setup +from kiauh.modules.moonraker import moonraker_setup from kiauh.utils.constants import COLOR_RED, RESET_FORMAT @@ -73,7 +74,7 @@ class RemoveMenu(BaseMenu): klipper_setup.run_klipper_setup(install=False) def remove_moonraker(self): - print("remove_moonraker") + moonraker_setup.run_moonraker_setup(install=False) def remove_mainsail(self): print("remove_mainsail") diff --git a/kiauh/modules/moonraker/__init__.py b/kiauh/modules/moonraker/__init__.py new file mode 100644 index 0000000..af0e88f --- /dev/null +++ b/kiauh/modules/moonraker/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# Copyright (C) 2020 - 2023 Dominik Willner # +# # +# 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 +from pathlib import Path + +MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) + +MOONRAKER_DIR = f"{Path.home()}/moonraker" +MOONRAKER_ENV_DIR = f"{Path.home()}/moonraker-env" +MOONRAKER_REQUIREMENTS_TXT = f"{MOONRAKER_DIR}/scripts/moonraker-requirements.txt" +DEFAULT_MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker" +DEFAULT_MOONRAKER_PORT = 7125 + +# introduced due to +# https://github.com/Arksine/moonraker/issues/349 +# https://github.com/Arksine/moonraker/pull/346 +POLKIT_LEGACY_FILE = "/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla" +POLKIT_FILE = "/etc/polkit-1/rules.d/moonraker.rules" +POLKIT_USR_FILE = "/usr/share/polkit-1/rules.d/moonraker.rules" +POLKIT_SCRIPT = f"{Path.home()}/moonraker/scripts/set-policykit-rules.sh" + +EXIT_MOONRAKER_SETUP = "Exiting Moonraker setup ..." diff --git a/kiauh/modules/moonraker/moonraker.py b/kiauh/modules/moonraker/moonraker.py new file mode 100644 index 0000000..df648cf --- /dev/null +++ b/kiauh/modules/moonraker/moonraker.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# Copyright (C) 2020 - 2023 Dominik Willner # +# # +# 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 +from pathlib import Path +from typing import List, Union + +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.base_instance import BaseInstance +from kiauh.modules.moonraker import MOONRAKER_DIR, MOONRAKER_ENV_DIR, MODULE_PATH +from kiauh.utils.constants import SYSTEMD +from kiauh.utils.logger import Logger + + +# noinspection PyMethodMayBeStatic +class Moonraker(BaseInstance): + @classmethod + def blacklist(cls) -> List[str]: + return ["None", "mcu"] + + def __init__(self, suffix: str = None): + super().__init__(instance_type=self, suffix=suffix) + self.moonraker_dir = MOONRAKER_DIR + self.env_dir = MOONRAKER_ENV_DIR + self.cfg_file = self._get_cfg() + self.port = self._get_port() + self.backup_dir = f"{self.data_dir}/backup" + self.certs_dir = f"{self.data_dir}/certs" + self.db_dir = f"{self.data_dir}/database" + self.log = f"{self.log_dir}/moonraker.log" + + def create(self, create_example_cfg: bool = False) -> None: + Logger.print_status("Creating new Moonraker Instance ...") + service_template_path = os.path.join(MODULE_PATH, "res", "moonraker.service") + env_template_file_path = os.path.join(MODULE_PATH, "res", "moonraker.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}/moonraker.env") + + try: + self.create_folders([self.backup_dir, self.certs_dir, self.db_dir]) + self.write_service_file( + service_template_path, service_file_target, env_file_target + ) + self.write_env_file(env_template_file_path, 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 writing file: {e}") + raise + + 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_status(f"Deleting Moonraker 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_moonraker_remnants() + + def write_service_file( + self, service_template_path: str, service_file_target: str, env_file_target: str + ): + 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}") + + def write_env_file(self, env_template_file_path: str, env_file_target: str): + 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 _delete_moonraker_remnants(self) -> None: + try: + Logger.print_status(f"Delete {self.moonraker_dir} ...") + shutil.rmtree(Path(self.moonraker_dir)) + Logger.print_status(f"Delete {self.env_dir} ...") + shutil.rmtree(Path(self.env_dir)) + except FileNotFoundError: + Logger.print_status("Cannot delete Moonraker directories. Not found.") + except PermissionError as e: + Logger.print_error(f"Error deleting Moonraker directories: {e}") + raise + + Logger.print_ok("Directories successfully deleted.") + + 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("%MOONRAKER_DIR%", self.moonraker_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( + "%MOONRAKER_DIR%", self.moonraker_dir + ) + env_file_content = env_file_content.replace("%PRINTER_DATA%", self.data_dir) + return env_file_content + + def _get_cfg(self): + cfg_file_loc = f"{self.cfg_dir}/moonraker.conf" + if Path(cfg_file_loc).is_file(): + return cfg_file_loc + return None + + def _get_port(self) -> Union[int, None]: + if self.cfg_file is None: + return None + + cm = ConfigManager(cfg_file=self.cfg_file) + cm.read_config() + port = cm.get_value("server", "port") + + return int(port) if port is not None else port diff --git a/kiauh/modules/moonraker/moonraker_dialogs.py b/kiauh/modules/moonraker/moonraker_dialogs.py new file mode 100644 index 0000000..6e79b49 --- /dev/null +++ b/kiauh/modules/moonraker/moonraker_dialogs.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# Copyright (C) 2020 - 2023 Dominik Willner # +# # +# This file is part of KIAUH - Klipper Installation And Update Helper # +# https://github.com/dw-0/kiauh # +# # +# This file may be distributed under the terms of the GNU GPLv3 license # +# ======================================================================= # + +import textwrap +from typing import List + +from kiauh.core.menus.base_menu import print_back_footer +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.utils.constants import COLOR_GREEN, RESET_FORMAT, COLOR_YELLOW, COLOR_CYAN + + +def print_moonraker_overview( + klipper_instances: List[Klipper], + moonraker_instances: List[Moonraker], + show_index=False, + show_select_all=False, +): + headline = f"{COLOR_GREEN}The following instances were found:{RESET_FORMAT}" + dialog = textwrap.dedent( + f""" + /=======================================================\\ + |{headline:^64}| + |-------------------------------------------------------| + """ + )[1:] + + if show_select_all: + select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}" + dialog += f"| {select_all:<63}|\n" + dialog += "| |\n" + + instance_map = { + k.get_service_file_name(): k.get_service_file_name().replace( + "klipper", "moonraker" + ) + if k.suffix in [m.suffix for m in moonraker_instances] + else "" + for k in klipper_instances + } + + for i, k in enumerate(instance_map): + mr_name = instance_map.get(k) + m = f"<-> {mr_name}" if mr_name != "" else "" + line = f"{COLOR_CYAN}{f'{i})' if show_index else '●'} {k} {m} {RESET_FORMAT}" + dialog += f"| {line:<63}|\n" + + warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}" + warn_l2 = f"{COLOR_YELLOW}If you select an instance with an existing Moonraker{RESET_FORMAT}" + warn_l3 = f"{COLOR_YELLOW}instance, that Moonraker instance will be re-created!{RESET_FORMAT}" + warning = textwrap.dedent( + f""" + | | + |-------------------------------------------------------| + | {warn_l1:<63}| + | {warn_l2:<63}| + | {warn_l3:<63}| + """ + )[1:] + + dialog += warning + + print(dialog, end="") + print_back_footer() diff --git a/kiauh/modules/moonraker/moonraker_setup.py b/kiauh/modules/moonraker/moonraker_setup.py new file mode 100644 index 0000000..0ca2e2f --- /dev/null +++ b/kiauh/modules/moonraker/moonraker_setup.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 + +# ======================================================================= # +# Copyright (C) 2020 - 2023 Dominik Willner # +# # +# 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 subprocess +from pathlib import Path +from typing import List + +from kiauh import KIAUH_CFG +from kiauh.core.backup_manager.backup_manager import BackupManager +from kiauh.core.config_manager.config_manager import ConfigManager +from kiauh.core.instance_manager.instance_manager import InstanceManager +from kiauh.modules.klipper.klipper import Klipper +from kiauh.modules.klipper.klipper_dialogs import ( + print_instance_overview, + print_update_warn_dialog, +) +from kiauh.core.repo_manager.repo_manager import RepoManager +from kiauh.modules.moonraker import ( + EXIT_MOONRAKER_SETUP, + DEFAULT_MOONRAKER_REPO_URL, + MOONRAKER_DIR, + MOONRAKER_ENV_DIR, + MOONRAKER_REQUIREMENTS_TXT, + POLKIT_LEGACY_FILE, + POLKIT_FILE, + POLKIT_USR_FILE, + POLKIT_SCRIPT, + DEFAULT_MOONRAKER_PORT, + MODULE_PATH, +) +from kiauh.modules.moonraker.moonraker import Moonraker +from kiauh.modules.moonraker.moonraker_dialogs import print_moonraker_overview +from kiauh.utils.input_utils import ( + get_confirm, + get_selection_input, +) +from kiauh.utils.logger import Logger +from kiauh.utils.system_utils import ( + parse_packages_from_file, + create_python_venv, + install_python_requirements, + update_system_package_lists, + install_system_packages, + check_file_exists, + get_ipv4_addr, +) + + +def run_moonraker_setup(install: bool) -> None: + kl_im = InstanceManager(Klipper) + kl_instance_list = kl_im.instances + kl_instance_count = len(kl_instance_list) + mr_im = InstanceManager(Moonraker) + mr_instance_list = mr_im.instances + mr_instance_count = len(mr_instance_list) + + is_klipper_installed = kl_instance_count > 0 + if install and not is_klipper_installed: + Logger.print_warn("Klipper not installed!") + Logger.print_warn("Moonraker cannot be installed! Install Klipper first.") + return + + is_moonraker_installed = mr_instance_count > 0 + if not install and not is_moonraker_installed: + Logger.print_warn("Moonraker not installed!") + return + + if install: + install_moonraker(mr_im, mr_instance_list, kl_instance_list) + + if not install: + remove_moonraker(mr_im, mr_instance_list) + + +def handle_existing_instances(instance_list: List[Klipper]) -> bool: + instance_count = len(instance_list) + + if instance_count > 0: + print_instance_overview(instance_list) + if not get_confirm("Add new instances?", allow_go_back=True): + return False + + return True + + +def install_moonraker( + instance_manager: InstanceManager, + moonraker_instances: List[Moonraker], + klipper_instances: List[Klipper], +) -> None: + print_moonraker_overview( + klipper_instances, moonraker_instances, show_index=True, show_select_all=True + ) + + options = [str(i) for i in range(len(klipper_instances))] + options.extend(["a", "A", "b", "B"]) + question = "Select Klipper instance to setup Moonraker for" + selection = get_selection_input(question, options).lower() + + instance_names = [] + if selection == "b": + Logger.print_status(EXIT_MOONRAKER_SETUP) + return + + elif selection == "a": + for instance in klipper_instances: + instance_names.append(instance.suffix) + + else: + index = int(selection) + instance_names.append(klipper_instances[index].suffix) + + create_example_cfg = get_confirm("Create example moonraker.conf?") + setup_moonraker_prerequesites() + install_moonraker_polkit() + + ports_in_use = [ + instance.port for instance in moonraker_instances if instance.port is not None + ] + for name in instance_names: + current_instance = Moonraker(suffix=name) + + instance_manager.current_instance = current_instance + instance_manager.create_instance() + instance_manager.enable_instance() + + if create_example_cfg: + cfg_dir = current_instance.cfg_dir + Logger.print_status(f"Creating example moonraker.conf in '{cfg_dir}'") + if current_instance.cfg_file is None: + create_example_moonraker_conf(current_instance, ports_in_use) + Logger.print_ok(f"Example moonraker.conf created in '{cfg_dir}'") + else: + Logger.print_info(f"moonraker.conf in '{cfg_dir}' already exists.") + + instance_manager.start_instance() + + instance_manager.reload_daemon() + + +def setup_moonraker_prerequesites() -> None: + cm = ConfigManager(cfg_file=KIAUH_CFG) + cm.read_config() + + repo = str( + cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL + ) + branch = str(cm.get_value("moonraker", "branch") or "master") + + repo_manager = RepoManager( + repo=repo, + branch=branch, + target_dir=MOONRAKER_DIR, + ) + repo_manager.clone_repo() + + # install moonraker dependencies and create python virtualenv + install_moonraker_packages(Path(MOONRAKER_DIR)) + create_python_venv(Path(MOONRAKER_ENV_DIR)) + moonraker_py_req = Path(MOONRAKER_REQUIREMENTS_TXT) + install_python_requirements(Path(MOONRAKER_ENV_DIR), moonraker_py_req) + + +def install_moonraker_packages(moonraker_dir: Path) -> None: + script = Path(f"{moonraker_dir}/scripts/install-moonraker.sh") + packages = parse_packages_from_file(script) + update_system_package_lists(silent=False) + install_system_packages(packages) + + +def install_moonraker_polkit() -> None: + Logger.print_status("Installing Moonraker policykit rules ...") + + legacy_file_exists = check_file_exists(Path(POLKIT_LEGACY_FILE)) + polkit_file_exists = check_file_exists(Path(POLKIT_FILE)) + usr_file_exists = check_file_exists(Path(POLKIT_USR_FILE)) + + if legacy_file_exists or (polkit_file_exists and usr_file_exists): + Logger.print_info("Moonraker policykit rules are already installed.") + return + + try: + command = [POLKIT_SCRIPT, "--disable-systemctl"] + result = subprocess.run( + command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True + ) + if result.returncode != 0 or result.stderr: + Logger.print_error(f"{result.stderr}", False) + Logger.print_error("Installing Moonraker policykit rules failed!") + return + + Logger.print_ok("Moonraker policykit rules successfully installed!") + except subprocess.CalledProcessError as e: + log = f"Error while installing Moonraker policykit rules: {e.stderr.decode()}" + Logger.print_error(log) + + +def remove_moonraker( + instance_manager: InstanceManager, instance_list: List[Moonraker] +) -> None: + print_instance_overview(instance_list, True, True) + + options = [str(i) for i in range(len(instance_list))] + options.extend(["a", "A", "b", "B"]) + + selection = get_selection_input("Select Moonraker instance to remove", options) + + del_remnants = False + remove_polkit = False + instances_to_remove = [] + if selection == "b".lower(): + return + elif selection == "a".lower(): + question = f"Delete {MOONRAKER_DIR} and {MOONRAKER_ENV_DIR}?" + del_remnants = get_confirm(question, False, True) + instances_to_remove.extend(instance_list) + remove_polkit = True + Logger.print_status("Removing all Moonraker instances ...") + else: + instance = instance_list[int(selection)] + instance_name = instance.get_service_file_name() + instances_to_remove.append(instance) + is_last_instance = len(instance_list) == 1 + if is_last_instance: + question = f"Delete {MOONRAKER_DIR} and {MOONRAKER_ENV_DIR}?" + del_remnants = get_confirm(question, False, True) + remove_polkit = True + Logger.print_status(f"Removing Moonraker instance {instance_name} ...") + + if del_remnants is None: + Logger.print_status("Exiting Moonraker Uninstaller ...") + return + + remove_instances( + instance_manager, + instances_to_remove, + remove_polkit, + del_remnants, + ) + + +def remove_instances( + instance_manager: InstanceManager, + instance_list: List[Moonraker], + remove_polkit: bool, + del_remnants: bool, +) -> None: + for instance in instance_list: + instance_manager.current_instance = instance + instance_manager.stop_instance() + instance_manager.disable_instance() + instance_manager.delete_instance(del_remnants=del_remnants) + + if remove_polkit: + remove_polkit_rules() + + instance_manager.reload_daemon() + + +def remove_polkit_rules() -> None: + Logger.print_status("Removing all Moonraker policykit rules ...") + if not Path(MOONRAKER_DIR).exists(): + log = "Cannot remove policykit rules. Moonraker directory not found." + Logger.print_warn(log) + return + + try: + command = [f"{MOONRAKER_DIR}/scripts/set-policykit-rules.sh", "--clear"] + subprocess.run( + command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=True + ) + except subprocess.CalledProcessError as e: + Logger.print_error(f"Error while removing policykit rules: {e}") + + Logger.print_ok("Policykit rules successfully removed!") + + +def update_moonraker() -> None: + print_update_warn_dialog() + if not get_confirm("Update Moonraker now?"): + return + + cm = ConfigManager(cfg_file=KIAUH_CFG) + cm.read_config() + + if cm.get_value("kiauh", "backup_before_update"): + backup_manager = BackupManager(source=MOONRAKER_DIR, backup_name="moonraker") + backup_manager.backup() + backup_manager.backup_name = "moonraker-env" + backup_manager.source = MOONRAKER_ENV_DIR + backup_manager.backup() + + instance_manager = InstanceManager(Moonraker) + instance_manager.stop_all_instance() + + repo = str( + cm.get_value("moonraker", "repository_url") or DEFAULT_MOONRAKER_REPO_URL + ) + branch = str(cm.get_value("moonraker", "branch") or "master") + + repo_manager = RepoManager( + repo=repo, + branch=branch, + target_dir=MOONRAKER_DIR, + ) + repo_manager.pull_repo() + instance_manager.start_all_instance() + + +def create_example_moonraker_conf(instance: Moonraker, ports: List[int]) -> None: + port = max(ports) + 1 if ports else DEFAULT_MOONRAKER_PORT + ports.append(port) + instance.port = port + example_cfg_path = os.path.join(MODULE_PATH, "res", "moonraker.conf") + + with open(f"{instance.cfg_dir}/moonraker.conf", "w") as cfg: + cfg.write(_prep_example_moonraker_conf(instance, example_cfg_path)) + + +def _prep_example_moonraker_conf(instance: Moonraker, example_cfg_path: str) -> str: + try: + with open(example_cfg_path, "r") as cfg: + example_cfg_content = cfg.read() + except FileNotFoundError: + Logger.print_error(f"Unable to open {example_cfg_path} - File not found") + raise + + example_cfg_content = example_cfg_content.replace("%PORT%", str(instance.port)) + example_cfg_content = example_cfg_content.replace( + "%UDS%", f"{instance.comms_dir}/klippy.sock" + ) + + ip = get_ipv4_addr().split(".")[:2] + ip.extend(["0", "0/16"]) + example_cfg_content = example_cfg_content.replace("%LAN%", ".".join(ip)) + + return example_cfg_content diff --git a/kiauh/modules/moonraker/res/moonraker.conf b/kiauh/modules/moonraker/res/moonraker.conf new file mode 100644 index 0000000..d6eadd8 --- /dev/null +++ b/kiauh/modules/moonraker/res/moonraker.conf @@ -0,0 +1,30 @@ +[server] +host: 0.0.0.0 +port: %PORT% +klippy_uds_address: %UDS% + +[authorization] +trusted_clients: + %LAN% + 10.0.0.0/8 + 127.0.0.0/8 + 169.254.0.0/16 + 172.16.0.0/12 + 192.168.0.0/16 + FE80::/10 + ::1/128 +cors_domains: + *.lan + *.local + *://localhost + *://localhost:* + *://my.mainsail.xyz + *://app.fluidd.xyz + +[octoprint_compat] + +[history] + +[update_manager] +channel: dev +refresh_interval: 168 diff --git a/kiauh/modules/moonraker/res/moonraker.env b/kiauh/modules/moonraker/res/moonraker.env new file mode 100644 index 0000000..bca6af5 --- /dev/null +++ b/kiauh/modules/moonraker/res/moonraker.env @@ -0,0 +1 @@ +MOONRAKER_ARGS="%MOONRAKER_DIR%/moonraker/moonraker.py -d %PRINTER_DATA%" \ No newline at end of file diff --git a/kiauh/modules/moonraker/res/moonraker.service b/kiauh/modules/moonraker/res/moonraker.service new file mode 100644 index 0000000..696d7ba --- /dev/null +++ b/kiauh/modules/moonraker/res/moonraker.service @@ -0,0 +1,19 @@ +[Unit] +Description=API Server for Klipper SV1 +Documentation=https://moonraker.readthedocs.io/ +Requires=network-online.target +After=network-online.target + +[Install] +WantedBy=multi-user.target + +[Service] +Type=simple +User=%USER% +SupplementaryGroups=moonraker-admin +RemainAfterExit=yes +WorkingDirectory=%MOONRAKER_DIR% +EnvironmentFile=%ENV_FILE% +ExecStart=%ENV%/bin/python $MOONRAKER_ARGS +Restart=always +RestartSec=10