refactor: rework webclient removal

* also backup fluidds config.json on update

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-08-18 13:42:27 +02:00
parent 184c5ac3ff
commit 5d678c4ff2
8 changed files with 93 additions and 186 deletions

View File

@@ -9,7 +9,8 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC
from dataclasses import dataclass
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
@@ -24,89 +25,32 @@ class WebClientConfigType(Enum):
FLUIDD: str = "fluidd-config" FLUIDD: str = "fluidd-config"
@dataclass()
class BaseWebClient(ABC): class BaseWebClient(ABC):
"""Base class for webclient data""" """Base class for webclient data"""
@property client: WebClientType
@abstractmethod name: str
def client(self) -> WebClientType: display_name: str
raise NotImplementedError client_dir: Path
config_file: Path
@property backup_dir: Path
@abstractmethod repo_path: str
def name(self) -> str: download_url: str
raise NotImplementedError nginx_access_log: Path
nginx_error_log: Path
@property client_config: BaseWebClientConfig
@abstractmethod
def display_name(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def client_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def backup_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def repo_path(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def download_url(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def client_config(self) -> BaseWebClientConfig:
raise NotImplementedError
@dataclass()
class BaseWebClientConfig(ABC): class BaseWebClientConfig(ABC):
"""Base class for webclient config data""" """Base class for webclient config data"""
@property client_config: WebClientConfigType
@abstractmethod name: str
def client_config(self) -> WebClientConfigType: display_name: str
raise NotImplementedError config_filename: str
config_dir: Path
@property backup_dir: Path
@abstractmethod repo_url: str
def name(self) -> str: config_section: str
raise NotImplementedError
@property
@abstractmethod
def display_name(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def config_filename(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def config_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def backup_dir(self) -> Path:
raise NotImplementedError
@property
@abstractmethod
def repo_url(self) -> str:
raise NotImplementedError
@property
@abstractmethod
def config_section(self) -> str:
raise NotImplementedError

View File

@@ -6,26 +6,23 @@
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
from typing import List 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 components.webui_client.base_data import ( from components.webui_client.base_data import (
BaseWebClient, BaseWebClient,
WebClientType,
) )
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 components.webui_client.client_utils import backup_mainsail_config_json from core.backup_manager.backup_manager import BackupManager
from core.constants import NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED
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.config_utils import remove_config_section from utils.config_utils import remove_config_section
from utils.fs_utils import ( from utils.fs_utils import (
remove_nginx_config, remove_with_sudo,
remove_nginx_logs,
run_remove_routines, run_remove_routines,
) )
@@ -34,21 +31,22 @@ def run_client_removal(
client: BaseWebClient, client: BaseWebClient,
remove_client: bool, remove_client: bool,
remove_client_cfg: bool, remove_client_cfg: bool,
backup_ms_config_json: bool, backup_config: bool,
) -> None: ) -> None:
mr_im = InstanceManager(Moonraker) mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances mr_instances: List[Moonraker] = mr_im.instances
kl_im = InstanceManager(Klipper) kl_im = InstanceManager(Klipper)
kl_instances: List[Klipper] = kl_im.instances kl_instances: List[Klipper] = kl_im.instances
if backup_ms_config_json and client.client == WebClientType.MAINSAIL: if backup_config:
backup_mainsail_config_json() bm = BackupManager()
bm.backup_file(client.config_file)
if remove_client: if remove_client:
client_name = client.name client_name = client.name
remove_client_dir(client) remove_client_dir(client)
remove_nginx_config(client_name) remove_client_nginx_config(client_name)
remove_nginx_logs(client_name, kl_instances) remove_client_nginx_logs(client, kl_instances)
section = f"update_manager {client_name}" section = f"update_manager {client_name}"
remove_config_section(section, mr_instances) remove_config_section(section, mr_instances)
@@ -64,3 +62,24 @@ def run_client_removal(
def remove_client_dir(client: BaseWebClient) -> None: def remove_client_dir(client: BaseWebClient) -> None:
Logger.print_status(f"Removing {client.display_name} ...") Logger.print_status(f"Removing {client.display_name} ...")
run_remove_routines(client.client_dir) run_remove_routines(client.client_dir)
def remove_client_nginx_config(name: str) -> None:
Logger.print_status(f"Removing NGINX config for {name.capitalize()} ...")
remove_with_sudo(NGINX_SITES_AVAILABLE.joinpath(name))
remove_with_sudo(NGINX_SITES_ENABLED.joinpath(name))
def remove_client_nginx_logs(client: BaseWebClient, instances: List[Klipper]) -> None:
Logger.print_status(f"Removing NGINX logs for {client.display_name} ...")
remove_with_sudo(client.nginx_access_log)
remove_with_sudo(client.nginx_error_log)
if not instances:
return
for instance in instances:
run_remove_routines(instance.log_dir.joinpath(client.nginx_access_log.name))
run_remove_routines(instance.log_dir.joinpath(client.nginx_error_log.name))

View File

@@ -6,7 +6,8 @@
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import shutil
import tempfile
from pathlib import Path from pathlib import Path
from typing import List from typing import List
@@ -27,10 +28,8 @@ from components.webui_client.client_dialogs import (
print_moonraker_not_found_dialog, print_moonraker_not_found_dialog,
) )
from components.webui_client.client_utils import ( from components.webui_client.client_utils import (
backup_mainsail_config_json,
detect_client_cfg_conflict, detect_client_cfg_conflict,
enable_mainsail_remotemode, enable_mainsail_remotemode,
restore_mainsail_config_json,
symlink_webui_nginx_log, symlink_webui_nginx_log,
) )
from core.instance_manager.instance_manager import InstanceManager from core.instance_manager.instance_manager import InstanceManager
@@ -185,10 +184,10 @@ def update_client(client: BaseWebClient) -> None:
) )
return return
if client.client == WebClientType.MAINSAIL: with tempfile.NamedTemporaryFile(suffix=".json") as tmp_file:
backup_mainsail_config_json(is_temp=True) Logger.print_status(
f"Creating temporary backup of {client.config_file} as {tmp_file.name} ..."
)
shutil.copy(client.config_file, tmp_file.name)
download_client(client) download_client(client)
shutil.copy(tmp_file.name, client.config_file)
if client.client == WebClientType.MAINSAIL:
restore_mainsail_config_json()

View File

@@ -9,7 +9,6 @@
from __future__ import annotations from __future__ import annotations
import json import json
import shutil
from pathlib import Path from pathlib import Path
from typing import List, get_args from typing import List, get_args
@@ -78,26 +77,6 @@ def get_current_client_config(clients: List[BaseWebClient]) -> str:
return f"{COLOR_CYAN}-{RESET_FORMAT}" return f"{COLOR_CYAN}-{RESET_FORMAT}"
def backup_mainsail_config_json(is_temp: bool = False) -> None:
c_json = MainsailData().client_dir.joinpath("config.json")
bm = BackupManager()
if is_temp:
fn = Path.home().joinpath("config.json.kiauh.bak")
bm.backup_file(c_json, custom_filename=fn)
else:
bm.backup_file(c_json)
def restore_mainsail_config_json() -> None:
try:
c_json = MainsailData().client_dir.joinpath("config.json")
Logger.print_status(f"Restore '{c_json}' ...")
source = Path.home().joinpath("config.json.kiauh.bak")
shutil.copy(source, c_json)
except OSError:
Logger.print_info("Unable to restore config.json. Skipped ...")
def enable_mainsail_remotemode() -> None: def enable_mainsail_remotemode() -> None:
Logger.print_status("Enable Mainsails remote mode ...") Logger.print_status("Enable Mainsails remote mode ...")
c_json = MainsailData().client_dir.joinpath("config.json") c_json = MainsailData().client_dir.joinpath("config.json")

View File

@@ -21,7 +21,7 @@ from components.webui_client.base_data import (
from core.backup_manager import BACKUP_ROOT_DIR from core.backup_manager import BACKUP_ROOT_DIR
@dataclass(frozen=True) @dataclass()
class FluiddConfigWeb(BaseWebClientConfig): class FluiddConfigWeb(BaseWebClientConfig):
client_config: WebClientConfigType = WebClientConfigType.FLUIDD client_config: WebClientConfigType = WebClientConfigType.FLUIDD
name: str = client_config.value name: str = client_config.value
@@ -33,7 +33,7 @@ class FluiddConfigWeb(BaseWebClientConfig):
repo_url: str = "https://github.com/fluidd-core/fluidd-config.git" repo_url: str = "https://github.com/fluidd-core/fluidd-config.git"
@dataclass(frozen=True) @dataclass()
class FluiddData(BaseWebClient): class FluiddData(BaseWebClient):
BASE_DL_URL = "https://github.com/fluidd-core/fluidd/releases" BASE_DL_URL = "https://github.com/fluidd-core/fluidd/releases"
@@ -41,16 +41,16 @@ class FluiddData(BaseWebClient):
name: str = client.value name: str = client.value
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")
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups") backup_dir: Path = BACKUP_ROOT_DIR.joinpath("fluidd-backups")
repo_path: str = "fluidd-core/fluidd" repo_path: str = "fluidd-core/fluidd"
nginx_access_log: Path = Path("/var/log/nginx/fluidd-access.log")
nginx_error_log: Path = Path("/var/log/nginx/fluidd-error.log")
client_config: BaseWebClientConfig = None
download_url: str | None = None
@property def __post_init__(self):
def download_url(self) -> str:
from components.webui_client.client_utils import get_download_url from components.webui_client.client_utils import get_download_url
url: str = get_download_url(self.BASE_DL_URL, self) self.client_config = FluiddConfigWeb()
return url self.download_url = get_download_url(self.BASE_DL_URL, self)
@property
def client_config(self) -> BaseWebClientConfig:
return FluiddConfigWeb()

View File

@@ -21,7 +21,7 @@ from components.webui_client.base_data import (
from core.backup_manager import BACKUP_ROOT_DIR from core.backup_manager import BACKUP_ROOT_DIR
@dataclass(frozen=True) @dataclass()
class MainsailConfigWeb(BaseWebClientConfig): class MainsailConfigWeb(BaseWebClientConfig):
client_config: WebClientConfigType = WebClientConfigType.MAINSAIL client_config: WebClientConfigType = WebClientConfigType.MAINSAIL
name: str = client_config.value name: str = client_config.value
@@ -33,7 +33,7 @@ class MainsailConfigWeb(BaseWebClientConfig):
repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git" repo_url: str = "https://github.com/mainsail-crew/mainsail-config.git"
@dataclass(frozen=True) @dataclass()
class MainsailData(BaseWebClient): class MainsailData(BaseWebClient):
BASE_DL_URL: str = "https://github.com/mainsail-crew/mainsail/releases" BASE_DL_URL: str = "https://github.com/mainsail-crew/mainsail/releases"
@@ -41,16 +41,16 @@ class MainsailData(BaseWebClient):
name: str = WebClientType.MAINSAIL.value name: str = WebClientType.MAINSAIL.value
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")
backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups") backup_dir: Path = BACKUP_ROOT_DIR.joinpath("mainsail-backups")
repo_path: str = "mainsail-crew/mainsail" repo_path: str = "mainsail-crew/mainsail"
nginx_access_log: Path = Path("/var/log/nginx/mainsail-access.log")
nginx_error_log: Path = Path("/var/log/nginx/mainsail-error.log")
client_config: BaseWebClientConfig = None
download_url: str | None = None
@property def __post_init__(self):
def download_url(self) -> str:
from components.webui_client.client_utils import get_download_url from components.webui_client.client_utils import get_download_url
url: str = get_download_url(self.BASE_DL_URL, self) self.client_config = MainsailConfigWeb()
return url self.download_url = get_download_url(self.BASE_DL_URL, self)
@property
def client_config(self) -> BaseWebClientConfig:
return MainsailConfigWeb()

View File

@@ -12,7 +12,7 @@ import textwrap
from typing import Type from typing import Type
from components.webui_client import client_remove from components.webui_client import client_remove
from components.webui_client.base_data import BaseWebClient, WebClientType from components.webui_client.base_data import BaseWebClient
from core.constants import COLOR_CYAN, COLOR_RED, RESET_FORMAT from core.constants import COLOR_CYAN, COLOR_RED, RESET_FORMAT
from core.menus import Option from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
@@ -28,7 +28,7 @@ class ClientRemoveMenu(BaseMenu):
self.client: BaseWebClient = client self.client: BaseWebClient = client
self.remove_client: bool = False self.remove_client: bool = False
self.remove_client_cfg: bool = False self.remove_client_cfg: bool = False
self.backup_mainsail_config_json: bool = False self.backup_config_json: bool = False
self.selection_state: bool = False self.selection_state: bool = False
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
@@ -41,10 +41,9 @@ class ClientRemoveMenu(BaseMenu):
"a": Option(method=self.toggle_all, menu=False), "a": Option(method=self.toggle_all, menu=False),
"1": Option(method=self.toggle_rm_client, menu=False), "1": Option(method=self.toggle_rm_client, menu=False),
"2": Option(method=self.toggle_rm_client_config, menu=False), "2": Option(method=self.toggle_rm_client_config, menu=False),
"3": Option(method=self.toggle_backup_config_json, menu=False),
"c": Option(method=self.run_removal_process, menu=False), "c": Option(method=self.run_removal_process, menu=False),
} }
if self.client.client == WebClientType.MAINSAIL:
self.options["3"] = Option(self.toggle_backup_mainsail_config_json, False)
def print_menu(self) -> None: def print_menu(self) -> None:
client_name = self.client.display_name client_name = self.client.display_name
@@ -58,6 +57,7 @@ class ClientRemoveMenu(BaseMenu):
unchecked = "[ ]" unchecked = "[ ]"
o1 = checked if self.remove_client else unchecked o1 = checked if self.remove_client else unchecked
o2 = checked if self.remove_client_cfg else unchecked o2 = checked if self.remove_client_cfg else unchecked
o3 = checked if self.backup_config_json else unchecked
menu = textwrap.dedent( menu = textwrap.dedent(
f""" f"""
╔═══════════════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════════════╗
@@ -70,19 +70,7 @@ class ClientRemoveMenu(BaseMenu):
╟───────────────────────────────────────────────────────╢ ╟───────────────────────────────────────────────────────╢
║ 1) {o1} Remove {client_name:16} ║ 1) {o1} Remove {client_name:16}
║ 2) {o2} Remove {client_config_name:24} ║ 2) {o2} Remove {client_config_name:24}
"""
)[1:]
if self.client.client == WebClientType.MAINSAIL:
o3 = checked if self.backup_mainsail_config_json else unchecked
menu += textwrap.dedent(
f"""
║ 3) {o3} Backup config.json ║ ║ 3) {o3} Backup config.json ║
"""
)[1:]
menu += textwrap.dedent(
"""
╟───────────────────────────────────────────────────────╢ ╟───────────────────────────────────────────────────────╢
║ C) Continue ║ ║ C) Continue ║
╟───────────────────────────────────────────────────────╢ ╟───────────────────────────────────────────────────────╢
@@ -94,7 +82,7 @@ class ClientRemoveMenu(BaseMenu):
self.selection_state = not self.selection_state self.selection_state = not self.selection_state
self.remove_client = self.selection_state self.remove_client = self.selection_state
self.remove_client_cfg = self.selection_state self.remove_client_cfg = self.selection_state
self.backup_mainsail_config_json = self.selection_state self.backup_config_json = self.selection_state
def toggle_rm_client(self, **kwargs) -> None: def toggle_rm_client(self, **kwargs) -> None:
self.remove_client = not self.remove_client self.remove_client = not self.remove_client
@@ -102,14 +90,14 @@ class ClientRemoveMenu(BaseMenu):
def toggle_rm_client_config(self, **kwargs) -> None: def toggle_rm_client_config(self, **kwargs) -> None:
self.remove_client_cfg = not self.remove_client_cfg self.remove_client_cfg = not self.remove_client_cfg
def toggle_backup_mainsail_config_json(self, **kwargs) -> None: def toggle_backup_config_json(self, **kwargs) -> None:
self.backup_mainsail_config_json = not self.backup_mainsail_config_json self.backup_config_json = not self.backup_config_json
def run_removal_process(self, **kwargs) -> None: def run_removal_process(self, **kwargs) -> None:
if ( if (
not self.remove_client not self.remove_client
and not self.remove_client_cfg and not self.remove_client_cfg
and not self.backup_mainsail_config_json and not self.backup_config_json
): ):
error = f"{COLOR_RED}Nothing selected ...{RESET_FORMAT}" error = f"{COLOR_RED}Nothing selected ...{RESET_FORMAT}"
print(error) print(error)
@@ -119,12 +107,12 @@ class ClientRemoveMenu(BaseMenu):
client=self.client, client=self.client,
remove_client=self.remove_client, remove_client=self.remove_client,
remove_client_cfg=self.remove_client_cfg, remove_client_cfg=self.remove_client_cfg,
backup_ms_config_json=self.backup_mainsail_config_json, backup_config=self.backup_config_json,
) )
self.remove_client = False self.remove_client = False
self.remove_client_cfg = False self.remove_client_cfg = False
self.backup_mainsail_config_json = False self.backup_config_json = False
self._go_back() self._go_back()

