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(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
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
{
43 f
.write_str(match self {
51 #[derive(Debug, Clone, Deserialize, Serialize)]
52 pub struct ProductConfig
{
54 pub product
: ProxmoxProduct
,
55 #[serde(deserialize_with = "deserialize_bool_from_int")]
56 pub enable_btrfs
: bool
,
60 /// A mocked ProductConfig simulating a Proxmox VE environment.
61 pub fn mocked() -> Self {
63 fullname
: String
::from("Proxmox VE (mocked)"),
64 product
: ProxmoxProduct
::PVE
,
70 #[derive(Debug, Clone, Deserialize, Serialize)]
73 pub isorelease
: String
,
77 /// A mocked IsoInfo with some edge case to convey that this is not necessarily purely numeric.
78 pub fn mocked() -> Self {
80 release
: String
::from("42.1"),
81 isorelease
: String
::from("mocked-1"),
86 /// Paths in the ISO environment containing installer data.
87 #[derive(Clone, Deserialize)]
88 pub struct IsoLocations
{
93 /// A mocked location, uses the current working directory by default
94 pub fn mocked() -> Self {
96 iso
: std
::env
::current_dir().unwrap_or("/dev/null".into()),
101 #[derive(Clone, Deserialize)]
102 pub struct SetupInfo
{
103 #[serde(rename = "product-cfg")]
104 pub config
: ProductConfig
,
105 #[serde(rename = "iso-info")]
106 pub iso_info
: IsoInfo
,
107 pub locations
: IsoLocations
,
111 /// Return a mocked SetupInfo that is very similar to how our actual ones look like and should
112 /// be good enough for testing.
113 pub fn mocked() -> Self {
115 config
: ProductConfig
::mocked(),
116 iso_info
: IsoInfo
::mocked(),
117 locations
: IsoLocations
::mocked(),
122 #[derive(Clone, Deserialize)]
123 pub struct CountryInfo
{
130 #[derive(Clone, Deserialize, Eq, PartialEq)]
131 pub struct KeyboardMapping
{
133 #[serde(rename = "kvm")]
135 #[serde(rename = "x11")]
136 pub xkb_layout
: String
,
137 #[serde(rename = "x11var")]
138 pub xkb_variant
: String
,
141 impl cmp
::PartialOrd
for KeyboardMapping
{
142 fn partial_cmp(&self, other
: &Self) -> Option
<cmp
::Ordering
> {
143 self.name
.partial_cmp(&other
.name
)
147 impl cmp
::Ord
for KeyboardMapping
{
148 fn cmp(&self, other
: &Self) -> cmp
::Ordering
{
149 self.name
.cmp(&other
.name
)
153 #[derive(Clone, Deserialize)]
154 pub struct LocaleInfo
{
155 #[serde(deserialize_with = "deserialize_cczones_map")]
156 pub cczones
: HashMap
<String
, Vec
<String
>>,
157 #[serde(rename = "country")]
158 pub countries
: HashMap
<String
, CountryInfo
>,
159 pub kmap
: HashMap
<String
, KeyboardMapping
>,
162 /// Fetches basic information needed for the installer which is required to work
163 pub fn installer_setup(in_test_mode
: bool
) -> Result
<(SetupInfo
, LocaleInfo
, RuntimeInfo
), String
> {
164 let base_path
= if in_test_mode { "./testdir" }
else { "/" }
;
165 let mut path
= PathBuf
::from(base_path
);
168 path
.push("proxmox-installer");
170 let installer_info
: SetupInfo
= {
171 let mut path
= path
.clone();
172 path
.push("iso-info.json");
174 read_json(&path
).map_err(|err
| format
!("Failed to retrieve setup info: {err}"))?
178 let mut path
= path
.clone();
179 path
.push("locales.json");
181 read_json(&path
).map_err(|err
| format
!("Failed to retrieve locale info: {err}"))?
184 let mut runtime_info
: RuntimeInfo
= {
185 let mut path
= path
.clone();
186 path
.push("run-env-info.json");
189 .map_err(|err
| format
!("Failed to retrieve runtime environment info: {err}"))?
192 runtime_info
.disks
.sort();
193 if runtime_info
.disks
.is_empty() {
194 Err("The installer could not find any supported hard disks.".to_owned())
196 Ok((installer_info
, locale_info
, runtime_info
))
200 #[derive(Debug, Deserialize, Serialize)]
201 pub struct InstallZfsOption
{
203 #[serde(serialize_with = "serialize_as_display")]
204 pub compress
: ZfsCompressOption
,
205 #[serde(serialize_with = "serialize_as_display")]
206 pub checksum
: ZfsChecksumOption
,
211 impl From
<ZfsBootdiskOptions
> for InstallZfsOption
{
212 fn from(opts
: ZfsBootdiskOptions
) -> Self {
215 compress
: opts
.compress
,
216 checksum
: opts
.checksum
,
218 arc_max
: opts
.arc_max
,
223 pub fn read_json
<T
: for<'de
> Deserialize
<'de
>, P
: AsRef
<Path
>>(path
: P
) -> Result
<T
, String
> {
224 let file
= File
::open(path
).map_err(|err
| err
.to_string())?
;
225 let reader
= BufReader
::new(file
);
227 serde_json
::from_reader(reader
).map_err(|err
| format
!("failed to parse JSON: {err}"))
230 fn deserialize_bool_from_int
<'de
, D
>(deserializer
: D
) -> Result
<bool
, D
::Error
>
232 D
: Deserializer
<'de
>,
234 let val
: u32 = Deserialize
::deserialize(deserializer
)?
;
238 fn deserialize_cczones_map
<'de
, D
>(
240 ) -> Result
<HashMap
<String
, Vec
<String
>>, D
::Error
>
242 D
: Deserializer
<'de
>,
244 let map
: HashMap
<String
, HashMap
<String
, u32>> = Deserialize
::deserialize(deserializer
)?
;
246 let mut result
= HashMap
::new();
247 for (cc
, list
) in map
.into_iter() {
248 result
.insert(cc
, list
.into_keys().collect());
254 fn deserialize_disks_map
<'de
, D
>(deserializer
: D
) -> Result
<Vec
<Disk
>, D
::Error
>
256 D
: Deserializer
<'de
>,
259 <Vec
<(usize, String
, f64, String
, Option
<usize>, String
)>>::deserialize(deserializer
)?
;
263 |(index
, device
, size_mb
, model
, logical_bsize
, _syspath
)| Disk
{
264 index
: index
.to_string(),
265 // Linux always reports the size of block devices in sectors, where one sector is
266 // defined as being 2^9 = 512 bytes in size.
267 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30
268 size
: (size_mb
* 512.) / 1024. / 1024. / 1024.,
269 block_size
: logical_bsize
,
271 model
: (!model
.is_empty()).then_some(model
),
277 fn deserialize_cidr_list
<'de
, D
>(deserializer
: D
) -> Result
<Option
<Vec
<CidrAddress
>>, D
::Error
>
279 D
: Deserializer
<'de
>,
281 #[derive(Deserialize)]
282 struct CidrDescriptor
{
285 // family is implied anyway by parsing the address
288 let list
: Vec
<CidrDescriptor
> = Deserialize
::deserialize(deserializer
)?
;
290 let mut result
= Vec
::with_capacity(list
.len());
295 .map_err(|err
| de
::Error
::custom(format
!("{:?}", err
)))?
;
298 CidrAddress
::new(ip_addr
, desc
.prefix
)
299 .map_err(|err
| de
::Error
::custom(format
!("{:?}", err
)))?
,
306 fn serialize_as_display
<S
, T
>(value
: &T
, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
311 serializer
.collect_str(value
)
314 #[derive(Clone, Deserialize)]
315 pub struct RuntimeInfo
{
316 /// Whether is system was booted in (legacy) BIOS or UEFI mode.
317 pub boot_type
: BootType
,
319 /// Detected country if available.
320 pub country
: Option
<String
>,
322 /// Maps devices to their information.
323 #[serde(deserialize_with = "deserialize_disks_map")]
324 pub disks
: Vec
<Disk
>,
326 /// Network addresses, gateways and DNS info.
327 pub network
: NetworkInfo
,
329 /// Total memory of the system in MiB.
330 pub total_memory
: usize,
332 /// Whether the CPU supports hardware-accelerated virtualization
333 #[serde(deserialize_with = "deserialize_bool_from_int")]
334 pub hvm_supported
: bool
,
337 #[derive(Copy, Clone, Eq, Deserialize, PartialEq)]
338 #[serde(rename_all = "lowercase")]
344 #[derive(Clone, Deserialize)]
345 pub struct NetworkInfo
{
347 pub routes
: Option
<Routes
>,
349 /// Maps devices to their configuration, if it has a usable configuration.
350 /// (Contains no entries for devices with only link-local addresses.)
352 pub interfaces
: BTreeMap
<String
, Interface
>,
354 /// The hostname of this machine, if set by the DHCP server.
355 pub hostname
: Option
<String
>,
358 #[derive(Clone, Deserialize)]
360 pub domain
: Option
<String
>,
362 /// List of stringified IP addresses.
364 pub dns
: Vec
<IpAddr
>,
367 #[derive(Clone, Deserialize)]
370 pub gateway4
: Option
<Gateway
>,
373 pub gateway6
: Option
<Gateway
>,
376 #[derive(Clone, Deserialize)]
378 /// Outgoing network device.
381 /// Stringified gateway IP address.
385 #[derive(Clone, Deserialize)]
386 #[serde(rename_all = "UPPERCASE")]
387 pub enum InterfaceState
{
394 impl InterfaceState
{
395 // avoid display trait as this is not the string representation for a serializer
396 pub fn render(&self) -> String
{
398 Self::Up
=> "\u{25CF}",
399 Self::Down
| Self::Unknown
=> " ",
405 #[derive(Clone, Deserialize)]
406 pub struct Interface
{
413 pub state
: InterfaceState
,
416 #[serde(deserialize_with = "deserialize_cidr_list")]
417 pub addresses
: Option
<Vec
<CidrAddress
>>,
421 // avoid display trait as this is not the string representation for a serializer
422 pub fn render(&self) -> String
{
423 format
!("{} {}", self.state
.render(), self.name
)
427 pub fn spawn_low_level_installer(test_mode
: bool
) -> io
::Result
<process
::Child
> {
428 let (path
, args
, envs
): (&str, &[&str], Vec
<(&str, &str)>) = if test_mode
{
430 "./proxmox-low-level-installer",
431 &["-t", "/dev/null", "start-session-test"],
432 vec
![("PERL5LIB", ".")],
435 ("proxmox-low-level-installer", &["start-session"], vec
![])
441 .stdin(Stdio
::piped())
442 .stdout(Stdio
::piped())
446 /// See Proxmox::Install::Config
447 #[derive(Debug, Deserialize, Serialize)]
448 pub struct InstallConfig
{
449 pub autoreboot
: usize,
452 serialize_with
= "serialize_fstype",
453 deserialize_with
= "deserialize_fs_type"
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub swapsize
: Option
<f64>,
459 #[serde(skip_serializing_if = "Option::is_none")]
460 pub maxroot
: Option
<f64>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 pub minfree
: Option
<f64>,
463 #[serde(skip_serializing_if = "Option::is_none")]
464 pub maxvz
: Option
<f64>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 pub zfs_opts
: Option
<InstallZfsOption
>,
470 serialize_with
= "serialize_disk_opt",
471 skip_serializing_if
= "Option::is_none",
472 // only the 'path' property is serialized -> deserialization is problematic
473 // The information would be present in the 'run-env-info-json', but for now there is no
474 // need for it in any code that deserializes the low-level config. Therefore we are
475 // currently skipping it on deserialization
478 pub target_hd
: Option
<Disk
>,
479 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
480 pub disk_selection
: BTreeMap
<String
, String
>,
482 pub lvm_auto_rename
: usize,
485 pub timezone
: String
,
488 pub password
: String
,
491 pub mngmt_nic
: String
,
493 pub hostname
: String
,
495 #[serde(serialize_with = "serialize_as_display")]
496 pub cidr
: CidrAddress
,
501 fn serialize_disk_opt
<S
>(value
: &Option
<Disk
>, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
505 if let Some(disk
) = value
{
506 serializer
.serialize_str(&disk
.path
)
508 serializer
.serialize_none()
512 fn serialize_fstype
<S
>(value
: &FsType
, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
517 let value
= match value
{
518 // proxinstall::$fssetup
521 // proxinstall::get_zfs_raid_setup()
522 Zfs(ZfsRaidLevel
::Raid0
) => "zfs (RAID0)",
523 Zfs(ZfsRaidLevel
::Raid1
) => "zfs (RAID1)",
524 Zfs(ZfsRaidLevel
::Raid10
) => "zfs (RAID10)",
525 Zfs(ZfsRaidLevel
::RaidZ
) => "zfs (RAIDZ-1)",
526 Zfs(ZfsRaidLevel
::RaidZ2
) => "zfs (RAIDZ-2)",
527 Zfs(ZfsRaidLevel
::RaidZ3
) => "zfs (RAIDZ-3)",
528 // proxinstall::get_btrfs_raid_setup()
529 Btrfs(BtrfsRaidLevel
::Raid0
) => "btrfs (RAID0)",
530 Btrfs(BtrfsRaidLevel
::Raid1
) => "btrfs (RAID1)",
531 Btrfs(BtrfsRaidLevel
::Raid10
) => "btrfs (RAID10)",
534 serializer
.collect_str(value
)
537 pub fn deserialize_fs_type
<'de
, D
>(deserializer
: D
) -> Result
<FsType
, D
::Error
>
539 D
: Deserializer
<'de
>,
542 let de_fs
: String
= Deserialize
::deserialize(deserializer
)?
;
544 match de_fs
.as_str() {
547 "zfs (RAID0)" => Ok(Zfs(ZfsRaidLevel
::Raid0
)),
548 "zfs (RAID1)" => Ok(Zfs(ZfsRaidLevel
::Raid1
)),
549 "zfs (RAID10)" => Ok(Zfs(ZfsRaidLevel
::Raid10
)),
550 "zfs (RAIDZ-1)" => Ok(Zfs(ZfsRaidLevel
::RaidZ
)),
551 "zfs (RAIDZ-2)" => Ok(Zfs(ZfsRaidLevel
::RaidZ2
)),
552 "zfs (RAIDZ-3)" => Ok(Zfs(ZfsRaidLevel
::RaidZ3
)),
553 "btrfs (RAID0)" => Ok(Btrfs(BtrfsRaidLevel
::Raid0
)),
554 "btrfs (RAID1)" => Ok(Btrfs(BtrfsRaidLevel
::Raid1
)),
555 "btrfs (RAID10)" => Ok(Btrfs(BtrfsRaidLevel
::Raid10
)),
556 _
=> Err(de
::Error
::custom("could not find file system: {de_fs}")),