From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id D013E1FF13E for ; Fri, 03 Apr 2026 18:57:10 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 95B5B8C65; Fri, 3 Apr 2026 18:57:41 +0200 (CEST) From: Christoph Heiss To: pdm-devel@lists.proxmox.com Subject: [PATCH installer v3 32/38] post-hook: switch to types in proxmox-installer-types Date: Fri, 3 Apr 2026 18:54:04 +0200 Message-ID: <20260403165437.2166551-33-c.heiss@proxmox.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260403165437.2166551-1-c.heiss@proxmox.com> References: <20260403165437.2166551-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1775235391151 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.068 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: BAJY2U4SRMTJ247YLX72THSR2DBTHM5W X-Message-ID-Hash: BAJY2U4SRMTJ247YLX72THSR2DBTHM5W X-MailFrom: c.heiss@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: No functional changes. Signed-off-by: Christoph Heiss --- Changes v2 -> v3: * new patch Cargo.toml | 2 +- proxmox-installer-common/src/dmi.rs | 43 ++ proxmox-installer-common/src/lib.rs | 1 + proxmox-post-hook/Cargo.toml | 2 +- proxmox-post-hook/src/main.rs | 682 +++++++++++----------------- 5 files changed, 313 insertions(+), 417 deletions(-) create mode 100644 proxmox-installer-common/src/dmi.rs diff --git a/Cargo.toml b/Cargo.toml index 9d95796..2466822 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ toml = "0.8" proxmox-auto-installer.path = "./proxmox-auto-installer" proxmox-installer-common.path = "./proxmox-installer-common" proxmox-network-types = "1.0" -proxmox-installer-types = "0.1" +proxmox-installer-types = { version = "0.1", features = ["legacy"] } # Local path overrides # NOTE: You must run `cargo update` after changing this for it to take effect! diff --git a/proxmox-installer-common/src/dmi.rs b/proxmox-installer-common/src/dmi.rs new file mode 100644 index 0000000..76ae4a5 --- /dev/null +++ b/proxmox-installer-common/src/dmi.rs @@ -0,0 +1,43 @@ +use std::{collections::HashMap, fs}; + +use anyhow::{Result, bail}; +use proxmox_installer_types::SystemDMI; + +const DMI_PATH: &str = "/sys/devices/virtual/dmi/id"; + +pub fn get() -> Result { + let system_files = [ + "product_serial", + "product_sku", + "product_uuid", + "product_name", + ]; + let baseboard_files = ["board_asset_tag", "board_serial", "board_name"]; + let chassis_files = ["chassis_serial", "chassis_sku", "chassis_asset_tag"]; + + Ok(SystemDMI { + system: get_dmi_infos_for(&system_files)?, + baseboard: get_dmi_infos_for(&baseboard_files)?, + chassis: get_dmi_infos_for(&chassis_files)?, + }) +} + +fn get_dmi_infos_for(files: &[&str]) -> Result> { + let mut res: HashMap = HashMap::new(); + + for file in files { + let path = format!("{DMI_PATH}/{file}"); + let content = match fs::read_to_string(&path) { + Err(ref err) if err.kind() == std::io::ErrorKind::NotFound => continue, + Err(ref err) if err.kind() == std::io::ErrorKind::PermissionDenied => { + bail!("Could not read data. Are you running as root or with sudo?") + } + Err(err) => bail!("Error: '{err}' on '{path}'"), + Ok(content) => content.trim().into(), + }; + let key = file.splitn(2, '_').last().unwrap(); + res.insert(key.into(), content); + } + + Ok(res) +} diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs index 7cdb1de..05445d5 100644 --- a/proxmox-installer-common/src/lib.rs +++ b/proxmox-installer-common/src/lib.rs @@ -1,4 +1,5 @@ pub mod disk_checks; +pub mod dmi; pub mod options; pub mod setup; pub mod sysinfo; diff --git a/proxmox-post-hook/Cargo.toml b/proxmox-post-hook/Cargo.toml index f0c344e..748b922 100644 --- a/proxmox-post-hook/Cargo.toml +++ b/proxmox-post-hook/Cargo.toml @@ -12,9 +12,9 @@ homepage = "https://www.proxmox.com" [dependencies] anyhow.workspace = true -proxmox-auto-installer.workspace = true proxmox-installer-common = { workspace = true, features = ["http"] } proxmox-network-types.workspace = true proxmox-installer-types.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +toml.workspace = true diff --git a/proxmox-post-hook/src/main.rs b/proxmox-post-hook/src/main.rs index a05b30f..9025c01 100644 --- a/proxmox-post-hook/src/main.rs +++ b/proxmox-post-hook/src/main.rs @@ -9,212 +9,49 @@ //! Relies on `proxmox-chroot` as an external dependency to (bind-)mount the //! previously installed system. -use anyhow::{Context, Result, anyhow, bail}; -use proxmox_auto_installer::{ - answer::{ - Answer, FqdnConfig, FqdnExtendedConfig, FqdnSourceMode, PostNotificationHookInfo, - RebootMode, - }, - udevinfo::{UdevInfo, UdevProperties}, -}; -use proxmox_installer_common::http::{self, header::HeaderMap}; -use proxmox_installer_common::{ - options::{Disk, NetworkOptions}, - setup::{ - BootType, InstallConfig, IsoInfo, ProxmoxProduct, RuntimeInfo, SetupInfo, - load_installer_setup_files, - }, - sysinfo::SystemDMI, -}; -use proxmox_installer_types::answer::FilesystemType; -use proxmox_network_types::ip_address::Cidr; -use serde::Serialize; +use anyhow::{Context, Result, bail}; +use proxmox_installer_types::answer::{AutoInstallerConfig, PostNotificationHookInfo}; use std::{ - collections::HashSet, - ffi::CStr, - fs::{self, File}, - io::BufReader, - os::unix::fs::FileExt, - path::PathBuf, + fs, + io::Read, process::{Command, ExitCode}, }; -/// Information about the system boot status. -#[derive(Serialize)] -struct BootInfo { - /// Whether the system is booted using UEFI or legacy BIOS. - mode: BootType, - /// Whether SecureBoot is enabled for the installation. - #[serde(skip_serializing_if = "bool_is_false")] - secureboot: bool, -} +use proxmox_installer_common::http::{self, header::HeaderMap}; -/// Holds all the public keys for the different algorithms available. -#[derive(Serialize)] -struct SshPublicHostKeys { - // ECDSA-based public host key - ecdsa: String, - // ED25519-based public host key - ed25519: String, - // RSA-based public host key - rsa: String, -} +/// Current version of the schema sent by this implementation. +const POST_HOOK_SCHEMA_VERSION: &str = "1.2"; -/// Holds information about a single disk in the system. -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -struct DiskInfo { - /// Size in bytes - size: usize, - /// Set to true if the disk is used for booting. - #[serde(skip_serializing_if = "bool_is_false")] - is_bootdisk: bool, - /// Properties about the device as given by udev. - udev_properties: UdevProperties, -} +mod detail { + use anyhow::{Context, Result, anyhow, bail}; + use std::{ + collections::HashSet, + ffi::CStr, + fs::{self, File}, + io::BufReader, + os::unix::fs::FileExt, + path::PathBuf, + process::Command, + }; -/// Holds information about the management network interface. -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -struct NetworkInterfaceInfo { - /// Name of the interface - name: String, - /// MAC address of the interface - mac: String, - /// (Designated) IP address of the interface - #[serde(skip_serializing_if = "Option::is_none")] - address: Option, - /// Set to true if the interface is the chosen management interface during - /// installation. - #[serde(skip_serializing_if = "bool_is_false")] - is_management: bool, - /// Set to true if the network interface name was pinned based on the MAC - /// address during the installation. - #[serde(skip_serializing_if = "bool_is_false")] - is_pinned: bool, - /// Properties about the device as given by udev. - udev_properties: UdevProperties, -} + use proxmox_installer_common::{ + options::{Disk, NetworkOptions}, + setup::{ + InstallConfig, ProxmoxProduct, RuntimeInfo, SetupInfo, load_installer_setup_files, + }, + }; + use proxmox_installer_types::{ + BootType, IsoInfo, UdevInfo, + answer::{AutoInstallerConfig, FqdnConfig, FqdnFromDhcpConfig, FqdnSourceMode}, + post_hook::{ + BootInfo, CpuInfo, DiskInfo, KernelVersionInformation, NetworkInterfaceInfo, + PostHookInfo, PostHookInfoSchema, ProductInfo, SshPublicHostKeys, + }, + }; -fn bool_is_false(value: &bool) -> bool { - !value -} + /// Defines the size of a gibibyte in bytes. + const SIZE_GIB: usize = 1024 * 1024 * 1024; -/// Information about the installed product itself. -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -struct ProductInfo { - /// Full name of the product - fullname: String, - /// Product abbreviation - short: ProxmoxProduct, - /// Version of the installed product - version: String, -} - -/// The current kernel version. -/// Aligns with the format as used by the `/nodes//status` API of each product. -#[derive(Serialize)] -struct KernelVersionInformation { - /// The systemname/nodename - pub sysname: String, - /// The kernel release number - pub release: String, - /// The kernel version - pub version: String, - /// The machine architecture - pub machine: String, -} - -/// Information about the CPU(s) installed in the system -#[derive(Serialize)] -struct CpuInfo { - /// Number of physical CPU cores. - cores: usize, - /// Number of logical CPU cores aka. threads. - cpus: usize, - /// CPU feature flag set as a space-delimited list. - flags: String, - /// Whether hardware-accelerated virtualization is supported. - hvm: bool, - /// Reported model of the CPU(s) - model: String, - /// Number of physical CPU sockets - sockets: usize, -} - -/// Metadata of the hook, such as schema version of the document. -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -struct PostHookInfoSchema { - /// major.minor version describing the schema version of this document, in a semanticy-version - /// way. - /// - /// major: Incremented for incompatible/breaking API changes, e.g. removing an existing - /// field. - /// minor: Incremented when adding functionality in a backwards-compatible matter, e.g. - /// adding a new field. - version: String, -} - -impl PostHookInfoSchema { - const SCHEMA_VERSION: &str = "1.2"; -} - -impl Default for PostHookInfoSchema { - fn default() -> Self { - Self { - version: Self::SCHEMA_VERSION.to_owned(), - } - } -} - -/// All data sent as request payload with the post-installation-webhook POST request. -/// -/// NOTE: The format is versioned through `schema.version` (`$schema.version` in the -/// resulting JSON), ensure you update it when this struct or any of its members gets modified. -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -struct PostHookInfo { - // This field is prefixed by `$` on purpose, to indicate that it is document metadata and not - // part of the actual content itself. (E.g. JSON Schema uses a similar naming scheme) - #[serde(rename = "$schema")] - schema: PostHookInfoSchema, - /// major.minor version of Debian as installed, retrieved from /etc/debian_version - debian_version: String, - /// PVE/PMG/PBS/PDM version as reported by `pveversion`, `pmgversion`, - /// `proxmox-backup-manager version` or `proxmox-datacenter-manager version`, respectively. - product: ProductInfo, - /// Release information for the ISO used for the installation. - iso: IsoInfo, - /// Installed kernel version - kernel_version: KernelVersionInformation, - /// Describes the boot mode of the machine and the SecureBoot status. - boot_info: BootInfo, - /// Information about the installed CPU(s) - cpu_info: CpuInfo, - /// DMI information about the system - dmi: SystemDMI, - /// Filesystem used for boot disk(s) - filesystem: FilesystemType, - /// Fully qualified domain name of the installed system - fqdn: String, - /// Unique systemd-id128 identifier of the installed system (128-bit, 16 bytes) - machine_id: String, - /// All disks detected on the system. - disks: Vec, - /// All network interfaces detected on the system. - network_interfaces: Vec, - /// Public parts of SSH host keys of the installed system - ssh_public_host_keys: SshPublicHostKeys, - /// Action to will be performed, i.e. either reboot or power off the machine. - reboot_mode: RebootMode, -} - -/// Defines the size of a gibibyte in bytes. -const SIZE_GIB: usize = 1024 * 1024 * 1024; - -impl PostHookInfo { /// Gathers all needed information about the newly installed system for sending /// it to a specified server. /// @@ -222,7 +59,7 @@ impl PostHookInfo { /// /// * `target_path` - Path to where the chroot environment root is mounted /// * `answer` - Answer file as provided by the user - fn gather(target_path: &str, answer: &Answer) -> Result { + pub fn gather(target_path: &str, answer: &AutoInstallerConfig) -> Result { println!("Gathering installed system data ..."); let config: InstallConfig = @@ -265,34 +102,42 @@ impl PostHookInfo { let fqdn = match &answer.global.fqdn { FqdnConfig::Simple(name) => name.to_string(), - FqdnConfig::Extended(FqdnExtendedConfig { + FqdnConfig::FromDhcp(FqdnFromDhcpConfig { source: FqdnSourceMode::FromDhcp, domain, }) => NetworkOptions::construct_fqdn( &run_env.network, - setup_info.config.product.default_hostname(), + &setup_info.config.product.to_string(), domain.as_deref(), ) .to_string(), }; - Ok(Self { - schema: PostHookInfoSchema::default(), + Ok(PostHookInfo { + schema: PostHookInfoSchema { + version: super::POST_HOOK_SCHEMA_VERSION.to_owned(), + }, debian_version: read_file("/etc/debian_version")?, - product: Self::gather_product_info(&setup_info, &run_cmd)?, - iso: setup_info.iso_info.clone(), - kernel_version: Self::gather_kernel_version(&run_cmd, &open_file)?, + product: gather_product_info(&setup_info, &run_cmd)?, + iso: IsoInfo { + release: setup_info.iso_info.release, + isorelease: setup_info.iso_info.isorelease, + }, + kernel_version: gather_kernel_version(&run_cmd, &open_file)?, boot_info: BootInfo { - mode: run_env.boot_type, + mode: match run_env.boot_type { + proxmox_installer_common::setup::BootType::Bios => BootType::Bios, + proxmox_installer_common::setup::BootType::Efi => BootType::Efi, + }, secureboot: run_env.secure_boot, }, - cpu_info: Self::gather_cpu_info(&run_env)?, - dmi: SystemDMI::get()?, - filesystem: answer.disks.fs_type, + cpu_info: gather_cpu_info(&run_env)?, + dmi: proxmox_installer_common::dmi::get()?, + filesystem: answer.disks.filesystem_details()?.to_type(), fqdn, machine_id: read_file("/etc/machine-id")?, - disks: Self::gather_disks(&config, &run_env, &udev)?, - network_interfaces: Self::gather_nic(&config, &run_env, &udev)?, + disks: gather_disks(&config, &run_env, &udev)?, + network_interfaces: gather_nic(&config, &run_env, &udev)?, ssh_public_host_keys: SshPublicHostKeys { ecdsa: read_file("/etc/ssh/ssh_host_ecdsa_key.pub")?, ed25519: read_file("/etc/ssh/ssh_host_ed25519_key.pub")?, @@ -335,10 +180,10 @@ impl PostHookInfo { .target_hd .as_ref() .map(|hd| *hd == disk.path) - .unwrap_or_default(); + .unwrap_or(false); anyhow::Ok(DiskInfo { - size: (config.hdsize * (SIZE_GIB as f64)) as usize, + size: (config.hdsize * (SIZE_GIB as f64)) as u64, is_bootdisk, udev_properties: get_udev_properties(disk)?, }) @@ -346,7 +191,7 @@ impl PostHookInfo { .collect() } else { // If the filesystem is not LVM-based (thus Btrfs or ZFS), `config.disk_selection` - // contains a list of indices identifiying the boot disks, as given by udev. + // contains a list of indices identifying the boot disks, as given by udev. let selected_disks_indices: Vec<&String> = config.disk_selection.values().collect(); run_env @@ -356,7 +201,7 @@ impl PostHookInfo { let is_bootdisk = selected_disks_indices.contains(&&disk.index); anyhow::Ok(DiskInfo { - size: (config.hdsize * (SIZE_GIB as f64)) as usize, + size: (config.hdsize * (SIZE_GIB as f64)) as u64, is_bootdisk, udev_properties: get_udev_properties(disk)?, }) @@ -443,7 +288,12 @@ impl PostHookInfo { Ok(ProductInfo { fullname: setup_info.config.fullname.clone(), - short: setup_info.config.product, + short: match setup_info.config.product { + ProxmoxProduct::PVE => proxmox_installer_types::ProxmoxProduct::Pve, + ProxmoxProduct::PBS => proxmox_installer_types::ProxmoxProduct::Pbs, + ProxmoxProduct::PMG => proxmox_installer_types::ProxmoxProduct::Pmg, + ProxmoxProduct::PDM => proxmox_installer_types::ProxmoxProduct::Pdm, + }, version, }) } @@ -465,7 +315,7 @@ impl PostHookInfo { run_cmd: &dyn Fn(&[&str]) -> Result, open_file: &dyn Fn(&str) -> Result, ) -> Result { - let file = open_file(&Self::find_kernel_image_path(run_cmd)?)?; + let file = open_file(&find_kernel_image_path(run_cmd)?)?; // Read the 2-byte `kernel_version` field at offset 0x20e [0] from the file .. // https://www.kernel.org/doc/html/latest/arch/x86/boot.html#the-real-mode-kernel-header @@ -525,7 +375,7 @@ impl PostHookInfo { run_cmd: &dyn Fn(&[&str]) -> Result, _open_file: &dyn Fn(&str) -> Result, ) -> Result { - let image_path = Self::find_kernel_image_path(run_cmd)?; + let image_path = find_kernel_image_path(run_cmd)?; let release = image_path .strip_prefix("/boot/vmlinuz-") @@ -556,7 +406,7 @@ impl PostHookInfo { /// /// * `run_cmd` - Callback to run a command inside the target chroot. fn find_kernel_image_path(run_cmd: &dyn Fn(&[&str]) -> Result) -> Result { - let pkg_name = Self::find_kernel_package_name(run_cmd)?; + let pkg_name = find_kernel_package_name(run_cmd)?; let all_files = run_cmd(&["dpkg-query", "--listfiles", &pkg_name])?; for file in all_files.lines() { @@ -663,6 +513,200 @@ impl PostHookInfo { Ok(result) } + + #[cfg(test)] + mod tests { + use super::{find_kernel_image_path, find_kernel_package_name}; + + #[test] + fn finds_correct_kernel_package_name() { + let mocked_run_cmd = |cmd: &[&str]| { + if cmd[0] == "dpkg" { + assert_eq!(cmd, &["dpkg", "--print-architecture"]); + Ok("amd64\n".to_owned()) + } else { + assert_eq!( + cmd, + &[ + "dpkg-query", + "--showformat", + "${db:Status-Abbrev}|${Architecture}|${Package}\\n", + "--show", + "proxmox-kernel-[0-9]*", + ] + ); + Ok(r#"ii |all|proxmox-kernel-6.8 +un ||proxmox-kernel-6.8.8-2-pve +ii |amd64|proxmox-kernel-6.8.8-2-pve-signed + "# + .to_owned()) + } + }; + + assert_eq!( + find_kernel_package_name(&mocked_run_cmd).unwrap(), + "proxmox-kernel-6.8.8-2-pve-signed" + ); + } + + #[test] + fn finds_correct_kernel_package_name_arm64() { + let mocked_run_cmd = |cmd: &[&str]| { + if cmd[0] == "dpkg" { + assert_eq!(cmd, &["dpkg", "--print-architecture"]); + Ok("arm64\n".to_owned()) + } else { + assert_eq!( + cmd, + &[ + "dpkg-query", + "--showformat", + "${db:Status-Abbrev}|${Architecture}|${Package}\\n", + "--show", + "proxmox-kernel-[0-9]*", + ] + ); + Ok(r#"ii |all|proxmox-kernel-6.17 +un ||proxmox-kernel-6.17.2-1-pve +ii |arm64|proxmox-kernel-6.17.2-1-pve-signed + "# + .to_owned()) + } + }; + + assert_eq!( + find_kernel_package_name(&mocked_run_cmd).unwrap(), + "proxmox-kernel-6.17.2-1-pve-signed" + ); + } + + #[test] + fn find_kernel_package_name_fails_on_wrong_architecture() { + let mocked_run_cmd = |cmd: &[&str]| { + if cmd[0] == "dpkg" { + assert_eq!(cmd, &["dpkg", "--print-architecture"]); + Ok("arm64\n".to_owned()) + } else { + assert_eq!( + cmd, + &[ + "dpkg-query", + "--showformat", + "${db:Status-Abbrev}|${Architecture}|${Package}\\n", + "--show", + "proxmox-kernel-[0-9]*", + ] + ); + Ok(r#"ii |all|proxmox-kernel-6.8 +un ||proxmox-kernel-6.8.8-2-pve +ii |amd64|proxmox-kernel-6.8.8-2-pve-signed + "# + .to_owned()) + } + }; + + assert_eq!( + find_kernel_package_name(&mocked_run_cmd) + .unwrap_err() + .to_string(), + "failed to find installed kernel package" + ); + } + + #[test] + fn find_kernel_package_name_fails_on_missing_package() { + let mocked_run_cmd = |cmd: &[&str]| { + if cmd[0] == "dpkg" { + assert_eq!(cmd, &["dpkg", "--print-architecture"]); + Ok("amd64\n".to_owned()) + } else { + assert_eq!( + cmd, + &[ + "dpkg-query", + "--showformat", + "${db:Status-Abbrev}|${Architecture}|${Package}\\n", + "--show", + "proxmox-kernel-[0-9]*", + ] + ); + Ok(r#"ii |all|proxmox-kernel-6.8 +un ||proxmox-kernel-6.8.8-2-pve + "# + .to_owned()) + } + }; + + assert_eq!( + find_kernel_package_name(&mocked_run_cmd) + .unwrap_err() + .to_string(), + "failed to find installed kernel package" + ); + } + + #[test] + fn finds_correct_absolute_kernel_image_path() { + let mocked_run_cmd = |cmd: &[&str]| { + if cmd[0] == "dpkg" { + assert_eq!(cmd, &["dpkg", "--print-architecture"]); + Ok("amd64\n".to_owned()) + } else if cmd[0..=1] == ["dpkg-query", "--showformat"] { + assert_eq!( + cmd, + &[ + "dpkg-query", + "--showformat", + "${db:Status-Abbrev}|${Architecture}|${Package}\\n", + "--show", + "proxmox-kernel-[0-9]*", + ] + ); + Ok(r#"ii |all|proxmox-kernel-6.8 +un ||proxmox-kernel-6.8.8-2-pve +ii |amd64|proxmox-kernel-6.8.8-2-pve-signed + "# + .to_owned()) + } else { + assert_eq!( + cmd, + [ + "dpkg-query", + "--listfiles", + "proxmox-kernel-6.8.8-2-pve-signed" + ] + ); + Ok(r#" +/. +/boot +/boot/System.map-6.8.8-2-pve +/boot/config-6.8.8-2-pve +/boot/vmlinuz-6.8.8-2-pve +/lib +/lib/modules +/lib/modules/6.8.8-2-pve +/lib/modules/6.8.8-2-pve/kernel +/lib/modules/6.8.8-2-pve/kernel/arch +/lib/modules/6.8.8-2-pve/kernel/arch/x86 +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aegis128-aesni.ko +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aesni-intel.ko +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx-x86_64.ko +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx2-x86_64.ko +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-gfni-avx512-x86_64.ko +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/blowfish-x86_64.ko +/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/camellia-aesni-avx-x86_64.ko + "# + .to_owned()) + } + }; + + assert_eq!( + find_kernel_image_path(&mocked_run_cmd).unwrap(), + "/boot/vmlinuz-6.8.8-2-pve" + ); + } + } } /// Runs the specified callback with the mounted chroot, passing along the @@ -700,7 +744,9 @@ fn with_chroot Result>(callback: F) -> Result { /// optional certificate fingerprint for HTTPS). If configured, retrieves all relevant information /// about the installed system and sends them to the given endpoint. fn do_main() -> Result<()> { - let answer = Answer::try_from_reader(std::io::stdin().lock())?; + let mut raw_toml = String::new(); + std::io::stdin().read_to_string(&mut raw_toml)?; + let answer: AutoInstallerConfig = toml::from_str(&raw_toml)?; if let Some(PostNotificationHookInfo { url, @@ -709,7 +755,7 @@ fn do_main() -> Result<()> { { println!("Found post-installation-webhook; sending POST request to '{url}'."); - let info = with_chroot(|target_path| PostHookInfo::gather(target_path, &answer))?; + let info = with_chroot(|target_path| detail::gather(target_path, &answer))?; if let Err(err) = fs::write( "/run/proxmox-installer/post-hook-data.json", @@ -743,197 +789,3 @@ fn main() -> ExitCode { } } } - -#[cfg(test)] -mod tests { - use crate::PostHookInfo; - - #[test] - fn finds_correct_kernel_package_name() { - let mocked_run_cmd = |cmd: &[&str]| { - if cmd[0] == "dpkg" { - assert_eq!(cmd, &["dpkg", "--print-architecture"]); - Ok("amd64\n".to_owned()) - } else { - assert_eq!( - cmd, - &[ - "dpkg-query", - "--showformat", - "${db:Status-Abbrev}|${Architecture}|${Package}\\n", - "--show", - "proxmox-kernel-[0-9]*", - ] - ); - Ok(r#"ii |all|proxmox-kernel-6.8 -un ||proxmox-kernel-6.8.8-2-pve -ii |amd64|proxmox-kernel-6.8.8-2-pve-signed - "# - .to_owned()) - } - }; - - assert_eq!( - PostHookInfo::find_kernel_package_name(&mocked_run_cmd).unwrap(), - "proxmox-kernel-6.8.8-2-pve-signed" - ); - } - - #[test] - fn finds_correct_kernel_package_name_arm64() { - let mocked_run_cmd = |cmd: &[&str]| { - if cmd[0] == "dpkg" { - assert_eq!(cmd, &["dpkg", "--print-architecture"]); - Ok("arm64\n".to_owned()) - } else { - assert_eq!( - cmd, - &[ - "dpkg-query", - "--showformat", - "${db:Status-Abbrev}|${Architecture}|${Package}\\n", - "--show", - "proxmox-kernel-[0-9]*", - ] - ); - Ok(r#"ii |all|proxmox-kernel-6.17 -un ||proxmox-kernel-6.17.2-1-pve -ii |arm64|proxmox-kernel-6.17.2-1-pve-signed - "# - .to_owned()) - } - }; - - assert_eq!( - PostHookInfo::find_kernel_package_name(&mocked_run_cmd).unwrap(), - "proxmox-kernel-6.17.2-1-pve-signed" - ); - } - - #[test] - fn find_kernel_package_name_fails_on_wrong_architecture() { - let mocked_run_cmd = |cmd: &[&str]| { - if cmd[0] == "dpkg" { - assert_eq!(cmd, &["dpkg", "--print-architecture"]); - Ok("arm64\n".to_owned()) - } else { - assert_eq!( - cmd, - &[ - "dpkg-query", - "--showformat", - "${db:Status-Abbrev}|${Architecture}|${Package}\\n", - "--show", - "proxmox-kernel-[0-9]*", - ] - ); - Ok(r#"ii |all|proxmox-kernel-6.8 -un ||proxmox-kernel-6.8.8-2-pve -ii |amd64|proxmox-kernel-6.8.8-2-pve-signed - "# - .to_owned()) - } - }; - - assert_eq!( - PostHookInfo::find_kernel_package_name(&mocked_run_cmd) - .unwrap_err() - .to_string(), - "failed to find installed kernel package" - ); - } - - #[test] - fn find_kernel_package_name_fails_on_missing_package() { - let mocked_run_cmd = |cmd: &[&str]| { - if cmd[0] == "dpkg" { - assert_eq!(cmd, &["dpkg", "--print-architecture"]); - Ok("amd64\n".to_owned()) - } else { - assert_eq!( - cmd, - &[ - "dpkg-query", - "--showformat", - "${db:Status-Abbrev}|${Architecture}|${Package}\\n", - "--show", - "proxmox-kernel-[0-9]*", - ] - ); - Ok(r#"ii |all|proxmox-kernel-6.8 -un ||proxmox-kernel-6.8.8-2-pve - "# - .to_owned()) - } - }; - - assert_eq!( - PostHookInfo::find_kernel_package_name(&mocked_run_cmd) - .unwrap_err() - .to_string(), - "failed to find installed kernel package" - ); - } - - #[test] - fn finds_correct_absolute_kernel_image_path() { - let mocked_run_cmd = |cmd: &[&str]| { - if cmd[0] == "dpkg" { - assert_eq!(cmd, &["dpkg", "--print-architecture"]); - Ok("amd64\n".to_owned()) - } else if cmd[0..=1] == ["dpkg-query", "--showformat"] { - assert_eq!( - cmd, - &[ - "dpkg-query", - "--showformat", - "${db:Status-Abbrev}|${Architecture}|${Package}\\n", - "--show", - "proxmox-kernel-[0-9]*", - ] - ); - Ok(r#"ii |all|proxmox-kernel-6.8 -un ||proxmox-kernel-6.8.8-2-pve -ii |amd64|proxmox-kernel-6.8.8-2-pve-signed - "# - .to_owned()) - } else { - assert_eq!( - cmd, - [ - "dpkg-query", - "--listfiles", - "proxmox-kernel-6.8.8-2-pve-signed" - ] - ); - Ok(r#" -/. -/boot -/boot/System.map-6.8.8-2-pve -/boot/config-6.8.8-2-pve -/boot/vmlinuz-6.8.8-2-pve -/lib -/lib/modules -/lib/modules/6.8.8-2-pve -/lib/modules/6.8.8-2-pve/kernel -/lib/modules/6.8.8-2-pve/kernel/arch -/lib/modules/6.8.8-2-pve/kernel/arch/x86 -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aegis128-aesni.ko -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aesni-intel.ko -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx-x86_64.ko -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx2-x86_64.ko -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-gfni-avx512-x86_64.ko -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/blowfish-x86_64.ko -/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/camellia-aesni-avx-x86_64.ko - "# - .to_owned()) - } - }; - - assert_eq!( - PostHookInfo::find_kernel_image_path(&mocked_run_cmd).unwrap(), - "/boot/vmlinuz-6.8.8-2-pve" - ); - } -} -- 2.53.0