feat: add system upgrades

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-08-10 11:55:39 +02:00
parent 88f784348b
commit 9e66c8093b
2 changed files with 88 additions and 7 deletions

View File

@@ -9,7 +9,7 @@
from __future__ import annotations from __future__ import annotations
import textwrap import textwrap
from typing import Callable, Type from typing import Callable, List, Type
from components.crowsnest.crowsnest import get_crowsnest_status, update_crowsnest from components.crowsnest.crowsnest import get_crowsnest_status, update_crowsnest
from components.klipper.klipper_setup import update_klipper from components.klipper.klipper_setup import update_klipper
@@ -48,8 +48,14 @@ from utils.constants import (
COLOR_YELLOW, COLOR_YELLOW,
RESET_FORMAT, RESET_FORMAT,
) )
from utils.logger import Logger from utils.input_utils import get_confirm
from utils.logger import DialogType, Logger
from utils.spinner import Spinner from utils.spinner import Spinner
from utils.sys_utils import (
get_upgradable_packages,
update_system_package_lists,
upgrade_system_packages,
)
from utils.types import ComponentStatus from utils.types import ComponentStatus
@@ -60,6 +66,9 @@ class UpdateMenu(BaseMenu):
super().__init__() super().__init__()
self.previous_menu: Type[BaseMenu] | None = previous_menu self.previous_menu: Type[BaseMenu] | None = previous_menu
self.packages: List[str] = []
self.package_count: int = 0
self.klipper_local = self.klipper_remote = "" self.klipper_local = self.klipper_remote = ""
self.moonraker_local = self.moonraker_remote = "" self.moonraker_local = self.moonraker_remote = ""
self.mainsail_local = self.mainsail_remote = "" self.mainsail_local = self.mainsail_remote = ""
@@ -108,7 +117,7 @@ class UpdateMenu(BaseMenu):
} }
def print_menu(self) -> None: def print_menu(self) -> None:
spinner = Spinner() spinner = Spinner("Loading update menu, please wait")
spinner.start() spinner.start()
self._fetch_update_status() self._fetch_update_status()
@@ -118,6 +127,15 @@ class UpdateMenu(BaseMenu):
header = " [ Update Menu ] " header = " [ Update Menu ] "
color = COLOR_GREEN color = COLOR_GREEN
count = 62 - len(color) - len(RESET_FORMAT) count = 62 - len(color) - len(RESET_FORMAT)
sysupgrades: str = "No upgrades available."
padding = 29
if self.package_count > 0:
sysupgrades = (
f"{COLOR_GREEN}{self.package_count} upgrades available!{RESET_FORMAT}"
)
padding = 38
menu = textwrap.dedent( menu = textwrap.dedent(
f""" f"""
╔═══════════════════════════════════════════════════════╗ ╔═══════════════════════════════════════════════════════╗
@@ -143,7 +161,7 @@ class UpdateMenu(BaseMenu):
║ 9) Crowsnest │ {self.crowsnest_local:<22}{self.crowsnest_remote:<22} ║ 9) Crowsnest │ {self.crowsnest_local:<22}{self.crowsnest_remote:<22}
║ 10) OctoEverywhere │ {self.octoeverywhere_local:<22}{self.octoeverywhere_remote:<22} ║ 10) OctoEverywhere │ {self.octoeverywhere_local:<22}{self.octoeverywhere_remote:<22}
║ ├───────────────┴───────────────╢ ║ ├───────────────┴───────────────╢
║ 11) System │ ║ 11) System │ {sysupgrades:^{padding}}
╟───────────────────────┴───────────────────────────────╢ ╟───────────────────────┴───────────────────────────────╢
""" """
)[1:] )[1:]
@@ -192,7 +210,8 @@ class UpdateMenu(BaseMenu):
if self._check_is_installed("octoeverywhere"): if self._check_is_installed("octoeverywhere"):
update_octoeverywhere() update_octoeverywhere()
def upgrade_system_packages(self, **kwargs) -> None: ... def upgrade_system_packages(self, **kwargs) -> None:
self._run_system_updates()
def _fetch_update_status(self) -> None: def _fetch_update_status(self) -> None:
self._set_status_data("klipper", get_klipper_status) self._set_status_data("klipper", get_klipper_status)
@@ -210,6 +229,10 @@ class UpdateMenu(BaseMenu):
self._set_status_data("crowsnest", get_crowsnest_status) self._set_status_data("crowsnest", get_crowsnest_status)
self._set_status_data("octoeverywhere", get_octoeverywhere_status) self._set_status_data("octoeverywhere", get_octoeverywhere_status)
update_system_package_lists(silent=True)
self.packages = get_upgradable_packages()
self.package_count = len(self.packages)
def _format_local_status(self, local_version, remote_version) -> str: def _format_local_status(self, local_version, remote_version) -> str:
color = COLOR_RED color = COLOR_RED
if not local_version: if not local_version:
@@ -246,3 +269,25 @@ class UpdateMenu(BaseMenu):
Logger.print_info(f"{name.capitalize()} is not installed! Skipped ...") Logger.print_info(f"{name.capitalize()} is not installed! Skipped ...")
return False return False
return True return True
def _run_system_updates(self) -> None:
if not self.packages:
Logger.print_info("No system upgrades available!")
return
try:
pkgs: str = ", ".join(self.packages)
Logger.print_dialog(
DialogType.CUSTOM,
["The following packages will be upgraded:", "\n\n", pkgs],
custom_title="UPGRADABLE SYSTEM UPDATES",
padding_top=0,
padding_bottom=0,
)
if not get_confirm("Continue?"):
return
Logger.print_status("Upgrading system packages ...")
upgrade_system_packages(self.packages)
except Exception as e:
Logger.print_error(f"Error upgrading system packages:\n{e}")
raise

