refactor: replace backup_manager with backup_service

This commit is contained in:
dw-0
2025-09-28 09:41:07 +02:00
parent 1ed1e0fc4c
commit aff63665de
24 changed files with 252 additions and 256 deletions

View File

@@ -9,7 +9,6 @@
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
from core.constants import SYSTEMD from core.constants import SYSTEMD
# repo # repo
@@ -20,7 +19,6 @@ CROWSNEST_SERVICE_NAME = "crowsnest.service"
# directories # directories
CROWSNEST_DIR = Path.home().joinpath("crowsnest") CROWSNEST_DIR = Path.home().joinpath("crowsnest")
CROWSNEST_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("crowsnest-backups")
# files # files
CROWSNEST_MULTI_CONFIG = CROWSNEST_DIR.joinpath("tools/.config") CROWSNEST_MULTI_CONFIG = CROWSNEST_DIR.joinpath("tools/.config")

View File

@@ -15,7 +15,6 @@ from subprocess import CalledProcessError, run
from typing import List from typing import List
from components.crowsnest import ( from components.crowsnest import (
CROWSNEST_BACKUP_DIR,
CROWSNEST_BIN_FILE, CROWSNEST_BIN_FILE,
CROWSNEST_DIR, CROWSNEST_DIR,
CROWSNEST_INSTALL_SCRIPT, CROWSNEST_INSTALL_SCRIPT,
@@ -26,8 +25,8 @@ from components.crowsnest import (
CROWSNEST_SERVICE_NAME, CROWSNEST_SERVICE_NAME,
) )
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from core.backup_manager.backup_manager import BackupManager
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from core.types.component_status import ComponentStatus from core.types.component_status import ComponentStatus
from utils.common import ( from utils.common import (
@@ -127,11 +126,11 @@ def update_crowsnest() -> None:
settings = KiauhSettings() settings = KiauhSettings()
if settings.kiauh.backup_before_update: if settings.kiauh.backup_before_update:
bm = BackupManager() svc = BackupService()
bm.backup_directory( svc.backup_directory(
CROWSNEST_DIR.name, source_path=CROWSNEST_DIR,
source=CROWSNEST_DIR, target_path="crowsnest",
target=CROWSNEST_BACKUP_DIR, backup_name="crowsnest",
) )
git_pull_wrapper(CROWSNEST_DIR) git_pull_wrapper(CROWSNEST_DIR)

View File

@@ -9,8 +9,6 @@
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent MODULE_PATH = Path(__file__).resolve().parent
KLIPPER_REPO_URL = "https://github.com/Klipper3d/klipper.git" 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_DIR = Path.home().joinpath("klipper")
KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs") KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs")
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")
# files # files
KLIPPER_REQ_FILE = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt") KLIPPER_REQ_FILE = KLIPPER_DIR.joinpath("scripts/klippy-requirements.txt")

View File

@@ -16,7 +16,6 @@ from subprocess import CalledProcessError, run
from typing import Dict, List from typing import Dict, List
from components.klipper import ( from components.klipper import (
KLIPPER_BACKUP_DIR,
KLIPPER_DIR, KLIPPER_DIR,
KLIPPER_ENV_DIR, KLIPPER_ENV_DIR,
KLIPPER_INSTALL_SCRIPT, 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 ( from components.webui_client.client_config.client_config_setup import (
create_client_config_symlink, create_client_config_symlink,
) )
from core.backup_manager.backup_manager import BackupManager
from core.constants import CURRENT_USER from core.constants import CURRENT_USER
from core.instance_manager.base_instance import SUFFIX_BLACKLIST from core.instance_manager.base_instance import SUFFIX_BLACKLIST
from core.logger import DialogType, Logger 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 ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser, SimpleConfigParser,
) )
@@ -198,9 +197,17 @@ def create_example_printer_cfg(
def backup_klipper_dir() -> None: def backup_klipper_dir() -> None:
bm = BackupManager() svc = BackupService()
bm.backup_directory("klipper", source=KLIPPER_DIR, target=KLIPPER_BACKUP_DIR) svc.backup_directory(
bm.backup_directory("klippy-env", source=KLIPPER_ENV_DIR, target=KLIPPER_BACKUP_DIR) 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: def install_klipper_packages() -> None:

View File

@@ -8,7 +8,6 @@
# ======================================================================= # # ======================================================================= #
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
from core.constants import SYSTEMD from core.constants import SYSTEMD
# repo # repo
@@ -22,7 +21,6 @@ KLIPPERSCREEN_LOG_NAME = "KlipperScreen.log"
# directories # directories
KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen") KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen")
KLIPPERSCREEN_ENV_DIR = Path.home().joinpath(".KlipperScreen-env") KLIPPERSCREEN_ENV_DIR = Path.home().joinpath(".KlipperScreen-env")
KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups")
# files # files
KLIPPERSCREEN_REQ_FILE = KLIPPERSCREEN_DIR.joinpath( KLIPPERSCREEN_REQ_FILE = KLIPPERSCREEN_DIR.joinpath(

View File

@@ -13,7 +13,6 @@ from typing import List
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.klipperscreen import ( from components.klipperscreen import (
KLIPPERSCREEN_BACKUP_DIR,
KLIPPERSCREEN_DIR, KLIPPERSCREEN_DIR,
KLIPPERSCREEN_ENV_DIR, KLIPPERSCREEN_ENV_DIR,
KLIPPERSCREEN_INSTALL_SCRIPT, KLIPPERSCREEN_INSTALL_SCRIPT,
@@ -25,10 +24,10 @@ from components.klipperscreen import (
KLIPPERSCREEN_UPDATER_SECTION_NAME, KLIPPERSCREEN_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.constants import SYSTEMD from core.constants import SYSTEMD
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from core.types.component_status import ComponentStatus from core.types.component_status import ComponentStatus
from utils.common import ( from utils.common import (
@@ -193,14 +192,14 @@ def remove_klipperscreen() -> None:
def backup_klipperscreen_dir() -> None: def backup_klipperscreen_dir() -> None:
bm = BackupManager() svc = BackupService()
bm.backup_directory( svc.backup_directory(
KLIPPERSCREEN_DIR.name, source_path=KLIPPERSCREEN_DIR,
source=KLIPPERSCREEN_DIR, backup_name="KlipperScreen",
target=KLIPPERSCREEN_BACKUP_DIR, target_path="KlipperScreen",
) )
bm.backup_directory( svc.backup_directory(
KLIPPERSCREEN_ENV_DIR.name, source_path=KLIPPERSCREEN_ENV_DIR,
source=KLIPPERSCREEN_ENV_DIR, backup_name="KlipperScreen-env",
target=KLIPPERSCREEN_BACKUP_DIR, target_path="KlipperScreen",
) )

View File

@@ -9,8 +9,6 @@
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
MODULE_PATH = Path(__file__).resolve().parent MODULE_PATH = Path(__file__).resolve().parent
MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker.git" MOONRAKER_REPO_URL = "https://github.com/Arksine/moonraker.git"
@@ -25,8 +23,6 @@ MOONRAKER_ENV_FILE_NAME = "moonraker.env"
# directories # 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_DB_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("moonraker-db-backups")
# files # files
MOONRAKER_INSTALL_SCRIPT = MOONRAKER_DIR.joinpath("scripts/install-moonraker.sh") MOONRAKER_INSTALL_SCRIPT = MOONRAKER_DIR.joinpath("scripts/install-moonraker.sh")

View File

@@ -14,8 +14,6 @@ from typing import Dict, List, Optional
from components.moonraker import ( from components.moonraker import (
MODULE_PATH, MODULE_PATH,
MOONRAKER_BACKUP_DIR,
MOONRAKER_DB_BACKUP_DIR,
MOONRAKER_DEFAULT_PORT, MOONRAKER_DEFAULT_PORT,
MOONRAKER_DEPS_JSON_FILE, MOONRAKER_DEPS_JSON_FILE,
MOONRAKER_DIR, MOONRAKER_DIR,
@@ -25,8 +23,8 @@ from components.moonraker import (
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from components.moonraker.utils.sysdeps_parser import SysDepsParser from components.moonraker.utils.sysdeps_parser import SysDepsParser
from components.webui_client.base_data import BaseWebClient from components.webui_client.base_data import BaseWebClient
from core.backup_manager.backup_manager import BackupManager
from core.logger import Logger 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 ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser, SimpleConfigParser,
) )
@@ -168,21 +166,31 @@ def create_example_moonraker_conf(
def backup_moonraker_dir() -> None: def backup_moonraker_dir() -> None:
bm = BackupManager() svc = BackupService()
bm.backup_directory("moonraker", source=MOONRAKER_DIR, target=MOONRAKER_BACKUP_DIR) svc.backup_directory(
bm.backup_directory( source_path=MOONRAKER_DIR, backup_name="moonraker", target_path="moonraker"
"moonraker-env", source=MOONRAKER_ENV_DIR, target=MOONRAKER_BACKUP_DIR )
svc.backup_directory(
source_path=MOONRAKER_ENV_DIR,
backup_name="moonraker-env",
target_path="moonraker",
) )
def backup_moonraker_db_dir() -> None: def backup_moonraker_db_dir() -> None:
instances: List[Moonraker] = get_instances(Moonraker) 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: for instance in instances:
name = f"database-{instance.data_dir.name}" svc.backup_directory(
bm.backup_directory( source_path=instance.db_dir,
name, source=instance.db_dir, target=MOONRAKER_DB_BACKUP_DIR target_path=f"{instance.data_dir.name}",
backup_name="database",
) )

View File

@@ -34,7 +34,6 @@ class BaseWebClient(ABC):
display_name: str display_name: str
client_dir: Path client_dir: Path
config_file: Path config_file: Path
backup_dir: Path
repo_path: str repo_path: str
download_url: str download_url: str
nginx_config: Path nginx_config: Path
@@ -52,6 +51,5 @@ class BaseWebClientConfig(ABC):
display_name: str display_name: str
config_filename: str config_filename: str
config_dir: Path config_dir: Path
backup_dir: Path
repo_url: str repo_url: str
config_section: str config_section: str

View File

@@ -16,9 +16,9 @@ from components.webui_client.base_data import (
from components.webui_client.client_config.client_config_remove import ( from components.webui_client.client_config.client_config_remove import (
run_client_config_removal, run_client_config_removal,
) )
from core.backup_manager.backup_manager import BackupManager
from core.constants import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED from core.constants import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
from core.logger import Logger from core.logger import Logger
from core.services.backup_service import BackupService
from core.services.message_service import Message from core.services.message_service import Message
from core.types.color import Color from core.types.color import Color
from utils.config_utils import remove_config_section from utils.config_utils import remove_config_section
@@ -43,8 +43,19 @@ def run_client_removal(
kl_instances: List[Klipper] = get_instances(Klipper) kl_instances: List[Klipper] = get_instances(Klipper)
if backup_config: if backup_config:
bm = BackupManager() version = ""
if bm.backup_file(client.config_file): 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") completion_msg.text.append(f"{client.config_file.name} backup created")
if remove_client: if remove_client:

View File

@@ -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.client_dialogs import print_client_port_select_dialog
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.backup_manager.backup_manager import BackupManager
from core.constants import ( from core.constants import (
NGINX_CONFD, NGINX_CONFD,
NGINX_SITES_AVAILABLE, NGINX_SITES_AVAILABLE,
NGINX_SITES_ENABLED, NGINX_SITES_ENABLED,
) )
from core.logger import Logger from core.logger import Logger
from core.services.backup_service import BackupService
from core.settings.kiauh_settings import KiauhSettings, WebUiSettings from core.settings.kiauh_settings import KiauhSettings, WebUiSettings
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser, SimpleConfigParser,
@@ -118,7 +118,7 @@ def enable_mainsail_remotemode() -> None:
c_json = MainsailData().client_dir.joinpath("config.json") c_json = MainsailData().client_dir.joinpath("config.json")
with open(c_json, "r") as f: with open(c_json, "r") as f:
config_data = json.load(f) config_data = json.load(f)
if config_data["instancesDB"] == "browser" or config_data["instancesDB"] == "json": if config_data["instancesDB"] == "browser" or config_data["instancesDB"] == "json":
Logger.print_info("Remote mode already configured. Skipped ...") Logger.print_info("Remote mode already configured. Skipped ...")
return return
@@ -175,26 +175,39 @@ def get_remote_client_version(client: BaseWebClient) -> str | None:
def backup_client_data(client: BaseWebClient) -> None: def backup_client_data(client: BaseWebClient) -> None:
name = client.name version = ""
src = client.client_dir 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: svc = BackupService()
version = v.readlines()[0] target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}")
svc.backup_directory(
bm = BackupManager() source_path=client.client_dir,
bm.backup_directory(f"{name}-{version}", src, dest) target_path=target_path,
bm.backup_file(client.config_file, dest) backup_name=client.name,
bm.backup_file(NGINX_SITES_AVAILABLE.joinpath(name), dest) )
svc.backup_file(
source_path=client.config_file,
target_path=target_path,
)
def backup_client_config_data(client: BaseWebClient) -> None: def backup_client_config_data(client: BaseWebClient) -> None:
client_config = client.client_config version = ""
name = client_config.name src = client.client_dir
source = client_config.config_dir if src.joinpath(".version").exists():
target = client_config.backup_dir with open(src.joinpath(".version"), "r") as v:
bm = BackupManager() version = v.readlines()[0]
bm.backup_directory(name, source, target)
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]: def get_existing_clients() -> List[BaseWebClient]:

View File

@@ -18,7 +18,6 @@ from components.webui_client.base_data import (
WebClientConfigType, WebClientConfigType,
WebClientType, WebClientType,
) )
from core.backup_manager import BACKUP_ROOT_DIR
from core.constants import NGINX_SITES_AVAILABLE from core.constants import NGINX_SITES_AVAILABLE
@@ -30,7 +29,6 @@ class FluiddConfigWeb(BaseWebClientConfig):
config_dir: Path = Path.home().joinpath("fluidd-config") config_dir: Path = Path.home().joinpath("fluidd-config")
config_filename: str = "fluidd.cfg" config_filename: str = "fluidd.cfg"
config_section: str = f"include {config_filename}" 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" repo_url: str = "https://github.com/fluidd-core/fluidd-config.git"
@@ -43,7 +41,6 @@ class FluiddData(BaseWebClient):
display_name: str = name.capitalize() display_name: str = name.capitalize()
client_dir: Path = Path.home().joinpath("fluidd") client_dir: Path = Path.home().joinpath("fluidd")
config_file: Path = client_dir.joinpath("config.json") config_file: Path = client_dir.joinpath("config.json")
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
repo_path: str = "fluidd-core/fluidd" repo_path: str = "fluidd-core/fluidd"
nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("fluidd") nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("fluidd")
nginx_access_log: Path = Path("/var/log/nginx/fluidd-access.log") nginx_access_log: Path = Path("/var/log/nginx/fluidd-access.log")

View File

@@ -18,7 +18,6 @@ from components.webui_client.base_data import (
WebClientConfigType, WebClientConfigType,
WebClientType, WebClientType,
) )
from core.backup_manager import BACKUP_ROOT_DIR
from core.constants import NGINX_SITES_AVAILABLE from core.constants import NGINX_SITES_AVAILABLE
@@ -30,7 +29,6 @@ class MainsailConfigWeb(BaseWebClientConfig):
config_dir: Path = Path.home().joinpath("mainsail-config") config_dir: Path = Path.home().joinpath("mainsail-config")
config_filename: str = "mainsail.cfg" config_filename: str = "mainsail.cfg"
config_section: str = f"include {config_filename}" 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" repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git"
@@ -43,7 +41,6 @@ class MainsailData(BaseWebClient):
display_name: str = name.capitalize() display_name: str = name.capitalize()
client_dir: Path = Path.home().joinpath("mainsail") client_dir: Path = Path.home().joinpath("mainsail")
config_file: Path = client_dir.joinpath("config.json") config_file: Path = client_dir.joinpath("config.json")
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
repo_path: str = "mainsail-crew/mainsail" repo_path: str = "mainsail-crew/mainsail"
nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("mainsail") nginx_config: Path = NGINX_SITES_AVAILABLE.joinpath("mainsail")
nginx_access_log: Path = Path("/var/log/nginx/mainsail-access.log") nginx_access_log: Path = Path("/var/log/nginx/mainsail-access.log")

View File

@@ -1,12 +0,0 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
from pathlib import Path
BACKUP_ROOT_DIR = Path.home().joinpath("kiauh-backups")

View File

@@ -1,108 +0,0 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
from __future__ import annotations
import 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 []
)

View File

@@ -11,8 +11,6 @@ import os
import pwd import pwd
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
# global dependencies # global dependencies
GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"] GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"]
@@ -24,7 +22,6 @@ CURRENT_USER = pwd.getpwuid(os.getuid())[0]
# dirs # dirs
SYSTEMD = Path("/etc/systemd/system") 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_AVAILABLE = Path("/etc/nginx/sites-available")
NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled") NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled")
NGINX_CONFD = Path("/etc/nginx/conf.d") NGINX_CONFD = Path("/etc/nginx/conf.d")

View File

@@ -0,0 +1,111 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
from __future__ import annotations
import 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

View File

@@ -14,8 +14,8 @@ from typing import Any, Callable, List, TypeVar
from components.klipper import KLIPPER_REPO_URL from components.klipper import KLIPPER_REPO_URL
from components.moonraker import MOONRAKER_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.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 ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser, SimpleConfigParser,
) )
@@ -374,8 +374,8 @@ class KiauhSettings:
kill() kill()
def _migrate_repo_config(self) -> None: def _migrate_repo_config(self) -> None:
bm = BackupManager() svc = BackupService()
if not bm.backup_file(CUSTOM_CFG): if not svc.backup_file(CUSTOM_CFG):
Logger.print_dialog( Logger.print_dialog(
DialogType.ERROR, DialogType.ERROR,
[ [

View File

@@ -9,12 +9,13 @@
import os import os
import shutil import shutil
from datetime import datetime
from typing import List from typing import List
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from core.backup_manager.backup_manager import BackupManager
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.logger import Logger from core.logger import Logger
from core.services.backup_service import BackupService
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,
) )
@@ -109,11 +110,13 @@ class GcodeShellCmdExtension(BaseExtension):
Logger.warn(f"Unable to create example config: {e}") Logger.warn(f"Unable to create example config: {e}")
# backup each printer.cfg before modification # backup each printer.cfg before modification
bm = BackupManager() svc = BackupService()
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
for instance in instances: for instance in instances:
bm.backup_file( svc.backup_file(
instance.cfg_file, source_path=instance.cfg_file,
custom_filename=f"{instance.suffix}.printer.cfg", target_path=f"{instance.data_dir.name}/config_{timestamp}",
target_name=instance.cfg_file.name,
) )
# add section to printer.cfg if not already defined # add section to printer.cfg if not already defined

View File

@@ -9,7 +9,6 @@
from pathlib import Path from pathlib import Path
from core.backup_manager import BACKUP_ROOT_DIR
from core.constants import SYSTEMD from core.constants import SYSTEMD
# repo # repo
@@ -23,7 +22,6 @@ MOBILERAKER_LOG_NAME = "mobileraker.log"
# directories # directories
MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion") MOBILERAKER_DIR = Path.home().joinpath("mobileraker_companion")
MOBILERAKER_ENV_DIR = Path.home().joinpath("mobileraker-env") MOBILERAKER_ENV_DIR = Path.home().joinpath("mobileraker-env")
MOBILERAKER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("mobileraker-backups")
# files # files
MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh") MOBILERAKER_INSTALL_SCRIPT = MOBILERAKER_DIR.joinpath("scripts/install.sh")

View File

@@ -13,13 +13,12 @@ from typing import List
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from core.backup_manager.backup_manager import BackupManager
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from extensions.base_extension import BaseExtension from extensions.base_extension import BaseExtension
from extensions.mobileraker import ( from extensions.mobileraker import (
MOBILERAKER_BACKUP_DIR,
MOBILERAKER_DIR, MOBILERAKER_DIR,
MOBILERAKER_ENV_DIR, MOBILERAKER_ENV_DIR,
MOBILERAKER_INSTALL_SCRIPT, MOBILERAKER_INSTALL_SCRIPT,
@@ -179,14 +178,14 @@ class MobilerakerExtension(BaseExtension):
) )
def _backup_mobileraker_dir(self) -> None: def _backup_mobileraker_dir(self) -> None:
bm = BackupManager() svc = BackupService()
bm.backup_directory( svc.backup_directory(
MOBILERAKER_DIR.name, source_path=MOBILERAKER_DIR,
source=MOBILERAKER_DIR, backup_name="mobileraker",
target=MOBILERAKER_BACKUP_DIR, target_path="mobileraker",
) )
bm.backup_directory( svc.backup_directory(
MOBILERAKER_ENV_DIR.name, source_path=MOBILERAKER_ENV_DIR,
source=MOBILERAKER_ENV_DIR, backup_name="mobileraker-env",
target=MOBILERAKER_BACKUP_DIR, target_path="mobileraker",
) )

View File

@@ -15,9 +15,9 @@ from components.moonraker.moonraker import Moonraker
from components.moonraker.services.moonraker_instance_service import ( from components.moonraker.services.moonraker_instance_service import (
MoonrakerInstanceService, MoonrakerInstanceService,
) )
from core.backup_manager.backup_manager import BackupManager
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from extensions.base_extension import BaseExtension from extensions.base_extension import BaseExtension
from extensions.spoolman import ( from extensions.spoolman import (
SPOOLMAN_COMPOSE_FILE, SPOOLMAN_COMPOSE_FILE,
@@ -123,16 +123,15 @@ class SpoolmanExtension(BaseExtension):
"Failed to remove Spoolman image! Please remove it manually." "Failed to remove Spoolman image! Please remove it manually."
) )
# backup Spoolman directory to ~/spoolman_data-<timestamp> before removing it
try: try:
bm = BackupManager() svc = BackupService()
result = bm.backup_directory( success = svc.backup_directory(
f"{SPOOLMAN_DIR.name}_data", source_path=SPOOLMAN_DIR,
source=SPOOLMAN_DIR, backup_name="spoolman",
target=SPOOLMAN_DIR.parent, target_path="spoolman",
) )
if result: if success:
Logger.print_ok(f"Spoolman data backed up to {result}") Logger.print_ok(f"Spoolman data backed up to {success}")
Logger.print_status("Removing Spoolman directory...") Logger.print_status("Removing Spoolman directory...")
if run_remove_routines(SPOOLMAN_DIR): if run_remove_routines(SPOOLMAN_DIR):
Logger.print_ok("Spoolman directory removed!") Logger.print_ok("Spoolman directory removed!")

View File

@@ -13,7 +13,6 @@ from pathlib import Path
from typing import Literal from typing import Literal
from components.klipper import ( from components.klipper import (
KLIPPER_BACKUP_DIR,
KLIPPER_DIR, KLIPPER_DIR,
KLIPPER_ENV_DIR, KLIPPER_ENV_DIR,
KLIPPER_REQ_FILE, KLIPPER_REQ_FILE,
@@ -21,7 +20,6 @@ from components.klipper import (
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.klipper.klipper_utils import install_klipper_packages from components.klipper.klipper_utils import install_klipper_packages
from components.moonraker import ( from components.moonraker import (
MOONRAKER_BACKUP_DIR,
MOONRAKER_DIR, MOONRAKER_DIR,
MOONRAKER_ENV_DIR, MOONRAKER_ENV_DIR,
MOONRAKER_REQ_FILE, MOONRAKER_REQ_FILE,
@@ -30,10 +28,10 @@ from components.moonraker.moonraker import Moonraker
from components.moonraker.services.moonraker_setup_service import ( from components.moonraker.services.moonraker_setup_service import (
install_moonraker_packages, install_moonraker_packages,
) )
from core.backup_manager.backup_manager import BackupManager, BackupManagerException
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
from core.logger import Logger from core.logger import 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.instance_utils import get_instances
from utils.sys_utils import ( from utils.sys_utils import (
VenvCreationFailedException, VenvCreationFailedException,
@@ -52,7 +50,6 @@ def run_switch_repo_routine(
repo_dir: Path = KLIPPER_DIR if name == "klipper" else MOONRAKER_DIR repo_dir: Path = KLIPPER_DIR if name == "klipper" else MOONRAKER_DIR
env_dir: Path = KLIPPER_ENV_DIR if name == "klipper" else MOONRAKER_ENV_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 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 _type = Klipper if name == "klipper" else Moonraker
# step 1: stop all instances # step 1: stop all instances
@@ -64,19 +61,17 @@ def run_switch_repo_routine(
env_dir_backup_path: Path | None = None env_dir_backup_path: Path | None = None
try: try:
# step 2: backup old repo and env svc = BackupService()
org, _ = get_repo_name(repo_dir) svc.backup_directory(
backup_dir = backup_dir.joinpath(org) source_path=repo_dir,
bm = BackupManager() backup_name=name,
repo_dir_backup_path = bm.backup_directory( target_path=name,
repo_dir.name,
repo_dir,
backup_dir,
) )
env_dir_backup_path = bm.backup_directory( env_backup_name: str = f"{name if name == 'moonraker' else 'klippy'}-env"
env_dir.name, svc.backup_directory(
env_dir, source_path=env_dir,
backup_dir, backup_name=env_backup_name,
target_path=name,
) )
if not (repo_url or branch): 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}!") 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: except (GitException, VenvCreationFailedException) as e:
# if something goes wrong during cloning or recreating the virtualenv, # if something goes wrong during cloning or recreating the virtualenv,
# we restore the backup of the repo and env # 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}") Logger.print_error(f"Something went wrong: {e}")
return return
except Exception as e:
raise RepoSwitchFailedException(e)
Logger.print_status(f"Restarting all {_type.__name__} instances ...") Logger.print_status(f"Restarting all {_type.__name__} instances ...")
InstanceManager.start_all(instances) InstanceManager.start_all(instances)

View File

@@ -18,9 +18,9 @@ from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker from components.moonraker.moonraker import Moonraker
from core.constants import ( from core.constants import (
GLOBAL_DEPS, GLOBAL_DEPS,
PRINTER_DATA_BACKUP_DIR,
) )
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from core.types.color import Color from core.types.color import Color
from core.types.component_status import ComponentStatus, StatusCode from core.types.component_status import ComponentStatus, StatusCode
from utils.git_utils import ( from utils.git_utils import (
@@ -152,11 +152,8 @@ def get_install_status(
def backup_printer_config_dir() -> None: 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) instances: List[Klipper] = get_instances(Klipper)
bm = BackupManager() svc = BackupService()
if not instances: if not instances:
Logger.print_info("Unable to find directory to backup!") Logger.print_info("Unable to find directory to backup!")
@@ -164,10 +161,10 @@ def backup_printer_config_dir() -> None:
return return
for instance in instances: for instance in instances:
bm.backup_directory( svc.backup_directory(
instance.data_dir.name, source_path=instance.base.cfg_dir,
source=instance.base.cfg_dir, target_path=f"{instance.data_dir.name}",
target=PRINTER_DATA_BACKUP_DIR, backup_name="config",
) )