3 collections
::{BTreeMap, HashMap}
,
9 process
::{self, Command, Stdio}
,
12 use serde
::{de, Deserialize, Deserializer, Serialize, Serializer}
;
16 BtrfsRaidLevel
, Disk
, FsType
, ZfsBootdiskOptions
, ZfsChecksumOption
, ZfsCompressOption
,
22 #[allow(clippy::upper_case_acronyms)]
23 #[derive(Clone, Copy, Deserialize, PartialEq)]
24 #[serde(rename_all = "lowercase")]
25 pub enum ProxmoxProduct
{
32 pub fn default_hostname(self) -> &'
static str {
41 impl fmt
::Display
for ProxmoxProduct
{
42 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
44 Self::PVE
=> write
!(f
, "pve"),
45 Self::PMG
=> write
!(f
, "pmg"),
46 Self::PBS
=> write
!(f
, "pbs"),
51 #[derive(Clone, Deserialize)]
52 pub struct ProductConfig
{
54 pub product
: ProxmoxProduct
,
55 #[serde(deserialize_with = "deserialize_bool_from_int")]
56 pub enable_btrfs
: bool
,
59 #[derive(Clone, Deserialize)]
62 pub isorelease
: String
,
65 /// Paths in the ISO environment containing installer data.
66 #[derive(Clone, Deserialize)]
67 pub struct IsoLocations
{
71 #[derive(Clone, Deserialize)]
72 pub struct SetupInfo
{
73 #[serde(rename = "product-cfg")]
74 pub config
: ProductConfig
,
75 #[serde(rename = "iso-info")]
76 pub iso_info
: IsoInfo
,
77 pub locations
: IsoLocations
,
80 #[derive(Clone, Deserialize)]
81 pub struct CountryInfo
{
88 #[derive(Clone, Deserialize, Eq, PartialEq)]
89 pub struct KeyboardMapping
{
91 #[serde(rename = "kvm")]
93 #[serde(rename = "x11")]
94 pub xkb_layout
: String
,
95 #[serde(rename = "x11var")]
96 pub xkb_variant
: String
,
99 impl cmp
::PartialOrd
for KeyboardMapping
{
100 fn partial_cmp(&self, other
: &Self) -> Option
<cmp
::Ordering
> {
101 self.name
.partial_cmp(&other
.name
)
105 impl cmp
::Ord
for KeyboardMapping
{
106 fn cmp(&self, other
: &Self) -> cmp
::Ordering
{
107 self.name
.cmp(&other
.name
)
111 #[derive(Clone, Deserialize)]
112 pub struct LocaleInfo
{
113 #[serde(deserialize_with = "deserialize_cczones_map")]
114 pub cczones
: HashMap
<String
, Vec
<String
>>,
115 #[serde(rename = "country")]
116 pub countries
: HashMap
<String
, CountryInfo
>,
117 pub kmap
: HashMap
<String
, KeyboardMapping
>,
120 /// Fetches basic information needed for the installer which is required to work
121 pub fn installer_setup(in_test_mode
: bool
) -> Result
<(SetupInfo
, LocaleInfo
, RuntimeInfo
), String
> {
122 let base_path
= if in_test_mode { "./testdir" }
else { "/" }
;
123 let mut path
= PathBuf
::from(base_path
);
126 path
.push("proxmox-installer");
128 let installer_info
: SetupInfo
= {
129 let mut path
= path
.clone();
130 path
.push("iso-info.json");
132 read_json(&path
).map_err(|err
| format
!("Failed to retrieve setup info: {err}"))?
136 let mut path
= path
.clone();
137 path
.push("locales.json");
139 read_json(&path
).map_err(|err
| format
!("Failed to retrieve locale info: {err}"))?
142 let mut runtime_info
: RuntimeInfo
= {
143 let mut path
= path
.clone();
144 path
.push("run-env-info.json");
147 .map_err(|err
| format
!("Failed to retrieve runtime environment info: {err}"))?
150 runtime_info
.disks
.sort();
151 if runtime_info
.disks
.is_empty() {
152 Err("The installer could not find any supported hard disks.".to_owned())
154 Ok((installer_info
, locale_info
, runtime_info
))
158 #[derive(Debug, Deserialize, Serialize)]
159 pub struct InstallZfsOption
{
161 #[serde(serialize_with = "serialize_as_display")]
162 pub compress
: ZfsCompressOption
,
163 #[serde(serialize_with = "serialize_as_display")]
164 pub checksum
: ZfsChecksumOption
,
169 impl From
<ZfsBootdiskOptions
> for InstallZfsOption
{
170 fn from(opts
: ZfsBootdiskOptions
) -> Self {
173 compress
: opts
.compress
,
174 checksum
: opts
.checksum
,
176 arc_max
: opts
.arc_max
,
181 pub fn read_json
<T
: for<'de
> Deserialize
<'de
>, P
: AsRef
<Path
>>(path
: P
) -> Result
<T
, String
> {
182 let file
= File
::open(path
).map_err(|err
| err
.to_string())?
;
183 let reader
= BufReader
::new(file
);
185 serde_json
::from_reader(reader
).map_err(|err
| format
!("failed to parse JSON: {err}"))
188 fn deserialize_bool_from_int
<'de
, D
>(deserializer
: D
) -> Result
<bool
, D
::Error
>
190 D
: Deserializer
<'de
>,
192 let val
: u32 = Deserialize
::deserialize(deserializer
)?
;
196 fn deserialize_cczones_map
<'de
, D
>(
198 ) -> Result
<HashMap
<String
, Vec
<String
>>, D
::Error
>
200 D
: Deserializer
<'de
>,
202 let map
: HashMap
<String
, HashMap
<String
, u32>> = Deserialize
::deserialize(deserializer
)?
;
204 let mut result
= HashMap
::new();
205 for (cc
, list
) in map
.into_iter() {
206 result
.insert(cc
, list
.into_keys().collect());
212 fn deserialize_disks_map
<'de
, D
>(deserializer
: D
) -> Result
<Vec
<Disk
>, D
::Error
>
214 D
: Deserializer
<'de
>,
217 <Vec
<(usize, String
, f64, String
, Option
<usize>, String
)>>::deserialize(deserializer
)?
;
221 |(index
, device
, size_mb
, model
, logical_bsize
, _syspath
)| Disk
{
222 index
: index
.to_string(),
223 // Linux always reports the size of block devices in sectors, where one sector is
224 // defined as being 2^9 = 512 bytes in size.
225 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30
226 size
: (size_mb
* 512.) / 1024. / 1024. / 1024.,
227 block_size
: logical_bsize
,
229 model
: (!model
.is_empty()).then_some(model
),
235 fn deserialize_cidr_list
<'de
, D
>(deserializer
: D
) -> Result
<Option
<Vec
<CidrAddress
>>, D
::Error
>
237 D
: Deserializer
<'de
>,
239 #[derive(Deserialize)]
240 struct CidrDescriptor
{
243 // family is implied anyway by parsing the address
246 let list
: Vec
<CidrDescriptor
> = Deserialize
::deserialize(deserializer
)?
;
248 let mut result
= Vec
::with_capacity(list
.len());
253 .map_err(|err
| de
::Error
::custom(format
!("{:?}", err
)))?
;
256 CidrAddress
::new(ip_addr
, desc
.prefix
)
257 .map_err(|err
| de
::Error
::custom(format
!("{:?}", err
)))?
,
264 fn serialize_as_display
<S
, T
>(value
: &T
, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
269 serializer
.collect_str(value
)
272 #[derive(Clone, Deserialize)]
273 pub struct RuntimeInfo
{
274 /// Whether is system was booted in (legacy) BIOS or UEFI mode.
275 pub boot_type
: BootType
,
277 /// Detected country if available.
278 pub country
: Option
<String
>,
280 /// Maps devices to their information.
281 #[serde(deserialize_with = "deserialize_disks_map")]
282 pub disks
: Vec
<Disk
>,
284 /// Network addresses, gateways and DNS info.
285 pub network
: NetworkInfo
,
287 /// Total memory of the system in MiB.
288 pub total_memory
: usize,
290 /// Whether the CPU supports hardware-accelerated virtualization
291 #[serde(deserialize_with = "deserialize_bool_from_int")]
292 pub hvm_supported
: bool
,
295 #[derive(Copy, Clone, Eq, Deserialize, PartialEq)]
296 #[serde(rename_all = "lowercase")]
302 #[derive(Clone, Deserialize)]
303 pub struct NetworkInfo
{
305 pub routes
: Option
<Routes
>,
307 /// Maps devices to their configuration, if it has a usable configuration.
308 /// (Contains no entries for devices with only link-local addresses.)
310 pub interfaces
: BTreeMap
<String
, Interface
>,
312 /// The hostname of this machine, if set by the DHCP server.
313 pub hostname
: Option
<String
>,
316 #[derive(Clone, Deserialize)]
318 pub domain
: Option
<String
>,
320 /// List of stringified IP addresses.
322 pub dns
: Vec
<IpAddr
>,
325 #[derive(Clone, Deserialize)]
328 pub gateway4
: Option
<Gateway
>,
331 pub gateway6
: Option
<Gateway
>,
334 #[derive(Clone, Deserialize)]
336 /// Outgoing network device.
339 /// Stringified gateway IP address.
343 #[derive(Clone, Deserialize)]
344 #[serde(rename_all = "UPPERCASE")]
345 pub enum InterfaceState
{
352 impl InterfaceState
{
353 // avoid display trait as this is not the string representation for a serializer
354 pub fn render(&self) -> String
{
356 Self::Up
=> "\u{25CF}",
357 Self::Down
| Self::Unknown
=> " ",
363 #[derive(Clone, Deserialize)]
364 pub struct Interface
{
371 pub state
: InterfaceState
,
374 #[serde(deserialize_with = "deserialize_cidr_list")]
375 pub addresses
: Option
<Vec
<CidrAddress
>>,
379 // avoid display trait as this is not the string representation for a serializer
380 pub fn render(&self) -> String
{
381 format
!("{} {}", self.state
.render(), self.name
)
385 pub fn spawn_low_level_installer(test_mode
: bool
) -> io
::Result
<process
::Child
> {
386 let (path
, args
, envs
): (&str, &[&str], Vec
<(&str, &str)>) = if test_mode
{
388 "./proxmox-low-level-installer",
389 &["-t", "/dev/null", "start-session-test"],
390 vec
![("PERL5LIB", ".")],
393 ("proxmox-low-level-installer", &["start-session"], vec
![])
399 .stdin(Stdio
::piped())
400 .stdout(Stdio
::piped())
404 /// See Proxmox::Install::Config
405 #[derive(Debug, Deserialize, Serialize)]
406 pub struct InstallConfig
{
407 pub autoreboot
: usize,
410 serialize_with
= "serialize_fstype",
411 deserialize_with
= "deserialize_fs_type"
415 #[serde(skip_serializing_if = "Option::is_none")]
416 pub swapsize
: Option
<f64>,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 pub maxroot
: Option
<f64>,
419 #[serde(skip_serializing_if = "Option::is_none")]
420 pub minfree
: Option
<f64>,
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub maxvz
: Option
<f64>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 pub zfs_opts
: Option
<InstallZfsOption
>,
428 serialize_with
= "serialize_disk_opt",
429 skip_serializing_if
= "Option::is_none",
430 // only the 'path' property is serialized -> deserialization is problematic
431 // The information would be present in the 'run-env-info-json', but for now there is no
432 // need for it in any code that deserializes the low-level config. Therefore we are
433 // currently skipping it on deserialization
436 pub target_hd
: Option
<Disk
>,
437 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
438 pub disk_selection
: BTreeMap
<String
, String
>,
441 pub timezone
: String
,
444 pub password
: String
,
447 pub mngmt_nic
: String
,
449 pub hostname
: String
,
451 #[serde(serialize_with = "serialize_as_display")]
452 pub cidr
: CidrAddress
,
457 fn serialize_disk_opt
<S
>(value
: &Option
<Disk
>, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
461 if let Some(disk
) = value
{
462 serializer
.serialize_str(&disk
.path
)
464 serializer
.serialize_none()
468 fn serialize_fstype
<S
>(value
: &FsType
, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
473 let value
= match value
{
474 // proxinstall::$fssetup
477 // proxinstall::get_zfs_raid_setup()
478 Zfs(ZfsRaidLevel
::Raid0
) => "zfs (RAID0)",
479 Zfs(ZfsRaidLevel
::Raid1
) => "zfs (RAID1)",
480 Zfs(ZfsRaidLevel
::Raid10
) => "zfs (RAID10)",
481 Zfs(ZfsRaidLevel
::RaidZ
) => "zfs (RAIDZ-1)",
482 Zfs(ZfsRaidLevel
::RaidZ2
) => "zfs (RAIDZ-2)",
483 Zfs(ZfsRaidLevel
::RaidZ3
) => "zfs (RAIDZ-3)",
484 // proxinstall::get_btrfs_raid_setup()
485 Btrfs(BtrfsRaidLevel
::Raid0
) => "btrfs (RAID0)",
486 Btrfs(BtrfsRaidLevel
::Raid1
) => "btrfs (RAID1)",
487 Btrfs(BtrfsRaidLevel
::Raid10
) => "btrfs (RAID10)",
490 serializer
.collect_str(value
)
493 pub fn deserialize_fs_type
<'de
, D
>(deserializer
: D
) -> Result
<FsType
, D
::Error
>
495 D
: Deserializer
<'de
>,
498 let de_fs
: String
= Deserialize
::deserialize(deserializer
)?
;
500 match de_fs
.as_str() {
503 "zfs (RAID0)" => Ok(Zfs(ZfsRaidLevel
::Raid0
)),
504 "zfs (RAID1)" => Ok(Zfs(ZfsRaidLevel
::Raid1
)),
505 "zfs (RAID10)" => Ok(Zfs(ZfsRaidLevel
::Raid10
)),
506 "zfs (RAIDZ-1)" => Ok(Zfs(ZfsRaidLevel
::RaidZ
)),
507 "zfs (RAIDZ-2)" => Ok(Zfs(ZfsRaidLevel
::RaidZ2
)),
508 "zfs (RAIDZ-3)" => Ok(Zfs(ZfsRaidLevel
::RaidZ3
)),
509 "btrfs (RAID0)" => Ok(Btrfs(BtrfsRaidLevel
::Raid0
)),
510 "btrfs (RAID1)" => Ok(Btrfs(BtrfsRaidLevel
::Raid1
)),
511 "btrfs (RAID10)" => Ok(Btrfs(BtrfsRaidLevel
::Raid10
)),
512 _
=> Err(de
::Error
::custom("could not find file system: {de_fs}")),