View File

@@ -18,7 +18,7 @@ import time
import urllib.error import urllib.error
import urllib.request import urllib.request
from pathlib import Path from pathlib import Path
from subprocess import DEVNULL, PIPE, CalledProcessError, Popen, run from subprocess import DEVNULL, PIPE, CalledProcessError, Popen, check_output, run
from typing import List, Literal, Set from typing import List, Literal, Set
from utils.constants import SYSTEMD from utils.constants import SYSTEMD
@@ -219,6 +219,25 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
raise raise
def get_upgradable_packages() -> List[str]:
"""
Reads all system packages that can be upgraded.
:return: A list of package names available for upgrade
"""
try:
command = ["apt", "list", "--upgradable"]
output: str = check_output(command, stderr=DEVNULL, text=True, encoding="utf-8")
pkglist = []
for line in output.split("\n"):
if "/" not in line:
continue
pkg = line.split("/")[0]
pkglist.append(pkg)
return pkglist
except CalledProcessError as e:
raise Exception(f"Error reading upgradable packages: {e}")
def check_package_install(packages: Set[str]) -> List[str]: def check_package_install(packages: Set[str]) -> List[str]:
""" """
Checks the system for installed packages | Checks the system for installed packages |
@@ -252,12 +271,29 @@ def install_system_packages(packages: List[str]) -> None:
command.append(pkg) command.append(pkg)
run(command, stderr=PIPE, check=True) run(command, stderr=PIPE, check=True)
Logger.print_ok("Packages installed successfully.") Logger.print_ok("Packages successfully installed.")
except CalledProcessError as e: except CalledProcessError as e:
Logger.print_error(f"Error installing packages:\n{e.stderr.decode()}") Logger.print_error(f"Error installing packages:\n{e.stderr.decode()}")
raise raise
def upgrade_system_packages(packages: List[str]) -> None:
"""
Updates a list of system packages |
:param packages: List of system package names
:return: None
"""
try:
command = ["sudo", "apt-get", "upgrade", "-y"]
for pkg in packages:
command.append(pkg)
run(command, stderr=PIPE, check=True)
Logger.print_ok("Packages successfully upgraded.")
except CalledProcessError as e:
raise Exception(f"Error upgrading packages:\n{e.stderr.decode()}")
# this feels hacky and not quite right, but for now it works # this feels hacky and not quite right, but for now it works
# see: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib # see: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib
def get_ipv4_addr() -> str: def get_ipv4_addr() -> str: