From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 3CBC99D3AA for ; Wed, 25 Oct 2023 18:00:52 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7DE1516AE6 for ; Wed, 25 Oct 2023 18:00:21 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 25 Oct 2023 18:00:17 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 05F2746275 for ; Wed, 25 Oct 2023 18:00:17 +0200 (CEST) From: Aaron Lauterer To: pve-devel@lists.proxmox.com Date: Wed, 25 Oct 2023 18:00:06 +0200 Message-Id: <20231025160011.3617524-8-a.lauterer@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.075 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 Subject: [pve-devel] [PATCH 07/12] tui: switch to common crate X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 25 Oct 2023 16:00:52 -0000 by switching dependencies and deleting doubled code to avoid ambiguities within the same scope. Signed-off-by: Aaron Lauterer --- proxmox-tui-installer/src/main.rs | 13 +- proxmox-tui-installer/src/options.rs | 403 +------------------- proxmox-tui-installer/src/setup.rs | 316 +-------------- proxmox-tui-installer/src/system.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 248 +----------- proxmox-tui-installer/src/views/mod.rs | 2 +- proxmox-tui-installer/src/views/timezone.rs | 4 +- 7 files changed, 44 insertions(+), 944 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 81fe3ca..875a33a 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -28,16 +28,19 @@ use cursive::{ use regex::Regex; mod options; -use options::*; +use options::InstallerOptions; + +use proxmox_installer_common::{ + options::{BootdiskOptions, NetworkOptions, PasswordOptions, TimezoneOptions}, + setup::{LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo}, + utils::Fqdn, +}; mod setup; -use setup::{InstallConfig, LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo}; +use setup::InstallConfig; mod system; -mod utils; -use utils::Fqdn; - mod views; use views::{ BootdiskOptionsView, CidrAddressEditView, FormView, TableView, TableViewItem, diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index 85b39b8..221ad01 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,77 +1,12 @@ -use std::net::{IpAddr, Ipv4Addr}; -use std::{cmp, fmt}; - -use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; -use crate::utils::{CidrAddress, Fqdn}; use crate::SummaryOption; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum BtrfsRaidLevel { - Raid0, - Raid1, - Raid10, -} - -impl fmt::Display for BtrfsRaidLevel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use BtrfsRaidLevel::*; - match self { - Raid0 => write!(f, "RAID0"), - Raid1 => write!(f, "RAID1"), - Raid10 => write!(f, "RAID10"), - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ZfsRaidLevel { - Raid0, - Raid1, - Raid10, - RaidZ, - RaidZ2, - RaidZ3, -} - -impl fmt::Display for ZfsRaidLevel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ZfsRaidLevel::*; - match self { - Raid0 => write!(f, "RAID0"), - Raid1 => write!(f, "RAID1"), - Raid10 => write!(f, "RAID10"), - RaidZ => write!(f, "RAIDZ-1"), - RaidZ2 => write!(f, "RAIDZ-2"), - RaidZ3 => write!(f, "RAIDZ-3"), - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum FsType { - Ext4, - Xfs, - Zfs(ZfsRaidLevel), - Btrfs(BtrfsRaidLevel), -} - -impl FsType { - pub fn is_btrfs(&self) -> bool { - matches!(self, FsType::Btrfs(_)) - } -} - -impl fmt::Display for FsType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use FsType::*; - match self { - Ext4 => write!(f, "ext4"), - Xfs => write!(f, "XFS"), - Zfs(level) => write!(f, "ZFS ({level})"), - Btrfs(level) => write!(f, "Btrfs ({level})"), - } - } -} +use proxmox_installer_common::{ + setup::LocaleInfo, + options::{ + BootdiskOptions, BtrfsRaidLevel, FsType, NetworkOptions, TimezoneOptions, + PasswordOptions, ZfsRaidLevel, + }, +}; pub const FS_TYPES: &[FsType] = { use FsType::*; @@ -90,320 +25,6 @@ pub const FS_TYPES: &[FsType] = { ] }; -#[derive(Clone, Debug)] -pub struct LvmBootdiskOptions { - pub total_size: f64, - pub swap_size: Option, - pub max_root_size: Option, - pub max_data_size: Option, - pub min_lvm_free: Option, -} - -impl LvmBootdiskOptions { - pub fn defaults_from(disk: &Disk) -> Self { - Self { - total_size: disk.size, - swap_size: None, - max_root_size: None, - max_data_size: None, - min_lvm_free: None, - } - } -} - -#[derive(Clone, Debug)] -pub struct BtrfsBootdiskOptions { - pub disk_size: f64, - pub selected_disks: Vec, -} - -impl BtrfsBootdiskOptions { - /// This panics if the provided slice is empty. - pub fn defaults_from(disks: &[Disk]) -> Self { - let disk = &disks[0]; - Self { - disk_size: disk.size, - selected_disks: (0..disks.len()).collect(), - } - } -} - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub enum ZfsCompressOption { - #[default] - On, - Off, - Lzjb, - Lz4, - Zle, - Gzip, - Zstd, -} - -impl fmt::Display for ZfsCompressOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", format!("{self:?}").to_lowercase()) - } -} - -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, Eq, PartialEq)] -pub enum ZfsChecksumOption { - #[default] - On, - Off, - Fletcher2, - Fletcher4, - Sha256, -} - -impl fmt::Display for ZfsChecksumOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", format!("{self:?}").to_lowercase()) - } -} - -impl From<&ZfsChecksumOption> for String { - fn from(value: &ZfsChecksumOption) -> Self { - value.to_string() - } -} - -pub const ZFS_CHECKSUM_OPTIONS: &[ZfsChecksumOption] = { - use ZfsChecksumOption::*; - &[On, Off, Fletcher2, Fletcher4, Sha256] -}; - -#[derive(Clone, Debug)] -pub struct ZfsBootdiskOptions { - pub ashift: usize, - pub compress: ZfsCompressOption, - pub checksum: ZfsChecksumOption, - pub copies: usize, - pub disk_size: f64, - pub selected_disks: Vec, -} - -impl ZfsBootdiskOptions { - /// This panics if the provided slice is empty. - pub fn defaults_from(disks: &[Disk]) -> Self { - let disk = &disks[0]; - Self { - ashift: 12, - compress: ZfsCompressOption::default(), - checksum: ZfsChecksumOption::default(), - copies: 1, - disk_size: disk.size, - selected_disks: (0..disks.len()).collect(), - } - } -} - -#[derive(Clone, Debug)] -pub enum AdvancedBootdiskOptions { - Lvm(LvmBootdiskOptions), - Zfs(ZfsBootdiskOptions), - Btrfs(BtrfsBootdiskOptions), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Disk { - pub index: String, - pub path: String, - pub model: Option, - pub size: f64, - pub block_size: Option, -} - -impl fmt::Display for Disk { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Format sizes properly with `proxmox-human-byte` once merged - // https://lists.proxmox.com/pipermail/pbs-devel/2023-May/006125.html - f.write_str(&self.path)?; - if let Some(model) = &self.model { - // FIXME: ellipsize too-long names? - write!(f, " ({model})")?; - } - write!(f, " ({:.2} GiB)", self.size) - } -} - -impl From<&Disk> for String { - fn from(value: &Disk) -> Self { - value.to_string() - } -} - -impl cmp::Eq for Disk {} - -impl cmp::PartialOrd for Disk { - fn partial_cmp(&self, other: &Self) -> Option { - self.index.partial_cmp(&other.index) - } -} - -impl cmp::Ord for Disk { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.index.cmp(&other.index) - } -} - -#[derive(Clone, Debug)] -pub struct BootdiskOptions { - pub disks: Vec, - pub fstype: FsType, - pub advanced: AdvancedBootdiskOptions, -} - -impl BootdiskOptions { - pub fn defaults_from(disk: &Disk) -> Self { - Self { - disks: vec![disk.clone()], - fstype: FsType::Ext4, - advanced: AdvancedBootdiskOptions::Lvm(LvmBootdiskOptions::defaults_from(disk)), - } - } -} - -#[derive(Clone, Debug)] -pub struct TimezoneOptions { - pub country: String, - pub timezone: String, - pub kb_layout: String, -} - -impl TimezoneOptions { - pub fn defaults_from(runtime: &RuntimeInfo, locales: &LocaleInfo) -> Self { - let country = runtime.country.clone().unwrap_or_else(|| "at".to_owned()); - - let timezone = locales - .cczones - .get(&country) - .and_then(|zones| zones.get(0)) - .cloned() - .unwrap_or_else(|| "UTC".to_owned()); - - let kb_layout = locales - .countries - .get(&country) - .and_then(|c| { - if c.kmap.is_empty() { - None - } else { - Some(c.kmap.clone()) - } - }) - .unwrap_or_else(|| "en-us".to_owned()); - - Self { - country, - timezone, - kb_layout, - } - } -} - -#[derive(Clone, Debug)] -pub struct PasswordOptions { - pub email: String, - pub root_password: String, -} - -impl Default for PasswordOptions { - fn default() -> Self { - Self { - email: "mail@example.invalid".to_string(), - root_password: String::new(), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct NetworkOptions { - pub ifname: String, - pub fqdn: Fqdn, - pub address: CidrAddress, - pub gateway: IpAddr, - pub dns_server: IpAddr, -} - -impl NetworkOptions { - const DEFAULT_DOMAIN: &str = "example.invalid"; - - pub fn defaults_from(setup: &SetupInfo, network: &NetworkInfo) -> Self { - let mut this = Self { - ifname: String::new(), - fqdn: Self::construct_fqdn(network, setup.config.product.default_hostname()), - // Safety: The provided mask will always be valid. - address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(), - gateway: Ipv4Addr::UNSPECIFIED.into(), - dns_server: Ipv4Addr::UNSPECIFIED.into(), - }; - - if let Some(ip) = network.dns.dns.first() { - this.dns_server = *ip; - } - - if let Some(routes) = &network.routes { - let mut filled = false; - if let Some(gw) = &routes.gateway4 { - if let Some(iface) = network.interfaces.get(&gw.dev) { - this.ifname = iface.name.clone(); - if let Some(addresses) = &iface.addresses { - if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv4()) { - this.gateway = gw.gateway; - this.address = addr.clone(); - filled = true; - } - } - } - } - if !filled { - if let Some(gw) = &routes.gateway6 { - if let Some(iface) = network.interfaces.get(&gw.dev) { - if let Some(addresses) = &iface.addresses { - if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv6()) { - this.ifname = iface.name.clone(); - this.gateway = gw.gateway; - this.address = addr.clone(); - } - } - } - } - } - } - - this - } - - fn construct_fqdn(network: &NetworkInfo, default_hostname: &str) -> Fqdn { - let hostname = network.hostname.as_deref().unwrap_or(default_hostname); - - let domain = network - .dns - .domain - .as_deref() - .unwrap_or(Self::DEFAULT_DOMAIN); - - Fqdn::from(&format!("{hostname}.{domain}")).unwrap_or_else(|_| { - // Safety: This will always result in a valid FQDN, as we control & know - // the values of default_hostname (one of "pve", "pmg" or "pbs") and - // constant-defined DEFAULT_DOMAIN. - Fqdn::from(&format!("{}.{}", default_hostname, Self::DEFAULT_DOMAIN)).unwrap() - }) - } -} - #[derive(Clone, Debug)] pub struct InstallerOptions { pub bootdisk: BootdiskOptions, @@ -447,11 +68,15 @@ impl InstallerOptions { #[cfg(test)] mod tests { use super::*; - use crate::setup::{ - Dns, Gateway, Interface, InterfaceState, IsoInfo, IsoLocations, NetworkInfo, ProductConfig, - ProxmoxProduct, Routes, SetupInfo, + use proxmox_installer_common::{ + setup::{ + Dns, Gateway, Interface, InterfaceState, IsoInfo, IsoLocations, NetworkInfo, ProductConfig, + ProxmoxProduct, Routes, SetupInfo, + }, + utils::{CidrAddress, Fqdn}, }; use std::{collections::HashMap, path::PathBuf}; + use std::net::{IpAddr, Ipv4Addr}; fn dummy_setup_info() -> SetupInfo { SetupInfo { diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 5575759..211a96b 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -1,131 +1,20 @@ use std::{ - cmp, collections::HashMap, fmt, fs::File, io::BufReader, net::IpAddr, - path::{Path, PathBuf}, + path::Path, }; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; -use crate::{ - options::{ - AdvancedBootdiskOptions, BtrfsRaidLevel, Disk, FsType, InstallerOptions, - ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption, ZfsRaidLevel, - }, - utils::CidrAddress, -}; - -#[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Copy, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum ProxmoxProduct { - PVE, - PBS, - PMG, -} - -impl ProxmoxProduct { - pub fn default_hostname(self) -> &'static str { - match self { - Self::PVE => "pve", - Self::PMG => "pmg", - Self::PBS => "pbs", - } - } -} - -#[derive(Clone, Deserialize)] -pub struct ProductConfig { - pub fullname: String, - pub product: ProxmoxProduct, - #[serde(deserialize_with = "deserialize_bool_from_int")] - pub enable_btrfs: bool, -} - -#[derive(Clone, Deserialize)] -pub struct IsoInfo { - pub release: String, - pub isorelease: String, -} - -/// Paths in the ISO environment containing installer data. -#[derive(Clone, Deserialize)] -pub struct IsoLocations { - pub iso: PathBuf, -} - -#[derive(Clone, Deserialize)] -pub struct SetupInfo { - #[serde(rename = "product-cfg")] - pub config: ProductConfig, - #[serde(rename = "iso-info")] - pub iso_info: IsoInfo, - pub locations: IsoLocations, -} - -#[derive(Clone, Deserialize)] -pub struct CountryInfo { - pub name: String, - #[serde(default)] - pub zone: String, - pub kmap: String, -} - -#[derive(Clone, Deserialize, Eq, PartialEq)] -pub struct KeyboardMapping { - pub name: String, - #[serde(rename = "kvm")] - pub id: String, - #[serde(rename = "x11")] - pub xkb_layout: String, - #[serde(rename = "x11var")] - pub xkb_variant: String, -} - -impl cmp::PartialOrd for KeyboardMapping { - fn partial_cmp(&self, other: &Self) -> Option { - self.name.partial_cmp(&other.name) - } -} - -impl cmp::Ord for KeyboardMapping { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.name.cmp(&other.name) - } -} - -#[derive(Clone, Deserialize)] -pub struct LocaleInfo { - #[serde(deserialize_with = "deserialize_cczones_map")] - pub cczones: HashMap>, - #[serde(rename = "country")] - pub countries: HashMap, - pub kmap: HashMap, -} - -#[derive(Serialize)] -struct InstallZfsOption { - ashift: usize, - #[serde(serialize_with = "serialize_as_display")] - compress: ZfsCompressOption, - #[serde(serialize_with = "serialize_as_display")] - checksum: ZfsChecksumOption, - copies: usize, -} - -impl From for InstallZfsOption { - fn from(opts: ZfsBootdiskOptions) -> Self { - InstallZfsOption { - ashift: opts.ashift, - compress: opts.compress, - checksum: opts.checksum, - copies: opts.copies, - } - } -} +use crate::options::InstallerOptions; +use proxmox_installer_common::{ + options::{AdvancedBootdiskOptions, BtrfsRaidLevel, Disk, FsType, ZfsRaidLevel}, + setup::InstallZfsOption, + utils::CidrAddress, + }; /// See Proxmox::Install::Config #[derive(Serialize)] @@ -247,82 +136,6 @@ pub fn read_json Deserialize<'de>, P: AsRef>(path: P) -> Resul serde_json::from_reader(reader).map_err(|err| format!("failed to parse JSON: {err}")) } -fn deserialize_bool_from_int<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let val: u32 = Deserialize::deserialize(deserializer)?; - Ok(val != 0) -} - -fn deserialize_cczones_map<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let map: HashMap> = Deserialize::deserialize(deserializer)?; - - let mut result = HashMap::new(); - for (cc, list) in map.into_iter() { - result.insert(cc, list.into_keys().collect()); - } - - Ok(result) -} - -fn deserialize_disks_map<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let disks = - , String)>>::deserialize(deserializer)?; - Ok(disks - .into_iter() - .map( - |(index, device, size_mb, model, logical_bsize, _syspath)| Disk { - index: index.to_string(), - // Linux always reports the size of block devices in sectors, where one sector is - // defined as being 2^9 = 512 bytes in size. - // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30 - size: (size_mb * 512.) / 1024. / 1024. / 1024., - block_size: logical_bsize, - path: device, - model: (!model.is_empty()).then_some(model), - }, - ) - .collect()) -} - -fn deserialize_cidr_list<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct CidrDescriptor { - address: String, - prefix: usize, - // family is implied anyway by parsing the address - } - - let list: Vec = Deserialize::deserialize(deserializer)?; - - let mut result = Vec::with_capacity(list.len()); - for desc in list { - let ip_addr = desc - .address - .parse::() - .map_err(|err| de::Error::custom(format!("{:?}", err)))?; - - result.push( - CidrAddress::new(ip_addr, desc.prefix) - .map_err(|err| de::Error::custom(format!("{:?}", err)))?, - ); - } - - Ok(Some(result)) -} - fn serialize_disk_opt(value: &Option, serializer: S) -> Result where S: Serializer, @@ -366,116 +179,3 @@ where { serializer.collect_str(value) } - -#[derive(Clone, Deserialize)] -pub struct RuntimeInfo { - /// Whether is system was booted in (legacy) BIOS or UEFI mode. - pub boot_type: BootType, - - /// Detected country if available. - pub country: Option, - - /// Maps devices to their information. - #[serde(deserialize_with = "deserialize_disks_map")] - pub disks: Vec, - - /// Network addresses, gateways and DNS info. - pub network: NetworkInfo, - - /// Total memory of the system in MiB. - pub total_memory: usize, - - /// Whether the CPU supports hardware-accelerated virtualization - #[serde(deserialize_with = "deserialize_bool_from_int")] - pub hvm_supported: bool, -} - -#[derive(Copy, Clone, Eq, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum BootType { - Bios, - Efi, -} - -#[derive(Clone, Deserialize)] -pub struct NetworkInfo { - pub dns: Dns, - pub routes: Option, - - /// Maps devices to their configuration, if it has a usable configuration. - /// (Contains no entries for devices with only link-local addresses.) - #[serde(default)] - pub interfaces: HashMap, - - /// The hostname of this machine, if set by the DHCP server. - pub hostname: Option, -} - -#[derive(Clone, Deserialize)] -pub struct Dns { - pub domain: Option, - - /// List of stringified IP addresses. - #[serde(default)] - pub dns: Vec, -} - -#[derive(Clone, Deserialize)] -pub struct Routes { - /// Ipv4 gateway. - pub gateway4: Option, - - /// Ipv6 gateway. - pub gateway6: Option, -} - -#[derive(Clone, Deserialize)] -pub struct Gateway { - /// Outgoing network device. - pub dev: String, - - /// Stringified gateway IP address. - pub gateway: IpAddr, -} - -#[derive(Clone, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum InterfaceState { - Up, - Down, - #[serde(other)] - Unknown, -} - -impl InterfaceState { - // avoid display trait as this is not the string representation for a serializer - pub fn render(&self) -> String { - match self { - Self::Up => "\u{25CF}", - Self::Down | Self::Unknown => " ", - } - .into() - } -} - -#[derive(Clone, Deserialize)] -pub struct Interface { - pub name: String, - - pub index: usize, - - pub mac: String, - - pub state: InterfaceState, - - #[serde(default)] - #[serde(deserialize_with = "deserialize_cidr_list")] - pub addresses: Option>, -} - -impl Interface { - // avoid display trait as this is not the string representation for a serializer - pub fn render(&self) -> String { - format!("{} {}", self.state.render(), self.name) - } -} diff --git a/proxmox-tui-installer/src/system.rs b/proxmox-tui-installer/src/system.rs index bbf13b8..d1675a9 100644 --- a/proxmox-tui-installer/src/system.rs +++ b/proxmox-tui-installer/src/system.rs @@ -1,6 +1,6 @@ use std::{fs::OpenOptions, io::Write, process::Command}; -use crate::setup::KeyboardMapping; +use proxmox_installer_common::setup::KeyboardMapping; pub fn set_keyboard_layout(kmap: &KeyboardMapping) -> Result<(), String> { Command::new("setxkbmap") diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index ba08c8b..38a6521 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashSet, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, marker::PhantomData, rc::Rc}; use cursive::{ view::{Nameable, Resizable, ViewWrapper}, @@ -10,15 +10,18 @@ use cursive::{ }; use super::{DiskSizeEditView, FormView, IntegerEditView}; -use crate::{ +use crate::options::FS_TYPES; +use crate::InstallerState; + +use proxmox_installer_common::{ options::{ - AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, BtrfsRaidLevel, Disk, - FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZfsRaidLevel, FS_TYPES, + AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, Disk, + FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, }, - setup::{BootType, ProductConfig}, + setup::{BootType, ProductConfig, ProxmoxProduct}, + disk_checks::{check_btrfs_raid_config, check_for_duplicate_disks, check_disks_4kn_legacy_boot, check_zfs_raid_config}, }; -use crate::{setup::ProxmoxProduct, InstallerState}; pub struct BootdiskOptionsView { view: LinearLayout, @@ -619,236 +622,3 @@ fn advanced_options_view( .with_name("advanced-bootdisk-options-dialog") .max_size((120, 40)) } - -/// Checks a list of disks for duplicate entries, using their index as key. -/// -/// # Arguments -/// -/// * `disks` - A list of disks to check for duplicates. -fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { - let mut set = HashSet::new(); - - for disk in disks { - if !set.insert(&disk.index) { - return Err(disk); - } - } - - Ok(()) -} - -/// Simple wrapper which returns an descriptive error if the list of disks is too short. -/// -/// # Arguments -/// -/// * `disks` - A list of disks to check the lenght of. -/// * `min` - Minimum number of disks -fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { - if disks.len() < min { - Err(format!("Need at least {min} disks")) - } else { - Ok(()) - } -} - -/// Checks all disks for legacy BIOS boot compatibility and reports an error as appropriate. 4Kn -/// disks are generally broken with legacy BIOS and cannot be booted from. -/// -/// # Arguments -/// -/// * `runinfo` - `RuntimeInfo` instance of currently running system -/// * `disks` - List of disks designated as bootdisk targets. -fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { - let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false); - - if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) { - return Err("Booting from 4Kn drive in legacy BIOS mode is not supported."); - } - - Ok(()) -} - -/// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum -/// number of disks. -/// -/// # Arguments -/// -/// * `level` - The targeted ZFS RAID level by the user. -/// * `disks` - List of disks designated as RAID targets. -fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { - // See also Proxmox/Install.pm:get_zfs_raid_setup() - - let check_mirror_size = |disk1: &Disk, disk2: &Disk| { - if (disk1.size - disk2.size).abs() > disk1.size / 10. { - Err(format!( - "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}" - )) - } else { - Ok(()) - } - }; - - match level { - ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, - ZfsRaidLevel::Raid1 => { - check_raid_min_disks(disks, 2)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - ZfsRaidLevel::Raid10 => { - check_raid_min_disks(disks, 4)?; - // Pairs need to have the same size - for i in (0..disks.len()).step_by(2) { - check_mirror_size(&disks[i], &disks[i + 1])?; - } - } - // For RAID-Z: minimum disks number is level + 2 - ZfsRaidLevel::RaidZ => { - check_raid_min_disks(disks, 3)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - ZfsRaidLevel::RaidZ2 => { - check_raid_min_disks(disks, 4)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - ZfsRaidLevel::RaidZ3 => { - check_raid_min_disks(disks, 5)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - } - - Ok(()) -} - -/// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum -/// number of disks. -/// -/// # Arguments -/// -/// * `level` - The targeted Btrfs RAID level by the user. -/// * `disks` - List of disks designated as RAID targets. -fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { - // See also Proxmox/Install.pm:get_btrfs_raid_setup() - - match level { - BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, - BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?, - BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?, - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn dummy_disk(index: usize) -> Disk { - Disk { - index: index.to_string(), - path: format!("/dev/dummy{index}"), - model: Some("Dummy disk".to_owned()), - size: 1024. * 1024. * 1024. * 8., - block_size: Some(512), - } - } - - fn dummy_disks(num: usize) -> Vec { - (0..num).map(dummy_disk).collect() - } - - #[test] - fn duplicate_disks() { - assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok()); - assert_eq!( - check_for_duplicate_disks(&[ - dummy_disk(0), - dummy_disk(1), - dummy_disk(2), - dummy_disk(2), - dummy_disk(3), - ]), - Err(&dummy_disk(2)), - ); - } - - #[test] - fn raid_min_disks() { - let disks = dummy_disks(10); - - assert!(check_raid_min_disks(&disks[..1], 2).is_err()); - assert!(check_raid_min_disks(&disks[..1], 1).is_ok()); - assert!(check_raid_min_disks(&disks, 1).is_ok()); - } - - #[test] - fn bios_boot_compat_4kn() { - for i in 0..10 { - let mut disks = dummy_disks(10); - disks[i].block_size = Some(4096); - - // Must fail if /any/ of the disks are 4Kn - assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err()); - // For UEFI, we allow it for every configuration - assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok()); - } - } - - #[test] - fn btrfs_raid() { - let disks = dummy_disks(10); - - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok()); - - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok()); - - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok()); - } - - #[test] - fn zfs_raid() { - let disks = dummy_disks(10); - - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok()); - } -} diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index aa24fa4..aabae0e 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -7,7 +7,7 @@ use cursive::{ Rect, Vec2, View, }; -use crate::utils::CidrAddress; +use proxmox_installer_common::utils::CidrAddress; mod bootdisk; pub use bootdisk::*; diff --git a/proxmox-tui-installer/src/views/timezone.rs b/proxmox-tui-installer/src/views/timezone.rs index 6732286..77fbb10 100644 --- a/proxmox-tui-installer/src/views/timezone.rs +++ b/proxmox-tui-installer/src/views/timezone.rs @@ -6,9 +6,11 @@ use cursive::{ use super::FormView; use crate::{ + system, InstallerState, +}; +use proxmox_installer_common::{ options::TimezoneOptions, setup::{KeyboardMapping, LocaleInfo}, - system, InstallerState, }; pub struct TimezoneOptionsView { -- 2.39.2