[PATCH installer v3 37/38] tree-wide: switch out `Answer` -> `AutoInstallerConfig` types

Christoph Heiss
2026-04-03T16:58:02Z

The new `AutoInstallerConfig` type comes from proxmox-installer-types
and wholly replaces `Answer`.

No functional changes.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
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<Self> {
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<Path> + fmt::Debug) -> Result<bool
}
}

-fn verify_hashed_password_interactive(answer: &Answer) -> 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<Path> + fmt::Debug) -> Result<String> {
Ok(String::from_utf8(udev_output.stdout)?)
}

-fn parse_answer(path: impl AsRef<Path> + fmt::Debug) -> Result<Answer> {
+fn parse_answer(path: impl AsRef<Path> + fmt::Debug) -> Result<AutoInstallerConfig> {
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<SystemInfo> {
@@ -20,20 +17,8 @@ pub fn get() -> Result<SystemInfo> {
};

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<NetworkOptions> {
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<FilesystemOptions> {
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<NetworkInterfacePinningOptions> =
+ 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<InstallConfig> {
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<PathBuf, String> {
Ok(std::env::current_dir()
@@ -16,7 +14,7 @@ fn get_test_resource_path() -> Result<PathBuf, String> {
.join("tests/resources"))
}

-fn get_answer(path: impl AsRef<Path>) -> Result<Answer, String> {
+fn get_answer(path: impl AsRef<Path>) -> Result<AutoInstallerConfig, String> {
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<String>,
) -> Result<ProductInfo> {
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:
/// <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max>
@@ -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