diff --git a/kiauh/components/crowsnest/__init__.py b/kiauh/components/crowsnest/__init__.py index 01a0230..7f545ee 100644 --- a/kiauh/components/crowsnest/__init__.py +++ b/kiauh/components/crowsnest/__init__.py @@ -9,7 +9,6 @@ from pathlib import Path -from core.backup_manager import BACKUP_ROOT_DIR from core.constants import SYSTEMD # repo @@ -20,7 +19,6 @@ CROWSNEST_SERVICE_NAME = "crowsnest.service" # directories CROWSNEST_DIR = Path.home().joinpath("crowsnest") -CROWSNEST_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("crowsnest-backups") # files CROWSNEST_MULTI_CONFIG = CROWSNEST_DIR.joinpath("tools/.config") diff --git a/kiauh/components/crowsnest/crowsnest.py b/kiauh/components/crowsnest/crowsnest.py index 516f63f..1fa9a5e 100644 --- a/kiauh/components/crowsnest/crowsnest.py +++ b/kiauh/components/crowsnest/crowsnest.py @@ -15,7 +15,6 @@ from subprocess import CalledProcessError, run from typing import List from components.crowsnest import ( - CROWSNEST_BACKUP_DIR, CROWSNEST_BIN_FILE, CROWSNEST_DIR, CROWSNEST_INSTALL_SCRIPT, @@ -26,8 +25,8 @@ from components.crowsnest import ( CROWSNEST_SERVICE_NAME, ) from components.klipper.klipper import Klipper -from core.backup_manager.backup_manager import BackupManager from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from core.settings.kiauh_settings import KiauhSettings from core.types.component_status import ComponentStatus from utils.common import ( @@ -127,11 +126,11 @@ def update_crowsnest() -> None: settings = KiauhSettings() if settings.kiauh.backup_before_update: - bm = BackupManager() - bm.backup_directory( - CROWSNEST_DIR.name, - source=CROWSNEST_DIR, - target=CROWSNEST_BACKUP_DIR, + svc = BackupService() + svc.backup_directory( + source_path=CROWSNEST_DIR, + target_path="crowsnest", + backup_name="crowsnest", ) git_pull_wrapper(CROWSNEST_DIR) diff --git a/kiauh/components/klipper/__init__.py b/kiauh/components/klipper/__init__.py index 22efb95..f5bc5ae 100644 --- a/kiauh/components/klipper/__init__.py +++ b/kiauh/components/klipper/__init__.py @@ -9,8 +9,6 @@ from pathlib import Path -from core.backup_manager import BACKUP_ROOT_DIR - MODULE_PATH = Path(__file__).resolve().parent KLIPPER_REPO_URL = "https://github.com/Klipper3d/klipper.git" @@ -27,7 +25,6 @@ KLIPPER_SERVICE_NAME = "klipper.service" KLIPPER_DIR = Path.home().joinpath("klipper") KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs") KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") -KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") # files KLIPPER_REQ_FILE = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt") diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 6c75c68..a67d683 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -16,7 +16,6 @@ from subprocess import CalledProcessError, run from typing import Dict, List from components.klipper import ( - KLIPPER_BACKUP_DIR, KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_INSTALL_SCRIPT, @@ -31,10 +30,10 @@ from components.webui_client.base_data import BaseWebClient from components.webui_client.client_config.client_config_setup import ( create_client_config_symlink, ) -from core.backup_manager.backup_manager import BackupManager from core.constants import CURRENT_USER from core.instance_manager.base_instance import SUFFIX_BLACKLIST from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) @@ -198,9 +197,17 @@ def create_example_printer_cfg( def backup_klipper_dir() -> None: - bm = BackupManager() - bm.backup_directory("klipper", source=KLIPPER_DIR, target=KLIPPER_BACKUP_DIR) - bm.backup_directory("klippy-env", source=KLIPPER_ENV_DIR, target=KLIPPER_BACKUP_DIR) + svc = BackupService() + svc.backup_directory( + source_path=KLIPPER_DIR, + backup_name="klipper", + target_path="klipper", + ) + svc.backup_directory( + source_path=KLIPPER_ENV_DIR, + backup_name="klippy-env", + target_path="klipper", + ) def install_klipper_packages() -> None: diff --git a/kiauh/components/klipperscreen/__init__.py b/kiauh/components/klipperscreen/__init__.py index c8333bb..8640b8a 100644 --- a/kiauh/components/klipperscreen/__init__.py +++ b/kiauh/components/klipperscreen/__init__.py @@ -8,7 +8,6 @@ # ======================================================================= # from pathlib import Path -from core.backup_manager import BACKUP_ROOT_DIR from core.constants import SYSTEMD # repo @@ -22,7 +21,6 @@ KLIPPERSCREEN_LOG_NAME = "KlipperScreen.log" # directories KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen") KLIPPERSCREEN_ENV_DIR = Path.home().joinpath(".KlipperScreen-env") -KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups") # files KLIPPERSCREEN_REQ_FILE = KLIPPERSCREEN_DIR.joinpath( diff --git a/kiauh/components/klipperscreen/klipperscreen.py b/kiauh/components/klipperscreen/klipperscreen.py index a73f87a..8fe461c 100644 --- a/kiauh/components/klipperscreen/klipperscreen.py +++ b/kiauh/components/klipperscreen/klipperscreen.py @@ -13,7 +13,6 @@ from typing import List from components.klipper.klipper import Klipper from components.klipperscreen import ( - KLIPPERSCREEN_BACKUP_DIR, KLIPPERSCREEN_DIR, KLIPPERSCREEN_ENV_DIR, KLIPPERSCREEN_INSTALL_SCRIPT, @@ -25,10 +24,10 @@ from components.klipperscreen import ( KLIPPERSCREEN_UPDATER_SECTION_NAME, ) from components.moonraker.moonraker import Moonraker -from core.backup_manager.backup_manager import BackupManager from core.constants import SYSTEMD from core.instance_manager.instance_manager import InstanceManager from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from core.settings.kiauh_settings import KiauhSettings from core.types.component_status import ComponentStatus from utils.common import ( @@ -193,14 +192,14 @@ def remove_klipperscreen() -> None: def backup_klipperscreen_dir() -> None: - bm = BackupManager() - bm.backup_directory( - KLIPPERSCREEN_DIR.name, - source=KLIPPERSCREEN_DIR, - target=KLIPPERSCREEN_BACKUP_DIR, + svc = BackupService() + svc.backup_directory( + source_path=KLIPPERSCREEN_DIR, + backup_name="KlipperScreen", + target_path="KlipperScreen", ) - bm.backup_directory( - KLIPPERSCREEN_ENV_DIR.name, - source=KLIPPERSCREEN_ENV_DIR, - target=KLIPPERSCREEN_BACKUP_DIR, + svc.backup_directory( + source_path=KLIPPERSCREEN_ENV_DIR, + backup_name="KlipperScreen-env", + target_path="KlipperScreen", ) diff --git a/kiauh/components/moonraker/__init__.py b/kiauh/components/moonraker/__init__.py index 79bdf99..50b98c6 100644 --- a/kiauh/components/moonraker/__init__.py +++ b/kiauh/components/moonraker/__init__.py @@ -9,8 +9,6 @@ from pathlib import Path -from core.backup_manager import BACKUP_ROOT_DIR - MODULE_PATH = Path(__file__).resolve().parent MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker.git" @@ -25,8 +23,6 @@ MOONRAKER_ENV_FILE_NAME = "moonraker.env" # directories MOONRAKER_DIR = Path.home().joinpath("moonraker") MOONRAKER_ENV_DIR = Path.home().joinpath("moonraker-env") -MOONRAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-backups") -MOONRAKER_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups") # files MOONRAKER_INSTALL_SCRIPT = MOONRAKER_DIR.joinpath("scripts/install-moonraker.sh") diff --git a/kiauh/components/moonraker/utils/utils.py b/kiauh/components/moonraker/utils/utils.py index 55b9cea..8aa36f7 100644 --- a/kiauh/components/moonraker/utils/utils.py +++ b/kiauh/components/moonraker/utils/utils.py @@ -14,8 +14,6 @@ from typing import Dict, List, Optional from components.moonraker import ( MODULE_PATH, - MOONRAKER_BACKUP_DIR, - MOONRAKER_DB_BACKUP_DIR, MOONRAKER_DEFAULT_PORT, MOONRAKER_DEPS_JSON_FILE, MOONRAKER_DIR, @@ -25,8 +23,8 @@ from components.moonraker import ( from components.moonraker.moonraker import Moonraker from components.moonraker.utils.sysdeps_parser import SysDepsParser from components.webui_client.base_data import BaseWebClient -from core.backup_manager.backup_manager import BackupManager from core.logger import Logger +from core.services.backup_service import BackupService from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) @@ -168,21 +166,31 @@ def create_example_moonraker_conf( def backup_moonraker_dir() -> None: - bm = BackupManager() - bm.backup_directory("moonraker", source=MOONRAKER_DIR, target=MOONRAKER_BACKUP_DIR) - bm.backup_directory( - "moonraker-env", source=MOONRAKER_ENV_DIR, target=MOONRAKER_BACKUP_DIR + svc = BackupService() + svc.backup_directory( + source_path=MOONRAKER_DIR, backup_name="moonraker", target_path="moonraker" + ) + svc.backup_directory( + source_path=MOONRAKER_ENV_DIR, + backup_name="moonraker-env", + target_path="moonraker", ) def backup_moonraker_db_dir() -> None: instances: List[Moonraker] = get_instances(Moonraker) - bm = BackupManager() + svc = BackupService() + + if not instances: + Logger.print_info("Unable to find directory to backup!") + Logger.print_info("Are there no Moonraker instances installed?") + return for instance in instances: - name = f"database-{instance.data_dir.name}" - bm.backup_directory( - name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR + svc.backup_directory( + source_path=instance.db_dir, + target_path=f"{instance.data_dir.name}", + backup_name="database", ) diff --git a/kiauh/components/webui_client/base_data.py b/kiauh/components/webui_client/base_data.py index b53edb7..cbf2554 100644 --- a/kiauh/components/webui_client/base_data.py +++ b/kiauh/components/webui_client/base_data.py @@ -34,7 +34,6 @@ class BaseWebClient(ABC): display_name: str client_dir: Path config_file: Path - backup_dir: Path repo_path: str download_url: str nginx_config: Path @@ -52,6 +51,5 @@ class BaseWebClientConfig(ABC): display_name: str config_filename: str config_dir: Path - backup_dir: Path repo_url: str config_section: str diff --git a/kiauh/components/webui_client/client_remove.py b/kiauh/components/webui_client/client_remove.py index 471b3a0..3c8ea00 100644 --- a/kiauh/components/webui_client/client_remove.py +++ b/kiauh/components/webui_client/client_remove.py @@ -16,9 +16,9 @@ from components.webui_client.base_data import ( from components.webui_client.client_config.client_config_remove import ( run_client_config_removal, ) -from core.backup_manager.backup_manager import BackupManager from core.constants import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from core.logger import Logger +from core.services.backup_service import BackupService from core.services.message_service import Message from core.types.color import Color from utils.config_utils import remove_config_section @@ -43,8 +43,19 @@ def run_client_removal( kl_instances: List[Klipper] = get_instances(Klipper) if backup_config: - bm = BackupManager() - if bm.backup_file(client.config_file): + version = "" + src = client.client_dir + if src.joinpath(".version").exists(): + with open(src.joinpath(".version"), "r") as v: + version = v.readlines()[0] + + svc = BackupService() + target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}") + success = svc.backup_file( + source_path=client.config_file, + target_path=target_path, + ) + if success: completion_msg.text.append(f"● {client.config_file.name} backup created") if remove_client: diff --git a/kiauh/components/webui_client/client_utils.py b/kiauh/components/webui_client/client_utils.py index 6b2d595..aea56a4 100644 --- a/kiauh/components/webui_client/client_utils.py +++ b/kiauh/components/webui_client/client_utils.py @@ -24,13 +24,13 @@ from components.webui_client.base_data import ( from components.webui_client.client_dialogs import print_client_port_select_dialog from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData -from core.backup_manager.backup_manager import BackupManager from core.constants import ( NGINX_CONFD, NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED, ) from core.logger import Logger +from core.services.backup_service import BackupService from core.settings.kiauh_settings import KiauhSettings, WebUiSettings from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, @@ -118,7 +118,7 @@ def enable_mainsail_remotemode() -> None: c_json = MainsailData().client_dir.joinpath("config.json") with open(c_json, "r") as f: config_data = json.load(f) - + if config_data["instancesDB"] == "browser" or config_data["instancesDB"] == "json": Logger.print_info("Remote mode already configured. Skipped ...") return @@ -175,26 +175,39 @@ def get_remote_client_version(client: BaseWebClient) -> str | None: def backup_client_data(client: BaseWebClient) -> None: - name = client.name + version = "" src = client.client_dir - dest = client.backup_dir + if src.joinpath(".version").exists(): + with open(src.joinpath(".version"), "r") as v: + version = v.readlines()[0] - with open(src.joinpath(".version"), "r") as v: - version = v.readlines()[0] - - bm = BackupManager() - bm.backup_directory(f"{name}-{version}", src, dest) - bm.backup_file(client.config_file, dest) - bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest) + svc = BackupService() + target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}") + svc.backup_directory( + source_path=client.client_dir, + target_path=target_path, + backup_name=client.name, + ) + svc.backup_file( + source_path=client.config_file, + target_path=target_path, + ) def backup_client_config_data(client: BaseWebClient) -> None: - client_config = client.client_config - name = client_config.name - source = client_config.config_dir - target = client_config.backup_dir - bm = BackupManager() - bm.backup_directory(name, source, target) + version = "" + src = client.client_dir + if src.joinpath(".version").exists(): + with open(src.joinpath(".version"), "r") as v: + version = v.readlines()[0] + + svc = BackupService() + target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}") + svc.backup_directory( + source_path=client.client_config.config_dir, + target_path=target_path, + backup_name=client.client_config.name, + ) def get_existing_clients() -> List[BaseWebClient]: diff --git a/kiauh/components/webui_client/fluidd_data.py b/kiauh/components/webui_client/fluidd_data.py index 8958995..f2f6ee2 100644 --- a/kiauh/components/webui_client/fluidd_data.py +++ b/kiauh/components/webui_client/fluidd_data.py @@ -18,7 +18,6 @@ from components.webui_client.base_data import ( WebClientConfigType, WebClientType, ) -from core.backup_manager import BACKUP_ROOT_DIR from core.constants import NGINX_SITES_AVAILABLE @@ -30,7 +29,6 @@ class FluiddConfigWeb(BaseWebClientConfig): config_dir: Path = Path.home().joinpath("fluidd-config") config_filename: str = "fluidd.cfg" config_section: str = f"include {config_filename}" - backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-config-backups") repo_url: str = "https://github.com/fluidd-core/fluidd-config.git" @@ -43,7 +41,6 @@ class FluiddData(BaseWebClient): display_name: str = name.capitalize() client_dir: Path = Path.home().joinpath("fluidd") config_file: Path = client_dir.joinpath("config.json") - backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups") repo_path: str = "fluidd-core/fluidd" nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("fluidd") nginx_access_log: Path = Path("/var/log/nginx/fluidd-access.log") diff --git a/kiauh/components/webui_client/mainsail_data.py b/kiauh/components/webui_client/mainsail_data.py index 2323746..bfb5062 100644 --- a/kiauh/components/webui_client/mainsail_data.py +++ b/kiauh/components/webui_client/mainsail_data.py @@ -18,7 +18,6 @@ from components.webui_client.base_data import ( WebClientConfigType, WebClientType, ) -from core.backup_manager import BACKUP_ROOT_DIR from core.constants import NGINX_SITES_AVAILABLE @@ -30,7 +29,6 @@ class MainsailConfigWeb(BaseWebClientConfig): config_dir: Path = Path.home().joinpath("mainsail-config") config_filename: str = "mainsail.cfg" config_section: str = f"include {config_filename}" - backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-config-backups") repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git" @@ -43,7 +41,6 @@ class MainsailData(BaseWebClient): display_name: str = name.capitalize() client_dir: Path = Path.home().joinpath("mainsail") config_file: Path = client_dir.joinpath("config.json") - backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups") repo_path: str = "mainsail-crew/mainsail" nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("mainsail") nginx_access_log: Path = Path("/var/log/nginx/mainsail-access.log") diff --git a/kiauh/core/backup_manager/__init__.py b/kiauh/core/backup_manager/__init__.py deleted file mode 100644 index 0e9e8e8..0000000 --- a/kiauh/core/backup_manager/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# ======================================================================= # -# Copyright (C) 2020 - 2025 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 # -# ======================================================================= # - -from pathlib import Path - -BACKUP_ROOT_DIR = Path.home().joinpath("kiauh-backups") diff --git a/kiauh/core/backup_manager/backup_manager.py b/kiauh/core/backup_manager/backup_manager.py deleted file mode 100644 index 0497898..0000000 --- a/kiauh/core/backup_manager/backup_manager.py +++ /dev/null @@ -1,108 +0,0 @@ -# ======================================================================= # -# Copyright (C) 2020 - 2025 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 # -# ======================================================================= # -from __future__ import annotations - -import shutil -from pathlib import Path -from typing import List - -from core.backup_manager import BACKUP_ROOT_DIR -from core.logger import Logger -from utils.common import get_current_date - - -class BackupManagerException(Exception): - pass - - -# noinspection PyUnusedLocal -# noinspection PyMethodMayBeStatic -class BackupManager: - def __init__(self, backup_root_dir: Path = BACKUP_ROOT_DIR): - self._backup_root_dir: Path = backup_root_dir - self._ignore_folders: List[str] = [] - - @property - def backup_root_dir(self) -> Path: - return self._backup_root_dir - - @backup_root_dir.setter - def backup_root_dir(self, value: Path): - self._backup_root_dir = value - - @property - def ignore_folders(self) -> List[str]: - return self._ignore_folders - - @ignore_folders.setter - def ignore_folders(self, value: List[str]): - self._ignore_folders = value - - def backup_file( - self, file: Path, target: Path | None = None, custom_filename=None - ) -> bool: - Logger.print_status(f"Creating backup of {file} ...") - - if not file.exists(): - Logger.print_info("File does not exist! Skipping ...") - return False - - target = self.backup_root_dir if target is None else target - - if Path(file).is_file(): - date = get_current_date().get("date") - time = get_current_date().get("time") - filename = f"{file.stem}-{date}-{time}{file.suffix}" - filename = custom_filename if custom_filename is not None else filename - try: - Path(target).mkdir(exist_ok=True) - shutil.copyfile(file, target.joinpath(filename)) - Logger.print_ok("Backup successful!") - return True - except OSError as e: - Logger.print_error(f"Unable to backup '{file}':\n{e}") - return False - else: - Logger.print_info(f"File '{file}' not found ...") - return False - - def backup_directory( - self, name: str, source: Path, target: Path | None = None - ) -> Path | None: - Logger.print_status(f"Creating backup of {name} in {target} ...") - - if source is None or not Path(source).exists(): - Logger.print_info("Source directory does not exist! Skipping ...") - return None - - target = self.backup_root_dir if target is None else target - try: - date = get_current_date().get("date") - time = get_current_date().get("time") - backup_target = target.joinpath(f"{name.lower()}-{date}-{time}") - shutil.copytree( - source, - backup_target, - ignore=self.ignore_folders_func, - ignore_dangling_symlinks=True, - ) - Logger.print_ok("Backup successful!") - - return backup_target - - except OSError as e: - Logger.print_error(f"Unable to backup directory '{source}':\n{e}") - raise BackupManagerException(f"Unable to backup directory '{source}':\n{e}") - - def ignore_folders_func(self, dirpath, filenames) -> List[str]: - return ( - [f for f in filenames if f in self._ignore_folders] - if self._ignore_folders - else [] - ) diff --git a/kiauh/core/constants.py b/kiauh/core/constants.py index f9ccba2..9a9224a 100644 --- a/kiauh/core/constants.py +++ b/kiauh/core/constants.py @@ -11,8 +11,6 @@ import os import pwd from pathlib import Path -from core.backup_manager import BACKUP_ROOT_DIR - # global dependencies GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"] @@ -24,7 +22,6 @@ CURRENT_USER = pwd.getpwuid(os.getuid())[0] # dirs SYSTEMD = Path("/etc/systemd/system") -PRINTER_DATA_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-data-backups") NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available") NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled") NGINX_CONFD = Path("/etc/nginx/conf.d") diff --git a/kiauh/core/services/backup_service.py b/kiauh/core/services/backup_service.py new file mode 100644 index 0000000..08aa330 --- /dev/null +++ b/kiauh/core/services/backup_service.py @@ -0,0 +1,111 @@ +# ======================================================================= # +# Copyright (C) 2020 - 2025 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 # +# ======================================================================= # +from __future__ import annotations + +import shutil +from datetime import datetime +from pathlib import Path +from typing import Optional + +from core.logger import Logger + + +class BackupService: + def __init__(self): + self._backup_root = Path.home().joinpath("kiauh_backups") + + @property + def backup_root(self) -> Path: + return self._backup_root + + def backup_file( + self, + source_path: Path, + target_path: Optional[Path | str] = None, + target_name: Optional[str] = None, + ) -> bool: + source_path = Path(source_path) + + Logger.print_status(f"Creating backup of {source_path} ...") + + if not source_path.exists(): + Logger.print_info( + f"File '{source_path}' does not exist! Skipping backup..." + ) + return False + + if not source_path.is_file(): + Logger.print_info(f"'{source_path}' is not a file! Skipping backup...") + return False + + try: + self._backup_root.mkdir(parents=True, exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + filename = ( + target_name or f"{source_path.stem}_{timestamp}{source_path.suffix}" + ) + if target_path is not None: + backup_path = self._backup_root.joinpath(target_path, filename) + else: + backup_path = self._backup_root.joinpath(filename) + + backup_path.mkdir(parents=True, exist_ok=True) + shutil.copy2(source_path, backup_path) + + Logger.print_ok( + f"Successfully backed up '{source_path}' to '{backup_path}'" + ) + return True + + except Exception as e: + Logger.print_error(f"Failed to backup '{source_path}': {e}") + return False + + def backup_directory( + self, + source_path: Path, + backup_name: str, + target_path: Optional[Path | str] = None, + ) -> Optional[Path]: + source_path = Path(source_path) + + Logger.print_status(f"Creating backup of {source_path} ...") + + if not source_path.exists(): + Logger.print_info( + f"Directory '{source_path}' does not exist! Skipping backup..." + ) + return None + + if not source_path.is_dir(): + Logger.print_info(f"'{source_path}' is not a directory! Skipping backup...") + return None + + try: + self._backup_root.mkdir(parents=True, exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + backup_dir_name = f"{backup_name}_{timestamp}" + + if target_path is not None: + backup_path = self._backup_root.joinpath(target_path, backup_dir_name) + else: + backup_path = self._backup_root.joinpath(backup_dir_name) + + shutil.copytree(source_path, backup_path) + + Logger.print_ok( + f"Successfully backed up '{source_path}' to '{backup_path}'" + ) + return backup_path + + except Exception as e: + Logger.print_error(f"Failed to backup directory '{source_path}': {e}") + return None diff --git a/kiauh/core/settings/kiauh_settings.py b/kiauh/core/settings/kiauh_settings.py index 1d4fadf..e002e56 100644 --- a/kiauh/core/settings/kiauh_settings.py +++ b/kiauh/core/settings/kiauh_settings.py @@ -14,8 +14,8 @@ from typing import Any, Callable, List, TypeVar from components.klipper import KLIPPER_REPO_URL from components.moonraker import MOONRAKER_REPO_URL -from core.backup_manager.backup_manager import BackupManager from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) @@ -374,8 +374,8 @@ class KiauhSettings: kill() def _migrate_repo_config(self) -> None: - bm = BackupManager() - if not bm.backup_file(CUSTOM_CFG): + svc = BackupService() + if not svc.backup_file(CUSTOM_CFG): Logger.print_dialog( DialogType.ERROR, [ diff --git a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py index 511aef0..2f57762 100644 --- a/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py +++ b/kiauh/extensions/gcode_shell_cmd/gcode_shell_cmd_extension.py @@ -9,12 +9,13 @@ import os import shutil +from datetime import datetime from typing import List from components.klipper.klipper import Klipper -from core.backup_manager.backup_manager import BackupManager from core.instance_manager.instance_manager import InstanceManager from core.logger import Logger +from core.services.backup_service import BackupService from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) @@ -109,11 +110,13 @@ class GcodeShellCmdExtension(BaseExtension): Logger.warn(f"Unable to create example config: {e}") # backup each printer.cfg before modification - bm = BackupManager() + svc = BackupService() + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") for instance in instances: - bm.backup_file( - instance.cfg_file, - custom_filename=f"{instance.suffix}.printer.cfg", + svc.backup_file( + source_path=instance.cfg_file, + target_path=f"{instance.data_dir.name}/config_{timestamp}", + target_name=instance.cfg_file.name, ) # add section to printer.cfg if not already defined diff --git a/kiauh/extensions/mobileraker/__init__.py b/kiauh/extensions/mobileraker/__init__.py index 8241381..2169512 100644 --- a/kiauh/extensions/mobileraker/__init__.py +++ b/kiauh/extensions/mobileraker/__init__.py @@ -9,7 +9,6 @@ from pathlib import Path -from core.backup_manager import BACKUP_ROOT_DIR from core.constants import SYSTEMD # repo @@ -23,7 +22,6 @@ MOBILERAKER_LOG_NAME = "mobileraker.log" # directories MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion") MOBILERAKER_ENV_DIR = Path.home().joinpath("mobileraker-env") -MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups") # files MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh") diff --git a/kiauh/extensions/mobileraker/mobileraker_extension.py b/kiauh/extensions/mobileraker/mobileraker_extension.py index a584105..5dd7ebe 100644 --- a/kiauh/extensions/mobileraker/mobileraker_extension.py +++ b/kiauh/extensions/mobileraker/mobileraker_extension.py @@ -13,13 +13,12 @@ from typing import List from components.klipper.klipper import Klipper from components.moonraker.moonraker import Moonraker -from core.backup_manager.backup_manager import BackupManager from core.instance_manager.instance_manager import InstanceManager from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from core.settings.kiauh_settings import KiauhSettings from extensions.base_extension import BaseExtension from extensions.mobileraker import ( - MOBILERAKER_BACKUP_DIR, MOBILERAKER_DIR, MOBILERAKER_ENV_DIR, MOBILERAKER_INSTALL_SCRIPT, @@ -179,14 +178,14 @@ class MobilerakerExtension(BaseExtension): ) def _backup_mobileraker_dir(self) -> None: - bm = BackupManager() - bm.backup_directory( - MOBILERAKER_DIR.name, - source=MOBILERAKER_DIR, - target=MOBILERAKER_BACKUP_DIR, + svc = BackupService() + svc.backup_directory( + source_path=MOBILERAKER_DIR, + backup_name="mobileraker", + target_path="mobileraker", ) - bm.backup_directory( - MOBILERAKER_ENV_DIR.name, - source=MOBILERAKER_ENV_DIR, - target=MOBILERAKER_BACKUP_DIR, + svc.backup_directory( + source_path=MOBILERAKER_ENV_DIR, + backup_name="mobileraker-env", + target_path="mobileraker", ) diff --git a/kiauh/extensions/spoolman/spoolman_extension.py b/kiauh/extensions/spoolman/spoolman_extension.py index 8360928..42aa521 100644 --- a/kiauh/extensions/spoolman/spoolman_extension.py +++ b/kiauh/extensions/spoolman/spoolman_extension.py @@ -15,9 +15,9 @@ from components.moonraker.moonraker import Moonraker from components.moonraker.services.moonraker_instance_service import ( MoonrakerInstanceService, ) -from core.backup_manager.backup_manager import BackupManager from core.instance_manager.instance_manager import InstanceManager from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from extensions.base_extension import BaseExtension from extensions.spoolman import ( SPOOLMAN_COMPOSE_FILE, @@ -123,16 +123,15 @@ class SpoolmanExtension(BaseExtension): "Failed to remove Spoolman image! Please remove it manually." ) - # backup Spoolman directory to ~/spoolman_data- before removing it try: - bm = BackupManager() - result = bm.backup_directory( - f"{SPOOLMAN_DIR.name}_data", - source=SPOOLMAN_DIR, - target=SPOOLMAN_DIR.parent, + svc = BackupService() + success = svc.backup_directory( + source_path=SPOOLMAN_DIR, + backup_name="spoolman", + target_path="spoolman", ) - if result: - Logger.print_ok(f"Spoolman data backed up to {result}") + if success: + Logger.print_ok(f"Spoolman data backed up to {success}") Logger.print_status("Removing Spoolman directory...") if run_remove_routines(SPOOLMAN_DIR): Logger.print_ok("Spoolman directory removed!") diff --git a/kiauh/procedures/switch_repo.py b/kiauh/procedures/switch_repo.py index 920e440..e7fbdfd 100644 --- a/kiauh/procedures/switch_repo.py +++ b/kiauh/procedures/switch_repo.py @@ -13,7 +13,6 @@ from pathlib import Path from typing import Literal from components.klipper import ( - KLIPPER_BACKUP_DIR, KLIPPER_DIR, KLIPPER_ENV_DIR, KLIPPER_REQ_FILE, @@ -21,7 +20,6 @@ from components.klipper import ( from components.klipper.klipper import Klipper from components.klipper.klipper_utils import install_klipper_packages from components.moonraker import ( - MOONRAKER_BACKUP_DIR, MOONRAKER_DIR, MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE, @@ -30,10 +28,10 @@ from components.moonraker.moonraker import Moonraker from components.moonraker.services.moonraker_setup_service import ( install_moonraker_packages, ) -from core.backup_manager.backup_manager import BackupManager, BackupManagerException from core.instance_manager.instance_manager import InstanceManager from core.logger import Logger -from utils.git_utils import GitException, get_repo_name, git_clone_wrapper +from core.services.backup_service import BackupService +from utils.git_utils import GitException, git_clone_wrapper from utils.instance_utils import get_instances from utils.sys_utils import ( VenvCreationFailedException, @@ -52,7 +50,6 @@ def run_switch_repo_routine( repo_dir: Path = KLIPPER_DIR if name == "klipper" else MOONRAKER_DIR env_dir: Path = KLIPPER_ENV_DIR if name == "klipper" else MOONRAKER_ENV_DIR req_file = KLIPPER_REQ_FILE if name == "klipper" else MOONRAKER_REQ_FILE - backup_dir: Path = KLIPPER_BACKUP_DIR if name == "klipper" else MOONRAKER_BACKUP_DIR _type = Klipper if name == "klipper" else Moonraker # step 1: stop all instances @@ -64,19 +61,17 @@ def run_switch_repo_routine( env_dir_backup_path: Path | None = None try: - # step 2: backup old repo and env - org, _ = get_repo_name(repo_dir) - backup_dir = backup_dir.joinpath(org) - bm = BackupManager() - repo_dir_backup_path = bm.backup_directory( - repo_dir.name, - repo_dir, - backup_dir, + svc = BackupService() + svc.backup_directory( + source_path=repo_dir, + backup_name=name, + target_path=name, ) - env_dir_backup_path = bm.backup_directory( - env_dir.name, - env_dir, - backup_dir, + env_backup_name: str = f"{name if name == 'moonraker' else 'klippy'}-env" + svc.backup_directory( + source_path=env_dir, + backup_name=env_backup_name, + target_path=name, ) if not (repo_url or branch): @@ -101,10 +96,6 @@ def run_switch_repo_routine( Logger.print_ok(f"Switched to {repo_url} at branch {branch}!") - except BackupManagerException as e: - Logger.print_error(f"Error during backup of repository: {e}") - raise RepoSwitchFailedException(e) - except (GitException, VenvCreationFailedException) as e: # if something goes wrong during cloning or recreating the virtualenv, # we restore the backup of the repo and env @@ -122,6 +113,9 @@ def run_switch_repo_routine( Logger.print_error(f"Something went wrong: {e}") return + except Exception as e: + raise RepoSwitchFailedException(e) + Logger.print_status(f"Restarting all {_type.__name__} instances ...") InstanceManager.start_all(instances) diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 6ad2037..48c3591 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -18,9 +18,9 @@ from components.klipper.klipper import Klipper from components.moonraker.moonraker import Moonraker from core.constants import ( GLOBAL_DEPS, - PRINTER_DATA_BACKUP_DIR, ) from core.logger import DialogType, Logger +from core.services.backup_service import BackupService from core.types.color import Color from core.types.component_status import ComponentStatus, StatusCode from utils.git_utils import ( @@ -152,11 +152,8 @@ def get_install_status( def backup_printer_config_dir() -> None: - # local import to prevent circular import - from core.backup_manager.backup_manager import BackupManager - instances: List[Klipper] = get_instances(Klipper) - bm = BackupManager() + svc = BackupService() if not instances: Logger.print_info("Unable to find directory to backup!") @@ -164,10 +161,10 @@ def backup_printer_config_dir() -> None: return for instance in instances: - bm.backup_directory( - instance.data_dir.name, - source=instance.base.cfg_dir, - target=PRINTER_DATA_BACKUP_DIR, + svc.backup_directory( + source_path=instance.base.cfg_dir, + target_path=f"{instance.data_dir.name}", + backup_name="config", )