View File

@@ -16,7 +16,6 @@ from subprocess import DEVNULL, PIPE, CalledProcessError, check_output, run
from typing import List from typing import List
from zipfile import ZipFile from zipfile import ZipFile
from components.klipper.klipper import Klipper
from core.constants import ( from core.constants import (
NGINX_CONFD, NGINX_CONFD,
NGINX_SITES_AVAILABLE, NGINX_SITES_AVAILABLE,
@@ -236,24 +235,3 @@ def get_next_free_port(ports_in_use: List[int]) -> int:
used_ports = set(map(int, ports_in_use)) used_ports = set(map(int, ports_in_use))
return min(valid_ports - used_ports) return min(valid_ports - used_ports)
def remove_nginx_config(name: str) -> None:
Logger.print_status(f"Removing NGINX config for {name.capitalize()} ...")
run_remove_routines(NGINX_SITES_AVAILABLE.joinpath(name))
run_remove_routines(NGINX_SITES_ENABLED.joinpath(name))
def remove_nginx_logs(name: str, instances: List[Klipper]) -> None:
Logger.print_status(f"Removing NGINX logs for {name.capitalize()} ...")
run_remove_routines(Path(f"/var/log/nginx/{name}-access.log"))
run_remove_routines(Path(f"/var/log/nginx/{name}-error.log"))
if not instances:
return
for instance in instances:
run_remove_routines(instance.log_dir.joinpath(f"{name}-access.log"))
run_remove_routines(instance.log_dir.joinpath(f"{name}-error.log"))