From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 9F37B1FF13E for ; Fri, 03 Apr 2026 18:58:02 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6A35095BF; Fri, 3 Apr 2026 18:58:33 +0200 (CEST) From: Christoph Heiss To: pdm-devel@lists.proxmox.com Subject: [PATCH installer v3 37/38] tree-wide: switch out `Answer` -> `AutoInstallerConfig` types Date: Fri, 3 Apr 2026 18:54:09 +0200 Message-ID: <20260403165437.2166551-38-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: 1775235416912 X-SPAM-LEVEL: Spam detection results: 0 AWL -1.431 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 URIBL_BLACK 3 Contains an URL listed in the URIBL blacklist [openzfs.github.io] Message-ID-Hash: ABFERBN7T2RKSAO43ZIMB5DJRD4J4KBS X-Message-ID-Hash: ABFERBN7T2RKSAO43ZIMB5DJRD4J4KBS 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: The new `AutoInstallerConfig` type comes from proxmox-installer-types and wholly replaces `Answer`. No functional changes. Signed-off-by: Christoph Heiss --- Changes v2 -> v3: * new patch proxmox-auto-install-assistant/Cargo.toml | 1 + proxmox-auto-install-assistant/src/main.rs | 8 +- proxmox-auto-installer/src/answer.rs | 7 +- .../src/bin/proxmox-auto-installer.rs | 20 +-- proxmox-auto-installer/src/sysinfo.rs | 21 +-- proxmox-auto-installer/src/utils.rs | 142 ++++++++++-------- proxmox-auto-installer/tests/parse-answer.rs | 6 +- .../tests/resources/iso-info.json | 4 +- ...rface_pinning_overlong_interface_name.json | 2 +- proxmox-installer-common/src/disk_checks.rs | 5 +- proxmox-installer-common/src/lib.rs | 3 - proxmox-installer-common/src/options.rs | 119 +++++---------- proxmox-installer-common/src/setup.rs | 87 +---------- proxmox-post-hook/src/main.rs | 31 ++-- proxmox-tui-installer/src/main.rs | 12 +- proxmox-tui-installer/src/options.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 18 ++- 17 files changed, 183 insertions(+), 305 deletions(-) diff --git a/proxmox-auto-install-assistant/Cargo.toml b/proxmox-auto-install-assistant/Cargo.toml index 9a61fb5..61253be 100644 --- a/proxmox-auto-install-assistant/Cargo.toml +++ b/proxmox-auto-install-assistant/Cargo.toml @@ -14,6 +14,7 @@ homepage = "https://www.proxmox.com" anyhow.workspace = true proxmox-auto-installer.workspace = true proxmox-installer-common = { workspace = true, features = [ "cli" ] } +proxmox-installer-types.workspace = true serde_json.workspace = true toml.workspace = true diff --git a/proxmox-auto-install-assistant/src/main.rs b/proxmox-auto-install-assistant/src/main.rs index a92ac75..ee12c1e 100644 --- a/proxmox-auto-install-assistant/src/main.rs +++ b/proxmox-auto-install-assistant/src/main.rs @@ -18,7 +18,6 @@ use std::{ }; use proxmox_auto_installer::{ - answer::{Answer, FilterMatch}, sysinfo, utils::{ AutoInstSettings, FetchAnswerFrom, HttpOptions, default_partition_label, @@ -28,6 +27,7 @@ use proxmox_auto_installer::{ }, }; use proxmox_installer_common::{FIRST_BOOT_EXEC_MAX_SIZE, FIRST_BOOT_EXEC_NAME, cli}; +use proxmox_installer_types::answer::{AutoInstallerConfig, FilterMatch}; static PROXMOX_ISO_FLAG: &str = "/auto-installer-capable"; @@ -95,7 +95,7 @@ impl cli::Subcommand for CommandDeviceMatchArgs { fn parse(args: &mut cli::Arguments) -> Result { let filter_match = args .opt_value_from_str("--filter-match")? - .unwrap_or(FilterMatch::Any); + .unwrap_or_default(); let device_type = args.free_from_str().context("parsing device type")?; let mut filter = vec![]; @@ -630,7 +630,7 @@ fn validate_answer_file_keys(path: impl AsRef + fmt::Debug) -> Result Result<()> { +fn verify_hashed_password_interactive(answer: &AutoInstallerConfig) -> Result<()> { if let Some(hashed) = &answer.global.root_password_hashed { println!("Verifying hashed root password."); @@ -1313,7 +1313,7 @@ fn get_udev_properties(path: impl AsRef + fmt::Debug) -> Result { Ok(String::from_utf8(udev_output.stdout)?) } -fn parse_answer(path: impl AsRef + fmt::Debug) -> Result { +fn parse_answer(path: impl AsRef + fmt::Debug) -> Result { let mut file = match fs::File::open(&path) { Ok(file) => file, Err(err) => bail!("Opening answer file {path:?} failed: {err}"), diff --git a/proxmox-auto-installer/src/answer.rs b/proxmox-auto-installer/src/answer.rs index eec5b58..c7e7298 100644 --- a/proxmox-auto-installer/src/answer.rs +++ b/proxmox-auto-installer/src/answer.rs @@ -6,10 +6,11 @@ use std::{ net::IpAddr, }; -use proxmox_installer_common::options::{ - BtrfsCompressOption, NetworkInterfacePinningOptions, ZfsChecksumOption, ZfsCompressOption, +use proxmox_installer_common::options::NetworkInterfacePinningOptions; +use proxmox_installer_types::answer::{ + BtrfsCompressOption, BtrfsRaidLevel, FilesystemType, ZfsChecksumOption, ZfsCompressOption, + ZfsRaidLevel, }; -use proxmox_installer_types::answer::{BtrfsRaidLevel, FilesystemType, ZfsRaidLevel}; use proxmox_network_types::{Cidr, fqdn::Fqdn}; // NOTE New answer file properties must use kebab-case, but should allow snake_case for backwards diff --git a/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs b/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs index 7614fbb..0ced7d4 100644 --- a/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs +++ b/proxmox-auto-installer/src/bin/proxmox-auto-installer.rs @@ -3,11 +3,12 @@ use log::{LevelFilter, error, info}; use std::{ env, fs::{self, File}, - io::{BufRead, BufReader, Write}, + io::{BufRead, BufReader, Read, Write}, path::PathBuf, process::ExitCode, }; +use proxmox_auto_installer::{log::AutoInstLogger, utils::parse_answer}; use proxmox_installer_common::{ FIRST_BOOT_EXEC_MAX_SIZE, FIRST_BOOT_EXEC_NAME, RUNTIME_DIR, http, setup::{ @@ -15,12 +16,9 @@ use proxmox_installer_common::{ spawn_low_level_installer, }, }; - -use proxmox_auto_installer::{ - answer::{Answer, FirstBootHookInfo, FirstBootHookSourceMode, RebootMode}, - log::AutoInstLogger, - udevinfo::UdevInfo, - utils::parse_answer, +use proxmox_installer_types::{ + UdevInfo, + answer::{AutoInstallerConfig, FirstBootHookInfo, FirstBootHookSourceMode, RebootMode}, }; static LOGGER: AutoInstLogger = AutoInstLogger; @@ -70,7 +68,7 @@ fn setup_first_boot_executable(first_boot: &FirstBootHookInfo) -> Result<()> { } } -fn auto_installer_setup(in_test_mode: bool) -> Result<(Answer, UdevInfo)> { +fn auto_installer_setup(in_test_mode: bool) -> Result<(AutoInstallerConfig, UdevInfo)> { let base_path = if in_test_mode { "./testdir" } else { "/" }; let mut path = PathBuf::from(base_path); @@ -85,7 +83,9 @@ fn auto_installer_setup(in_test_mode: bool) -> Result<(Answer, UdevInfo)> { .map_err(|err| format_err!("Failed to retrieve udev info details: {err}"))? }; - 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(first_boot) = &answer.first_boot { setup_first_boot_executable(first_boot)?; @@ -151,7 +151,7 @@ fn main() -> ExitCode { } fn run_installation( - answer: &Answer, + answer: &AutoInstallerConfig, locales: &LocaleInfo, runtime_info: &RuntimeInfo, udevadm_info: &UdevInfo, diff --git a/proxmox-auto-installer/src/sysinfo.rs b/proxmox-auto-installer/src/sysinfo.rs index 5129829..38f419f 100644 --- a/proxmox-auto-installer/src/sysinfo.rs +++ b/proxmox-auto-installer/src/sysinfo.rs @@ -2,10 +2,7 @@ use anyhow::{Result, bail}; use std::{fs, io, path::PathBuf}; use crate::utils::get_nic_list; -use proxmox_installer_common::{ - RUNTIME_DIR, - setup::{ProxmoxProduct, SetupInfo}, -}; +use proxmox_installer_common::{RUNTIME_DIR, setup::SetupInfo}; use proxmox_installer_types::{NetworkInterface, SystemInfo}; pub fn get() -> Result { @@ -20,20 +17,8 @@ pub fn get() -> Result { }; Ok(SystemInfo { - product: proxmox_installer_types::ProductConfig { - fullname: setup_info.config.fullname, - product: 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, - }, - enable_btrfs: setup_info.config.enable_btrfs, - }, - iso: proxmox_installer_types::IsoInfo { - release: setup_info.iso_info.release, - isorelease: setup_info.iso_info.isorelease, - }, + product: setup_info.config, + iso: setup_info.iso_info, network_interfaces: get_all_network_interfaces()?, dmi: proxmox_installer_common::dmi::get()?, }) diff --git a/proxmox-auto-installer/src/utils.rs b/proxmox-auto-installer/src/utils.rs index 83be913..8173ee8 100644 --- a/proxmox-auto-installer/src/utils.rs +++ b/proxmox-auto-installer/src/utils.rs @@ -6,33 +6,40 @@ use std::{ process::Command, }; -use crate::{ +use proxmox_installer_types::{ + UdevInfo, answer::{ - self, Answer, DiskSelection, FirstBootHookSourceMode, FqdnConfig, FqdnExtendedConfig, - FqdnSourceMode, Network, + AutoInstallerConfig, DiskSelection, Filesystem, FilesystemOptions, FilesystemType, + FilterMatch, FirstBootHookSourceMode, FqdnConfig, FqdnFromDhcpConfig, FqdnSourceMode, + NetworkConfig, }, - udevinfo::UdevInfo, }; + use proxmox_installer_common::{ ROOT_PASSWORD_MIN_LENGTH, disk_checks::check_swapsize, - options::{NetworkOptions, RaidLevel, ZfsChecksumOption, ZfsCompressOption, email_validate}, + options::{FilesystemDiskInfo, NetworkInterfacePinningOptions, NetworkOptions, email_validate}, setup::{ InstallBtrfsOption, InstallConfig, InstallFirstBootSetup, InstallRootPassword, InstallZfsOption, LocaleInfo, RuntimeInfo, SetupInfo, }, }; -use proxmox_installer_types::answer::FilesystemType; + use serde::{Deserialize, Serialize}; fn get_network_settings( - answer: &Answer, + answer: &AutoInstallerConfig, udev_info: &UdevInfo, runtime_info: &RuntimeInfo, setup_info: &SetupInfo, ) -> Result { info!("Setting up network configuration"); + let pinning_opts = answer + .network + .interface_name_pinning() + .map(|answer| answer.into()); + let mut network_options = match &answer.global.fqdn { // If the user set a static FQDN in the answer file, override it FqdnConfig::Simple(name) => { @@ -40,12 +47,12 @@ fn get_network_settings( setup_info, &runtime_info.network, None, - answer.network.interface_name_pinning.as_ref(), + pinning_opts.as_ref(), ); opts.fqdn = name.to_owned(); opts } - FqdnConfig::Extended(FqdnExtendedConfig { + FqdnConfig::FromDhcp(FqdnFromDhcpConfig { source: FqdnSourceMode::FromDhcp, domain, }) => { @@ -68,12 +75,12 @@ fn get_network_settings( setup_info, &runtime_info.network, domain.as_deref(), - answer.network.interface_name_pinning.as_ref(), + pinning_opts.as_ref(), ) } }; - if let answer::NetworkSettings::Manual(settings) = &answer.network.network_settings { + if let NetworkConfig::FromAnswer(settings) = &answer.network { network_options.address = settings.cidr; network_options.dns_server = settings.dns; network_options.gateway = settings.gateway; @@ -206,7 +213,7 @@ pub fn get_matched_udev_indexes( } fn set_disks( - answer: &Answer, + answer: &AutoInstallerConfig, udev_info: &UdevInfo, runtime_info: &RuntimeInfo, config: &mut InstallConfig, @@ -222,13 +229,13 @@ fn set_disks( } fn set_single_disk( - answer: &Answer, + answer: &AutoInstallerConfig, udev_info: &UdevInfo, runtime_info: &RuntimeInfo, config: &mut InstallConfig, ) -> Result<()> { - match &answer.disks.disk_selection { - answer::DiskSelection::Selection(disk_list) => { + match answer.disks.disk_selection()? { + DiskSelection::Selection(disk_list) => { let disk_name = disk_list[0].clone(); let disk = runtime_info .disks @@ -239,8 +246,8 @@ fn set_single_disk( None => bail!("disk in 'disk-selection' not found"), } } - answer::DiskSelection::Filter(filter) => { - let disk_index = get_single_udev_index(filter, &udev_info.disks)?; + DiskSelection::Filter(filter) => { + let disk_index = get_single_udev_index(&filter, &udev_info.disks)?; let disk = runtime_info .disks .iter() @@ -253,13 +260,13 @@ fn set_single_disk( } fn set_selected_disks( - answer: &Answer, + answer: &AutoInstallerConfig, udev_info: &UdevInfo, runtime_info: &RuntimeInfo, config: &mut InstallConfig, ) -> Result<()> { - match &answer.disks.disk_selection { - answer::DiskSelection::Selection(disk_list) => { + match answer.disks.disk_selection()? { + DiskSelection::Selection(disk_list) => { info!("Disk selection found"); for disk_name in disk_list.clone() { let disk = runtime_info @@ -273,17 +280,13 @@ fn set_selected_disks( } } } - answer::DiskSelection::Filter(filter) => { + DiskSelection::Filter(filter) => { info!("No disk list found, looking for disk filters"); - let filter_match = answer - .disks - .filter_match - .clone() - .unwrap_or(answer::FilterMatch::Any); + let filter_match = answer.disks.filter_match.unwrap_or_default(); let selected_disk_indexes = get_matched_udev_indexes( - filter, + &filter, &udev_info.disks, - filter_match == answer::FilterMatch::All, + filter_match == FilterMatch::All, )?; for i in selected_disk_indexes.into_iter() { @@ -336,19 +339,23 @@ fn get_first_selected_disk(config: &InstallConfig) -> usize { .expect("could not parse key to usize") } -fn verify_filesystem_settings(answer: &Answer, setup_info: &SetupInfo) -> Result<()> { +fn verify_filesystem_settings( + answer: &AutoInstallerConfig, + setup_info: &SetupInfo, +) -> Result { info!("Verifying filesystem settings"); - if answer.disks.fs_type.is_btrfs() && !setup_info.config.enable_btrfs { + let fs_options = answer.disks.filesystem_details()?; + if answer.disks.filesystem == Filesystem::Btrfs && !setup_info.config.enable_btrfs { bail!( "BTRFS is not supported as a root filesystem for the product or the release of this ISO." ); } - Ok(()) + Ok(fs_options) } -pub fn verify_locale_settings(answer: &Answer, locales: &LocaleInfo) -> Result<()> { +pub fn verify_locale_settings(answer: &AutoInstallerConfig, locales: &LocaleInfo) -> Result<()> { info!("Verifying locale settings"); if !locales .countries @@ -385,7 +392,7 @@ pub fn verify_locale_settings(answer: &Answer, locales: &LocaleInfo) -> Result<( /// /// Ensures that the provided email-address is of valid format and that one /// of the two root password options is set appropriately. -pub fn verify_email_and_root_password_settings(answer: &Answer) -> Result<()> { +pub fn verify_email_and_root_password_settings(answer: &AutoInstallerConfig) -> Result<()> { info!("Verifying email and root password settings"); email_validate(&answer.global.mailto).with_context(|| answer.global.mailto.clone())?; @@ -411,40 +418,41 @@ pub fn verify_email_and_root_password_settings(answer: &Answer) -> Result<()> { } } -pub fn verify_disks_settings(answer: &Answer) -> Result<()> { - if let DiskSelection::Selection(selection) = &answer.disks.disk_selection { - let min_disks = match answer.disks.fs_type { - FilesystemType::Ext4 | FilesystemType::Xfs => 1, - FilesystemType::Zfs(level) => level.get_min_disks(), - FilesystemType::Btrfs(level) => level.get_min_disks(), - }; +pub fn verify_disks_settings(answer: &AutoInstallerConfig) -> Result<()> { + let fs_options = answer.disks.filesystem_details()?; + + if let DiskSelection::Selection(selection) = answer.disks.disk_selection()? { + let min_disks = fs_options.to_type().get_min_disks(); if selection.len() < min_disks { bail!( "{}: need at least {} disks", - answer.disks.fs_type, + fs_options.to_type(), min_disks ); } let mut disk_set = HashSet::new(); - for disk in selection { + for disk in &selection { if !disk_set.insert(disk) { bail!("List of disks contains duplicate device {disk}"); } } } - if let answer::FsOptions::LVM(lvm) = &answer.disks.fs_options - && let Some((swapsize, hdsize)) = lvm.swapsize.zip(lvm.hdsize) - { - check_swapsize(swapsize, hdsize)?; + match fs_options { + FilesystemOptions::Ext4(lvm) | FilesystemOptions::Xfs(lvm) => { + if let Some((swapsize, hdsize)) = lvm.swapsize.zip(lvm.hdsize) { + check_swapsize(swapsize, hdsize)?; + } + } + _ => {} } Ok(()) } -pub fn verify_first_boot_settings(answer: &Answer) -> Result<()> { +pub fn verify_first_boot_settings(answer: &AutoInstallerConfig) -> Result<()> { info!("Verifying first boot settings"); if let Some(first_boot) = &answer.first_boot @@ -457,10 +465,16 @@ pub fn verify_first_boot_settings(answer: &Answer) -> Result<()> { Ok(()) } -pub fn verify_network_settings(network: &Network, run_env: Option<&RuntimeInfo>) -> Result<()> { +pub fn verify_network_settings( + network: &NetworkConfig, + run_env: Option<&RuntimeInfo>, +) -> Result<()> { info!("Verifying network settings"); - if let Some(pin_opts) = &network.interface_name_pinning { + let pin_opts: Option = + network.interface_name_pinning().map(|v| v.into()); + + if let Some(pin_opts) = pin_opts { pin_opts.verify()?; if let Some(run_env) = run_env { @@ -483,7 +497,7 @@ pub fn verify_network_settings(network: &Network, run_env: Option<&RuntimeInfo>) } pub fn parse_answer( - answer: &Answer, + answer: &AutoInstallerConfig, udev_info: &UdevInfo, runtime_info: &RuntimeInfo, locales: &LocaleInfo, @@ -491,11 +505,10 @@ pub fn parse_answer( ) -> Result { info!("Parsing answer file"); - verify_filesystem_settings(answer, setup_info)?; + let fs_options = verify_filesystem_settings(answer, setup_info)?; info!("Setting File system"); - let filesystem = answer.disks.fs_type; - info!("File system selected: {}", filesystem); + info!("File system selected: {}", fs_options.to_type()); let network_settings = get_network_settings(answer, udev_info, runtime_info, setup_info)?; @@ -517,7 +530,7 @@ pub fn parse_answer( let mut config = InstallConfig { autoreboot: 1_usize, - filesys: filesystem, + filesys: fs_options.to_type(), hdsize: 0., swapsize: None, maxroot: None, @@ -553,8 +566,8 @@ pub fn parse_answer( }; set_disks(answer, udev_info, runtime_info, &mut config)?; - match &answer.disks.fs_options { - answer::FsOptions::LVM(lvm) => { + match fs_options { + FilesystemOptions::Ext4(lvm) | FilesystemOptions::Xfs(lvm) => { let disk = runtime_info .disks .iter() @@ -568,21 +581,24 @@ pub fn parse_answer( config.maxvz = lvm.maxvz; config.minfree = lvm.minfree; } - answer::FsOptions::ZFS(zfs) => { + FilesystemOptions::Zfs(zfs) => { let first_selected_disk = get_first_selected_disk(&config); config.hdsize = zfs .hdsize .unwrap_or(runtime_info.disks[first_selected_disk].size); config.zfs_opts = Some(InstallZfsOption { - ashift: zfs.ashift.unwrap_or(12), - arc_max: zfs.arc_max.unwrap_or(runtime_info.default_zfs_arc_max), - compress: zfs.compress.unwrap_or(ZfsCompressOption::On), - checksum: zfs.checksum.unwrap_or(ZfsChecksumOption::On), - copies: zfs.copies.unwrap_or(1), + ashift: zfs.ashift.unwrap_or(12) as usize, + arc_max: zfs + .arc_max + .map(|v| v as usize) + .unwrap_or(runtime_info.default_zfs_arc_max), + compress: zfs.compress.unwrap_or_default(), + checksum: zfs.checksum.unwrap_or_default(), + copies: zfs.copies.unwrap_or(1) as usize, }); } - answer::FsOptions::BTRFS(btrfs) => { + FilesystemOptions::Btrfs(btrfs) => { let first_selected_disk = get_first_selected_disk(&config); config.hdsize = btrfs diff --git a/proxmox-auto-installer/tests/parse-answer.rs b/proxmox-auto-installer/tests/parse-answer.rs index 7dd4a9d..675678a 100644 --- a/proxmox-auto-installer/tests/parse-answer.rs +++ b/proxmox-auto-installer/tests/parse-answer.rs @@ -2,13 +2,11 @@ use serde_json::Value; use std::fs; use std::path::{Path, PathBuf}; -use proxmox_auto_installer::answer::Answer; -use proxmox_auto_installer::udevinfo::UdevInfo; use proxmox_auto_installer::utils::parse_answer; - use proxmox_installer_common::setup::{ LocaleInfo, RuntimeInfo, SetupInfo, load_installer_setup_files, read_json, }; +use proxmox_installer_types::{UdevInfo, answer::AutoInstallerConfig}; fn get_test_resource_path() -> Result { Ok(std::env::current_dir() @@ -16,7 +14,7 @@ fn get_test_resource_path() -> Result { .join("tests/resources")) } -fn get_answer(path: impl AsRef) -> Result { +fn get_answer(path: impl AsRef) -> Result { let answer_raw = fs::read_to_string(path).unwrap(); toml::from_str(&answer_raw) .map_err(|err| format!("error parsing answer.toml: {}", err.message())) diff --git a/proxmox-auto-installer/tests/resources/iso-info.json b/proxmox-auto-installer/tests/resources/iso-info.json index 881dafd..2cfbd6d 100644 --- a/proxmox-auto-installer/tests/resources/iso-info.json +++ b/proxmox-auto-installer/tests/resources/iso-info.json @@ -14,8 +14,8 @@ }, "product": "pve", "product-cfg": { - "bridged_network": 1, - "enable_btrfs": 1, + "bridged_network": true, + "enable_btrfs": true, "fullname": "Proxmox VE", "port": "8006", "product": "pve" diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_interface_pinning_overlong_interface_name.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_interface_pinning_overlong_interface_name.json index af4ed79..f3c9169 100644 --- a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_interface_pinning_overlong_interface_name.json +++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_interface_pinning_overlong_interface_name.json @@ -1,3 +1,3 @@ { - "parse-error": "error parsing answer.toml: interface name 'waytoolonginterfacename' for 'ab:cd:ef:12:34:56' cannot be longer than 15 characters" + "error": "interface name 'waytoolonginterfacename' for 'ab:cd:ef:12:34:56' cannot be longer than 15 characters" } diff --git a/proxmox-installer-common/src/disk_checks.rs b/proxmox-installer-common/src/disk_checks.rs index f17a7a6..fbed578 100644 --- a/proxmox-installer-common/src/disk_checks.rs +++ b/proxmox-installer-common/src/disk_checks.rs @@ -1,9 +1,8 @@ +use anyhow::ensure; use std::collections::HashSet; -use anyhow::ensure; - use crate::options::{Disk, LvmBootdiskOptions}; -use crate::setup::BootType; +use proxmox_installer_types::BootType; /// Checks a list of disks for duplicate entries, using their index as key. /// diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs index fde17b7..ee34096 100644 --- a/proxmox-installer-common/src/lib.rs +++ b/proxmox-installer-common/src/lib.rs @@ -19,9 +19,6 @@ pub mod net { pub const RUNTIME_DIR: &str = "/run/proxmox-installer"; -/// Default placeholder value for the administrator email address. -pub const EMAIL_DEFAULT_PLACEHOLDER: &str = "mail@example.invalid"; - /// Name of the executable for the first-boot hook. pub const FIRST_BOOT_EXEC_NAME: &str = "proxmox-first-boot"; diff --git a/proxmox-installer-common/src/options.rs b/proxmox-installer-common/src/options.rs index 8e19663..ed00b4b 100644 --- a/proxmox-installer-common/src/options.rs +++ b/proxmox-installer-common/src/options.rs @@ -1,6 +1,6 @@ use anyhow::{Result, bail}; use regex::{Regex, RegexBuilder}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::{ cmp, collections::HashMap, @@ -12,7 +12,13 @@ use std::{ use crate::disk_checks::check_raid_min_disks; use crate::net::{MAX_IFNAME_LEN, MIN_IFNAME_LEN}; use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; -use proxmox_installer_types::answer::{BtrfsRaidLevel, FilesystemType, ZfsRaidLevel}; +use proxmox_installer_types::{ + EMAIL_DEFAULT_PLACEHOLDER, + answer::{ + BtrfsCompressOption, BtrfsRaidLevel, FilesystemType, NetworkInterfacePinningOptionsAnswer, + ZfsChecksumOption, ZfsCompressOption, ZfsRaidLevel, + }, +}; use proxmox_network_types::{fqdn::Fqdn, ip_address::Cidr}; pub trait RaidLevel { @@ -123,35 +129,22 @@ impl LvmBootdiskOptions { } } -/// See the accompanying mount option in btrfs(5). -#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq)] -#[serde(rename_all(deserialize = "lowercase"))] -pub enum BtrfsCompressOption { - On, - #[default] - Off, - Zlib, - Lzo, - Zstd, +pub trait FilesystemDiskInfo { + /// Returns the minimum number of disks needed for this filesystem. + fn get_min_disks(&self) -> usize; } -impl fmt::Display for BtrfsCompressOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", format!("{self:?}").to_lowercase()) +impl FilesystemDiskInfo for FilesystemType { + fn get_min_disks(&self) -> usize { + match self { + FilesystemType::Ext4 => 1, + FilesystemType::Xfs => 1, + FilesystemType::Zfs(level) => level.get_min_disks(), + FilesystemType::Btrfs(level) => level.get_min_disks(), + } } } -impl From<&BtrfsCompressOption> for String { - fn from(value: &BtrfsCompressOption) -> Self { - value.to_string() - } -} - -pub const BTRFS_COMPRESS_OPTIONS: &[BtrfsCompressOption] = { - use BtrfsCompressOption::*; - &[On, Off, Zlib, Lzo, Zstd] -}; - #[derive(Clone, Debug)] pub struct BtrfsBootdiskOptions { pub disk_size: f64, @@ -171,54 +164,6 @@ impl BtrfsBootdiskOptions { } } -#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum ZfsCompressOption { - #[default] - On, - Off, - Lzjb, - Lz4, - Zle, - Gzip, - Zstd, -} - -serde_plain::derive_display_from_serialize!(ZfsCompressOption); - -impl From<&ZfsCompressOption> for String { - fn from(value: &ZfsCompressOption) -> Self { - value.to_string() - } -} - -pub const ZFS_COMPRESS_OPTIONS: &[ZfsCompressOption] = { - use ZfsCompressOption::*; - &[On, Off, Lzjb, Lz4, Zle, Gzip, Zstd] -}; - -#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "kebab-case")] -pub enum ZfsChecksumOption { - #[default] - On, - Fletcher4, - Sha256, -} - -serde_plain::derive_display_from_serialize!(ZfsChecksumOption); - -impl From<&ZfsChecksumOption> for String { - fn from(value: &ZfsChecksumOption) -> Self { - value.to_string() - } -} - -pub const ZFS_CHECKSUM_OPTIONS: &[ZfsChecksumOption] = { - use ZfsChecksumOption::*; - &[On, Fletcher4, Sha256] -}; - #[derive(Clone, Debug)] pub struct ZfsBootdiskOptions { pub ashift: usize, @@ -430,6 +375,24 @@ impl NetworkInterfacePinningOptions { } } +impl From<&NetworkInterfacePinningOptionsAnswer> for NetworkInterfacePinningOptions { + fn from(answer: &NetworkInterfacePinningOptionsAnswer) -> Self { + if answer.enabled { + Self { + // convert all MAC addresses to lowercase before further usage, + // to enable easy comparison + mapping: answer + .mapping + .iter() + .map(|(k, v)| (k.to_lowercase(), v.clone())) + .collect(), + } + } else { + Self::default() + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct NetworkOptions { pub ifname: String, @@ -453,11 +416,7 @@ impl NetworkOptions { // worse case nothing breaks down *completely*. let mut this = Self { ifname: String::new(), - fqdn: Self::construct_fqdn( - network, - setup.config.product.default_hostname(), - default_domain, - ), + fqdn: Self::construct_fqdn(network, &setup.config.product.to_string(), default_domain), // Safety: The provided IP address/mask is always valid. // These are the same as used in the GTK-based installer. address: Cidr::new_v4([192, 168, 100, 2], 24).unwrap(), @@ -576,7 +535,7 @@ pub fn email_validate(email: &str) -> Result<()> { if !re.is_match(email) { bail!("Email does not look like a valid address (user@domain.tld)") - } else if email == crate::EMAIL_DEFAULT_PLACEHOLDER { + } else if email == EMAIL_DEFAULT_PLACEHOLDER { bail!("Invalid (default) email address") } diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs index 91f1250..57f9cf3 100644 --- a/proxmox-installer-common/src/setup.rs +++ b/proxmox-installer-common/src/setup.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use std::{ cmp, collections::{BTreeMap, HashMap}, @@ -10,81 +11,14 @@ use std::{ process::{self, Command, Stdio}, }; -use proxmox_network_types::Cidr; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; - use crate::options::{ - BtrfsBootdiskOptions, BtrfsCompressOption, Disk, NetworkInterfacePinningOptions, - ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption, + BtrfsBootdiskOptions, Disk, NetworkInterfacePinningOptions, ZfsBootdiskOptions, }; -use proxmox_installer_types::answer::FilesystemType; - -#[allow(clippy::upper_case_acronyms)] -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum ProxmoxProduct { - PVE, - PBS, - PMG, - PDM, -} - -impl ProxmoxProduct { - pub fn default_hostname(self) -> &'static str { - match self { - Self::PVE => "pve", - Self::PMG => "pmg", - Self::PBS => "pbs", - Self::PDM => "pdm", - } - } -} - -impl fmt::Display for ProxmoxProduct { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::PVE => "pve", - Self::PMG => "pmg", - Self::PBS => "pbs", - Self::PDM => "pdm", - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ProductConfig { - pub fullname: String, - pub product: ProxmoxProduct, - #[serde(deserialize_with = "deserialize_bool_from_int")] - pub enable_btrfs: bool, -} - -impl ProductConfig { - /// A mocked ProductConfig simulating a Proxmox VE environment. - pub fn mocked() -> Self { - Self { - fullname: String::from("Proxmox VE (mocked)"), - product: ProxmoxProduct::PVE, - enable_btrfs: true, - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct IsoInfo { - pub release: String, - pub isorelease: String, -} - -impl IsoInfo { - /// A mocked IsoInfo with some edge case to convey that this is not necessarily purely numeric. - pub fn mocked() -> Self { - Self { - release: String::from("42.1"), - isorelease: String::from("mocked-1"), - } - } -} +use proxmox_installer_types::{ + BootType, IsoInfo, ProductConfig, + answer::{BtrfsCompressOption, FilesystemType, ZfsChecksumOption, ZfsCompressOption}, +}; +use proxmox_network_types::Cidr; /// Paths in the ISO environment containing installer data. #[derive(Clone, Deserialize)] @@ -387,13 +321,6 @@ pub struct RuntimeInfo { pub default_zfs_arc_max: usize, } -#[derive(Copy, Clone, Eq, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum BootType { - Bios, - Efi, -} - #[derive(Clone, Deserialize)] pub struct NetworkInfo { pub dns: Dns, diff --git a/proxmox-post-hook/src/main.rs b/proxmox-post-hook/src/main.rs index 9025c01..9d7932a 100644 --- a/proxmox-post-hook/src/main.rs +++ b/proxmox-post-hook/src/main.rs @@ -36,12 +36,10 @@ mod detail { use proxmox_installer_common::{ options::{Disk, NetworkOptions}, - setup::{ - InstallConfig, ProxmoxProduct, RuntimeInfo, SetupInfo, load_installer_setup_files, - }, + setup::{InstallConfig, RuntimeInfo, SetupInfo, load_installer_setup_files}, }; use proxmox_installer_types::{ - BootType, IsoInfo, UdevInfo, + ProxmoxProduct, UdevInfo, answer::{AutoInstallerConfig, FqdnConfig, FqdnFromDhcpConfig, FqdnSourceMode}, post_hook::{ BootInfo, CpuInfo, DiskInfo, KernelVersionInformation, NetworkInterfaceInfo, @@ -119,16 +117,10 @@ mod detail { }, debian_version: read_file("/etc/debian_version")?, product: gather_product_info(&setup_info, &run_cmd)?, - iso: IsoInfo { - release: setup_info.iso_info.release, - isorelease: setup_info.iso_info.isorelease, - }, + iso: setup_info.iso_info, kernel_version: gather_kernel_version(&run_cmd, &open_file)?, boot_info: BootInfo { - mode: match run_env.boot_type { - proxmox_installer_common::setup::BootType::Bios => BootType::Bios, - proxmox_installer_common::setup::BootType::Efi => BootType::Efi, - }, + mode: run_env.boot_type, secureboot: run_env.secure_boot, }, cpu_info: gather_cpu_info(&run_env)?, @@ -271,10 +263,10 @@ mod detail { run_cmd: &dyn Fn(&[&str]) -> Result, ) -> Result { let package = match setup_info.config.product { - ProxmoxProduct::PVE => "pve-manager", - ProxmoxProduct::PMG => "pmg-api", - ProxmoxProduct::PBS => "proxmox-backup-server", - ProxmoxProduct::PDM => "proxmox-datacenter-manager", + ProxmoxProduct::Pve => "pve-manager", + ProxmoxProduct::Pmg => "pmg-api", + ProxmoxProduct::Pbs => "proxmox-backup-server", + ProxmoxProduct::Pdm => "proxmox-datacenter-manager", }; let version = run_cmd(&[ @@ -288,12 +280,7 @@ mod detail { Ok(ProductInfo { fullname: setup_info.config.fullname.clone(), - 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, - }, + short: setup_info.config.product, version, }) } diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index d2fd3d8..6c457aa 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -13,17 +13,19 @@ use cursive::{ }, }; -mod options; -use options::{InstallerOptions, PasswordOptions}; - use proxmox_installer_common::{ ROOT_PASSWORD_MIN_LENGTH, options::{ BootdiskOptions, NetworkInterfacePinningOptions, NetworkOptions, TimezoneOptions, email_validate, }, - setup::{LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo, installer_setup}, + setup::{LocaleInfo, RuntimeInfo, SetupInfo, installer_setup}, }; +use proxmox_installer_types::ProxmoxProduct; + +mod options; +use options::{InstallerOptions, PasswordOptions}; + mod setup; mod system; @@ -213,7 +215,7 @@ fn installer_setup_late(siv: &mut Cursive) { ); } - if state.setup_info.config.product == ProxmoxProduct::PVE && !state.runtime_info.hvm_supported { + if state.setup_info.config.product == ProxmoxProduct::Pve && !state.runtime_info.hvm_supported { display_setup_warning( siv, concat!( diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index ff15fa0..2c156e8 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,10 +1,10 @@ use crate::SummaryOption; use proxmox_installer_common::{ - EMAIL_DEFAULT_PLACEHOLDER, options::{BootdiskOptions, NetworkOptions, TimezoneOptions}, setup::LocaleInfo, }; +use proxmox_installer_types::EMAIL_DEFAULT_PLACEHOLDER; #[derive(Clone)] pub struct PasswordOptions { diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index ed3936f..a0267f1 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -22,13 +22,19 @@ use proxmox_installer_common::{ check_disks_4kn_legacy_boot, check_for_duplicate_disks, check_lvm_bootdisk_opts, }, options::{ - AdvancedBootdiskOptions, BTRFS_COMPRESS_OPTIONS, BootdiskOptions, BtrfsBootdiskOptions, - Disk, LvmBootdiskOptions, RaidLevel, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, - ZfsBootdiskOptions, + AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, Disk, LvmBootdiskOptions, + RaidLevel, ZfsBootdiskOptions, + }, + setup::RuntimeInfo, +}; + +use proxmox_installer_types::{ + BootType, ProductConfig, ProxmoxProduct, + answer::{ + BTRFS_COMPRESS_OPTIONS, FILESYSTEM_TYPE_OPTIONS, FilesystemType, ZFS_CHECKSUM_OPTIONS, + ZFS_COMPRESS_OPTIONS, }, - setup::{BootType, ProductConfig, ProxmoxProduct, RuntimeInfo}, }; -use proxmox_installer_types::answer::{FILESYSTEM_TYPE_OPTIONS, FilesystemType}; /// OpenZFS specifies 64 MiB as the absolute minimum: /// @@ -328,7 +334,7 @@ struct LvmBootdiskOptionsView { impl LvmBootdiskOptionsView { fn new(disk: &Disk, options: &LvmBootdiskOptions, product_conf: &ProductConfig) -> Self { - let show_extra_fields = product_conf.product == ProxmoxProduct::PVE; + let show_extra_fields = product_conf.product == ProxmoxProduct::Pve; let view = FormView::new() .child( -- 2.53.0