all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup} v2 0/4] move upgrade checks to a common crate under proxmox-rs
@ 2025-11-24 10:34 Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox v2 1/1] upgrade-checks: fix meta package version check Shannon Sterz
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Shannon Sterz @ 2025-11-24 10:34 UTC (permalink / raw)
  To: pdm-devel

these patches move the largely identical upgrade checks between pbs and
pdm to a common crate in the proxmox repository. the crate there was
already applied, so this is a follow-up actually changing the
proxmox-backup and proxmox-datacenter-manager to use it and clean up
duplicate code.

Changelog
---------

changes since v1:

- the common crate parts of this series were already applied by Thomas
  Lamprecht, thanks!
- rebased on current master
- added a patch adapting the proxmox-upgrade-checks crate to
  `old_version` being optional now in proxmox-apt-api-types
- added a patch removing pbs2to3 from proxmox-backup as it is now unused

proxmox:

Shannon Sterz (1):
  upgrade-checks: fix meta package version check

 proxmox-upgrade-checks/src/lib.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)


proxmox-backup:

Shannon Sterz (2):
  pbs3to4: move pbs3to4 to common proxmox-upgrade-checks crate
  pbs2to3: remove now unused script

 Cargo.toml         |   3 +
 src/bin/pbs2to3.rs | 672 -----------------------------------------
 src/bin/pbs3to4.rs | 737 ++-------------------------------------------
 3 files changed, 20 insertions(+), 1392 deletions(-)
 delete mode 100644 src/bin/pbs2to3.rs


proxmox-datacenter-manager:

Shannon Sterz (1):
  upgrade-checks: use common proxmox-upgrade-checks crate

 Cargo.toml                            |   4 +-
 debian/control                        |   1 +
 lib/proxmox-upgrade-checks/Cargo.toml |  19 -
 lib/proxmox-upgrade-checks/src/lib.rs | 847 --------------------------
 4 files changed, 3 insertions(+), 868 deletions(-)
 delete mode 100644 lib/proxmox-upgrade-checks/Cargo.toml
 delete mode 100644 lib/proxmox-upgrade-checks/src/lib.rs


Summary over all repositories:
  8 files changed, 25 insertions(+), 2262 deletions(-)

--
Generated by git-murpp 0.8.1


_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pdm-devel] [PATCH proxmox v2 1/1] upgrade-checks: fix meta package version check
  2025-11-24 10:34 [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup} v2 0/4] move upgrade checks to a common crate under proxmox-rs Shannon Sterz
@ 2025-11-24 10:34 ` Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox-backup v2 1/2] pbs3to4: move pbs3to4 to common proxmox-upgrade-checks crate Shannon Sterz
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Shannon Sterz @ 2025-11-24 10:34 UTC (permalink / raw)
  To: pdm-devel

old_version is an optional value, so adapt to this new reality.

Originally-by: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 proxmox-upgrade-checks/src/lib.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/proxmox-upgrade-checks/src/lib.rs b/proxmox-upgrade-checks/src/lib.rs
index 4f76555a..f9b66876 100644
--- a/proxmox-upgrade-checks/src/lib.rs
+++ b/proxmox-upgrade-checks/src/lib.rs
@@ -202,9 +202,9 @@ impl UpgradeChecker {
             .iter()
             .find(|pkg| pkg.package.as_str() == self.meta_package_name);
 
-        if let Some(meta_pkg) = meta_pkg {
+        if let Some(old_version) = meta_pkg.and_then(|m| m.old_version.as_ref()) {
             let pkg_version = Regex::new(r"^(\d+)\.(\d+)[.-](\d+)")?;
-            let captures = pkg_version.captures(&meta_pkg.old_version);
+            let captures = pkg_version.captures(old_version);
             if let Some(captures) = captures {
                 let maj = Self::extract_version_from_captures(1, &captures)?;
                 let min = Self::extract_version_from_captures(2, &captures)?;
-- 
2.47.3



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pdm-devel] [PATCH proxmox-backup v2 1/2] pbs3to4: move pbs3to4 to common proxmox-upgrade-checks crate
  2025-11-24 10:34 [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup} v2 0/4] move upgrade checks to a common crate under proxmox-rs Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox v2 1/1] upgrade-checks: fix meta package version check Shannon Sterz
@ 2025-11-24 10:34 ` Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox-backup v2 2/2] pbs2to3: remove now unused script Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH datacenter-manager v2 1/1] upgrade-checks: use common proxmox-upgrade-checks crate Shannon Sterz
  3 siblings, 0 replies; 5+ messages in thread
From: Shannon Sterz @ 2025-11-24 10:34 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 Cargo.toml         |   3 +
 src/bin/pbs3to4.rs | 737 ++-------------------------------------------
 2 files changed, 20 insertions(+), 720 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 7316a07f..5a872cd4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -93,6 +93,7 @@ proxmox-sys = "1"
 proxmox-systemd = "1"
 proxmox-tfa = { version = "6.0.3", features = [ "api", "api-types" ] }
 proxmox-time = "2"
+proxmox-upgrade-checks = "1"
 proxmox-uuid = { version = "1", features = [ "serde" ] }
 proxmox-worker-task = "1"
 pbs-api-types = "1.0.7"
@@ -243,6 +244,7 @@ proxmox-sys = { workspace = true, features = [ "timer" ] }
 proxmox-systemd.workspace = true
 proxmox-tfa.workspace = true
 proxmox-time.workspace = true
+proxmox-upgrade-checks.workspace = true
 proxmox-uuid.workspace = true
 proxmox-worker-task.workspace = true
 pbs-api-types.workspace = true
@@ -308,6 +310,7 @@ proxmox-rrd-api-types.workspace = true
 #proxmox-systemd = { path = "../proxmox/proxmox-systemd" }
 #proxmox-tfa = { path = "../proxmox/proxmox-tfa" }
 #proxmox-time = { path = "../proxmox/proxmox-time" }
+#proxmox-upgrade-checks = { path = "../proxmox/proxmox-upgrade-checks" }
 #proxmox-uuid = { path = "../proxmox/proxmox-uuid" }
 #proxmox-worker-task = { path = "../proxmox/proxmox-worker-task" }
 
diff --git a/src/bin/pbs3to4.rs b/src/bin/pbs3to4.rs
index 8f0bfc0d..ec39d6bc 100644
--- a/src/bin/pbs3to4.rs
+++ b/src/bin/pbs3to4.rs
@@ -1,722 +1,19 @@
-use std::io::Write;
-use std::path::Path;
+use pbs_buildcfg::{APT_PKG_STATE_FN, PROXMOX_PKG_RELEASE, PROXMOX_PKG_VERSION};
+use proxmox_upgrade_checks::UpgradeCheckerBuilder;
 
