From e338f4ce5ee310b87553eec5a475a45f6afe56f7 Mon Sep 17 00:00:00 2001 From: SandakovMM Date: Wed, 29 Jul 2026 15:16:33 +0300 Subject: [PATCH 1/3] Update cloudlinux leapp data to modern packages --- cloudlinux7to8/upgrader.py | 6 +++--- dist-upgrader | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudlinux7to8/upgrader.py b/cloudlinux7to8/upgrader.py index 16cc2fa..b169ad5 100644 --- a/cloudlinux7to8/upgrader.py +++ b/cloudlinux7to8/upgrader.py @@ -120,10 +120,10 @@ def construct_actions( [ "leapp-0.18.0-2.el7", "python2-leapp-0.18.0-2.el7", - "leapp-data-cloudlinux-0.3-8.el7.20240821", + "leapp-data-cloudlinux-0.3-9.el7.20240821", "leapp-deps-0.18.0-2.el7", - "leapp-upgrade-el7toel8-0.20.0-7.el7", - "leapp-upgrade-el7toel8-deps-0.20.0-7.el7", + "leapp-upgrade-el7toel8-0.20.0-9.el7", + "leapp-upgrade-el7toel8-deps-0.20.0-9.el7", ], elevate_repo_id="cloudlinux-elevate", remove_logs_on_finish=self.remove_leapp_logs diff --git a/dist-upgrader b/dist-upgrader index 7bf6120..ac30384 160000 --- a/dist-upgrader +++ b/dist-upgrader @@ -1 +1 @@ -Subproject commit 7bf6120eed07a56037293980e36700a20f7ec750 +Subproject commit ac30384fc3367810029d097db0c3557e7fc83c61 From 059b91dc82a8e2e21611444e54b023af895e5115 Mon Sep 17 00:00:00 2001 From: SandakovMM Date: Wed, 29 Jul 2026 19:22:53 +0300 Subject: [PATCH 2/3] Remove rhn packages when spacewalk plugin is not installed/enabled In this case trhn packages are conflicted with conversion process, so we have to remove them before conversion --- cloudlinux7to8/actions/packages.py | 124 +++++++++++++++++++++++++++++ cloudlinux7to8/upgrader.py | 1 + 2 files changed, 125 insertions(+) diff --git a/cloudlinux7to8/actions/packages.py b/cloudlinux7to8/actions/packages.py index 73ea1bb..1dc5da8 100644 --- a/cloudlinux7to8/actions/packages.py +++ b/cloudlinux7to8/actions/packages.py @@ -39,6 +39,130 @@ def estimate_revert_time(self) -> int: return 10 +class RemoveClnClientPackages(action.ActiveAction): + """Remove the CLN client stack when CLN is not the channel serving packages. + + ``cloudlinux-release`` 8.10-15 and newer declare ``Conflicts: rhn-client-tools < 2.11.5``, + and the only el8 build satisfying that bound ships inside the ``satellite-5-client`` + module. leapp enables that module only *after* it localinstalls the release package into + the target userspace, so the conflict is unsolvable at that point and + ``leapp preupgrade`` dies in ``target_userspace_creator``. + + Dropping the stack beforehand sidesteps the conflict, but only when CLN is not the + active package channel — on a system where it is, removing these packages would destroy + the package source the upgrade itself depends on. Hence the + ``_is_cln_package_channel_active()`` guard. + """ + + SYSTEMID_PATH = "/etc/sysconfig/rhn/systemid" + # Packages shipping the spacewalk-protocol DNF/YUM plugin. If none of them is installed + # the plugin cannot run, no matter what config files happen to be lying around. + # Except the case plugin was installed not by package, but the case seems too narrow + SPACEWALK_PLUGIN_PACKAGES = [ + "dnf-plugin-spacewalk", + "python3-dnf-plugin-spacewalk", + "yum-rhn-plugin", + ] + SPACEWALK_CONFIG_PATHS = [ + "/etc/dnf/plugins/spacewalk.conf", + "/etc/yum/pluginconf.d/spacewalk.conf", + ] + + removed_packages_file: str + cln_client_pkgs: typing.List[str] + + def __init__(self, temp_directory: str) -> None: + self.name = "removing unused CLN client packages" + self.removed_packages_file = temp_directory + "/cloudlinux7to8_removed_cln_packages.txt" + self.cln_client_pkgs = [ + "rhn-client-tools", + "rhn-check", + "rhn-setup", + ] + + @staticmethod + def _is_spacewalk_plugin_explicitly_disabled(config_path: str) -> bool: + try: + with open(config_path) as f: + for line in f: + stripped = line.strip().lower() + if not stripped or stripped.startswith("#") or stripped.startswith("["): + continue + if stripped.startswith("enabled") and "=" in stripped: + return stripped.split("=", 1)[1].strip() == "0" + except (OSError, IOError): + pass + return False + + def _is_cln_package_channel_active(self) -> bool: + """Return True when CLN is the channel actually serving packages to this system. + + Deliberately mirrors `is_cln_package_channel_active()` from cloudlinux leapp. + """ + if not os.path.exists(self.SYSTEMID_PATH): + return False + + if not rpm.filter_installed_packages(self.SPACEWALK_PLUGIN_PACKAGES): + return False + + existing_configs = [path for path in self.SPACEWALK_CONFIG_PATHS if os.path.exists(path)] + if not existing_configs: + return False + + return not any(self._is_spacewalk_plugin_explicitly_disabled(path) for path in existing_configs) + + def _is_required(self) -> bool: + if self._is_cln_package_channel_active(): + return False + + return len(rpm.filter_installed_packages(self.cln_client_pkgs)) > 0 + + def _prepare_action(self) -> action.ActionResult: + packages_to_remove = rpm.filter_installed_packages(self.cln_client_pkgs) + removed_packages = list(packages_to_remove) + rpm.remove_packages(packages_to_remove) + + # We need to re-install packages on revert, so we have to save it + with open(self.removed_packages_file, "a") as f: + f.write("\n".join(removed_packages) + "\n") + + return action.ActionResult() + + def _post_action(self) -> action.ActionResult: + # Nothing to restore. CloudLinux 8 keeps CLN registration but serves packages from + # the no-auth (SWNG) repositories, and rhn-client-tools >= 3.0.1 disables the + # spacewalk plugin to enforce that. If anything on the target system still needs the + # stack it arrives as an ordinary dependency of the upgrade transaction. + if os.path.exists(self.removed_packages_file): + os.unlink(self.removed_packages_file) + + return action.ActionResult() + + def _revert_action(self) -> action.ActionResult: + if not os.path.exists(self.removed_packages_file): + log.warn( + "File with the list of removed CLN client packages does not exist, " + "while the action itself was not skipped. Skip reinstalling packages." + ) + return action.ActionResult() + + # Reinstall only what we actually removed, so hosts that never carried part of the + # stack don't gain it on revert. + with open(self.removed_packages_file, "r") as f: + packages_to_install = sorted({line.strip() for line in f if line.strip()}) + + rpm.install_packages(packages_to_install) + + os.unlink(self.removed_packages_file) + return action.ActionResult() + + def estimate_prepare_time(self) -> int: + return 2 + + def estimate_revert_time(self) -> int: + return 10 + + class RemovePleskOutdatedPackages(action.ActiveAction): outdated_pkgs: typing.List[str] diff --git a/cloudlinux7to8/upgrader.py b/cloudlinux7to8/upgrader.py index b169ad5..2679626 100644 --- a/cloudlinux7to8/upgrader.py +++ b/cloudlinux7to8/upgrader.py @@ -195,6 +195,7 @@ def construct_actions( ], "Remove conflicting packages": [ custom_actions.RemovingPleskConflictPackages(), + custom_actions.RemoveClnClientPackages(options.state_dir), custom_actions.RemovePleskOutdatedPackages(), ], "Update databases": [ From 52f6ce079eab31799fbb9e15f9ca59209a9a7466 Mon Sep 17 00:00:00 2001 From: SandakovMM Date: Thu, 30 Jul 2026 11:39:53 +0300 Subject: [PATCH 3/3] Move to common RecreateAwstatsConfigurationFiles action --- cloudlinux7to8/actions/common.py | 36 -------------------------------- cloudlinux7to8/upgrader.py | 2 +- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/cloudlinux7to8/actions/common.py b/cloudlinux7to8/actions/common.py index 27839b2..d19faf7 100644 --- a/cloudlinux7to8/actions/common.py +++ b/cloudlinux7to8/actions/common.py @@ -145,39 +145,3 @@ def _post_action(self) -> action.ActionResult: def _revert_action(self) -> action.ActionResult: return action.ActionResult() - - -class RecreateAwstatsConfigurationFiles(action.ActiveAction): - def __init__(self) -> None: - self.name = "recreate AWStats configuration files for domains" - - def get_awstats_domains(self) -> typing.Set[str]: - domains_awstats_directory = "/usr/local/psa/etc/awstats/" - domains = set() - for awstats_config_file in os.listdir(domains_awstats_directory): - if awstats_config_file.startswith("awstats.") and awstats_config_file.endswith("-http.conf"): - domains.add(awstats_config_file.split("awstats.")[-1].rsplit("-http.conf")[0]) - return domains - - def _prepare_action(self) -> action.ActionResult: - return action.ActionResult() - - def _post_action(self) -> action.ActionResult: - rpm.handle_all_rpmnew_files("/etc/awstats") - - for domain in self.get_awstats_domains(): - log.info(f"Recreating AWStats configuration for domain: {domain}") - util.logged_check_call( - [ - "/usr/sbin/plesk", "sbin", "webstatmng", "--set-configs", - "--stat-prog", "awstats", "--domain-name", domain - ], stdin=subprocess.DEVNULL - ) - return action.ActionResult() - - def _revert_action(self) -> action.ActionResult: - return action.ActionResult() - - def estimate_post_time(self) -> int: - # Estimate 100 ms per configuration we have to recreate - return int(len(self.get_awstats_domains()) / 10) + 5 diff --git a/cloudlinux7to8/upgrader.py b/cloudlinux7to8/upgrader.py index 2679626..69c9f7d 100644 --- a/cloudlinux7to8/upgrader.py +++ b/cloudlinux7to8/upgrader.py @@ -156,7 +156,7 @@ def construct_actions( common_actions.SetMinDovecotDhParamSize(dhparam_size=2048), common_actions.RestoreDovecotConfiguration(options.state_dir), common_actions.RestoreRoundcubeConfiguration(options.state_dir), - custom_actions.RecreateAwstatsConfigurationFiles(), + common_actions.RecreateAwstatsConfigurationFiles(), common_actions.UninstallTuxcareEls(), common_actions.PreserveMariadbConfig(), common_actions.SubstituteSshPermitRootLoginConfigured(),