]>
git.proxmox.com Git - pve-installer.git/blob - proxmox-installer-common/src/options.rs
1 use serde
::Deserialize
;
2 use std
::net
::{IpAddr, Ipv4Addr}
;
6 LocaleInfo
, NetworkInfo
, ProductConfig
, ProxmoxProduct
, RuntimeInfo
, SetupInfo
,
8 use crate::utils
::{CidrAddress, Fqdn}
;
10 #[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
11 #[serde(rename_all = "lowercase")]
12 pub enum BtrfsRaidLevel
{
18 impl fmt
::Display
for BtrfsRaidLevel
{
19 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
20 use BtrfsRaidLevel
::*;
22 Raid0
=> write
!(f
, "RAID0"),
23 Raid1
=> write
!(f
, "RAID1"),
24 Raid10
=> write
!(f
, "RAID10"),
29 #[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
30 #[serde(rename_all = "lowercase")]
31 pub enum ZfsRaidLevel
{
35 #[serde(rename = "raidz-1")]
37 #[serde(rename = "raidz-2")]
39 #[serde(rename = "raidz-3")]
43 impl fmt
::Display
for ZfsRaidLevel
{
44 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
47 Raid0
=> write
!(f
, "RAID0"),
48 Raid1
=> write
!(f
, "RAID1"),
49 Raid10
=> write
!(f
, "RAID10"),
50 RaidZ
=> write
!(f
, "RAIDZ-1"),
51 RaidZ2
=> write
!(f
, "RAIDZ-2"),
52 RaidZ3
=> write
!(f
, "RAIDZ-3"),
57 #[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
58 #[serde(rename_all = "lowercase")]
63 Btrfs(BtrfsRaidLevel
),
67 pub fn is_btrfs(&self) -> bool
{
68 matches
!(self, FsType
::Btrfs(_
))
72 impl fmt
::Display
for FsType
{
73 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
76 Ext4
=> write
!(f
, "ext4"),
77 Xfs
=> write
!(f
, "XFS"),
78 Zfs(level
) => write
!(f
, "ZFS ({level})"),
79 Btrfs(level
) => write
!(f
, "Btrfs ({level})"),
84 #[derive(Clone, Debug)]
85 pub struct LvmBootdiskOptions
{
87 pub swap_size
: Option
<f64>,
88 pub max_root_size
: Option
<f64>,
89 pub max_data_size
: Option
<f64>,
90 pub min_lvm_free
: Option
<f64>,
93 impl LvmBootdiskOptions
{
94 pub fn defaults_from(disk
: &Disk
) -> Self {
96 total_size
: disk
.size
,
105 #[derive(Clone, Debug)]
106 pub struct BtrfsBootdiskOptions
{
108 pub selected_disks
: Vec
<usize>,
111 impl BtrfsBootdiskOptions
{
112 /// This panics if the provided slice is empty.
113 pub fn defaults_from(disks
: &[Disk
]) -> Self {
114 let disk
= &disks
[0];
116 disk_size
: disk
.size
,
117 selected_disks
: (0..disks
.len()).collect(),
122 #[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq)]
123 #[serde(rename_all(deserialize = "lowercase"))]
124 pub enum ZfsCompressOption
{
135 impl fmt
::Display
for ZfsCompressOption
{
136 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
137 write
!(f
, "{}", format
!("{self:?}").to_lowercase())
141 impl From
<&ZfsCompressOption
> for String
{
142 fn from(value
: &ZfsCompressOption
) -> Self {
147 pub const ZFS_COMPRESS_OPTIONS
: &[ZfsCompressOption
] = {
148 use ZfsCompressOption
::*;
149 &[On
, Off
, Lzjb
, Lz4
, Zle
, Gzip
, Zstd
]
152 #[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq)]
153 #[serde(rename_all = "kebab-case")]
154 pub enum ZfsChecksumOption
{
161 impl fmt
::Display
for ZfsChecksumOption
{
162 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
163 write
!(f
, "{}", format
!("{self:?}").to_lowercase())
167 impl From
<&ZfsChecksumOption
> for String
{
168 fn from(value
: &ZfsChecksumOption
) -> Self {
173 pub const ZFS_CHECKSUM_OPTIONS
: &[ZfsChecksumOption
] = {
174 use ZfsChecksumOption
::*;
175 &[On
, Fletcher4
, Sha256
]
178 #[derive(Clone, Debug)]
179 pub struct ZfsBootdiskOptions
{
181 pub compress
: ZfsCompressOption
,
182 pub checksum
: ZfsChecksumOption
,
186 pub selected_disks
: Vec
<usize>,
189 impl ZfsBootdiskOptions
{
190 /// Panics if the disk list is empty.
191 pub fn defaults_from(runinfo
: &RuntimeInfo
, product_conf
: &ProductConfig
) -> Self {
192 let disk
= &runinfo
.disks
[0];
195 compress
: ZfsCompressOption
::default(),
196 checksum
: ZfsChecksumOption
::default(),
198 arc_max
: default_zfs_arc_max(product_conf
.product
, runinfo
.total_memory
),
199 disk_size
: disk
.size
,
200 selected_disks
: (0..runinfo
.disks
.len()).collect(),
205 /// Calculates the default upper limit for the ZFS ARC size.
206 /// See also <https://bugzilla.proxmox.com/show_bug.cgi?id=4829> and
207 /// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max
210 /// * `product` - The product to be installed
211 /// * `total_memory` - Total memory installed in the system, in MiB
214 /// The default ZFS maximum ARC size in MiB for this system.
215 fn default_zfs_arc_max(product
: ProxmoxProduct
, total_memory
: usize) -> usize {
216 if product
!= ProxmoxProduct
::PVE
{
217 // Use ZFS default for non-PVE
220 ((total_memory
as f64) / 10.)
222 .clamp(64., 16. * 1024.) as usize
226 #[derive(Clone, Debug)]
227 pub enum AdvancedBootdiskOptions
{
228 Lvm(LvmBootdiskOptions
),
229 Zfs(ZfsBootdiskOptions
),
230 Btrfs(BtrfsBootdiskOptions
),
233 #[derive(Clone, Debug, Deserialize, PartialEq)]
237 pub model
: Option
<String
>,
239 pub block_size
: Option
<usize>,
242 impl fmt
::Display
for Disk
{
243 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
244 // TODO: Format sizes properly with `proxmox-human-byte` once merged
245 // https://lists.proxmox.com/pipermail/pbs-devel/2023-May/006125.html
246 f
.write_str(&self.path
)?
;
247 if let Some(model
) = &self.model
{
248 // FIXME: ellipsize too-long names?
249 write
!(f
, " ({model})")?
;
251 write
!(f
, " ({:.2} GiB)", self.size
)
255 impl From
<&Disk
> for String
{
256 fn from(value
: &Disk
) -> Self {
261 impl cmp
::Eq
for Disk {}
263 impl cmp
::PartialOrd
for Disk
{
264 fn partial_cmp(&self, other
: &Self) -> Option
<cmp
::Ordering
> {
265 self.index
.partial_cmp(&other
.index
)
269 impl cmp
::Ord
for Disk
{
270 fn cmp(&self, other
: &Self) -> cmp
::Ordering
{
271 self.index
.cmp(&other
.index
)
275 #[derive(Clone, Debug)]
276 pub struct BootdiskOptions
{
277 pub disks
: Vec
<Disk
>,
279 pub advanced
: AdvancedBootdiskOptions
,
282 impl BootdiskOptions
{
283 pub fn defaults_from(disk
: &Disk
) -> Self {
285 disks
: vec
![disk
.clone()],
286 fstype
: FsType
::Ext4
,
287 advanced
: AdvancedBootdiskOptions
::Lvm(LvmBootdiskOptions
::defaults_from(disk
)),
292 #[derive(Clone, Debug)]
293 pub struct TimezoneOptions
{
295 pub timezone
: String
,
296 pub kb_layout
: String
,
299 impl TimezoneOptions
{
300 pub fn defaults_from(runtime
: &RuntimeInfo
, locales
: &LocaleInfo
) -> Self {
301 let country
= runtime
.country
.clone().unwrap_or_else(|| "at".to_owned());
303 let timezone
= locales
306 .and_then(|zones
| zones
.first())
308 .unwrap_or_else(|| "UTC".to_owned());
310 let kb_layout
= locales
314 if c
.kmap
.is_empty() {
320 .unwrap_or_else(|| "en-us".to_owned());
330 #[derive(Clone, Debug)]
331 pub struct PasswordOptions
{
333 pub root_password
: String
,
336 impl Default
for PasswordOptions
{
337 fn default() -> Self {
339 email
: "mail@example.invalid".to_string(),
340 root_password
: String
::new(),
345 #[derive(Clone, Debug, PartialEq)]
346 pub struct NetworkOptions
{
349 pub address
: CidrAddress
,
351 pub dns_server
: IpAddr
,
354 impl NetworkOptions
{
355 const DEFAULT_DOMAIN
: &'
static str = "example.invalid";
357 pub fn defaults_from(setup
: &SetupInfo
, network
: &NetworkInfo
) -> Self {
358 let mut this
= Self {
359 ifname
: String
::new(),
360 fqdn
: Self::construct_fqdn(network
, setup
.config
.product
.default_hostname()),
361 // Safety: The provided mask will always be valid.
362 address
: CidrAddress
::new(Ipv4Addr
::UNSPECIFIED
, 0).unwrap(),
363 gateway
: Ipv4Addr
::UNSPECIFIED
.into(),
364 dns_server
: Ipv4Addr
::UNSPECIFIED
.into(),
367 if let Some(ip
) = network
.dns
.dns
.first() {
368 this
.dns_server
= *ip
;
371 if let Some(routes
) = &network
.routes
{
372 let mut filled
= false;
373 if let Some(gw
) = &routes
.gateway4
{
374 if let Some(iface
) = network
.interfaces
.get(&gw
.dev
) {
375 this
.ifname
= iface
.name
.clone();
376 if let Some(addresses
) = &iface
.addresses
{
377 if let Some(addr
) = addresses
.iter().find(|addr
| addr
.is_ipv4()) {
378 this
.gateway
= gw
.gateway
;
379 this
.address
= addr
.clone();
386 if let Some(gw
) = &routes
.gateway6
{
387 if let Some(iface
) = network
.interfaces
.get(&gw
.dev
) {
388 if let Some(addresses
) = &iface
.addresses
{
389 if let Some(addr
) = addresses
.iter().find(|addr
| addr
.is_ipv6()) {
390 this
.ifname
= iface
.name
.clone();
391 this
.gateway
= gw
.gateway
;
392 this
.address
= addr
.clone();
403 fn construct_fqdn(network
: &NetworkInfo
, default_hostname
: &str) -> Fqdn
{
404 let hostname
= network
.hostname
.as_deref().unwrap_or(default_hostname
);
410 .unwrap_or(Self::DEFAULT_DOMAIN
);
412 Fqdn
::from(&format
!("{hostname}.{domain}")).unwrap_or_else(|_
| {
413 // Safety: This will always result in a valid FQDN, as we control & know
414 // the values of default_hostname (one of "pve", "pmg" or "pbs") and
415 // constant-defined DEFAULT_DOMAIN.
416 Fqdn
::from(&format
!("{}.{}", default_hostname
, Self::DEFAULT_DOMAIN
)).unwrap()
427 const TESTS
: &[(usize, usize)] = &[
428 (16, 64), // at least 64 MiB
434 (1024 * 1024, 16384), // maximum of 16 GiB
437 for (total_memory
, expected
) in TESTS
{
439 default_zfs_arc_max(ProxmoxProduct
::PVE
, *total_memory
),
442 assert_eq
!(default_zfs_arc_max(ProxmoxProduct
::PBS
, *total_memory
), 0);
443 assert_eq
!(default_zfs_arc_max(ProxmoxProduct
::PMG
, *total_memory
), 0);