-use anyhow::{format_err, Error};
-use const_format::concatcp;
-use regex::Regex;
-use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
-
-use proxmox_apt::repositories;
-use proxmox_apt_api_types::{APTRepositoryFile, APTRepositoryPackageType};
-use proxmox_backup::api2::node::apt;
-
-const OLD_SUITE: &str = "bookworm";
-const NEW_SUITE: &str = "trixie";
-const PROXMOX_BACKUP_META: &str = "proxmox-backup";
-const MIN_PBS_MAJOR: u8 = 3;
-const MIN_PBS_MINOR: u8 = 4;
-const MIN_PBS_PKGREL: u8 = 0;
-
-fn main() -> Result<(), Error> {
-    let mut checker = Checker::new();
-    checker.check_pbs_packages()?;
-    checker.check_misc()?;
-    checker.summary()?;
-    Ok(())
-}
-
-struct Checker {
-    output: ConsoleOutput,
-    upgraded: bool,
-}
-
-impl Checker {
-    pub fn new() -> Self {
-        Self {
-            output: ConsoleOutput::new(),
-            upgraded: false,
-        }
-    }
-
-    pub fn check_pbs_packages(&mut self) -> Result<(), Error> {
-        self.output
-            .print_header("CHECKING VERSION INFORMATION FOR PBS PACKAGES")?;
-
-        self.check_upgradable_packages()?;
-        let pkg_versions = apt::get_versions()?;
-        self.check_meta_package_version(&pkg_versions)?;
-        self.check_kernel_compat(&pkg_versions)?;
-        Ok(())
-    }
-
-    fn check_upgradable_packages(&mut self) -> Result<(), Error> {
-        self.output.log_info("Checking for package updates..")?;
-
-        let result = apt::apt_update_available();
-        match result {
-            Err(err) => {
-                self.output.log_warn(format!("{err}"))?;
-                self.output
-                    .log_fail("unable to retrieve list of package updates!")?;
-            }
-            Ok(package_status) => {
-                if package_status.is_empty() {
-                    self.output.log_pass("all packages up-to-date")?;
-                } else {
-                    let pkgs = package_status
-                        .iter()
-                        .map(|pkg| pkg.package.clone())
-                        .collect::<Vec<String>>()
-                        .join(", ");
-                    self.output.log_warn(format!(
-                        "updates for the following packages are available:\n      {pkgs}",
-                    ))?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    #[allow(clippy::absurd_extreme_comparisons)]
-    fn check_meta_package_version(
-        &mut self,
-        pkg_versions: &[pbs_api_types::APTUpdateInfo],
-    ) -> Result<(), Error> {
-        self.output
-            .log_info("Checking proxmox backup server package version..")?;
-
-        let pbs_meta_pkg = pkg_versions
-            .iter()
-            .find(|pkg| pkg.package.as_str() == PROXMOX_BACKUP_META);
-
-        if let Some(old_version) = pbs_meta_pkg.and_then(|m| m.old_version.as_ref()) {
-            let pkg_version = Regex::new(r"^(\d+)\.(\d+)[.-](\d+)")?;
-            let captures = pkg_version.captures(old_version);
-            if let Some(captures) = captures {
-                let maj = Self::extract_version_from_captures(1, &captures)?;
-                let min = Self::extract_version_from_captures(2, &captures)?;
-                let pkgrel = Self::extract_version_from_captures(3, &captures)?;
-
-                let min_version = format!("{MIN_PBS_MAJOR}.{MIN_PBS_MINOR}.{MIN_PBS_PKGREL}");
-
-                if maj > MIN_PBS_MAJOR {
-                    self.output
-                        .log_pass(format!("Already upgraded to Proxmox Backup Server {maj}"))?;
-                    self.upgraded = true;
-                } else if maj >= MIN_PBS_MAJOR && min >= MIN_PBS_MINOR && pkgrel >= MIN_PBS_PKGREL {
-                    self.output.log_pass(format!(
-                        "'{PROXMOX_BACKUP_META}' has version >= {min_version}"
-                    ))?;
-                } else {
-                    self.output.log_fail(format!(
-                        "'{PROXMOX_BACKUP_META}' package is too old, please upgrade to >= {min_version}"
-                    ))?;
-                }
-            } else {
-                self.output.log_fail(format!(
-                    "could not match the '{PROXMOX_BACKUP_META}' package version, \
-                    is it installed?",
-                ))?;
-            }
-        } else {
-            self.output
-                .log_fail(format!("'{PROXMOX_BACKUP_META}' package not found!"))?;
-        }
-        Ok(())
-    }
-
-    fn is_kernel_version_compatible(&self, running_version: &str) -> bool {
-        // TODO: rework this to parse out maj.min.patch and do numerical comparission and detect
-        // those with a "bpo12" as backport kernels from the older release.
-        const MINIMUM_RE: &str = r"6\.(?:14\.(?:[1-9]\d+|[6-9])|1[5-9])[^~]*";
-        const ARBITRARY_RE: &str = r"(?:1[4-9]|2\d+)\.(?:[0-9]|\d{2,})[^~]*-pve";
-
-        let re = if self.upgraded {
-            concatcp!(r"^(?:", MINIMUM_RE, r"|", ARBITRARY_RE, r")$")
-        } else {
-            r"^(?:6\.(?:2|5|8|11|14))"
-        };
-        let re = Regex::new(re).expect("failed to compile kernel compat regex");
-
-        re.is_match(running_version)
-    }
-
-    fn check_kernel_compat(
-        &mut self,
-        pkg_versions: &[pbs_api_types::APTUpdateInfo],
-    ) -> Result<(), Error> {
-        self.output.log_info("Check running kernel version..")?;
-
-        let kinstalled = if self.upgraded {
-            "proxmox-kernel-6.14"
-        } else {
-            "proxmox-kernel-6.8"
-        };
-
-        let output = std::process::Command::new("uname").arg("-r").output();
-        match output {
-            Err(_err) => self
-                .output
-                .log_fail("unable to determine running kernel version.")?,
-            Ok(ret) => {
-                let running_version = std::str::from_utf8(&ret.stdout[..ret.stdout.len() - 1])?;
-                if self.is_kernel_version_compatible(running_version) {
-                    if self.upgraded {
-                        self.output.log_pass(format!(
-                            "running new kernel '{running_version}' after upgrade."
-                        ))?;
-                    } else {
-                        self.output.log_pass(format!(
-                            "running kernel '{running_version}' is considered suitable for \
-                            upgrade."
-                        ))?;
-                    }
-                } else {
-                    let installed_kernel = pkg_versions
-                        .iter()
-                        .find(|pkg| pkg.package.as_str() == kinstalled);
-                    if installed_kernel.is_some() {
-                        self.output.log_warn(format!(
-                            "a suitable kernel '{kinstalled}' is installed, but an \
-                            unsuitable '{running_version}' is booted, missing reboot?!",
-                        ))?;
-                    } else {
-                        self.output.log_warn(format!(
-                            "unexpected running and installed kernel '{running_version}'.",
-                        ))?;
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn extract_version_from_captures(
-        index: usize,
-        captures: &regex::Captures,
-    ) -> Result<u8, Error> {
-        if let Some(capture) = captures.get(index) {
-            let val = capture.as_str().parse::<u8>()?;
-            Ok(val)
-        } else {
-            Ok(0)
-        }
-    }
-
-    fn check_bootloader(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking bootloader configuration...")?;
-
-        let sd_boot_installed =
-            Path::new("/usr/share/doc/systemd-boot/changelog.Debian.gz").is_file();
-
-        if !Path::new("/sys/firmware/efi").is_dir() {
-            if sd_boot_installed {
-                self.output.log_warn(
-                    "systemd-boot package installed on legacy-boot system is not \
-                    necessary, consider removing it",
-                )?;
-                return Ok(());
-            }
-            self.output
-                .log_skip("System booted in legacy-mode - no need for additional packages.")?;
-            return Ok(());
-        }
-
-        let mut boot_ok = true;
-        if Path::new("/etc/kernel/proxmox-boot-uuids").is_file() {
-            // PBS packages version check needs to be run before
-            if !self.upgraded {
-                let output = std::process::Command::new("proxmox-boot-tool")
-                    .arg("status")
-                    .output()
-                    .map_err(|err| {
-                        format_err!("failed to retrieve proxmox-boot-tool status - {err}")
-                    })?;
-                let re = Regex::new(r"configured with:.* (uefi|systemd-boot) \(versions:")
-                    .expect("failed to proxmox-boot-tool status");
-                if re.is_match(std::str::from_utf8(&output.stdout)?) {
-                    self.output
-                        .log_skip("not yet upgraded, systemd-boot still needed for bootctl")?;
-                    return Ok(());
-                }
-            }
-        } else {
-            if !Path::new("/usr/share/doc/grub-efi-amd64/changelog.Debian.gz").is_file() {
-                self.output.log_warn(
-                    "System booted in uefi mode but grub-efi-amd64 meta-package not installed, \
-                     new grub versions will not be installed to /boot/efi!
-                     Install grub-efi-amd64.",
-                )?;
-                boot_ok = false;
-            }
-            if Path::new("/boot/efi/EFI/BOOT/BOOTX64.efi").is_file() {
-                let output = std::process::Command::new("debconf-show")
-                    .arg("--db")
-                    .arg("configdb")
-                    .arg("grub-efi-amd64")
-                    .arg("grub-pc")
-                    .output()
-                    .map_err(|err| format_err!("failed to retrieve debconf settings - {err}"))?;
-                let re = Regex::new(r"grub2/force_efi_extra_removable: +true(?:\n|$)")
-                    .expect("failed to compile dbconfig regex");
-                if !re.is_match(std::str::from_utf8(&output.stdout)?) {
-                    self.output.log_warn("Removable bootloader found at '/boot/efi/EFI/BOOT/BOOTX64.efi', but GRUB packages \
-                        not set up to update it!\nRun the following command:\n\
-                        echo 'grub-efi-amd64 grub2/force_efi_extra_removable boolean true' | debconf-set-selections -v -u\n\
-                        Then reinstall GRUB with 'apt install --reinstall grub-efi-amd64'")?;
-                    boot_ok = false;
-                }
-            }
-        }
-        if sd_boot_installed {
-            self.output.log_fail(
-                "systemd-boot meta-package installed. This will cause problems on upgrades of other \
-                boot-related packages.\n\
-                Remove the 'systemd-boot' package.\n\
-                See https://pbs.proxmox.com/wiki/Upgrade_from_3_to_4#sd-boot-warning for more information."
-            )?;
-            boot_ok = false;
-        }
-        if boot_ok {
-            self.output
-                .log_pass("bootloader packages installed correctly")?;
-        }
-        Ok(())
-    }
-
-    fn check_apt_repos(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking for package repository suite mismatches..")?;
-
-        let mut strange_suite = false;
-        let mut mismatches = Vec::new();
-        let mut found_suite: Option<(String, String)> = None;
-
-        let (repo_files, _repo_errors, _digest) = repositories::repositories()?;
-        for repo_file in repo_files {
-            self.check_repo_file(
-                &mut found_suite,
-                &mut mismatches,
-                &mut strange_suite,
-                repo_file,
-            )?;
-        }
-
-        match (mismatches.is_empty(), strange_suite) {
-            (true, false) => self.output.log_pass("found no suite mismatch")?,
-            (true, true) => self
-                .output
-                .log_notice("found no suite mismatches, but found at least one strange suite")?,
-            (false, _) => {
-                let mut message = String::from(
-                    "Found mixed old and new packages repository suites, fix before upgrading!\
-                    \n      Mismatches:",
-                );
-                for (suite, location) in mismatches.iter() {
-                    message.push_str(
-                        format!("\n      found suite '{suite}' at '{location}'").as_str(),
-                    );
-                }
-                message.push('\n');
-                self.output.log_fail(message)?
-            }
-        }
-
-        Ok(())
-    }
-
-    fn check_dkms_modules(&mut self) -> Result<(), Error> {
-        let kver = std::process::Command::new("uname")
-            .arg("-r")
-            .output()
-            .map_err(|err| format_err!("failed to retrieve running kernel version - {err}"))?;
-
-        let output = std::process::Command::new("dkms")
-            .arg("status")
-            .arg("-k")
-            .arg(std::str::from_utf8(&kver.stdout)?)
-            .output();
-        match output {
-            Err(_err) => self.output.log_skip("could not get dkms status")?,
-            Ok(ret) => {
-                let num_dkms_modules = std::str::from_utf8(&ret.stdout)?.lines().count();
-                if num_dkms_modules == 0 {
-                    self.output.log_pass("no dkms modules found")?;
-                } else {
-                    self.output
-                        .log_warn("dkms modules found, this might cause issues during upgrade.")?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    pub fn check_misc(&mut self) -> Result<(), Error> {
-        self.output.print_header("MISCELLANEOUS CHECKS")?;
-        self.check_pbs_services()?;
-        self.check_time_sync()?;
-        self.check_apt_repos()?;
-        self.check_bootloader()?;
-        self.check_dkms_modules()?;
-        Ok(())
-    }
-
-    pub fn summary(&mut self) -> Result<(), Error> {
-        self.output.print_summary()
-    }
-
-    fn check_repo_file(
-        &mut self,
-        found_suite: &mut Option<(String, String)>,
-        mismatches: &mut Vec<(String, String)>,
-        strange_suite: &mut bool,
-        repo_file: APTRepositoryFile,
-    ) -> Result<(), Error> {
-        for repo in repo_file.repositories {
-            if !repo.enabled || repo.types == [APTRepositoryPackageType::DebSrc] {
-                continue;
-            }
-            for suite in &repo.suites {
-                let suite = match suite.find(&['-', '/'][..]) {
-                    Some(n) => &suite[0..n],
-                    None => suite,
-                };
-
-                if suite != OLD_SUITE && suite != NEW_SUITE {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    self.output.log_notice(format!(
-                        "found unusual suite '{suite}', neither old '{OLD_SUITE}' nor new \
-                            '{NEW_SUITE}'..\n        Affected file {location}\n        Please \
-                            assure this is shipping compatible packages for the upgrade!"
-                    ))?;
-                    *strange_suite = true;
-                    continue;
-                }
-
-                if let Some((ref current_suite, ref current_location)) = found_suite {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    if suite != current_suite {
-                        if mismatches.is_empty() {
-                            mismatches.push((current_suite.clone(), current_location.clone()));
-                            mismatches.push((suite.to_string(), location));
-                        } else {
-                            mismatches.push((suite.to_string(), location));
-                        }
-                    }
-                } else {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    *found_suite = Some((suite.to_string(), location));
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn get_systemd_unit_state(
-        &mut self,
-        unit: &str,
-    ) -> Result<(SystemdUnitState, SystemdUnitState), Error> {
-        let output = std::process::Command::new("systemctl")
-            .arg("is-enabled")
-            .arg(unit)
-            .output()
-            .map_err(|err| format_err!("failed to execute - {err}"))?;
-
-        let enabled_state = match output.stdout.as_slice() {
-            b"enabled\n" => SystemdUnitState::Enabled,
-            b"disabled\n" => SystemdUnitState::Disabled,
-            _ => SystemdUnitState::Unknown,
-        };
-
-        let output = std::process::Command::new("systemctl")
-            .arg("is-active")
-            .arg(unit)
-            .output()
-            .map_err(|err| format_err!("failed to execute - {err}"))?;
-
-        let active_state = match output.stdout.as_slice() {
-            b"active\n" => SystemdUnitState::Active,
-            b"inactive\n" => SystemdUnitState::Inactive,
-            b"failed\n" => SystemdUnitState::Failed,
-            _ => SystemdUnitState::Unknown,
-        };
-        Ok((enabled_state, active_state))
-    }
-
-    fn check_pbs_services(&mut self) -> Result<(), Error> {
-        self.output.log_info("Checking PBS daemon services..")?;
-
-        for service in ["proxmox-backup.service", "proxmox-backup-proxy.service"] {
-            match self.get_systemd_unit_state(service)? {
-                (_, SystemdUnitState::Active) => {
-                    self.output
-                        .log_pass(format!("systemd unit '{service}' is in state 'active'"))?;
-                }
-                (_, SystemdUnitState::Inactive) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is in state 'inactive'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-                (_, SystemdUnitState::Failed) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is in state 'failed'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-                (_, _) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is not in state 'active'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn check_time_sync(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking for supported & active NTP service..")?;
-        if self.get_systemd_unit_state("systemd-timesyncd.service")?.1 == SystemdUnitState::Active {
-            self.output.log_warn(
-                "systemd-timesyncd is not the best choice for time-keeping on servers, due to only \
-                applying updates on boot.\
-                \n       While not necessary for the upgrade it's recommended to use one of:\
-                \n        * chrony (Default in new Proxmox Backup Server installations)\
-                \n        * ntpsec\
-                \n        * openntpd"
-            )?;
-        } else if self.get_systemd_unit_state("ntp.service")?.1 == SystemdUnitState::Active {
-            self.output.log_info(
-                "Debian deprecated and removed the ntp package for Bookworm, but the system \
-                    will automatically migrate to the 'ntpsec' replacement package on upgrade.",
-            )?;
-        } else if self.get_systemd_unit_state("chrony.service")?.1 == SystemdUnitState::Active
-            || self.get_systemd_unit_state("openntpd.service")?.1 == SystemdUnitState::Active
-            || self.get_systemd_unit_state("ntpsec.service")?.1 == SystemdUnitState::Active
-        {
-            self.output
-                .log_pass("Detected active time synchronisation unit")?;
-        } else {
-            self.output.log_warn(
-                "No (active) time synchronisation daemon (NTP) detected, but synchronized systems \
-                are important!",
-            )?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(PartialEq)]
-enum SystemdUnitState {
-    Active,
-    Enabled,
-    Disabled,
-    Failed,
-    Inactive,
-    Unknown,
-}
-
-#[derive(Default)]
-struct Counters {
-    pass: u64,
-    skip: u64,
-    notice: u64,
-    warn: u64,
-    fail: u64,
-}
-
-enum LogLevel {
-    Pass,
-    Info,
-    Skip,
-    Notice,
-    Warn,
-    Fail,
-}
-
-struct ConsoleOutput {
-    stream: StandardStream,
-    first_header: bool,
-    counters: Counters,
-}
-
-impl ConsoleOutput {
-    pub fn new() -> Self {
-        Self {
-            stream: StandardStream::stdout(ColorChoice::Always),
-            first_header: true,
-            counters: Counters::default(),
-        }
-    }
-
-    pub fn print_header(&mut self, message: &str) -> Result<(), Error> {
-        if !self.first_header {
-            writeln!(&mut self.stream)?;
-        }
-        self.first_header = false;
-        writeln!(&mut self.stream, "= {message} =\n")?;
-        Ok(())
-    }
-
-    pub fn set_color(&mut self, color: Color, bold: bool) -> Result<(), Error> {
-        self.stream
-            .set_color(ColorSpec::new().set_fg(Some(color)).set_bold(bold))?;
-        Ok(())
-    }
-
-    pub fn reset(&mut self) -> Result<(), std::io::Error> {
-        self.stream.reset()
-    }
-
-    pub fn log_line(&mut self, level: LogLevel, message: &str) -> Result<(), Error> {
-        match level {
-            LogLevel::Pass => {
-                self.counters.pass += 1;
-                self.set_color(Color::Green, false)?;
-                writeln!(&mut self.stream, "PASS: {message}")?;
-            }
-            LogLevel::Info => {
-                writeln!(&mut self.stream, "INFO: {message}")?;
-            }
-            LogLevel::Skip => {
-                self.counters.skip += 1;
-                writeln!(&mut self.stream, "SKIP: {message}")?;
-            }
-            LogLevel::Notice => {
-                self.counters.notice += 1;
-                self.set_color(Color::White, true)?;
-                writeln!(&mut self.stream, "NOTICE: {message}")?;
-            }
-            LogLevel::Warn => {
-                self.counters.warn += 1;
-                self.set_color(Color::Yellow, false)?;
-                writeln!(&mut self.stream, "WARN: {message}")?;
-            }
-            LogLevel::Fail => {
-                self.counters.fail += 1;
-                self.set_color(Color::Red, true)?;
-                writeln!(&mut self.stream, "FAIL: {message}")?;
-            }
-        }
-        self.reset()?;
-        Ok(())
-    }
-
-    pub fn log_pass<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Pass, message.as_ref())
-    }
-
-    pub fn log_info<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Info, message.as_ref())
-    }
-
-    pub fn log_skip<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Skip, message.as_ref())
-    }
-
-    pub fn log_notice<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Notice, message.as_ref())
-    }
-
-    pub fn log_warn<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Warn, message.as_ref())
-    }
-
-    pub fn log_fail<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Fail, message.as_ref())
-    }
-
-    pub fn print_summary(&mut self) -> Result<(), Error> {
-        self.print_header("SUMMARY")?;
-
-        let total = self.counters.fail
-            + self.counters.pass
-            + self.counters.notice
-            + self.counters.skip
-            + self.counters.warn;
-
-        writeln!(&mut self.stream, "TOTAL:     {total}")?;
-        self.set_color(Color::Green, false)?;
-        writeln!(&mut self.stream, "PASSED:    {}", self.counters.pass)?;
-        self.reset()?;
-        writeln!(&mut self.stream, "SKIPPED:   {}", self.counters.skip)?;
-        writeln!(&mut self.stream, "NOTICE:    {}", self.counters.notice)?;
-        if self.counters.warn > 0 {
-            self.set_color(Color::Yellow, false)?;
-            writeln!(&mut self.stream, "WARNINGS:  {}", self.counters.warn)?;
-        }
-        if self.counters.fail > 0 {
-            self.set_color(Color::Red, true)?;
-            writeln!(&mut self.stream, "FAILURES:  {}", self.counters.fail)?;
-        }
-        if self.counters.warn > 0 || self.counters.fail > 0 {
-            let (color, bold) = if self.counters.fail > 0 {
-                (Color::Red, true)
-            } else {
-                (Color::Yellow, false)
-            };
-
-            self.set_color(color, bold)?;
-            writeln!(
-                &mut self.stream,
-                "\nATTENTION: Please check the output for detailed information!",
-            )?;
-            if self.counters.fail > 0 {
-                writeln!(
-                    &mut self.stream,
-                    "Try to solve the problems one at a time and rerun this checklist tool again.",
-                )?;
-            }
-        }
-        self.reset()?;
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn test_is_kernel_version_compatible(
-        expected_versions: &[&str],
-        unexpected_versions: &[&str],
-        upgraded: bool,
-    ) {
-        let checker = Checker {
-            output: ConsoleOutput::new(),
-            upgraded,
-        };
-
-        for version in expected_versions {
-            assert!(
-                checker.is_kernel_version_compatible(version),
-                "compatible kernel version '{version}' did not pass as expected!"
-            );
-        }
-        for version in unexpected_versions {
-            assert!(
-                !checker.is_kernel_version_compatible(version),
-                "incompatible kernel version '{version}' passed as expected!"
-            );
-        }
-    }
-
-    #[test]
-    fn test_before_upgrade_kernel_version_compatibility() {
-        let expected_versions = &["6.2.16-20-pve", "6.5.13-6-pve", "6.8.12-1-pve"];
-        let unexpected_versions = &["6.1.10-1-pve", "5.19.17-2-pve"];
-
-        test_is_kernel_version_compatible(expected_versions, unexpected_versions, false);
-    }
-
-    #[test]
-    fn test_after_upgrade_kernel_version_compatibility() {
-        let expected_versions = &["6.14.6-1-pve", "6.17.0-1-pve"];
-        let unexpected_versions = &["6.12.1-1-pve", "6.2.1-1-pve"];
-
-        test_is_kernel_version_compatible(expected_versions, unexpected_versions, true);
-    }
+fn main() -> Result<(), anyhow::Error> {
+    UpgradeCheckerBuilder::new(
+        "bookworm",
+        "trixie",
+        "proxmox-backup",
+        3,
+        4,
+        0,
+        &format!("{PROXMOX_PKG_VERSION}.{PROXMOX_PKG_RELEASE}"),
+    )
+    .apt_state_file_location(APT_PKG_STATE_FN)
+    .add_service_to_checks("proxmox-backup.service")
+    .add_service_to_checks("proxmox-backup-proxy.service")
+    .build()
+    .run()
 }
-- 
2.47.3



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pdm-devel] [PATCH proxmox-backup v2 2/2] pbs2to3: remove now unused script
  2025-11-24 10:34 [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup} v2 0/4] move upgrade checks to a common crate under proxmox-rs Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox v2 1/1] upgrade-checks: fix meta package version check Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox-backup v2 1/2] pbs3to4: move pbs3to4 to common proxmox-upgrade-checks crate Shannon Sterz
@ 2025-11-24 10:34 ` Shannon Sterz
  2025-11-24 10:34 ` [pdm-devel] [PATCH datacenter-manager v2 1/1] upgrade-checks: use common proxmox-upgrade-checks crate Shannon Sterz
  3 siblings, 0 replies; 5+ messages in thread
From: Shannon Sterz @ 2025-11-24 10:34 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 src/bin/pbs2to3.rs | 672 ---------------------------------------------
 1 file changed, 672 deletions(-)
 delete mode 100644 src/bin/pbs2to3.rs

diff --git a/src/bin/pbs2to3.rs b/src/bin/pbs2to3.rs
deleted file mode 100644
index 6f9ad9d1..00000000
--- a/src/bin/pbs2to3.rs
+++ /dev/null
@@ -1,672 +0,0 @@
-use std::io::Write;
-use std::path::Path;
-
-use anyhow::{format_err, Error};
-use const_format::concatcp;
-use regex::Regex;
-use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
-
-use proxmox_apt::repositories;
-use proxmox_apt_api_types::{APTRepositoryFile, APTRepositoryPackageType};
-use proxmox_backup::api2::node::apt;
-
-const OLD_SUITE: &str = "bullseye";
-const NEW_SUITE: &str = "bookworm";
-const PROXMOX_BACKUP_META: &str = "proxmox-backup";
-const MIN_PBS_MAJOR: u8 = 2;
-const MIN_PBS_MINOR: u8 = 4;
-const MIN_PBS_PKGREL: u8 = 1;
-
-fn main() -> Result<(), Error> {
-    let mut checker = Checker::new();
-    checker.check_pbs_packages()?;
-    checker.check_misc()?;
-    checker.summary()?;
-    Ok(())
-}
-
-struct Checker {
-    output: ConsoleOutput,
-    upgraded: bool,
-}
-
-impl Checker {
-    pub fn new() -> Self {
-        Self {
-            output: ConsoleOutput::new(),
-            upgraded: false,
-        }
-    }
-
-    pub fn check_pbs_packages(&mut self) -> Result<(), Error> {
-        self.output
-            .print_header("CHECKING VERSION INFORMATION FOR PBS PACKAGES")?;
-
-        self.check_upgradable_packages()?;
-        let pkg_versions = apt::get_versions()?;
-        self.check_meta_package_version(&pkg_versions)?;
-        self.check_kernel_compat(&pkg_versions)?;
-        Ok(())
-    }
-
-    fn check_upgradable_packages(&mut self) -> Result<(), Error> {
-        self.output.log_info("Checking for package updates..")?;
-
-        let result = apt::apt_update_available();
-        match result {
-            Err(err) => {
-                self.output.log_warn(format!("{err}"))?;
-                self.output
-                    .log_fail("unable to retrieve list of package updates!")?;
-            }
-            Ok(package_status) => {
-                if package_status.is_empty() {
-                    self.output.log_pass("all packages up-to-date")?;
-                } else {
-                    let pkgs = package_status
-                        .iter()
-                        .map(|pkg| pkg.package.clone())
-                        .collect::<Vec<String>>()
-                        .join(", ");
-                    self.output.log_warn(format!(
-                        "updates for the following packages are available:\n      {pkgs}",
-                    ))?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn check_meta_package_version(
-        &mut self,
-        pkg_versions: &[pbs_api_types::APTUpdateInfo],
-    ) -> Result<(), Error> {
-        self.output
-            .log_info("Checking proxmox backup server package version..")?;
-
-        let pbs_meta_pkg = pkg_versions
-            .iter()
-            .find(|pkg| pkg.package.as_str() == PROXMOX_BACKUP_META);
-
-        if let Some(old_version) = pbs_meta_pkg.and_then(|m| m.old_version.as_ref()) {
-            let pkg_version = Regex::new(r"^(\d+)\.(\d+)[.-](\d+)")?;
-            let captures = pkg_version.captures(old_version);
-            if let Some(captures) = captures {
-                let maj = Self::extract_version_from_captures(1, &captures)?;
-                let min = Self::extract_version_from_captures(2, &captures)?;
-                let pkgrel = Self::extract_version_from_captures(3, &captures)?;
-
-                if maj > MIN_PBS_MAJOR {
-                    self.output
-                        .log_pass(format!("Already upgraded to Proxmox Backup Server {maj}"))?;
-                    self.upgraded = true;
-                } else if maj >= MIN_PBS_MAJOR && min >= MIN_PBS_MINOR && pkgrel >= MIN_PBS_PKGREL {
-                    self.output.log_pass(format!(
-                        "'{PROXMOX_BACKUP_META}' has version >= {MIN_PBS_MAJOR}.{MIN_PBS_MINOR}-{MIN_PBS_PKGREL}",
-                    ))?;
-                } else {
-                    self.output.log_fail(format!(
-                        "'{PROXMOX_BACKUP_META}' package is too old, please upgrade to >= {MIN_PBS_MAJOR}.{MIN_PBS_MINOR}-{MIN_PBS_PKGREL}",
-                    ))?;
-                }
-            } else {
-                self.output.log_fail(format!(
-                    "could not match the '{PROXMOX_BACKUP_META}' package version, \
-                    is it installed?",
-                ))?;
-            }
-        } else {
-            self.output
-                .log_fail(format!("'{PROXMOX_BACKUP_META}' package not found!"))?;
-        }
-        Ok(())
-    }
-
-    fn is_kernel_version_compatible(&self, running_version: &str) -> bool {
-        const MINIMUM_RE: &str = r"6\.(?:2\.(?:[2-9]\d+|1[6-8]|1\d\d+)|5)[^~]*";
-        const ARBITRARY_RE: &str = r"(?:[6-9]|\d{2,})\.(?:[2-9]|\d{2,})[^~]*-pve";
-
-        let re = if self.upgraded {
-            concatcp!(r"^(?:", MINIMUM_RE, r"|", ARBITRARY_RE, r")$")
-        } else {
-            r"^(?:5\.(?:13|15)|6\.2)"
-        };
-        let re = Regex::new(re).expect("failed to compile kernel compat regex");
-
-        re.is_match(running_version)
-    }
-
-    fn check_kernel_compat(
-        &mut self,
-        pkg_versions: &[pbs_api_types::APTUpdateInfo],
-    ) -> Result<(), Error> {
-        self.output.log_info("Check running kernel version..")?;
-
-        let kinstalled = if self.upgraded {
-            "proxmox-kernel-6.2"
-        } else {
-            "pve-kernel-5.15"
-        };
-
-        let output = std::process::Command::new("uname").arg("-r").output();
-        match output {
-            Err(_err) => self
-                .output
-                .log_fail("unable to determine running kernel version.")?,
-            Ok(ret) => {
-                let running_version = std::str::from_utf8(&ret.stdout[..ret.stdout.len() - 1])?;
-                if self.is_kernel_version_compatible(running_version) {
-                    if self.upgraded {
-                        self.output.log_pass(format!(
-                            "running new kernel '{running_version}' after upgrade."
-                        ))?;
-                    } else {
-                        self.output.log_pass(format!(
-                            "running kernel '{running_version}' is considered suitable for \
-                            upgrade."
-                        ))?;
-                    }
-                } else {
-                    let installed_kernel = pkg_versions
-                        .iter()
-                        .find(|pkg| pkg.package.as_str() == kinstalled);
-                    if installed_kernel.is_some() {
-                        self.output.log_warn(format!(
-                            "a suitable kernel '{kinstalled}' is installed, but an \
-                            unsuitable '{running_version}' is booted, missing reboot?!",
-                        ))?;
-                    } else {
-                        self.output.log_warn(format!(
-                            "unexpected running and installed kernel '{running_version}'.",
-                        ))?;
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn extract_version_from_captures(
-        index: usize,
-        captures: &regex::Captures,
-    ) -> Result<u8, Error> {
-        if let Some(capture) = captures.get(index) {
-            let val = capture.as_str().parse::<u8>()?;
-            Ok(val)
-        } else {
-            Ok(0)
-        }
-    }
-
-    fn check_bootloader(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking bootloader configuration...")?;
-
-        if !Path::new("/sys/firmware/efi").is_dir() {
-            self.output
-                .log_skip("System booted in legacy-mode - no need for systemd-boot")?;
-            return Ok(());
-        }
-
-        if Path::new("/etc/kernel/proxmox-boot-uuids").is_file() {
-            // PBS packages version check needs to be run before
-            if !self.upgraded {
-                self.output
-                    .log_skip("not yet upgraded, no need to check the presence of systemd-boot")?;
-                return Ok(());
-            }
-            if Path::new("/usr/share/doc/systemd-boot/changelog.Debian.gz").is_file() {
-                self.output
-                    .log_pass("bootloader packages installed correctly")?;
-                return Ok(());
-            }
-            self.output.log_warn(
-                "proxmox-boot-tool is used for bootloader configuration in uefi mode \
-                 but the separate systemd-boot package, is not installed.\n\
-                 initializing new ESPs will not work until the package is installed.",
-            )?;
-            return Ok(());
-        } else if !Path::new("/usr/share/doc/grub-efi-amd64/changelog.Debian.gz").is_file() {
-            self.output.log_warn(
-                "System booted in uefi mode but grub-efi-amd64 meta-package not installed, \
-             new grub versions will not be installed to /boot/efi!
-             Install grub-efi-amd64.",
-            )?;
-            return Ok(());
-        } else {
-            self.output
-                .log_pass("bootloader packages installed correctly")?;
-        }
-
-        Ok(())
-    }
-
-    fn check_apt_repos(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking for package repository suite mismatches..")?;
-
-        let mut strange_suite = false;
-        let mut mismatches = Vec::new();
-        let mut found_suite: Option<(String, String)> = None;
-
-        let (repo_files, _repo_errors, _digest) = repositories::repositories()?;
-        for repo_file in repo_files {
-            self.check_repo_file(
-                &mut found_suite,
-                &mut mismatches,
-                &mut strange_suite,
-                repo_file,
-            )?;
-        }
-
-        match (mismatches.is_empty(), strange_suite) {
-            (true, false) => self.output.log_pass("found no suite mismatch")?,
-            (true, true) => self
-                .output
-                .log_notice("found no suite mismatches, but found at least one strange suite")?,
-            (false, _) => {
-                let mut message = String::from(
-                    "Found mixed old and new packages repository suites, fix before upgrading!\
-                    \n      Mismatches:",
-                );
-                for (suite, location) in mismatches.iter() {
-                    message.push_str(
-                        format!("\n      found suite '{suite}' at '{location}'").as_str(),
-                    );
-                }
-                message.push('\n');
-                self.output.log_fail(message)?
-            }
-        }
-
-        Ok(())
-    }
-
-    fn check_dkms_modules(&mut self) -> Result<(), Error> {
-        let kver = std::process::Command::new("uname")
-            .arg("-r")
-            .output()
-            .map_err(|err| format_err!("failed to retrieve running kernel version - {err}"))?;
-
-        let output = std::process::Command::new("dkms")
-            .arg("status")
-            .arg("-k")
-            .arg(std::str::from_utf8(&kver.stdout)?)
-            .output();
-        match output {
-            Err(_err) => self.output.log_skip("could not get dkms status")?,
-            Ok(ret) => {
-                let num_dkms_modules = std::str::from_utf8(&ret.stdout)?.lines().count();
-                if num_dkms_modules == 0 {
-                    self.output.log_pass("no dkms modules found")?;
-                } else {
-                    self.output
-                        .log_warn("dkms modules found, this might cause issues during upgrade.")?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    pub fn check_misc(&mut self) -> Result<(), Error> {
-        self.output.print_header("MISCELLANEOUS CHECKS")?;
-        self.check_pbs_services()?;
-        self.check_time_sync()?;
-        self.check_apt_repos()?;
-        self.check_bootloader()?;
-        self.check_dkms_modules()?;
-        Ok(())
-    }
-
-    pub fn summary(&mut self) -> Result<(), Error> {
-        self.output.print_summary()
-    }
-
-    fn check_repo_file(
-        &mut self,
-        found_suite: &mut Option<(String, String)>,
-        mismatches: &mut Vec<(String, String)>,
-        strange_suite: &mut bool,
-        repo_file: APTRepositoryFile,
-    ) -> Result<(), Error> {
-        for repo in repo_file.repositories {
-            if !repo.enabled || repo.types == [APTRepositoryPackageType::DebSrc] {
-                continue;
-            }
-            for suite in &repo.suites {
-                let suite = match suite.find(&['-', '/'][..]) {
-                    Some(n) => &suite[0..n],
-                    None => suite,
-                };
-
-                if suite != OLD_SUITE && suite != NEW_SUITE {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    self.output.log_notice(format!(
-                        "found unusual suite '{suite}', neither old '{OLD_SUITE}' nor new \
-                            '{NEW_SUITE}'..\n        Affected file {location}\n        Please \
-                            assure this is shipping compatible packages for the upgrade!"
-                    ))?;
-                    *strange_suite = true;
-                    continue;
-                }
-
-                if let Some((ref current_suite, ref current_location)) = found_suite {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    if suite != current_suite {
-                        if mismatches.is_empty() {
-                            mismatches.push((current_suite.clone(), current_location.clone()));
-                            mismatches.push((suite.to_string(), location));
-                        } else {
-                            mismatches.push((suite.to_string(), location));
-                        }
-                    }
-                } else {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    *found_suite = Some((suite.to_string(), location));
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn get_systemd_unit_state(
-        &mut self,
-        unit: &str,
-    ) -> Result<(SystemdUnitState, SystemdUnitState), Error> {
-        let output = std::process::Command::new("systemctl")
-            .arg("is-enabled")
-            .arg(unit)
-            .output()
-            .map_err(|err| format_err!("failed to execute - {err}"))?;
-
-        let enabled_state = match output.stdout.as_slice() {
-            b"enabled\n" => SystemdUnitState::Enabled,
-            b"disabled\n" => SystemdUnitState::Disabled,
-            _ => SystemdUnitState::Unknown,
-        };
-
-        let output = std::process::Command::new("systemctl")
-            .arg("is-active")
-            .arg(unit)
-            .output()
-            .map_err(|err| format_err!("failed to execute - {err}"))?;
-
-        let active_state = match output.stdout.as_slice() {
-            b"active\n" => SystemdUnitState::Active,
-            b"inactive\n" => SystemdUnitState::Inactive,
-            b"failed\n" => SystemdUnitState::Failed,
-            _ => SystemdUnitState::Unknown,
-        };
-        Ok((enabled_state, active_state))
-    }
-
-    fn check_pbs_services(&mut self) -> Result<(), Error> {
-        self.output.log_info("Checking PBS daemon services..")?;
-
-        for service in ["proxmox-backup.service", "proxmox-backup-proxy.service"] {
-            match self.get_systemd_unit_state(service)? {
-                (_, SystemdUnitState::Active) => {
-                    self.output
-                        .log_pass(format!("systemd unit '{service}' is in state 'active'"))?;
-                }
-                (_, SystemdUnitState::Inactive) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is in state 'inactive'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-                (_, SystemdUnitState::Failed) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is in state 'failed'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-                (_, _) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is not in state 'active'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn check_time_sync(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking for supported & active NTP service..")?;
-        if self.get_systemd_unit_state("systemd-timesyncd.service")?.1 == SystemdUnitState::Active {
-            self.output.log_warn(
-                "systemd-timesyncd is not the best choice for time-keeping on servers, due to only \
-                applying updates on boot.\
-                \n       While not necessary for the upgrade it's recommended to use one of:\
-                \n        * chrony (Default in new Proxmox Backup Server installations)\
-                \n        * ntpsec\
-                \n        * openntpd"
-            )?;
-        } else if self.get_systemd_unit_state("ntp.service")?.1 == SystemdUnitState::Active {
-            self.output.log_info(
-                "Debian deprecated and removed the ntp package for Bookworm, but the system \
-                    will automatically migrate to the 'ntpsec' replacement package on upgrade.",
-            )?;
-        } else if self.get_systemd_unit_state("chrony.service")?.1 == SystemdUnitState::Active
-            || self.get_systemd_unit_state("openntpd.service")?.1 == SystemdUnitState::Active
-            || self.get_systemd_unit_state("ntpsec.service")?.1 == SystemdUnitState::Active
-        {
-            self.output
-                .log_pass("Detected active time synchronisation unit")?;
-        } else {
-            self.output.log_warn(
-                "No (active) time synchronisation daemon (NTP) detected, but synchronized systems \
-                are important!",
-            )?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(PartialEq)]
-enum SystemdUnitState {
-    Active,
-    Enabled,
-    Disabled,
-    Failed,
-    Inactive,
-    Unknown,
-}
-
-#[derive(Default)]
-struct Counters {
-    pass: u64,
-    skip: u64,
-    notice: u64,
-    warn: u64,
-    fail: u64,
-}
-
-enum LogLevel {
-    Pass,
-    Info,
-    Skip,
-    Notice,
-    Warn,
-    Fail,
-}
-
-struct ConsoleOutput {
-    stream: StandardStream,
-    first_header: bool,
-    counters: Counters,
-}
-
-impl ConsoleOutput {
-    pub fn new() -> Self {
-        Self {
-            stream: StandardStream::stdout(ColorChoice::Always),
-            first_header: true,
-            counters: Counters::default(),
-        }
-    }
-
-    pub fn print_header(&mut self, message: &str) -> Result<(), Error> {
-        if !self.first_header {
-            writeln!(&mut self.stream)?;
-        }
-        self.first_header = false;
-        writeln!(&mut self.stream, "= {message} =\n")?;
-        Ok(())
-    }
-
-    pub fn set_color(&mut self, color: Color, bold: bool) -> Result<(), Error> {
-        self.stream
-            .set_color(ColorSpec::new().set_fg(Some(color)).set_bold(bold))?;
-        Ok(())
-    }
-
-    pub fn reset(&mut self) -> Result<(), std::io::Error> {
-        self.stream.reset()
-    }
-
-    pub fn log_line(&mut self, level: LogLevel, message: &str) -> Result<(), Error> {
-        match level {
-            LogLevel::Pass => {
-                self.counters.pass += 1;
-                self.set_color(Color::Green, false)?;
-                writeln!(&mut self.stream, "PASS: {message}")?;
-            }
-            LogLevel::Info => {
-                writeln!(&mut self.stream, "INFO: {message}")?;
-            }
-            LogLevel::Skip => {
-                self.counters.skip += 1;
-                writeln!(&mut self.stream, "SKIP: {message}")?;
-            }
-            LogLevel::Notice => {
-                self.counters.notice += 1;
-                self.set_color(Color::White, true)?;
-                writeln!(&mut self.stream, "NOTICE: {message}")?;
-            }
-            LogLevel::Warn => {
-                self.counters.warn += 1;
-                self.set_color(Color::Yellow, false)?;
-                writeln!(&mut self.stream, "WARN: {message}")?;
-            }
-            LogLevel::Fail => {
-                self.counters.fail += 1;
-                self.set_color(Color::Red, true)?;
-                writeln!(&mut self.stream, "FAIL: {message}")?;
-            }
-        }
-        self.reset()?;
-        Ok(())
-    }
-
-    pub fn log_pass<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Pass, message.as_ref())
-    }
-
-    pub fn log_info<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Info, message.as_ref())
-    }
-
-    pub fn log_skip<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Skip, message.as_ref())
-    }
-
-    pub fn log_notice<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Notice, message.as_ref())
-    }
-
-    pub fn log_warn<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Warn, message.as_ref())
-    }
-
-    pub fn log_fail<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Fail, message.as_ref())
-    }
-
-    pub fn print_summary(&mut self) -> Result<(), Error> {
-        self.print_header("SUMMARY")?;
-
-        let total = self.counters.fail
-            + self.counters.pass
-            + self.counters.notice
-            + self.counters.skip
-            + self.counters.warn;
-
-        writeln!(&mut self.stream, "TOTAL:     {total}")?;
-        self.set_color(Color::Green, false)?;
-        writeln!(&mut self.stream, "PASSED:    {}", self.counters.pass)?;
-        self.reset()?;
-        writeln!(&mut self.stream, "SKIPPED:   {}", self.counters.skip)?;
-        writeln!(&mut self.stream, "NOTICE:    {}", self.counters.notice)?;
-        if self.counters.warn > 0 {
-            self.set_color(Color::Yellow, false)?;
-            writeln!(&mut self.stream, "WARNINGS:  {}", self.counters.warn)?;
-        }
-        if self.counters.fail > 0 {
-            self.set_color(Color::Red, true)?;
-            writeln!(&mut self.stream, "FAILURES:  {}", self.counters.fail)?;
-        }
-        if self.counters.warn > 0 || self.counters.fail > 0 {
-            let (color, bold) = if self.counters.fail > 0 {
-                (Color::Red, true)
-            } else {
-                (Color::Yellow, false)
-            };
-
-            self.set_color(color, bold)?;
-            writeln!(
-                &mut self.stream,
-                "\nATTENTION: Please check the output for detailed information!",
-            )?;
-            if self.counters.fail > 0 {
-                writeln!(
-                    &mut self.stream,
-                    "Try to solve the problems one at a time and rerun this checklist tool again.",
-                )?;
-            }
-        }
-        self.reset()?;
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn test_is_kernel_version_compatible(
-        expected_versions: &[&str],
-        unexpected_versions: &[&str],
-        upgraded: bool,
-    ) {
-        let checker = Checker {
-            output: ConsoleOutput::new(),
-            upgraded,
-        };
-
-        for version in expected_versions {
-            assert!(checker.is_kernel_version_compatible(version));
-        }
-        for version in unexpected_versions {
-            assert!(!checker.is_kernel_version_compatible(version));
-        }
-    }
-
-    #[test]
-    fn test_is_pve_kernel_version_compatible() {
-        let expected_versions = &["5.13.19-6-pve", "5.15.158-2-pve", "6.2.16-5-pve"];
-        let unexpected_versions = &["6.1.10-1-pve", "5.19.17-2-pve"];
-
-        test_is_kernel_version_compatible(expected_versions, unexpected_versions, false);
-    }
-
-    #[test]
-    fn test_is_proxmox_kernel_version_compatible() {
-        let expected_versions = &["6.2.16-20-pve", "6.5.13-6-pve", "6.8.12-1-pve"];
-        let unexpected_versions = &["5.13.19-6-pve", "6.1.15-1-pve"];
-
-        test_is_kernel_version_compatible(expected_versions, unexpected_versions, true);
-    }
-}
-- 
2.47.3



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pdm-devel] [PATCH datacenter-manager v2 1/1] upgrade-checks: use common proxmox-upgrade-checks crate
  2025-11-24 10:34 [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup} v2 0/4] move upgrade checks to a common crate under proxmox-rs Shannon Sterz
                   ` (2 preceding siblings ...)
  2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox-backup v2 2/2] pbs2to3: remove now unused script Shannon Sterz
@ 2025-11-24 10:34 ` Shannon Sterz
  3 siblings, 0 replies; 5+ messages in thread
From: Shannon Sterz @ 2025-11-24 10:34 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 Cargo.toml                            |   4 +-
 debian/control                        |   1 +
 lib/proxmox-upgrade-checks/Cargo.toml |  19 -
 lib/proxmox-upgrade-checks/src/lib.rs | 847 --------------------------
 4 files changed, 3 insertions(+), 868 deletions(-)
 delete mode 100644 lib/proxmox-upgrade-checks/Cargo.toml
 delete mode 100644 lib/proxmox-upgrade-checks/src/lib.rs

diff --git a/Cargo.toml b/Cargo.toml
index 6ae8f69..0b82b15 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,7 +21,6 @@ members = [
     "lib/pdm-config",
     "lib/pdm-search",
     "lib/pdm-ui-shared",
-    "lib/proxmox-upgrade-checks",
 
     "cli/client",
     "cli/admin",
@@ -64,6 +63,7 @@ proxmox-sys = "1"
 proxmox-systemd = "1"
 proxmox-tfa = { version = "6", features = [ "api-types" ], default-features = false }
 proxmox-time = "2"
+proxmox-upgrade-checks = "1"
 proxmox-uuid = "1"
 
 # other proxmox crates
@@ -94,7 +94,6 @@ pdm-client = { version = "0.9", path = "lib/pdm-client" }
 pdm-search = { version = "0.2", path = "lib/pdm-search" }
 pdm-ui-shared = { version = "0.9", path = "lib/pdm-ui-shared" }
 proxmox-fido2 = { path = "cli/proxmox-fido2" }
-proxmox-upgrade-checks = { path = "lib/proxmox-upgrade-checks" }
 
 # regular crates
 anyhow = "1.0"
@@ -184,5 +183,6 @@ zstd = { version = "0.13" }
 # proxmox-tfa = { path = "../proxmox/proxmox-tfa" }
 # proxmox-time-api = { path = "../proxmox/proxmox-time-api" }
 # proxmox-time = { path = "../proxmox/proxmox-time" }
+# proxmox-upgrade-checks = { path = "../proxmox/proxmox-upgrade-checks" }
 # proxmox-uuid = { path = "../proxmox/proxmox-uuid" }
 # proxmox-worker-task = { path = "../proxmox/proxmox-worker-task" }
diff --git a/debian/control b/debian/control
index 65700ae..b8955bc 100644
--- a/debian/control
+++ b/debian/control
@@ -119,6 +119,7 @@ Build-Depends: cargo:native,
                librust-tokio-1+time-dev (>= 1.6-~~),
                librust-tokio-stream-0.1+default-dev,
                librust-tracing-0.1+default-dev,
+               librust-upgrade-checks-1+default-dev,
                librust-url-2+default-dev (>= 2.1-~~),
                librust-webauthn-rs-core-0.5+default-dev,
                librust-xdg-2+default-dev (>= 2.2-~~),
diff --git a/lib/proxmox-upgrade-checks/Cargo.toml b/lib/proxmox-upgrade-checks/Cargo.toml
deleted file mode 100644
index 574582e..0000000
--- a/lib/proxmox-upgrade-checks/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[package]
-name = "proxmox-upgrade-checks"
-description = "Helpers to implement upgrade checks for Proxmox products."
-version = "0.1.0"
-
-authors.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-exclude.workspace = true
-
-[dependencies]
-anyhow.workspace = true
-const_format.workspace = true
-regex.workspace = true
-termcolor.workspace = true
-
-proxmox-apt = { workspace = true, features = [ "cache" ] }
-proxmox-apt-api-types.workspace = true
diff --git a/lib/proxmox-upgrade-checks/src/lib.rs b/lib/proxmox-upgrade-checks/src/lib.rs
deleted file mode 100644
index c1c0dd9..0000000
--- a/lib/proxmox-upgrade-checks/src/lib.rs
+++ /dev/null
@@ -1,847 +0,0 @@
-use std::path::Path;
-use std::{io::Write, path::PathBuf};
-
-use anyhow::{format_err, Error};
-use const_format::concatcp;
-use regex::Regex;
-use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
-
-use proxmox_apt::repositories;
-use proxmox_apt_api_types::{APTRepositoryFile, APTRepositoryPackageType};
-
-/// Easily create and configure an upgrade checker for Proxmox products.
-pub struct UpgradeCheckerBuilder {
-    old_suite: String,
-    new_suite: String,
-    meta_package_name: String,
-    minimum_major_version: u8,
-    minimum_minor_version: u8,
-    minimum_pkgrel: u8,
-    apt_state_file: Option<PathBuf>,
-    api_server_package: Option<String>,
-    running_api_server_version: String,
-    services_list: Vec<String>,
-}
-
-impl UpgradeCheckerBuilder {
-    /// Create a new UpgradeCheckerBuilder
-    ///
-    /// * `old_suite`: The Debian suite before the upgrade.
-    /// * `new_suite`: The Debian suite after the upgrade.
-    /// * `meta_package_name`: The name of the product's meta package.
-    /// * `minimum_major_version`: The minimum major version before the upgrade.
-    /// * `minimum_minor_version`: The minimum minor version before the upgrade.
-    /// * `minimum_pkgrel`: The minimum package release before the upgrade.
-    /// * `running_api_server_version`: The currently running API server version.
-    pub fn new(
-        old_suite: &str,
-        new_suite: &str,
-        meta_package_name: &str,
-        minimum_major_version: u8,
-        minimum_minor_version: u8,
-        minimum_pkgrel: u8,
-        running_api_server_version: &str,
-    ) -> UpgradeCheckerBuilder {
-        UpgradeCheckerBuilder {
-            old_suite: old_suite.into(),
-            new_suite: new_suite.into(),
-            meta_package_name: meta_package_name.into(),
-            minimum_major_version,
-            minimum_minor_version,
-            minimum_pkgrel,
-            apt_state_file: None,
-            api_server_package: None,
-            running_api_server_version: running_api_server_version.into(),
-            services_list: Vec::new(),
-        }
-    }
-
-    /// Builder-style method to set the location of the APT state file.
-    pub fn apt_state_file_location<P: AsRef<Path>>(mut self, path: P) -> Self {
-        self.apt_state_file = Some(path.as_ref().into());
-        self
-    }
-
-    /// Builder-style method to set the API server package name.
-    pub fn api_server_package(mut self, api_server_package: &str) -> Self {
-        self.api_server_package = Some(api_server_package.into());
-        self
-    }
-
-    /// Add a service to the list of services that will be checked.
-    pub fn add_service_to_checks(mut self, service_name: &str) -> Self {
-        self.services_list.push(service_name.into());
-        self
-    }
-
-    /// Construct the UpgradeChecker, consumes the UpgradeCheckerBuilder
-    pub fn build(mut self) -> UpgradeChecker {
-        UpgradeChecker {
-            output: ConsoleOutput::new(),
-            upgraded: false,
-            old_suite: self.old_suite,
-            new_suite: self.new_suite,
-            minimum_major_version: self.minimum_major_version,
-            minimum_minor_version: self.minimum_minor_version,
-            minimum_pkgrel: self.minimum_pkgrel,
-            apt_state_file: self.apt_state_file.take().unwrap_or_else(|| {
-                PathBuf::from(format!(
-                    "/var/lib/{}/pkg-state.json",
-                    &self.meta_package_name
-                ))
-            }),
-            api_server_package: self
-                .api_server_package
-                .unwrap_or_else(|| self.meta_package_name.clone()),
-            running_api_server_version: self.running_api_server_version,
-            meta_package_name: self.meta_package_name,
-            services_list: self.services_list,
-        }
-    }
-}
-
-/// Helpers to easily construct a set of upgrade checks.
-pub struct UpgradeChecker {
-    output: ConsoleOutput,
-    upgraded: bool,
-    old_suite: String,
-    new_suite: String,
-    meta_package_name: String,
-    minimum_major_version: u8,
-    minimum_minor_version: u8,
-    minimum_pkgrel: u8,
-    apt_state_file: PathBuf,
-    api_server_package: String,
-    running_api_server_version: String,
-    services_list: Vec<String>,
-}
-
-impl UpgradeChecker {
-    /// Run all checks.
-    pub fn run(&mut self) -> Result<(), Error> {
-        self.check_packages()?;
-        self.check_misc()?;
-        self.summary()
-    }
-
-    /// Run miscellaneous checks.
-    pub fn check_misc(&mut self) -> Result<(), Error> {
-        self.output.print_header("MISCELLANEOUS CHECKS")?;
-        self.check_services()?;
-        self.check_time_sync()?;
-        self.check_apt_repos()?;
-        self.check_bootloader()?;
-        self.check_dkms_modules()?;
-        Ok(())
-    }
-
-    /// Print a summary of all checks run so far.
-    pub fn summary(&mut self) -> Result<(), Error> {
-        self.output.print_summary()
-    }
-
-    /// Run all package related checks.
-    pub fn check_packages(&mut self) -> Result<(), Error> {
-        self.output.print_header(&format!(
-            "CHECKING VERSION INFORMATION FOR {} PACKAGES",
-            self.meta_package_name.to_uppercase()
-        ))?;
-
-        self.check_upgradable_packages()?;
-
-        let pkg_versions = proxmox_apt::get_package_versions(
-            &self.meta_package_name,
-            &self.api_server_package,
-            &self.running_api_server_version,
-            &[],
-        )?;
-
-        self.check_meta_package_version(&pkg_versions)?;
-        self.check_kernel_compat(&pkg_versions)?;
-        Ok(())
-    }
-
-    fn check_upgradable_packages(&mut self) -> Result<(), Error> {
-        self.output.log_info("Checking for package updates..")?;
-
-        let result = proxmox_apt::list_available_apt_update(&self.apt_state_file);
-        match result {
-            Err(err) => {
-                self.output.log_warn(format!("{err}"))?;
-                self.output
-                    .log_fail("unable to retrieve list of package updates!")?;
-            }
-            Ok(package_status) => {
-                if package_status.is_empty() {
-                    self.output.log_pass("all packages up-to-date")?;
-                } else {
-                    let pkgs = package_status
-                        .iter()
-                        .map(|pkg| pkg.package.clone())
-                        .collect::<Vec<String>>()
-                        .join(", ");
-                    self.output.log_warn(format!(
-                        "updates for the following packages are available:\n      {pkgs}",
-                    ))?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn check_meta_package_version(
-        &mut self,
-        pkg_versions: &[proxmox_apt_api_types::APTUpdateInfo],
-    ) -> Result<(), Error> {
-        self.output.log_info(format!(
-            "Checking {} package version..",
-            self.meta_package_name
-        ))?;
-
-        let meta_pkg = pkg_versions
-            .iter()
-            .find(|pkg| pkg.package.as_str() == self.meta_package_name);
-
-        if let Some(old_version) = meta_pkg.as_ref().and_then(|m| m.old_version.as_ref()) {
-            let pkg_version = Regex::new(r"^(\d+)\.(\d+)[.-](\d+)")?;
-            let captures = pkg_version.captures(old_version);
-            if let Some(captures) = captures {
-                let maj = Self::extract_version_from_captures(1, &captures)?;
-                let min = Self::extract_version_from_captures(2, &captures)?;
-                let pkgrel = Self::extract_version_from_captures(3, &captures)?;
-
-                let min_version = format!(
-                    "{}.{}.{}",
-                    self.minimum_major_version, self.minimum_minor_version, self.minimum_pkgrel
-                );
-
-                if (maj > self.minimum_major_version && self.minimum_major_version != 0)
-                    // Handle alpha and beta version upgrade checks:
-                    || (self.minimum_major_version == 0 && min > self.minimum_minor_version)
-                {
-                    self.output
-                        .log_pass(format!("Already upgraded to {maj}.{min}"))?;
-                    self.upgraded = true;
-                } else if maj >= self.minimum_major_version
-                    && min >= self.minimum_minor_version
-                    && pkgrel >= self.minimum_pkgrel
-                {
-                    self.output.log_pass(format!(
-                        "'{}' has version >= {min_version}",
-                        self.meta_package_name
-                    ))?;
-                } else {
-                    self.output.log_fail(format!(
-                        "'{}' package is too old, please upgrade to >= {min_version}",
-                        self.meta_package_name
-                    ))?;
-                }
-            } else {
-                self.output.log_fail(format!(
-                    "could not match the '{}' package version, \
-                    is it installed?",
-                    self.meta_package_name
-                ))?;
-            }
-        } else {
-            self.output
-                .log_fail(format!("'{}' package not found!", self.meta_package_name))?;
-        }
-        Ok(())
-    }
-
-    fn is_kernel_version_compatible(&self, running_version: &str) -> bool {
-        // TODO: rework this to parse out maj.min.patch and do numerical comparison and detect
-        // those with a "bpo12" as backport kernels from the older release.
-        const MINIMUM_RE: &str = r"6\.(?:14\.(?:[1-9]\d+|[6-9])|1[5-9])[^~]*";
-        const ARBITRARY_RE: &str = r"(?:1[4-9]|2\d+)\.(?:[0-9]|\d{2,})[^~]*-pve";
-
-        let re = if self.upgraded {
-            concatcp!(r"^(?:", MINIMUM_RE, r"|", ARBITRARY_RE, r")$")
-        } else {
-            r"^(?:6\.(?:2|5|8|11|14))"
-        };
-        let re = Regex::new(re).expect("failed to compile kernel compat regex");
-
-        re.is_match(running_version)
-    }
-
-    fn check_kernel_compat(
-        &mut self,
-        pkg_versions: &[proxmox_apt_api_types::APTUpdateInfo],
-    ) -> Result<(), Error> {
-        self.output.log_info("Check running kernel version..")?;
-
-        let kinstalled = if self.upgraded {
-            "proxmox-kernel-6.14"
-        } else {
-            "proxmox-kernel-6.8"
-        };
-
-        let output = std::process::Command::new("uname").arg("-r").output();
-        match output {
-            Err(_err) => self
-                .output
-                .log_fail("unable to determine running kernel version.")?,
-            Ok(ret) => {
-                let running_version = std::str::from_utf8(&ret.stdout[..ret.stdout.len() - 1])?;
-                if self.is_kernel_version_compatible(running_version) {
-                    if self.upgraded {
-                        self.output.log_pass(format!(
-                            "running new kernel '{running_version}' after upgrade."
-                        ))?;
-                    } else {
-                        self.output.log_pass(format!(
-                            "running kernel '{running_version}' is considered suitable for \
-                            upgrade."
-                        ))?;
-                    }
-                } else {
-                    let installed_kernel = pkg_versions
-                        .iter()
-                        .find(|pkg| pkg.package.as_str() == kinstalled);
-                    if installed_kernel.is_some() {
-                        self.output.log_warn(format!(
-                            "a suitable kernel '{kinstalled}' is installed, but an \
-                            unsuitable '{running_version}' is booted, missing reboot?!",
-                        ))?;
-                    } else {
-                        self.output.log_warn(format!(
-                            "unexpected running and installed kernel '{running_version}'.",
-                        ))?;
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn extract_version_from_captures(
-        index: usize,
-        captures: &regex::Captures,
-    ) -> Result<u8, Error> {
-        if let Some(capture) = captures.get(index) {
-            let val = capture.as_str().parse::<u8>()?;
-            Ok(val)
-        } else {
-            Ok(0)
-        }
-    }
-
-    fn check_bootloader(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking bootloader configuration...")?;
-
-        let sd_boot_installed =
-            Path::new("/usr/share/doc/systemd-boot/changelog.Debian.gz").is_file();
-
-        if !Path::new("/sys/firmware/efi").is_dir() {
-            if sd_boot_installed {
-                self.output.log_warn(
-                    "systemd-boot package installed on legacy-boot system is not \
-                    necessary, consider removing it",
-                )?;
-                return Ok(());
-            }
-            self.output
-                .log_skip("System booted in legacy-mode - no need for additional packages.")?;
-            return Ok(());
-        }
-
-        let mut boot_ok = true;
-        if Path::new("/etc/kernel/proxmox-boot-uuids").is_file() {
-            // Package version check needs to be run before
-            if !self.upgraded {
-                let output = std::process::Command::new("proxmox-boot-tool")
-                    .arg("status")
-                    .output()
-                    .map_err(|err| {
-                        format_err!("failed to retrieve proxmox-boot-tool status - {err}")
-                    })?;
-                let re = Regex::new(r"configured with:.* (uefi|systemd-boot) \(versions:")
-                    .expect("failed to proxmox-boot-tool status");
-                if re.is_match(std::str::from_utf8(&output.stdout)?) {
-                    self.output
-                        .log_skip("not yet upgraded, systemd-boot still needed for bootctl")?;
-                    return Ok(());
-                }
-            }
-        } else {
-            if !Path::new("/usr/share/doc/grub-efi-amd64/changelog.Debian.gz").is_file() {
-                self.output.log_warn(
-                    "System booted in uefi mode but grub-efi-amd64 meta-package not installed, \
-                     new grub versions will not be installed to /boot/efi!
-                     Install grub-efi-amd64.",
-                )?;
-                boot_ok = false;
-            }
-            if Path::new("/boot/efi/EFI/BOOT/BOOTX64.efi").is_file() {
-                let output = std::process::Command::new("debconf-show")
-                    .arg("--db")
-                    .arg("configdb")
-                    .arg("grub-efi-amd64")
-                    .arg("grub-pc")
-                    .output()
-                    .map_err(|err| format_err!("failed to retrieve debconf settings - {err}"))?;
-                let re = Regex::new(r"grub2/force_efi_extra_removable: +true(?:\n|$)")
-                    .expect("failed to compile dbconfig regex");
-                if !re.is_match(std::str::from_utf8(&output.stdout)?) {
-                    self.output.log_warn(
-                        "Removable bootloader found at '/boot/efi/EFI/BOOT/BOOTX64.efi', but GRUB packages \
-                        not set up to update it!\nRun the following command:\n\
-                        echo 'grub-efi-amd64 grub2/force_efi_extra_removable boolean true' | debconf-set-selections -v -u\n\
-                        Then reinstall GRUB with 'apt install --reinstall grub-efi-amd64'"
-                    )?;
-                    boot_ok = false;
-                }
-            }
-        }
-        if sd_boot_installed {
-            self.output.log_fail(
-                "systemd-boot meta-package installed. This will cause problems on upgrades of other \
-                boot-related packages.\n\
-                Remove the 'systemd-boot' package.\n\
-                Please consult the upgrade guide for further information!"
-            )?;
-            boot_ok = false;
-        }
-        if boot_ok {
-            self.output
-                .log_pass("bootloader packages installed correctly")?;
-        }
-        Ok(())
-    }
-
-    fn check_apt_repos(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking for package repository suite mismatches..")?;
-
-        let mut strange_suite = false;
-        let mut mismatches = Vec::new();
-        let mut found_suite: Option<(String, String)> = None;
-
-        let (repo_files, _repo_errors, _digest) = repositories::repositories()?;
-        for repo_file in repo_files {
-            self.check_repo_file(
-                &mut found_suite,
-                &mut mismatches,
-                &mut strange_suite,
-                repo_file,
-            )?;
-        }
-
-        match (mismatches.is_empty(), strange_suite) {
-            (true, false) => self.output.log_pass("found no suite mismatch")?,
-            (true, true) => self
-                .output
-                .log_notice("found no suite mismatches, but found at least one strange suite")?,
-            (false, _) => {
-                let mut message = String::from(
-                    "Found mixed old and new packages repository suites, fix before upgrading!\
-                    \n      Mismatches:",
-                );
-                for (suite, location) in mismatches.iter() {
-                    message.push_str(
-                        format!("\n      found suite '{suite}' at '{location}'").as_str(),
-                    );
-                }
-                message.push('\n');
-                self.output.log_fail(message)?
-            }
-        }
-
-        Ok(())
-    }
-
-    fn check_dkms_modules(&mut self) -> Result<(), Error> {
-        let kver = std::process::Command::new("uname")
-            .arg("-r")
-            .output()
-            .map_err(|err| format_err!("failed to retrieve running kernel version - {err}"))?;
-
-        let output = std::process::Command::new("dkms")
-            .arg("status")
-            .arg("-k")
-            .arg(std::str::from_utf8(&kver.stdout)?)
-            .output();
-        match output {
-            Err(_err) => self.output.log_skip("could not get dkms status")?,
-            Ok(ret) => {
-                let num_dkms_modules = std::str::from_utf8(&ret.stdout)?.lines().count();
-                if num_dkms_modules == 0 {
-                    self.output.log_pass("no dkms modules found")?;
-                } else {
-                    self.output
-                        .log_warn("dkms modules found, this might cause issues during upgrade.")?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn check_repo_file(
-        &mut self,
-        found_suite: &mut Option<(String, String)>,
-        mismatches: &mut Vec<(String, String)>,
-        strange_suite: &mut bool,
-        repo_file: APTRepositoryFile,
-    ) -> Result<(), Error> {
-        for repo in repo_file.repositories {
-            if !repo.enabled || repo.types == [APTRepositoryPackageType::DebSrc] {
-                continue;
-            }
-            for suite in &repo.suites {
-                let suite = match suite.find(&['-', '/'][..]) {
-                    Some(n) => &suite[0..n],
-                    None => suite,
-                };
-
-                if suite != self.old_suite && suite != self.new_suite {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    self.output.log_notice(format!(
-                        "found unusual suite '{suite}', neither old '{}' nor new \
-                            '{}'..\n        Affected file {location}\n        Please \
-                            assure this is shipping compatible packages for the upgrade!",
-                        self.old_suite, self.new_suite
-                    ))?;
-                    *strange_suite = true;
-                    continue;
-                }
-
-                if let Some((ref current_suite, ref current_location)) = found_suite {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    if suite != current_suite {
-                        if mismatches.is_empty() {
-                            mismatches.push((current_suite.clone(), current_location.clone()));
-                            mismatches.push((suite.to_string(), location));
-                        } else {
-                            mismatches.push((suite.to_string(), location));
-                        }
-                    }
-                } else {
-                    let location = repo_file.path.clone().unwrap_or_default();
-                    *found_suite = Some((suite.to_string(), location));
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn get_systemd_unit_state(
-        &self,
-        unit: &str,
-    ) -> Result<(SystemdUnitState, SystemdUnitState), Error> {
-        let output = std::process::Command::new("systemctl")
-            .arg("is-enabled")
-            .arg(unit)
-            .output()
-            .map_err(|err| format_err!("failed to execute - {err}"))?;
-
-        let enabled_state = match output.stdout.as_slice() {
-            b"enabled\n" => SystemdUnitState::Enabled,
-            b"disabled\n" => SystemdUnitState::Disabled,
-            _ => SystemdUnitState::Unknown,
-        };
-
-        let output = std::process::Command::new("systemctl")
-            .arg("is-active")
-            .arg(unit)
-            .output()
-            .map_err(|err| format_err!("failed to execute - {err}"))?;
-
-        let active_state = match output.stdout.as_slice() {
-            b"active\n" => SystemdUnitState::Active,
-            b"inactive\n" => SystemdUnitState::Inactive,
-            b"failed\n" => SystemdUnitState::Failed,
-            _ => SystemdUnitState::Unknown,
-        };
-        Ok((enabled_state, active_state))
-    }
-
-    fn check_services(&mut self) -> Result<(), Error> {
-        self.output.log_info(format!(
-            "Checking {} daemon services..",
-            self.meta_package_name
-        ))?;
-
-        for service in self.services_list.as_slice() {
-            match self.get_systemd_unit_state(service)? {
-                (_, SystemdUnitState::Active) => {
-                    self.output
-                        .log_pass(format!("systemd unit '{service}' is in state 'active'"))?;
-                }
-                (_, SystemdUnitState::Inactive) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is in state 'inactive'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-                (_, SystemdUnitState::Failed) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is in state 'failed'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-                (_, _) => {
-                    self.output.log_fail(format!(
-                        "systemd unit '{service}' is not in state 'active'\
-                            \n    Please check the service for errors and start it.",
-                    ))?;
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn check_time_sync(&mut self) -> Result<(), Error> {
-        self.output
-            .log_info("Checking for supported & active NTP service..")?;
-        if self.get_systemd_unit_state("systemd-timesyncd.service")?.1 == SystemdUnitState::Active {
-            self.output.log_warn(
-                "systemd-timesyncd is not the best choice for time-keeping on servers, due to only \
-                applying updates on boot.\
-                \n       While not necessary for the upgrade it's recommended to use one of:\
-                \n        * chrony (Default in new Proxmox product installations)\
-                \n        * ntpsec\
-                \n        * openntpd"
-            )?;
-        } else if self.get_systemd_unit_state("ntp.service")?.1 == SystemdUnitState::Active {
-            self.output.log_info(
-                "Debian deprecated and removed the ntp package for Bookworm, but the system \
-                    will automatically migrate to the 'ntpsec' replacement package on upgrade.",
-            )?;
-        } else if self.get_systemd_unit_state("chrony.service")?.1 == SystemdUnitState::Active
-            || self.get_systemd_unit_state("openntpd.service")?.1 == SystemdUnitState::Active
-            || self.get_systemd_unit_state("ntpsec.service")?.1 == SystemdUnitState::Active
-        {
-            self.output
-                .log_pass("Detected active time synchronisation unit")?;
-        } else {
-            self.output.log_warn(
-                "No (active) time synchronisation daemon (NTP) detected, but synchronized systems \
-                are important!",
-            )?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(PartialEq)]
-enum SystemdUnitState {
-    Active,
-    Enabled,
-    Disabled,
-    Failed,
-    Inactive,
-    Unknown,
-}
-
-#[derive(Default)]
-struct Counters {
-    pass: u64,
-    skip: u64,
-    notice: u64,
-    warn: u64,
-    fail: u64,
-}
-
-enum LogLevel {
-    Pass,
-    Info,
-    Skip,
-    Notice,
-    Warn,
-    Fail,
-}
-
-struct ConsoleOutput {
-    stream: StandardStream,
-    first_header: bool,
-    counters: Counters,
-}
-
-impl ConsoleOutput {
-    fn new() -> Self {
-        Self {
-            stream: StandardStream::stdout(ColorChoice::Always),
-            first_header: true,
-            counters: Counters::default(),
-        }
-    }
-
-    fn print_header(&mut self, message: &str) -> Result<(), Error> {
-        if !self.first_header {
-            writeln!(&mut self.stream)?;
-        }
-        self.first_header = false;
-        writeln!(&mut self.stream, "= {message} =\n")?;
-        Ok(())
-    }
-
-    fn set_color(&mut self, color: Color, bold: bool) -> Result<(), Error> {
-        self.stream
-            .set_color(ColorSpec::new().set_fg(Some(color)).set_bold(bold))?;
-        Ok(())
-    }
-
-    fn reset(&mut self) -> Result<(), std::io::Error> {
-        self.stream.reset()
-    }
-
-    fn log_line(&mut self, level: LogLevel, message: &str) -> Result<(), Error> {
-        match level {
-            LogLevel::Pass => {
-                self.counters.pass += 1;
-                self.set_color(Color::Green, false)?;
-                writeln!(&mut self.stream, "PASS: {}", message)?;
-            }
-            LogLevel::Info => {
-                writeln!(&mut self.stream, "INFO: {}", message)?;
-            }
-            LogLevel::Skip => {
-                self.counters.skip += 1;
-                writeln!(&mut self.stream, "SKIP: {}", message)?;
-            }
-            LogLevel::Notice => {
-                self.counters.notice += 1;
-                self.set_color(Color::White, true)?;
-                writeln!(&mut self.stream, "NOTICE: {}", message)?;
-            }
-            LogLevel::Warn => {
-                self.counters.warn += 1;
-                self.set_color(Color::Yellow, false)?;
-                writeln!(&mut self.stream, "WARN: {}", message)?;
-            }
-            LogLevel::Fail => {
-                self.counters.fail += 1;
-                self.set_color(Color::Red, true)?;
-                writeln!(&mut self.stream, "FAIL: {}", message)?;
-            }
-        }
-        self.reset()?;
-        Ok(())
-    }
-
-    fn log_pass<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Pass, message.as_ref())
-    }
-
-    fn log_info<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Info, message.as_ref())
-    }
-
-    fn log_skip<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Skip, message.as_ref())
-    }
-
-    fn log_notice<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Notice, message.as_ref())
-    }
-
-    fn log_warn<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Warn, message.as_ref())
-    }
-
-    fn log_fail<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
-        self.log_line(LogLevel::Fail, message.as_ref())
-    }
-
-    fn print_summary(&mut self) -> Result<(), Error> {
-        self.print_header("SUMMARY")?;
-
-        let total = self.counters.fail
-            + self.counters.pass
-            + self.counters.notice
-            + self.counters.skip
-            + self.counters.warn;
-
-        writeln!(&mut self.stream, "TOTAL:     {total}")?;
-        self.set_color(Color::Green, false)?;
-        writeln!(&mut self.stream, "PASSED:    {}", self.counters.pass)?;
-        self.reset()?;
-        writeln!(&mut self.stream, "SKIPPED:   {}", self.counters.skip)?;
-        writeln!(&mut self.stream, "NOTICE:    {}", self.counters.notice)?;
-        if self.counters.warn > 0 {
-            self.set_color(Color::Yellow, false)?;
-            writeln!(&mut self.stream, "WARNINGS:  {}", self.counters.warn)?;
-        }
-        if self.counters.fail > 0 {
-            self.set_color(Color::Red, true)?;
-            writeln!(&mut self.stream, "FAILURES:  {}", self.counters.fail)?;
-        }
-        if self.counters.warn > 0 || self.counters.fail > 0 {
-            let (color, bold) = if self.counters.fail > 0 {
-                (Color::Red, true)
-            } else {
-                (Color::Yellow, false)
-            };
-
-            self.set_color(color, bold)?;
-            writeln!(
-                &mut self.stream,
-                "\nATTENTION: Please check the output for detailed information!",
-            )?;
-            if self.counters.fail > 0 {
-                writeln!(
-                    &mut self.stream,
-                    "Try to solve the problems one at a time and rerun this checklist tool again.",
-                )?;
-            }
-        }
-        self.reset()?;
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn test_is_kernel_version_compatible(
-        expected_versions: &[&str],
-        unexpected_versions: &[&str],
-        upgraded: bool,
-    ) {
-        let mut checker = UpgradeCheckerBuilder::new(
-            "bookworm",
-            "trixie",
-            "proxmox-backup",
-            3,
-            4,
-            0,
-            "running version: 3.4",
-        )
-        .build();
-
-        checker.upgraded = upgraded;
-
-        for version in expected_versions {
-            assert!(
-                checker.is_kernel_version_compatible(version),
-                "compatible kernel version '{version}' did not pass as expected!"
-            );
-        }
-        for version in unexpected_versions {
-            assert!(
-                !checker.is_kernel_version_compatible(version),
-                "incompatible kernel version '{version}' passed as expected!"
-            );
-        }
-    }
-
-    #[test]
-    fn test_before_upgrade_kernel_version_compatibility() {
-        let expected_versions = &["6.2.16-20-pve", "6.5.13-6-pve", "6.8.12-1-pve"];
-        let unexpected_versions = &["6.1.10-1-pve", "5.19.17-2-pve"];
-
-        test_is_kernel_version_compatible(expected_versions, unexpected_versions, false);
-    }
-
-    #[test]
-    fn test_after_upgrade_kernel_version_compatibility() {
-        let expected_versions = &["6.14.6-1-pve", "6.17.0-1-pve"];
-        let unexpected_versions = &["6.12.1-1-pve", "6.2.1-1-pve"];
-
-        test_is_kernel_version_compatible(expected_versions, unexpected_versions, true);
-    }
-}
-- 
2.47.3



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2025-11-24 10:35 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-11-24 10:34 [pdm-devel] [PATCH datacenter-manager/proxmox{, -backup} v2 0/4] move upgrade checks to a common crate under proxmox-rs Shannon Sterz
2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox v2 1/1] upgrade-checks: fix meta package version check Shannon Sterz
2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox-backup v2 1/2] pbs3to4: move pbs3to4 to common proxmox-upgrade-checks crate Shannon Sterz
2025-11-24 10:34 ` [pdm-devel] [PATCH proxmox-backup v2 2/2] pbs2to3: remove now unused script Shannon Sterz
2025-11-24 10:34 ` [pdm-devel] [PATCH datacenter-manager v2 1/1] upgrade-checks: use common proxmox-upgrade-checks crate Shannon Sterz

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal