9 process
::{self, Command, Stdio}
,
12 use serde
::{de, Deserialize, Deserializer, Serialize, Serializer}
;
15 options
::{Disk, ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption}
,
19 #[allow(clippy::upper_case_acronyms)]
20 #[derive(Clone, Copy, Deserialize, PartialEq)]
21 #[serde(rename_all = "lowercase")]
22 pub enum ProxmoxProduct
{
29 pub fn default_hostname(self) -> &'
static str {
38 #[derive(Clone, Deserialize)]
39 pub struct ProductConfig
{
41 pub product
: ProxmoxProduct
,
42 #[serde(deserialize_with = "deserialize_bool_from_int")]
43 pub enable_btrfs
: bool
,
46 #[derive(Clone, Deserialize)]
49 pub isorelease
: String
,
52 /// Paths in the ISO environment containing installer data.
53 #[derive(Clone, Deserialize)]
54 pub struct IsoLocations
{
58 #[derive(Clone, Deserialize)]
59 pub struct SetupInfo
{
60 #[serde(rename = "product-cfg")]
61 pub config
: ProductConfig
,
62 #[serde(rename = "iso-info")]
63 pub iso_info
: IsoInfo
,
64 pub locations
: IsoLocations
,
67 #[derive(Clone, Deserialize)]
68 pub struct CountryInfo
{
75 #[derive(Clone, Deserialize, Eq, PartialEq)]
76 pub struct KeyboardMapping
{
78 #[serde(rename = "kvm")]
80 #[serde(rename = "x11")]
81 pub xkb_layout
: String
,
82 #[serde(rename = "x11var")]
83 pub xkb_variant
: String
,
86 impl cmp
::PartialOrd
for KeyboardMapping
{
87 fn partial_cmp(&self, other
: &Self) -> Option
<cmp
::Ordering
> {
88 self.name
.partial_cmp(&other
.name
)
92 impl cmp
::Ord
for KeyboardMapping
{
93 fn cmp(&self, other
: &Self) -> cmp
::Ordering
{
94 self.name
.cmp(&other
.name
)
98 #[derive(Clone, Deserialize)]
99 pub struct LocaleInfo
{
100 #[serde(deserialize_with = "deserialize_cczones_map")]
101 pub cczones
: HashMap
<String
, Vec
<String
>>,
102 #[serde(rename = "country")]
103 pub countries
: HashMap
<String
, CountryInfo
>,
104 pub kmap
: HashMap
<String
, KeyboardMapping
>,
107 /// Fetches basic information needed for the installer which is required to work
108 pub fn installer_setup(in_test_mode
: bool
) -> Result
<(SetupInfo
, LocaleInfo
, RuntimeInfo
), String
> {
109 let base_path
= if in_test_mode { "./testdir" }
else { "/" }
;
110 let mut path
= PathBuf
::from(base_path
);
113 path
.push("proxmox-installer");
115 let installer_info
: SetupInfo
= {
116 let mut path
= path
.clone();
117 path
.push("iso-info.json");
119 read_json(&path
).map_err(|err
| format
!("Failed to retrieve setup info: {err}"))?
123 let mut path
= path
.clone();
124 path
.push("locales.json");
126 read_json(&path
).map_err(|err
| format
!("Failed to retrieve locale info: {err}"))?
129 let mut runtime_info
: RuntimeInfo
= {
130 let mut path
= path
.clone();
131 path
.push("run-env-info.json");
134 .map_err(|err
| format
!("Failed to retrieve runtime environment info: {err}"))?
137 runtime_info
.disks
.sort();
138 if runtime_info
.disks
.is_empty() {
139 Err("The installer could not find any supported hard disks.".to_owned())
141 Ok((installer_info
, locale_info
, runtime_info
))
146 pub struct InstallZfsOption
{
148 #[serde(serialize_with = "serialize_as_display")]
149 compress
: ZfsCompressOption
,
150 #[serde(serialize_with = "serialize_as_display")]
151 checksum
: ZfsChecksumOption
,
156 impl From
<ZfsBootdiskOptions
> for InstallZfsOption
{
157 fn from(opts
: ZfsBootdiskOptions
) -> Self {
160 compress
: opts
.compress
,
161 checksum
: opts
.checksum
,
163 arc_max
: opts
.arc_max
,
168 pub fn read_json
<T
: for<'de
> Deserialize
<'de
>, P
: AsRef
<Path
>>(path
: P
) -> Result
<T
, String
> {
169 let file
= File
::open(path
).map_err(|err
| err
.to_string())?
;
170 let reader
= BufReader
::new(file
);
172 serde_json
::from_reader(reader
).map_err(|err
| format
!("failed to parse JSON: {err}"))
175 fn deserialize_bool_from_int
<'de
, D
>(deserializer
: D
) -> Result
<bool
, D
::Error
>
177 D
: Deserializer
<'de
>,
179 let val
: u32 = Deserialize
::deserialize(deserializer
)?
;
183 fn deserialize_cczones_map
<'de
, D
>(
185 ) -> Result
<HashMap
<String
, Vec
<String
>>, D
::Error
>
187 D
: Deserializer
<'de
>,
189 let map
: HashMap
<String
, HashMap
<String
, u32>> = Deserialize
::deserialize(deserializer
)?
;
191 let mut result
= HashMap
::new();
192 for (cc
, list
) in map
.into_iter() {
193 result
.insert(cc
, list
.into_keys().collect());
199 fn deserialize_disks_map
<'de
, D
>(deserializer
: D
) -> Result
<Vec
<Disk
>, D
::Error
>
201 D
: Deserializer
<'de
>,
204 <Vec
<(usize, String
, f64, String
, Option
<usize>, String
)>>::deserialize(deserializer
)?
;
208 |(index
, device
, size_mb
, model
, logical_bsize
, _syspath
)| Disk
{
209 index
: index
.to_string(),
210 // Linux always reports the size of block devices in sectors, where one sector is
211 // defined as being 2^9 = 512 bytes in size.
212 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30
213 size
: (size_mb
* 512.) / 1024. / 1024. / 1024.,
214 block_size
: logical_bsize
,
216 model
: (!model
.is_empty()).then_some(model
),
222 fn deserialize_cidr_list
<'de
, D
>(deserializer
: D
) -> Result
<Option
<Vec
<CidrAddress
>>, D
::Error
>
224 D
: Deserializer
<'de
>,
226 #[derive(Deserialize)]
227 struct CidrDescriptor
{
230 // family is implied anyway by parsing the address
233 let list
: Vec
<CidrDescriptor
> = Deserialize
::deserialize(deserializer
)?
;
235 let mut result
= Vec
::with_capacity(list
.len());
240 .map_err(|err
| de
::Error
::custom(format
!("{:?}", err
)))?
;
243 CidrAddress
::new(ip_addr
, desc
.prefix
)
244 .map_err(|err
| de
::Error
::custom(format
!("{:?}", err
)))?
,
251 fn serialize_as_display
<S
, T
>(value
: &T
, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
256 serializer
.collect_str(value
)
259 #[derive(Clone, Deserialize)]
260 pub struct RuntimeInfo
{
261 /// Whether is system was booted in (legacy) BIOS or UEFI mode.
262 pub boot_type
: BootType
,
264 /// Detected country if available.
265 pub country
: Option
<String
>,
267 /// Maps devices to their information.
268 #[serde(deserialize_with = "deserialize_disks_map")]
269 pub disks
: Vec
<Disk
>,
271 /// Network addresses, gateways and DNS info.
272 pub network
: NetworkInfo
,
274 /// Total memory of the system in MiB.
275 pub total_memory
: usize,
277 /// Whether the CPU supports hardware-accelerated virtualization
278 #[serde(deserialize_with = "deserialize_bool_from_int")]
279 pub hvm_supported
: bool
,
282 #[derive(Copy, Clone, Eq, Deserialize, PartialEq)]
283 #[serde(rename_all = "lowercase")]
289 #[derive(Clone, Deserialize)]
290 pub struct NetworkInfo
{
292 pub routes
: Option
<Routes
>,
294 /// Maps devices to their configuration, if it has a usable configuration.
295 /// (Contains no entries for devices with only link-local addresses.)
297 pub interfaces
: HashMap
<String
, Interface
>,
299 /// The hostname of this machine, if set by the DHCP server.
300 pub hostname
: Option
<String
>,
303 #[derive(Clone, Deserialize)]
305 pub domain
: Option
<String
>,
307 /// List of stringified IP addresses.
309 pub dns
: Vec
<IpAddr
>,
312 #[derive(Clone, Deserialize)]
315 pub gateway4
: Option
<Gateway
>,
318 pub gateway6
: Option
<Gateway
>,
321 #[derive(Clone, Deserialize)]
323 /// Outgoing network device.
326 /// Stringified gateway IP address.
330 #[derive(Clone, Deserialize)]
331 #[serde(rename_all = "UPPERCASE")]
332 pub enum InterfaceState
{
339 impl InterfaceState
{
340 // avoid display trait as this is not the string representation for a serializer
341 pub fn render(&self) -> String
{
343 Self::Up
=> "\u{25CF}",
344 Self::Down
| Self::Unknown
=> " ",
350 #[derive(Clone, Deserialize)]
351 pub struct Interface
{
358 pub state
: InterfaceState
,
361 #[serde(deserialize_with = "deserialize_cidr_list")]
362 pub addresses
: Option
<Vec
<CidrAddress
>>,
366 // avoid display trait as this is not the string representation for a serializer
367 pub fn render(&self) -> String
{
368 format
!("{} {}", self.state
.render(), self.name
)
372 pub fn spawn_low_level_installer(test_mode
: bool
) -> io
::Result
<process
::Child
> {
373 let (path
, args
, envs
): (&str, &[&str], Vec
<(&str, &str)>) = if test_mode
{
375 "./proxmox-low-level-installer",
376 &["-t", "/dev/null", "start-session-test"],
377 vec
![("PERL5LIB", ".")],
380 ("proxmox-low-level-installer", &["start-session"], vec
![])
386 .stdin(Stdio
::piped())
387 .stdout(Stdio
::piped())