]>
git.proxmox.com Git - pve-installer.git/blob - proxmox-installer-common/src/options.rs
1 use std
::net
::{IpAddr, Ipv4Addr}
;
5 LocaleInfo
, NetworkInfo
, ProductConfig
, ProxmoxProduct
, RuntimeInfo
, SetupInfo
,
7 use crate::utils
::{CidrAddress, Fqdn}
;
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
10 pub enum BtrfsRaidLevel
{
16 impl fmt
::Display
for BtrfsRaidLevel
{
17 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
18 use BtrfsRaidLevel
::*;
20 Raid0
=> write
!(f
, "RAID0"),
21 Raid1
=> write
!(f
, "RAID1"),
22 Raid10
=> write
!(f
, "RAID10"),
27 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
28 pub enum ZfsRaidLevel
{
37 impl fmt
::Display
for ZfsRaidLevel
{
38 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
41 Raid0
=> write
!(f
, "RAID0"),
42 Raid1
=> write
!(f
, "RAID1"),
43 Raid10
=> write
!(f
, "RAID10"),
44 RaidZ
=> write
!(f
, "RAIDZ-1"),
45 RaidZ2
=> write
!(f
, "RAIDZ-2"),
46 RaidZ3
=> write
!(f
, "RAIDZ-3"),
51 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
56 Btrfs(BtrfsRaidLevel
),
60 pub fn is_btrfs(&self) -> bool
{
61 matches
!(self, FsType
::Btrfs(_
))
65 impl fmt
::Display
for FsType
{
66 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
69 Ext4
=> write
!(f
, "ext4"),
70 Xfs
=> write
!(f
, "XFS"),
71 Zfs(level
) => write
!(f
, "ZFS ({level})"),
72 Btrfs(level
) => write
!(f
, "Btrfs ({level})"),
77 #[derive(Clone, Debug)]
78 pub struct LvmBootdiskOptions
{
80 pub swap_size
: Option
<f64>,
81 pub max_root_size
: Option
<f64>,
82 pub max_data_size
: Option
<f64>,
83 pub min_lvm_free
: Option
<f64>,
86 impl LvmBootdiskOptions
{
87 pub fn defaults_from(disk
: &Disk
) -> Self {
89 total_size
: disk
.size
,
98 #[derive(Clone, Debug)]
99 pub struct BtrfsBootdiskOptions
{
101 pub selected_disks
: Vec
<usize>,
104 impl BtrfsBootdiskOptions
{
105 /// This panics if the provided slice is empty.
106 pub fn defaults_from(disks
: &[Disk
]) -> Self {
107 let disk
= &disks
[0];
109 disk_size
: disk
.size
,
110 selected_disks
: (0..disks
.len()).collect(),
115 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
116 pub enum ZfsCompressOption
{
127 impl fmt
::Display
for ZfsCompressOption
{
128 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
129 write
!(f
, "{}", format
!("{self:?}").to_lowercase())
133 impl From
<&ZfsCompressOption
> for String
{
134 fn from(value
: &ZfsCompressOption
) -> Self {
139 pub const ZFS_COMPRESS_OPTIONS
: &[ZfsCompressOption
] = {
140 use ZfsCompressOption
::*;
141 &[On
, Off
, Lzjb
, Lz4
, Zle
, Gzip
, Zstd
]
144 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
145 pub enum ZfsChecksumOption
{
153 impl fmt
::Display
for ZfsChecksumOption
{
154 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
155 write
!(f
, "{}", format
!("{self:?}").to_lowercase())
159 impl From
<&ZfsChecksumOption
> for String
{
160 fn from(value
: &ZfsChecksumOption
) -> Self {
165 pub const ZFS_CHECKSUM_OPTIONS
: &[ZfsChecksumOption
] = {
166 use ZfsChecksumOption
::*;
167 &[On
, Off
, Fletcher4
, Sha256
]
170 #[derive(Clone, Debug)]
171 pub struct ZfsBootdiskOptions
{
173 pub compress
: ZfsCompressOption
,
174 pub checksum
: ZfsChecksumOption
,
178 pub selected_disks
: Vec
<usize>,
181 impl ZfsBootdiskOptions
{
182 /// Panics if the disk list is empty.
183 pub fn defaults_from(runinfo
: &RuntimeInfo
, product_conf
: &ProductConfig
) -> Self {
184 let disk
= &runinfo
.disks
[0];
187 compress
: ZfsCompressOption
::default(),
188 checksum
: ZfsChecksumOption
::default(),
190 arc_max
: default_zfs_arc_max(product_conf
.product
, runinfo
.total_memory
),
191 disk_size
: disk
.size
,
192 selected_disks
: (0..runinfo
.disks
.len()).collect(),
197 /// Calculates the default upper limit for the ZFS ARC size.
198 /// See also <https://bugzilla.proxmox.com/show_bug.cgi?id=4829> and
199 /// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max
202 /// * `product` - The product to be installed
203 /// * `total_memory` - Total memory installed in the system, in MiB
206 /// The default ZFS maximum ARC size in MiB for this system.
207 fn default_zfs_arc_max(product
: ProxmoxProduct
, total_memory
: usize) -> usize {
208 if product
!= ProxmoxProduct
::PVE
{
209 // Use ZFS default for non-PVE
212 ((total_memory
as f64) / 10.)
214 .clamp(64., 16. * 1024.) as usize
218 #[derive(Clone, Debug)]
219 pub enum AdvancedBootdiskOptions
{
220 Lvm(LvmBootdiskOptions
),
221 Zfs(ZfsBootdiskOptions
),
222 Btrfs(BtrfsBootdiskOptions
),
225 #[derive(Clone, Debug, PartialEq)]
229 pub model
: Option
<String
>,
231 pub block_size
: Option
<usize>,
234 impl fmt
::Display
for Disk
{
235 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
236 // TODO: Format sizes properly with `proxmox-human-byte` once merged
237 // https://lists.proxmox.com/pipermail/pbs-devel/2023-May/006125.html
238 f
.write_str(&self.path
)?
;
239 if let Some(model
) = &self.model
{
240 // FIXME: ellipsize too-long names?
241 write
!(f
, " ({model})")?
;
243 write
!(f
, " ({:.2} GiB)", self.size
)
247 impl From
<&Disk
> for String
{
248 fn from(value
: &Disk
) -> Self {
253 impl cmp
::Eq
for Disk {}
255 impl cmp
::PartialOrd
for Disk
{
256 fn partial_cmp(&self, other
: &Self) -> Option
<cmp
::Ordering
> {
257 self.index
.partial_cmp(&other
.index
)
261 impl cmp
::Ord
for Disk
{
262 fn cmp(&self, other
: &Self) -> cmp
::Ordering
{
263 self.index
.cmp(&other
.index
)
267 #[derive(Clone, Debug)]
268 pub struct BootdiskOptions
{
269 pub disks
: Vec
<Disk
>,
271 pub advanced
: AdvancedBootdiskOptions
,
274 impl BootdiskOptions
{
275 pub fn defaults_from(disk
: &Disk
) -> Self {
277 disks
: vec
![disk
.clone()],
278 fstype
: FsType
::Ext4
,
279 advanced
: AdvancedBootdiskOptions
::Lvm(LvmBootdiskOptions
::defaults_from(disk
)),
284 #[derive(Clone, Debug)]
285 pub struct TimezoneOptions
{
287 pub timezone
: String
,
288 pub kb_layout
: String
,
291 impl TimezoneOptions
{
292 pub fn defaults_from(runtime
: &RuntimeInfo
, locales
: &LocaleInfo
) -> Self {
293 let country
= runtime
.country
.clone().unwrap_or_else(|| "at".to_owned());
295 let timezone
= locales
298 .and_then(|zones
| zones
.get(0))
300 .unwrap_or_else(|| "UTC".to_owned());
302 let kb_layout
= locales
306 if c
.kmap
.is_empty() {
312 .unwrap_or_else(|| "en-us".to_owned());
322 #[derive(Clone, Debug)]
323 pub struct PasswordOptions
{
325 pub root_password
: String
,
328 impl Default
for PasswordOptions
{
329 fn default() -> Self {
331 email
: "mail@example.invalid".to_string(),
332 root_password
: String
::new(),
337 #[derive(Clone, Debug, PartialEq)]
338 pub struct NetworkOptions
{
341 pub address
: CidrAddress
,
343 pub dns_server
: IpAddr
,
346 impl NetworkOptions
{
347 const DEFAULT_DOMAIN
: &str = "example.invalid";
349 pub fn defaults_from(setup
: &SetupInfo
, network
: &NetworkInfo
) -> Self {
350 let mut this
= Self {
351 ifname
: String
::new(),
352 fqdn
: Self::construct_fqdn(network
, setup
.config
.product
.default_hostname()),
353 // Safety: The provided mask will always be valid.
354 address
: CidrAddress
::new(Ipv4Addr
::UNSPECIFIED
, 0).unwrap(),
355 gateway
: Ipv4Addr
::UNSPECIFIED
.into(),
356 dns_server
: Ipv4Addr
::UNSPECIFIED
.into(),
359 if let Some(ip
) = network
.dns
.dns
.first() {
360 this
.dns_server
= *ip
;
363 if let Some(routes
) = &network
.routes
{
364 let mut filled
= false;
365 if let Some(gw
) = &routes
.gateway4
{
366 if let Some(iface
) = network
.interfaces
.get(&gw
.dev
) {
367 this
.ifname
= iface
.name
.clone();
368 if let Some(addresses
) = &iface
.addresses
{
369 if let Some(addr
) = addresses
.iter().find(|addr
| addr
.is_ipv4()) {
370 this
.gateway
= gw
.gateway
;
371 this
.address
= addr
.clone();
378 if let Some(gw
) = &routes
.gateway6
{
379 if let Some(iface
) = network
.interfaces
.get(&gw
.dev
) {
380 if let Some(addresses
) = &iface
.addresses
{
381 if let Some(addr
) = addresses
.iter().find(|addr
| addr
.is_ipv6()) {
382 this
.ifname
= iface
.name
.clone();
383 this
.gateway
= gw
.gateway
;
384 this
.address
= addr
.clone();
395 fn construct_fqdn(network
: &NetworkInfo
, default_hostname
: &str) -> Fqdn
{
396 let hostname
= network
.hostname
.as_deref().unwrap_or(default_hostname
);
402 .unwrap_or(Self::DEFAULT_DOMAIN
);
404 Fqdn
::from(&format
!("{hostname}.{domain}")).unwrap_or_else(|_
| {
405 // Safety: This will always result in a valid FQDN, as we control & know
406 // the values of default_hostname (one of "pve", "pmg" or "pbs") and
407 // constant-defined DEFAULT_DOMAIN.
408 Fqdn
::from(&format
!("{}.{}", default_hostname
, Self::DEFAULT_DOMAIN
)).unwrap()
419 const TESTS
: &[(usize, usize)] = &[
420 (16, 64), // at least 64 MiB
426 (1024 * 1024, 16384), // maximum of 16 GiB
429 for (total_memory
, expected
) in TESTS
{
431 default_zfs_arc_max(ProxmoxProduct
::PVE
, *total_memory
),
434 assert_eq
!(default_zfs_arc_max(ProxmoxProduct
::PBS
, *total_memory
), 0);
435 assert_eq
!(default_zfs_arc_max(ProxmoxProduct
::PMG
, *total_memory
), 0);