]> git.proxmox.com Git - pve-installer.git/blobdiff - proxmox-tui-installer/src/options.rs
bump version to 8.2.6
[pve-installer.git] / proxmox-tui-installer / src / options.rs
index fbc11ef4f9be7886999bbc8a0a939c4c7cfec787..73fbf2ab1f50557338cfcd443a8b314ce64adb0e 100644 (file)
@@ -1,73 +1,12 @@
-use std::net::{IpAddr, Ipv4Addr};
-use std::{cmp, fmt};
-
-use proxmox_sys::linux::procfs;
-
-use crate::setup::{LocaleInfo, NetworkInfo};
-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 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::{
+    options::{
+        BootdiskOptions, BtrfsRaidLevel, FsType, NetworkOptions, PasswordOptions, TimezoneOptions,
+        ZfsRaidLevel,
+    },
+    setup::LocaleInfo,
+};
 
 pub const FS_TYPES: &[FsType] = {
     use FsType::*;
@@ -86,299 +25,13 @@ pub const FS_TYPES: &[FsType] = {
     ]
 };
 
-#[derive(Clone, Debug)]
-pub struct LvmBootdiskOptions {
-    pub total_size: u64,
-    pub swap_size: u64,
-    pub max_root_size: u64,
-    pub max_data_size: u64,
-    pub min_lvm_free: u64,
-}
-
-impl LvmBootdiskOptions {
-    /// Sets the default values as described in the documentation:
-    /// https://pve.proxmox.com/pve-docs/pve-admin-guide.html#advanced_lvm_options
-    pub fn defaults_from(disk: &Disk) -> Self {
-        let mem_total = procfs::read_meminfo()
-            .map(|m| m.memtotal)
-            .unwrap_or(4 * 1024 * 1024 * 1024);
-
-        // Clamp to 4 GiB <= total system memory <= 8 GiB
-        let swap_size = mem_total.clamp(4 * 1024 * 1024 * 1024, 8 * 1024 * 1024 * 1024);
-
-        // If the disk size > 128 GiB use 16 GiB, else 1/8 of the disk
-        let min_lvm_free = if disk.size > 128 * 1024 * 1024 {
-            16 * 1024 * 1024 * 1024
-        } else {
-            disk.size / 8
-        };
-
-        Self {
-            total_size: disk.size,
-            swap_size,
-            max_root_size: 0,
-            max_data_size: 0,
-            min_lvm_free,
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct BtrfsBootdiskOptions {
-    pub disk_size: u64,
-}
-
-impl BtrfsBootdiskOptions {
-    pub fn defaults_from(disk: &Disk) -> Self {
-        Self {
-            disk_size: disk.size,
-        }
-    }
-}
-
-#[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: u64,
-}
-
-impl ZfsBootdiskOptions {
-    pub fn defaults_from(disk: &Disk) -> Self {
-        Self {
-            ashift: 12,
-            compress: ZfsCompressOption::default(),
-            checksum: ZfsChecksumOption::default(),
-            copies: 1,
-            disk_size: disk.size,
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub enum AdvancedBootdiskOptions {
-    Lvm(LvmBootdiskOptions),
-    Zfs(ZfsBootdiskOptions),
-    Btrfs(BtrfsBootdiskOptions),
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Disk {
-    pub path: String,
-    pub size: u64,
-}
-
-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
-        write!(
-            f,
-            "{} ({:.2} GiB)",
-            self.path,
-            (self.size as f64) / 1024. / 1024. / 1024.
-        )
-    }
-}
-
-impl From<&Disk> for String {
-    fn from(value: &Disk) -> Self {
-        value.to_string()
-    }
-}
-
-impl cmp::PartialOrd for Disk {
-    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-        self.path.partial_cmp(&other.path)
-    }
-}
-
-impl cmp::Ord for Disk {
-    fn cmp(&self, other: &Self) -> cmp::Ordering {
-        self.path.cmp(&other.path)
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct BootdiskOptions {
-    pub disks: Vec<Disk>,
-    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 Default for TimezoneOptions {
-    fn default() -> Self {
-        Self {
-            country: "at".to_owned(),
-            timezone: "Europe/Vienna".to_owned(),
-            kb_layout: "en-us".to_owned(),
-        }
-    }
-}
-
-#[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_owned(),
-            root_password: String::new(),
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct NetworkOptions {
-    pub ifname: String,
-    pub fqdn: Fqdn,
-    pub address: CidrAddress,
-    pub gateway: IpAddr,
-    pub dns_server: IpAddr,
-}
-
-impl Default for NetworkOptions {
-    fn default() -> Self {
-        // TODO: Retrieve automatically
-        Self {
-            ifname: String::new(),
-            fqdn: "pve.example.invalid".parse().unwrap(),
-            // Safety: The provided mask will always be valid.
-            address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(),
-            gateway: Ipv4Addr::UNSPECIFIED.into(),
-            dns_server: Ipv4Addr::UNSPECIFIED.into(),
-        }
-    }
-}
-
-impl From<&NetworkInfo> for NetworkOptions {
-    fn from(info: &NetworkInfo) -> Self {
-        let mut this = Self::default();
-
-        if let Some(ip) = info.dns.dns.first() {
-            this.dns_server = *ip;
-        }
-
-        if let Some(domain) = &info.dns.domain {
-            this.fqdn = domain.clone();
-        }
-
-        let mut filled = false;
-        if let Some(gw) = &info.routes.gateway4 {
-            if let Ok(gwip) = gw.gateway.parse() {
-                if let Some(iface) = info.interfaces.get(&gw.dev) {
-                    if let Some(addr) = iface.addresses.iter().find(|addr| addr.is_ipv4()) {
-                        this.ifname = iface.name.clone();
-                        this.gateway = gwip;
-                        this.address = addr.clone();
-                        filled = true;
-                    }
-                }
-            }
-        }
-        if !filled {
-            if let Some(gw) = &info.routes.gateway6 {
-                if let Ok(gwip) = gw.gateway.parse() {
-                    if let Some(iface) = info.interfaces.get(&gw.dev) {
-                        if let Some(addr) = iface.addresses.iter().find(|addr| addr.is_ipv6()) {
-                            this.ifname = iface.name.clone();
-                            this.gateway = gwip;
-                            this.address = addr.clone();
-                        }
-                    }
-                }
-            }
-        }
-
-        this
-    }
-}
-
 #[derive(Clone, Debug)]
 pub struct InstallerOptions {
     pub bootdisk: BootdiskOptions,
     pub timezone: TimezoneOptions,
     pub password: PasswordOptions,
     pub network: NetworkOptions,
-    pub reboot: bool,
+    pub autoreboot: bool,
 }
 
 impl InstallerOptions {
@@ -402,7 +55,7 @@ impl InstallerOptions {
             ),
             SummaryOption::new("Timezone", &self.timezone.timezone),
             SummaryOption::new("Keyboard layout", kb_layout),
-            SummaryOption::new("Administator email", &self.password.email),
+            SummaryOption::new("Administrator email", &self.password.email),
             SummaryOption::new("Management interface", &self.network.ifname),
             SummaryOption::new("Hostname", self.network.fqdn.to_string()),
             SummaryOption::new("Host IP (CIDR)", self.network.address.to_string()),
@@ -411,3 +64,116 @@ impl InstallerOptions {
         ]
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proxmox_installer_common::{
+        setup::{
+            Dns, Gateway, Interface, InterfaceState, IsoInfo, IsoLocations, NetworkInfo,
+            ProductConfig, ProxmoxProduct, Routes, SetupInfo,
+        },
+        utils::{CidrAddress, Fqdn},
+    };
+    use std::net::{IpAddr, Ipv4Addr};
+    use std::{collections::BTreeMap, path::PathBuf};
+
+    fn dummy_setup_info() -> SetupInfo {
+        SetupInfo {
+            config: ProductConfig {
+                fullname: "Proxmox VE".to_owned(),
+                product: ProxmoxProduct::PVE,
+                enable_btrfs: true,
+            },
+            iso_info: IsoInfo {
+                release: String::new(),
+                isorelease: String::new(),
+            },
+            locations: IsoLocations {
+                iso: PathBuf::new(),
+            },
+        }
+    }
+
+    #[test]
+    fn network_options_from_setup_network_info() {
+        let setup = dummy_setup_info();
+
+        let mut interfaces = BTreeMap::new();
+        interfaces.insert(
+            "eth0".to_owned(),
+            Interface {
+                name: "eth0".to_owned(),
+                index: 0,
+                state: InterfaceState::Up,
+                mac: "01:23:45:67:89:ab".to_owned(),
+                addresses: Some(vec![
+                    CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap()
+                ]),
+            },
+        );
+
+        let mut info = NetworkInfo {
+            dns: Dns {
+                domain: Some("bar.com".to_owned()),
+                dns: Vec::new(),
+            },
+            routes: Some(Routes {
+                gateway4: Some(Gateway {
+                    dev: "eth0".to_owned(),
+                    gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
+                }),
+                gateway6: None,
+            }),
+            interfaces,
+            hostname: Some("foo".to_owned()),
+        };
+
+        assert_eq!(
+            NetworkOptions::defaults_from(&setup, &info),
+            NetworkOptions {
+                ifname: "eth0".to_owned(),
+                fqdn: Fqdn::from("foo.bar.com").unwrap(),
+                address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(),
+                gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
+                dns_server: Ipv4Addr::UNSPECIFIED.into(),
+            }
+        );
+
+        info.hostname = None;
+        assert_eq!(
+            NetworkOptions::defaults_from(&setup, &info),
+            NetworkOptions {
+                ifname: "eth0".to_owned(),
+                fqdn: Fqdn::from("pve.bar.com").unwrap(),
+                address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(),
+                gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
+                dns_server: Ipv4Addr::UNSPECIFIED.into(),
+            }
+        );
+
+        info.dns.domain = None;
+        assert_eq!(
+            NetworkOptions::defaults_from(&setup, &info),
+            NetworkOptions {
+                ifname: "eth0".to_owned(),
+                fqdn: Fqdn::from("pve.example.invalid").unwrap(),
+                address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(),
+                gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
+                dns_server: Ipv4Addr::UNSPECIFIED.into(),
+            }
+        );
+
+        info.hostname = Some("foo".to_owned());
+        assert_eq!(
+            NetworkOptions::defaults_from(&setup, &info),
+            NetworkOptions {
+                ifname: "eth0".to_owned(),
+                fqdn: Fqdn::from("foo.example.invalid").unwrap(),
+                address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(),
+                gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
+                dns_server: Ipv4Addr::UNSPECIFIED.into(),
+            }
+        );
+    }
+}