From d2d6bf296acd25a47340fe1281255dc237015110 Mon Sep 17 00:00:00 2001 From: Ali Mirjamali Date: Sun, 1 Jun 2025 01:38:36 +0330 Subject: [PATCH] Add Alpine Linux (APK) support to vmupdate related: https://github.com/QubesOS/qubes-issues/issues/7323 --- vmupdate/agent/entrypoint.py | 3 + vmupdate/agent/source/apk/__init__.py | 20 +++++++ vmupdate/agent/source/apk/apk_cli.py | 80 +++++++++++++++++++++++++++ vmupdate/agent/source/utils.py | 3 + 4 files changed, 106 insertions(+) create mode 100644 vmupdate/agent/source/apk/__init__.py create mode 100644 vmupdate/agent/source/apk/apk_cli.py diff --git a/vmupdate/agent/entrypoint.py b/vmupdate/agent/entrypoint.py index 3f5a0df5..6326ffab 100755 --- a/vmupdate/agent/entrypoint.py +++ b/vmupdate/agent/entrypoint.py @@ -101,6 +101,9 @@ def get_package_manager(os_data, log, log_handler, log_level, no_progress): elif os_data["os_family"] == "ArchLinux": from source.pacman.pacman_cli import PACMANCLI as PackageManager print(f"Progress reporting not supported.", flush=True) + elif os_data["os_family"] == "Alpine": + from source.apk.apk_cli import APKCLI as PackageManager + print(f"Progress reporting not supported.", flush=True) else: raise NotImplementedError( "Only Debian, RedHat and ArchLinux based OS is supported.") diff --git a/vmupdate/agent/source/apk/__init__.py b/vmupdate/agent/source/apk/__init__.py new file mode 100644 index 00000000..c8d3ba86 --- /dev/null +++ b/vmupdate/agent/source/apk/__init__.py @@ -0,0 +1,20 @@ +# coding=utf-8 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2022 Piotr Bartman +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. \ No newline at end of file diff --git a/vmupdate/agent/source/apk/apk_cli.py b/vmupdate/agent/source/apk/apk_cli.py new file mode 100644 index 00000000..970a0448 --- /dev/null +++ b/vmupdate/agent/source/apk/apk_cli.py @@ -0,0 +1,80 @@ +# coding=utf-8 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2022 Piotr Bartman +# Copyright (C) 2025 Ali Mirjamali +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +from typing import List, Dict + +from source.common.package_manager import PackageManager +from source.common.process_result import ProcessResult +from source.common.exit_codes import EXIT + + +class APKCLI(PackageManager): + def __init__(self, log_handler, log_level): + super().__init__(log_handler, log_level) + self.package_manager = "apk" + + def refresh(self, hard_fail: bool) -> ProcessResult: + """ + Use package manager to refresh available packages. + + :param hard_fail: raise error if some repo is unavailable + :return: (exit_code, stdout, stderr) + """ + cmd = [self.package_manager, "update"] + result = self.run_cmd(cmd) + if result.code != 0: + result.code = 1 + result.error_from_messages() + return result + + def get_packages(self) -> Dict[str, List[str]]: + """ + Use apk to return the installed packages and their versions. + """ + + cmd = [self.package_manager, "info", "-v"] + # EXAMPLE OUTPUT: + # qubes-vm-core-4.2.25-r1 + result = self.run_cmd(cmd, realtime=False) + + packages: Dict[str, List[str]] = {} + for line in result.out.splitlines(): + package, version, release = line.rsplit("-", maxsplit=2) + packages.setdefault(package, []).append(version + "-" + release) + + return packages + + def get_action(self, remove_obsolete) -> List[str]: + """ + APK will handle obsoletions itself + """ + return ["upgrade", "--force", "--no-interactive"] + + def clean(self) -> int: + """ + Clean cache files of package manager. + Should return 0 on success or EXIT.ERR_VM_CLEANUP otherwise. + """ + cmd = [self.package_manager, "cache", "clean", "-f", "--no-interactive"] + result = self.run_cmd(cmd, realtime=False) + return_code = EXIT.ERR_VM_CLEANUP if result.code != 0 else 0 + return return_code diff --git a/vmupdate/agent/source/utils.py b/vmupdate/agent/source/utils.py index d486954d..9fc31891 100644 --- a/vmupdate/agent/source/utils.py +++ b/vmupdate/agent/source/utils.py @@ -65,6 +65,9 @@ def get_os_data(logger: Optional = None) -> Dict[str, Any]: if 'arch' in family: data["os_family"] = 'ArchLinux' + if 'alpine' in family: + data["os_family"] = 'Alpine' + return data