From: Christoph Heiss <c.heiss@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH installer v4 33/40] tree-wide: switch to filesystem types from proxmox-installer-types
Date: Thu, 30 Apr 2026 14:47:02 +0200 [thread overview]
Message-ID: <20260430124712.1614305-34-c.heiss@proxmox.com> (raw)
In-Reply-To: <20260430124712.1614305-1-c.heiss@proxmox.com>
No functional changes.
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v3 -> v4:
* no changes
Changes v2 -> v3:
* new patch
Cargo.toml | 2 +
proxmox-auto-installer/Cargo.toml | 1 +
proxmox-auto-installer/src/answer.rs | 26 +-
proxmox-auto-installer/src/utils.rs | 16 +-
proxmox-chroot/Cargo.toml | 1 +
proxmox-chroot/src/main.rs | 60 +-
proxmox-installer-common/Cargo.toml | 1 +
proxmox-installer-common/src/dmi.rs | 43 ++
proxmox-installer-common/src/lib.rs | 1 +
proxmox-installer-common/src/options.rs | 197 ++----
proxmox-installer-common/src/setup.rs | 5 +-
proxmox-post-hook/Cargo.toml | 3 +-
proxmox-post-hook/src/main.rs | 688 ++++++++------------
proxmox-tui-installer/Cargo.toml | 1 +
proxmox-tui-installer/src/options.rs | 21 +-
proxmox-tui-installer/src/views/bootdisk.rs | 30 +-
16 files changed, 426 insertions(+), 670 deletions(-)
create mode 100644 proxmox-installer-common/src/dmi.rs
diff --git a/Cargo.toml b/Cargo.toml
index 379ee6b..2466822 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,8 +28,10 @@ toml = "0.8"
proxmox-auto-installer.path = "./proxmox-auto-installer"
proxmox-installer-common.path = "./proxmox-installer-common"
proxmox-network-types = "1.0"
+proxmox-installer-types = { version = "0.1", features = ["legacy"] }
# Local path overrides
# NOTE: You must run `cargo update` after changing this for it to take effect!
[patch.crates-io]
# proxmox-network-types.path = "../proxmox/proxmox-network-types"
+# proxmox-installer-types.path = "../proxmox/proxmox-installer-types"
diff --git a/proxmox-auto-installer/Cargo.toml b/proxmox-auto-installer/Cargo.toml
index 0086e5d..5ef2f4f 100644
--- a/proxmox-auto-installer/Cargo.toml
+++ b/proxmox-auto-installer/Cargo.toml
@@ -15,6 +15,7 @@ anyhow.workspace = true
log.workspace = true
proxmox-installer-common = { workspace = true, features = ["http"] }
proxmox-network-types.workspace = true
+proxmox-installer-types.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde_plain.workspace = true
diff --git a/proxmox-auto-installer/src/answer.rs b/proxmox-auto-installer/src/answer.rs
index acb0d5b..eec5b58 100644
--- a/proxmox-auto-installer/src/answer.rs
+++ b/proxmox-auto-installer/src/answer.rs
@@ -7,9 +7,9 @@ use std::{
};
use proxmox_installer_common::options::{
- BtrfsCompressOption, BtrfsRaidLevel, FsType, NetworkInterfacePinningOptions, ZfsChecksumOption,
- ZfsCompressOption, ZfsRaidLevel,
+ BtrfsCompressOption, NetworkInterfacePinningOptions, ZfsChecksumOption, ZfsCompressOption,
};
+use proxmox_installer_types::answer::{BtrfsRaidLevel, FilesystemType, ZfsRaidLevel};
use proxmox_network_types::{Cidr, fqdn::Fqdn};
// NOTE New answer file properties must use kebab-case, but should allow snake_case for backwards
@@ -314,7 +314,7 @@ pub struct DiskSetup {
#[derive(Clone, Debug, Deserialize)]
#[serde(try_from = "DiskSetup", deny_unknown_fields)]
pub struct Disks {
- pub fs_type: FsType,
+ pub fs_type: FilesystemType,
pub disk_selection: DiskSelection,
pub filter_match: Option<FilterMatch>,
pub fs_options: FsOptions,
@@ -351,11 +351,17 @@ impl TryFrom<DiskSetup> for Disks {
let (fs, fs_options) = match source.filesystem {
Filesystem::Xfs => {
lvm_checks(&source)?;
- (FsType::Xfs, FsOptions::LVM(source.lvm.unwrap_or_default()))
+ (
+ FilesystemType::Xfs,
+ FsOptions::LVM(source.lvm.unwrap_or_default()),
+ )
}
Filesystem::Ext4 => {
lvm_checks(&source)?;
- (FsType::Ext4, FsOptions::LVM(source.lvm.unwrap_or_default()))
+ (
+ FilesystemType::Ext4,
+ FsOptions::LVM(source.lvm.unwrap_or_default()),
+ )
}
Filesystem::Zfs => {
if source.lvm.is_some() || source.btrfs.is_some() {
@@ -365,7 +371,10 @@ impl TryFrom<DiskSetup> for Disks {
None | Some(ZfsOptions { raid: None, .. }) => {
return Err("ZFS raid level 'zfs.raid' must be set");
}
- Some(opts) => (FsType::Zfs(opts.raid.unwrap()), FsOptions::ZFS(opts)),
+ Some(opts) => (
+ FilesystemType::Zfs(opts.raid.unwrap()),
+ FsOptions::ZFS(opts),
+ ),
}
}
Filesystem::Btrfs => {
@@ -376,7 +385,10 @@ impl TryFrom<DiskSetup> for Disks {
None | Some(BtrfsOptions { raid: None, .. }) => {
return Err("BTRFS raid level 'btrfs.raid' must be set");
}
- Some(opts) => (FsType::Btrfs(opts.raid.unwrap()), FsOptions::BTRFS(opts)),
+ Some(opts) => (
+ FilesystemType::Btrfs(opts.raid.unwrap()),
+ FsOptions::BTRFS(opts),
+ ),
}
}
};
diff --git a/proxmox-auto-installer/src/utils.rs b/proxmox-auto-installer/src/utils.rs
index f9cfcdd..83be913 100644
--- a/proxmox-auto-installer/src/utils.rs
+++ b/proxmox-auto-installer/src/utils.rs
@@ -16,12 +16,13 @@ use crate::{
use proxmox_installer_common::{
ROOT_PASSWORD_MIN_LENGTH,
disk_checks::check_swapsize,
- options::{FsType, NetworkOptions, ZfsChecksumOption, ZfsCompressOption, email_validate},
+ options::{NetworkOptions, RaidLevel, ZfsChecksumOption, ZfsCompressOption, email_validate},
setup::{
InstallBtrfsOption, InstallConfig, InstallFirstBootSetup, InstallRootPassword,
InstallZfsOption, LocaleInfo, RuntimeInfo, SetupInfo,
},
};
+use proxmox_installer_types::answer::FilesystemType;
use serde::{Deserialize, Serialize};
fn get_network_settings(
@@ -211,8 +212,10 @@ fn set_disks(
config: &mut InstallConfig,
) -> Result<()> {
match config.filesys {
- FsType::Ext4 | FsType::Xfs => set_single_disk(answer, udev_info, runtime_info, config),
- FsType::Zfs(_) | FsType::Btrfs(_) => {
+ FilesystemType::Ext4 | FilesystemType::Xfs => {
+ set_single_disk(answer, udev_info, runtime_info, config)
+ }
+ FilesystemType::Zfs(_) | FilesystemType::Btrfs(_) => {
set_selected_disks(answer, udev_info, runtime_info, config)
}
}
@@ -410,7 +413,12 @@ pub fn verify_email_and_root_password_settings(answer: &Answer) -> Result<()> {
pub fn verify_disks_settings(answer: &Answer) -> Result<()> {
if let DiskSelection::Selection(selection) = &answer.disks.disk_selection {
- let min_disks = answer.disks.fs_type.get_min_disks();
+ let min_disks = match answer.disks.fs_type {
+ FilesystemType::Ext4 | FilesystemType::Xfs => 1,
+ FilesystemType::Zfs(level) => level.get_min_disks(),
+ FilesystemType::Btrfs(level) => level.get_min_disks(),
+ };
+
if selection.len() < min_disks {
bail!(
"{}: need at least {} disks",
diff --git a/proxmox-chroot/Cargo.toml b/proxmox-chroot/Cargo.toml
index a6a705d..e1e0e4c 100644
--- a/proxmox-chroot/Cargo.toml
+++ b/proxmox-chroot/Cargo.toml
@@ -10,5 +10,6 @@ homepage = "https://www.proxmox.com"
[dependencies]
anyhow.workspace = true
proxmox-installer-common = { workspace = true, features = [ "cli" ] }
+proxmox-installer-types.workspace = true
serde = { workspace = true, features = [ "derive" ] }
serde_json.workspace = true
diff --git a/proxmox-chroot/src/main.rs b/proxmox-chroot/src/main.rs
index 2cff630..5f087bb 100644
--- a/proxmox-chroot/src/main.rs
+++ b/proxmox-chroot/src/main.rs
@@ -5,20 +5,19 @@
#![forbid(unsafe_code)]
+use anyhow::{Result, bail};
+use serde::Deserialize;
use std::{
env, fs, io,
path::{self, Path, PathBuf},
process::{self, Command},
- str::FromStr,
};
-use anyhow::{Result, bail};
use proxmox_installer_common::{
RUNTIME_DIR, cli,
- options::FsType,
setup::{InstallConfig, SetupInfo},
};
-use serde::Deserialize;
+use proxmox_installer_types::answer::Filesystem;
const ANSWER_MP: &str = "answer";
static BINDMOUNTS: [&str; 4] = ["dev", "proc", "run", "sys"];
@@ -29,7 +28,7 @@ const ZPOOL_NAME: &str = "rpool";
struct CommandPrepareArgs {
/// Filesystem used for the installation. Will try to automatically detect it after a
/// successful installation.
- filesystem: Option<Filesystems>,
+ filesystem: Option<Filesystem>,
/// Numerical ID of the `rpool` ZFS pool to import. Needed if multiple pools of name `rpool`
/// are present.
@@ -74,7 +73,7 @@ OPTIONS:
/// Arguments for the `cleanup` command.
struct CommandCleanupArgs {
/// Filesystem used for the installation. Will try to automatically detect it by default.
- filesystem: Option<Filesystems>,
+ filesystem: Option<Filesystem>,
}
impl cli::Subcommand for CommandCleanupArgs {
@@ -105,39 +104,6 @@ OPTIONS:
}
}
-#[derive(Copy, Clone, Debug)]
-enum Filesystems {
- Zfs,
- Ext4,
- Xfs,
- Btrfs,
-}
-
-impl From<FsType> for Filesystems {
- fn from(fs: FsType) -> Self {
- match fs {
- FsType::Xfs => Self::Xfs,
- FsType::Ext4 => Self::Ext4,
- FsType::Zfs(_) => Self::Zfs,
- FsType::Btrfs(_) => Self::Btrfs,
- }
- }
-}
-
-impl FromStr for Filesystems {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> Result<Self> {
- match s {
- "ext4" => Ok(Filesystems::Ext4),
- "xfs" => Ok(Filesystems::Xfs),
- _ if s.starts_with("zfs") => Ok(Filesystems::Zfs),
- _ if s.starts_with("btrfs") => Ok(Filesystems::Btrfs),
- _ => bail!("unknown filesystem"),
- }
- }
-}
-
fn main() -> process::ExitCode {
cli::run(cli::AppInfo {
global_help: &format!(
@@ -171,10 +137,10 @@ fn prepare(args: &CommandPrepareArgs) -> Result<()> {
fs::create_dir_all(TARGET_DIR)?;
match fs {
- Filesystems::Zfs => mount_zpool(args.rpool_id)?,
- Filesystems::Xfs => mount_fs()?,
- Filesystems::Ext4 => mount_fs()?,
- Filesystems::Btrfs => mount_btrfs(args.btrfs_uuid.clone())?,
+ Filesystem::Zfs => mount_zpool(args.rpool_id)?,
+ Filesystem::Xfs => mount_fs()?,
+ Filesystem::Ext4 => mount_fs()?,
+ Filesystem::Btrfs => mount_btrfs(args.btrfs_uuid.clone())?,
}
if let Err(e) = bindmount() {
@@ -193,15 +159,15 @@ fn cleanup(args: &CommandCleanupArgs) -> Result<()> {
}
match fs {
- Filesystems::Zfs => umount_zpool(),
- Filesystems::Btrfs | Filesystems::Xfs | Filesystems::Ext4 => umount(Path::new(TARGET_DIR))?,
+ Filesystem::Zfs => umount_zpool(),
+ Filesystem::Btrfs | Filesystem::Xfs | Filesystem::Ext4 => umount(Path::new(TARGET_DIR))?,
}
println!("Chroot cleanup done. You can now reboot or leave the shell.");
Ok(())
}
-fn get_fs(filesystem: Option<Filesystems>) -> Result<Filesystems> {
+fn get_fs(filesystem: Option<Filesystem>) -> Result<Filesystem> {
let fs = match filesystem {
None => {
let low_level_config = match get_low_level_config() {
@@ -210,7 +176,7 @@ fn get_fs(filesystem: Option<Filesystems>) -> Result<Filesystems> {
"Could not fetch config from previous installation. Please specify file system with -f."
),
};
- Filesystems::from(low_level_config.filesys)
+ low_level_config.filesys.into()
}
Some(fs) => fs,
};
diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml
index 7469627..7682680 100644
--- a/proxmox-installer-common/Cargo.toml
+++ b/proxmox-installer-common/Cargo.toml
@@ -14,6 +14,7 @@ serde = { workspace = true, features = [ "derive" ] }
serde_json.workspace = true
serde_plain.workspace = true
proxmox-network-types.workspace = true
+proxmox-installer-types.workspace = true
# `http` feature
hex = { version = "0.4", optional = true }
diff --git a/proxmox-installer-common/src/dmi.rs b/proxmox-installer-common/src/dmi.rs
new file mode 100644
index 0000000..76ae4a5
--- /dev/null
+++ b/proxmox-installer-common/src/dmi.rs
@@ -0,0 +1,43 @@
+use std::{collections::HashMap, fs};
+
+use anyhow::{Result, bail};
+use proxmox_installer_types::SystemDMI;
+
+const DMI_PATH: &str = "/sys/devices/virtual/dmi/id";
+
+pub fn get() -> Result<SystemDMI> {
+ let system_files = [
+ "product_serial",
+ "product_sku",
+ "product_uuid",
+ "product_name",
+ ];
+ let baseboard_files = ["board_asset_tag", "board_serial", "board_name"];
+ let chassis_files = ["chassis_serial", "chassis_sku", "chassis_asset_tag"];
+
+ Ok(SystemDMI {
+ system: get_dmi_infos_for(&system_files)?,
+ baseboard: get_dmi_infos_for(&baseboard_files)?,
+ chassis: get_dmi_infos_for(&chassis_files)?,
+ })
+}
+
+fn get_dmi_infos_for(files: &[&str]) -> Result<HashMap<String, String>> {
+ let mut res: HashMap<String, String> = HashMap::new();
+
+ for file in files {
+ let path = format!("{DMI_PATH}/{file}");
+ let content = match fs::read_to_string(&path) {
+ Err(ref err) if err.kind() == std::io::ErrorKind::NotFound => continue,
+ Err(ref err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
+ bail!("Could not read data. Are you running as root or with sudo?")
+ }
+ Err(err) => bail!("Error: '{err}' on '{path}'"),
+ Ok(content) => content.trim().into(),
+ };
+ let key = file.splitn(2, '_').last().unwrap();
+ res.insert(key.into(), content);
+ }
+
+ Ok(res)
+}
diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs
index 7cdb1de..05445d5 100644
--- a/proxmox-installer-common/src/lib.rs
+++ b/proxmox-installer-common/src/lib.rs
@@ -1,4 +1,5 @@
pub mod disk_checks;
+pub mod dmi;
pub mod options;
pub mod setup;
pub mod sysinfo;
diff --git a/proxmox-installer-common/src/options.rs b/proxmox-installer-common/src/options.rs
index f903f7e..8e19663 100644
--- a/proxmox-installer-common/src/options.rs
+++ b/proxmox-installer-common/src/options.rs
@@ -1,36 +1,23 @@
use anyhow::{Result, bail};
use regex::{Regex, RegexBuilder};
use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
-use std::net::{IpAddr, Ipv4Addr};
-use std::str::FromStr;
-use std::sync::OnceLock;
-use std::{cmp, fmt};
+use std::{
+ cmp,
+ collections::HashMap,
+ fmt,
+ net::{IpAddr, Ipv4Addr},
+ sync::OnceLock,
+};
use crate::disk_checks::check_raid_min_disks;
use crate::net::{MAX_IFNAME_LEN, MIN_IFNAME_LEN};
use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo};
+use proxmox_installer_types::answer::{BtrfsRaidLevel, FilesystemType, ZfsRaidLevel};
use proxmox_network_types::{fqdn::Fqdn, ip_address::Cidr};
-#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(rename_all(deserialize = "lowercase", serialize = "UPPERCASE"))]
-pub enum BtrfsRaidLevel {
- #[serde(alias = "RAID0")]
- Raid0,
- #[serde(alias = "RAID1")]
- Raid1,
- #[serde(alias = "RAID10")]
- Raid10,
-}
-
-impl BtrfsRaidLevel {
- pub fn get_min_disks(&self) -> usize {
- match self {
- BtrfsRaidLevel::Raid0 => 1,
- BtrfsRaidLevel::Raid1 => 2,
- BtrfsRaidLevel::Raid10 => 4,
- }
- }
+pub trait RaidLevel {
+ /// Returns the minimum number of disks needed for this RAID level.
+ fn get_min_disks(&self) -> usize;
/// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum
/// number of disks.
@@ -38,42 +25,31 @@ impl BtrfsRaidLevel {
/// # Arguments
///
/// * `disks` - List of disks designated as RAID targets.
- pub fn check_raid_disks_setup(&self, disks: &[Disk]) -> Result<(), String> {
+ fn check_raid_disks_setup(&self, disks: &[Disk]) -> Result<(), String>;
+
+ /// Checks whether the given disk sizes are compatible for the RAID level, if it is a mirror.
+ fn check_mirror_size(&self, _disk1: &Disk, _disk2: &Disk) -> Result<(), String> {
+ Ok(())
+ }
+}
+
+impl RaidLevel for BtrfsRaidLevel {
+ fn get_min_disks(&self) -> usize {
+ match self {
+ Self::Raid0 => 1,
+ Self::Raid1 => 2,
+ Self::Raid10 => 4,
+ }
+ }
+
+ fn check_raid_disks_setup(&self, disks: &[Disk]) -> Result<(), String> {
check_raid_min_disks(disks, self.get_min_disks())?;
Ok(())
}
}
-serde_plain::derive_display_from_serialize!(BtrfsRaidLevel);
-
-#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
-#[serde(rename_all(deserialize = "lowercase", serialize = "UPPERCASE"))]
-pub enum ZfsRaidLevel {
- #[serde(alias = "RAID0")]
- Raid0,
- #[serde(alias = "RAID1")]
- Raid1,
- #[serde(alias = "RAID10")]
- Raid10,
- #[serde(
- alias = "RAIDZ-1",
- rename(deserialize = "raidz-1", serialize = "RAIDZ-1")
- )]
- RaidZ,
- #[serde(
- alias = "RAIDZ-2",
- rename(deserialize = "raidz-2", serialize = "RAIDZ-2")
- )]
- RaidZ2,
- #[serde(
- alias = "RAIDZ-3",
- rename(deserialize = "raidz-3", serialize = "RAIDZ-3")
- )]
- RaidZ3,
-}
-
-impl ZfsRaidLevel {
- pub fn get_min_disks(&self) -> usize {
+impl RaidLevel for ZfsRaidLevel {
+ fn get_min_disks(&self) -> usize {
match self {
ZfsRaidLevel::Raid0 => 1,
ZfsRaidLevel::Raid1 => 2,
@@ -84,23 +60,7 @@ impl ZfsRaidLevel {
}
}
- fn check_mirror_size(&self, disk1: &Disk, disk2: &Disk) -> Result<(), String> {
- if (disk1.size - disk2.size).abs() > disk1.size / 10. {
- Err(format!(
- "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}"
- ))
- } else {
- Ok(())
- }
- }
-
- /// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum
- /// number of disks.
- ///
- /// # Arguments
- ///
- /// * `disks` - List of disks designated as RAID targets.
- pub fn check_raid_disks_setup(&self, disks: &[Disk]) -> Result<(), String> {
+ fn check_raid_disks_setup(&self, disks: &[Disk]) -> Result<(), String> {
check_raid_min_disks(disks, self.get_min_disks())?;
match self {
@@ -130,93 +90,18 @@ impl ZfsRaidLevel {
Ok(())
}
-}
-serde_plain::derive_display_from_serialize!(ZfsRaidLevel);
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum FsType {
- Ext4,
- Xfs,
- Zfs(ZfsRaidLevel),
- Btrfs(BtrfsRaidLevel),
-}
-
-impl FsType {
- pub fn is_btrfs(&self) -> bool {
- matches!(self, FsType::Btrfs(_))
- }
-
- /// Returns true if the filesystem is used on top of LVM, e.g. ext4 or XFS.
- pub fn is_lvm(&self) -> bool {
- matches!(self, FsType::Ext4 | FsType::Xfs)
- }
-
- pub fn get_min_disks(&self) -> usize {
- match self {
- FsType::Ext4 => 1,
- FsType::Xfs => 1,
- FsType::Zfs(level) => level.get_min_disks(),
- FsType::Btrfs(level) => level.get_min_disks(),
+ fn check_mirror_size(&self, disk1: &Disk, disk2: &Disk) -> Result<(), String> {
+ if (disk1.size - disk2.size).abs() > disk1.size / 10. {
+ Err(format!(
+ "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}"
+ ))
+ } else {
+ Ok(())
}
}
}
-impl fmt::Display for FsType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- // Values displayed to the user in the installer UI
- match self {
- FsType::Ext4 => write!(f, "ext4"),
- FsType::Xfs => write!(f, "XFS"),
- FsType::Zfs(level) => write!(f, "ZFS ({level})"),
- FsType::Btrfs(level) => write!(f, "BTRFS ({level})"),
- }
- }
-}
-
-impl Serialize for FsType {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: serde::Serializer,
- {
- // These values must match exactly what the low-level installer expects
- let value = match self {
- // proxinstall::$fssetup
- FsType::Ext4 => "ext4",
- FsType::Xfs => "xfs",
- // proxinstall::get_zfs_raid_setup()
- FsType::Zfs(level) => &format!("zfs ({level})"),
- // proxinstall::get_btrfs_raid_setup()
- FsType::Btrfs(level) => &format!("btrfs ({level})"),
- };
-
- serializer.collect_str(value)
- }
-}
-
-impl FromStr for FsType {
- type Err = String;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "ext4" => Ok(FsType::Ext4),
- "xfs" => Ok(FsType::Xfs),
- "zfs (RAID0)" => Ok(FsType::Zfs(ZfsRaidLevel::Raid0)),
- "zfs (RAID1)" => Ok(FsType::Zfs(ZfsRaidLevel::Raid1)),
- "zfs (RAID10)" => Ok(FsType::Zfs(ZfsRaidLevel::Raid10)),
- "zfs (RAIDZ-1)" => Ok(FsType::Zfs(ZfsRaidLevel::RaidZ)),
- "zfs (RAIDZ-2)" => Ok(FsType::Zfs(ZfsRaidLevel::RaidZ2)),
- "zfs (RAIDZ-3)" => Ok(FsType::Zfs(ZfsRaidLevel::RaidZ3)),
- "btrfs (RAID0)" => Ok(FsType::Btrfs(BtrfsRaidLevel::Raid0)),
- "btrfs (RAID1)" => Ok(FsType::Btrfs(BtrfsRaidLevel::Raid1)),
- "btrfs (RAID10)" => Ok(FsType::Btrfs(BtrfsRaidLevel::Raid10)),
- _ => Err(format!("Could not find file system: {s}")),
- }
- }
-}
-
-serde_plain::derive_deserialize_from_fromstr!(FsType, "valid filesystem");
-
#[derive(Clone, Debug)]
pub struct LvmBootdiskOptions {
pub total_size: f64,
@@ -426,7 +311,7 @@ impl cmp::Ord for Disk {
#[derive(Clone, Debug)]
pub struct BootdiskOptions {
pub disks: Vec<Disk>,
- pub fstype: FsType,
+ pub fstype: FilesystemType,
pub advanced: AdvancedBootdiskOptions,
}
@@ -434,7 +319,7 @@ impl BootdiskOptions {
pub fn defaults_from(disk: &Disk) -> Self {
Self {
disks: vec![disk.clone()],
- fstype: FsType::Ext4,
+ fstype: FilesystemType::Ext4,
advanced: AdvancedBootdiskOptions::Lvm(LvmBootdiskOptions::defaults_from(disk)),
}
}
diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs
index 35a5436..91f1250 100644
--- a/proxmox-installer-common/src/setup.rs
+++ b/proxmox-installer-common/src/setup.rs
@@ -14,9 +14,10 @@ use proxmox_network_types::Cidr;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use crate::options::{
- BtrfsBootdiskOptions, BtrfsCompressOption, Disk, FsType, NetworkInterfacePinningOptions,
+ BtrfsBootdiskOptions, BtrfsCompressOption, Disk, NetworkInterfacePinningOptions,
ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption,
};
+use proxmox_installer_types::answer::FilesystemType;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
@@ -565,7 +566,7 @@ pub fn spawn_low_level_installer(test_mode: bool) -> io::Result<process::Child>
pub struct InstallConfig {
pub autoreboot: usize,
- pub filesys: FsType,
+ pub filesys: FilesystemType,
pub hdsize: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub swapsize: Option<f64>,
diff --git a/proxmox-post-hook/Cargo.toml b/proxmox-post-hook/Cargo.toml
index beaaa26..748b922 100644
--- a/proxmox-post-hook/Cargo.toml
+++ b/proxmox-post-hook/Cargo.toml
@@ -12,8 +12,9 @@ homepage = "https://www.proxmox.com"
[dependencies]
anyhow.workspace = true
-proxmox-auto-installer.workspace = true
proxmox-installer-common = { workspace = true, features = ["http"] }
proxmox-network-types.workspace = true
+proxmox-installer-types.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
+toml.workspace = true
diff --git a/proxmox-post-hook/src/main.rs b/proxmox-post-hook/src/main.rs
index 8afe310..9086dc1 100644
--- a/proxmox-post-hook/src/main.rs
+++ b/proxmox-post-hook/src/main.rs
@@ -9,212 +9,49 @@
//! Relies on `proxmox-chroot` as an external dependency to (bind-)mount the
//! previously installed system.
-use anyhow::{Context, Result, anyhow, bail};
-use serde::Serialize;
+use anyhow::{Context, Result, bail};
use std::{
- collections::HashSet,
- ffi::CStr,
- fs::{self, File},
- io::BufReader,
- os::unix::fs::FileExt,
- path::PathBuf,
+ fs,
+ io::Read,
process::{Command, ExitCode},
};
-use proxmox_auto_installer::{
- answer::{
- Answer, FqdnConfig, FqdnExtendedConfig, FqdnSourceMode, PostNotificationHookInfo,
- RebootMode,
- },
- udevinfo::{UdevInfo, UdevProperties},
-};
-use proxmox_installer_common::{
- http::{self, header::HeaderMap},
- options::{Disk, FsType, NetworkOptions},
- setup::{
- BootType, InstallConfig, IsoInfo, ProxmoxProduct, RuntimeInfo, SetupInfo,
- load_installer_setup_files,
- },
- sysinfo::SystemDMI,
-};
-use proxmox_network_types::ip_address::Cidr;
+use proxmox_installer_common::http::{self, header::HeaderMap};
+use proxmox_installer_types::answer::{AutoInstallerConfig, PostNotificationHookInfo};
-/// Information about the system boot status.
-#[derive(Serialize)]
-struct BootInfo {
- /// Whether the system is booted using UEFI or legacy BIOS.
- mode: BootType,
- /// Whether SecureBoot is enabled for the installation.
- #[serde(skip_serializing_if = "bool_is_false")]
- secureboot: bool,
-}
+/// Current version of the schema sent by this implementation.
+const POST_HOOK_SCHEMA_VERSION: &str = "1.2";
-/// Holds all the public keys for the different algorithms available.
-#[derive(Serialize)]
-struct SshPublicHostKeys {
- // ECDSA-based public host key
- ecdsa: String,
- // ED25519-based public host key
- ed25519: String,
- // RSA-based public host key
- rsa: String,
-}
+mod detail {
+ use anyhow::{Context, Result, anyhow, bail};
+ use std::{
+ collections::HashSet,
+ ffi::CStr,
+ fs::{self, File},
+ io::BufReader,
+ os::unix::fs::FileExt,
+ path::PathBuf,
+ process::Command,
+ };
-/// Holds information about a single disk in the system.
-#[derive(Serialize)]
-#[serde(rename_all = "kebab-case")]
-struct DiskInfo {
- /// Size in bytes
- size: usize,
- /// Set to true if the disk is used for booting.
- #[serde(skip_serializing_if = "bool_is_false")]
- is_bootdisk: bool,
- /// Properties about the device as given by udev.
- udev_properties: UdevProperties,
-}
+ use proxmox_installer_common::{
+ options::{Disk, NetworkOptions},
+ setup::{
+ InstallConfig, ProxmoxProduct, RuntimeInfo, SetupInfo, load_installer_setup_files,
+ },
+ };
+ use proxmox_installer_types::{
+ BootType, IsoInfo, UdevInfo,
+ answer::{AutoInstallerConfig, FqdnConfig, FqdnFromDhcpConfig, FqdnSourceMode},
+ post_hook::{
+ BootInfo, CpuInfo, DiskInfo, KernelVersionInformation, NetworkInterfaceInfo,
+ PostHookInfo, PostHookInfoSchema, ProductInfo, SshPublicHostKeys,
+ },
+ };
-/// Holds information about the management network interface.
-#[derive(Serialize)]
-#[serde(rename_all = "kebab-case")]
-struct NetworkInterfaceInfo {
- /// Name of the interface
- name: String,
- /// MAC address of the interface
- mac: String,
- /// (Designated) IP address of the interface
- #[serde(skip_serializing_if = "Option::is_none")]
- address: Option<Cidr>,
- /// Set to true if the interface is the chosen management interface during
- /// installation.
- #[serde(skip_serializing_if = "bool_is_false")]
- is_management: bool,
- /// Set to true if the network interface name was pinned based on the MAC
- /// address during the installation.
- #[serde(skip_serializing_if = "bool_is_false")]
- is_pinned: bool,
- /// Properties about the device as given by udev.
- udev_properties: UdevProperties,
-}
+ /// Defines the size of a gibibyte in bytes.
+ const SIZE_GIB: usize = 1024 * 1024 * 1024;
-fn bool_is_false(value: &bool) -> bool {
- !value
-}
-
-/// Information about the installed product itself.
-#[derive(Serialize)]
-#[serde(rename_all = "kebab-case")]
-struct ProductInfo {
- /// Full name of the product
- fullname: String,
- /// Product abbreviation
- short: ProxmoxProduct,
- /// Version of the installed product
- version: String,
-}
-
-/// The current kernel version.
-/// Aligns with the format as used by the `/nodes/<node>/status` API of each product.
-#[derive(Serialize)]
-struct KernelVersionInformation {
- /// The systemname/nodename
- pub sysname: String,
- /// The kernel release number
- pub release: String,
- /// The kernel version
- pub version: String,
- /// The machine architecture
- pub machine: String,
-}
-
-/// Information about the CPU(s) installed in the system
-#[derive(Serialize)]
-struct CpuInfo {
- /// Number of physical CPU cores.
- cores: usize,
- /// Number of logical CPU cores aka. threads.
- cpus: usize,
- /// CPU feature flag set as a space-delimited list.
- flags: String,
- /// Whether hardware-accelerated virtualization is supported.
- hvm: bool,
- /// Reported model of the CPU(s)
- model: String,
- /// Number of physical CPU sockets
- sockets: usize,
-}
-
-/// Metadata of the hook, such as schema version of the document.
-#[derive(Serialize)]
-#[serde(rename_all = "kebab-case")]
-struct PostHookInfoSchema {
- /// major.minor version describing the schema version of this document, in a semanticy-version
- /// way.
- ///
- /// major: Incremented for incompatible/breaking API changes, e.g. removing an existing
- /// field.
- /// minor: Incremented when adding functionality in a backwards-compatible matter, e.g.
- /// adding a new field.
- version: String,
-}
-
-impl PostHookInfoSchema {
- const SCHEMA_VERSION: &str = "1.2";
-}
-
-impl Default for PostHookInfoSchema {
- fn default() -> Self {
- Self {
- version: Self::SCHEMA_VERSION.to_owned(),
- }
- }
-}
-
-/// All data sent as request payload with the post-installation-webhook POST request.
-///
-/// NOTE: The format is versioned through `schema.version` (`$schema.version` in the
-/// resulting JSON), ensure you update it when this struct or any of its members gets modified.
-#[derive(Serialize)]
-#[serde(rename_all = "kebab-case")]
-struct PostHookInfo {
- // This field is prefixed by `$` on purpose, to indicate that it is document metadata and not
- // part of the actual content itself. (E.g. JSON Schema uses a similar naming scheme)
- #[serde(rename = "$schema")]
- schema: PostHookInfoSchema,
- /// major.minor version of Debian as installed, retrieved from /etc/debian_version
- debian_version: String,
- /// PVE/PMG/PBS/PDM version as reported by `pveversion`, `pmgversion`,
- /// `proxmox-backup-manager version` or `proxmox-datacenter-manager version`, respectively.
- product: ProductInfo,
- /// Release information for the ISO used for the installation.
- iso: IsoInfo,
- /// Installed kernel version
- kernel_version: KernelVersionInformation,
- /// Describes the boot mode of the machine and the SecureBoot status.
- boot_info: BootInfo,
- /// Information about the installed CPU(s)
- cpu_info: CpuInfo,
- /// DMI information about the system
- dmi: SystemDMI,
- /// Filesystem used for boot disk(s)
- filesystem: FsType,
- /// Fully qualified domain name of the installed system
- fqdn: String,
- /// Unique systemd-id128 identifier of the installed system (128-bit, 16 bytes)
- machine_id: String,
- /// All disks detected on the system.
- disks: Vec<DiskInfo>,
- /// All network interfaces detected on the system.
- network_interfaces: Vec<NetworkInterfaceInfo>,
- /// Public parts of SSH host keys of the installed system
- ssh_public_host_keys: SshPublicHostKeys,
- /// Action to will be performed, i.e. either reboot or power off the machine.
- reboot_mode: RebootMode,
-}
-
-/// Defines the size of a gibibyte in bytes.
-const SIZE_GIB: usize = 1024 * 1024 * 1024;
-
-impl PostHookInfo {
/// Gathers all needed information about the newly installed system for sending
/// it to a specified server.
///
@@ -222,7 +59,7 @@ impl PostHookInfo {
///
/// * `target_path` - Path to where the chroot environment root is mounted
/// * `answer` - Answer file as provided by the user
- fn gather(target_path: &str, answer: &Answer) -> Result<Self> {
+ pub fn gather(target_path: &str, answer: &AutoInstallerConfig) -> Result<PostHookInfo> {
println!("Gathering installed system data ...");
let config: InstallConfig =
@@ -265,34 +102,42 @@ impl PostHookInfo {
let fqdn = match &answer.global.fqdn {
FqdnConfig::Simple(name) => name.to_string(),
- FqdnConfig::Extended(FqdnExtendedConfig {
+ FqdnConfig::FromDhcp(FqdnFromDhcpConfig {
source: FqdnSourceMode::FromDhcp,
domain,
}) => NetworkOptions::construct_fqdn(
&run_env.network,
- setup_info.config.product.default_hostname(),
+ &setup_info.config.product.to_string(),
domain.as_deref(),
)
.to_string(),
};
- Ok(Self {
- schema: PostHookInfoSchema::default(),
+ Ok(PostHookInfo {
+ schema: PostHookInfoSchema {
+ version: super::POST_HOOK_SCHEMA_VERSION.to_owned(),
+ },
debian_version: read_file("/etc/debian_version")?,
- product: Self::gather_product_info(&setup_info, &run_cmd)?,
- iso: setup_info.iso_info.clone(),
- kernel_version: Self::gather_kernel_version(&run_cmd, &open_file)?,
+ product: gather_product_info(&setup_info, &run_cmd)?,
+ iso: IsoInfo {
+ release: setup_info.iso_info.release,
+ isorelease: setup_info.iso_info.isorelease,
+ },
+ kernel_version: gather_kernel_version(&run_cmd, &open_file)?,
boot_info: BootInfo {
- mode: run_env.boot_type,
+ mode: match run_env.boot_type {
+ proxmox_installer_common::setup::BootType::Bios => BootType::Bios,
+ proxmox_installer_common::setup::BootType::Efi => BootType::Efi,
+ },
secureboot: run_env.secure_boot,
},
- cpu_info: Self::gather_cpu_info(&run_env)?,
- dmi: SystemDMI::get()?,
- filesystem: answer.disks.fs_type,
+ cpu_info: gather_cpu_info(&run_env)?,
+ dmi: proxmox_installer_common::dmi::get()?,
+ filesystem: answer.disks.filesystem_details()?.to_type(),
fqdn,
machine_id: read_file("/etc/machine-id")?,
- disks: Self::gather_disks(&config, &run_env, &udev)?,
- network_interfaces: Self::gather_nic(&config, &run_env, &udev)?,
+ disks: gather_disks(&config, &run_env, &udev)?,
+ network_interfaces: gather_nic(&config, &run_env, &udev)?,
ssh_public_host_keys: SshPublicHostKeys {
ecdsa: read_file("/etc/ssh/ssh_host_ecdsa_key.pub")?,
ed25519: read_file("/etc/ssh/ssh_host_ed25519_key.pub")?,
@@ -335,10 +180,10 @@ impl PostHookInfo {
.target_hd
.as_ref()
.map(|hd| *hd == disk.path)
- .unwrap_or_default();
+ .unwrap_or(false);
anyhow::Ok(DiskInfo {
- size: (config.hdsize * (SIZE_GIB as f64)) as usize,
+ size: (config.hdsize * (SIZE_GIB as f64)) as u64,
is_bootdisk,
udev_properties: get_udev_properties(disk)?,
})
@@ -346,7 +191,7 @@ impl PostHookInfo {
.collect()
} else {
// If the filesystem is not LVM-based (thus Btrfs or ZFS), `config.disk_selection`
- // contains a list of indices identifiying the boot disks, as given by udev.
+ // contains a list of indices identifying the boot disks, as given by udev.
let selected_disks_indices: Vec<&String> = config.disk_selection.values().collect();
run_env
@@ -356,7 +201,7 @@ impl PostHookInfo {
let is_bootdisk = selected_disks_indices.contains(&&disk.index);
anyhow::Ok(DiskInfo {
- size: (config.hdsize * (SIZE_GIB as f64)) as usize,
+ size: (config.hdsize * (SIZE_GIB as f64)) as u64,
is_bootdisk,
udev_properties: get_udev_properties(disk)?,
})
@@ -443,7 +288,12 @@ impl PostHookInfo {
Ok(ProductInfo {
fullname: setup_info.config.fullname.clone(),
- short: setup_info.config.product,
+ short: match setup_info.config.product {
+ ProxmoxProduct::PVE => proxmox_installer_types::ProxmoxProduct::Pve,
+ ProxmoxProduct::PBS => proxmox_installer_types::ProxmoxProduct::Pbs,
+ ProxmoxProduct::PMG => proxmox_installer_types::ProxmoxProduct::Pmg,
+ ProxmoxProduct::PDM => proxmox_installer_types::ProxmoxProduct::Pdm,
+ },
version,
})
}
@@ -465,7 +315,7 @@ impl PostHookInfo {
run_cmd: &dyn Fn(&[&str]) -> Result<String>,
open_file: &dyn Fn(&str) -> Result<File>,
) -> Result<KernelVersionInformation> {
- let file = open_file(&Self::find_kernel_image_path(run_cmd)?)?;
+ let file = open_file(&find_kernel_image_path(run_cmd)?)?;
// Read the 2-byte `kernel_version` field at offset 0x20e [0] from the file ..
// https://www.kernel.org/doc/html/latest/arch/x86/boot.html#the-real-mode-kernel-header
@@ -525,7 +375,7 @@ impl PostHookInfo {
run_cmd: &dyn Fn(&[&str]) -> Result<String>,
_open_file: &dyn Fn(&str) -> Result<File>,
) -> Result<KernelVersionInformation> {
- let image_path = Self::find_kernel_image_path(run_cmd)?;
+ let image_path = find_kernel_image_path(run_cmd)?;
let release = image_path
.strip_prefix("/boot/vmlinuz-")
@@ -537,11 +387,7 @@ impl PostHookInfo {
.map(|v| {
// /proc/version: "Linux version 6.17.2-1-pve (...) #1 SMP ..."
// extract everything after the second space
- v.splitn(3, ' ')
- .nth(2)
- .unwrap_or("")
- .trim()
- .to_owned()
+ v.splitn(3, ' ').nth(2).unwrap_or("").trim().to_owned()
})
.unwrap_or_default();
@@ -560,7 +406,7 @@ impl PostHookInfo {
///
/// * `run_cmd` - Callback to run a command inside the target chroot.
fn find_kernel_image_path(run_cmd: &dyn Fn(&[&str]) -> Result<String>) -> Result<String> {
- let pkg_name = Self::find_kernel_package_name(run_cmd)?;
+ let pkg_name = find_kernel_package_name(run_cmd)?;
let all_files = run_cmd(&["dpkg-query", "--listfiles", &pkg_name])?;
for file in all_files.lines() {
@@ -667,6 +513,200 @@ impl PostHookInfo {
Ok(result)
}
+
+ #[cfg(test)]
+ mod tests {
+ use super::{find_kernel_image_path, find_kernel_package_name};
+
+ #[test]
+ fn finds_correct_kernel_package_name() {
+ let mocked_run_cmd = |cmd: &[&str]| {
+ if cmd[0] == "dpkg" {
+ assert_eq!(cmd, &["dpkg", "--print-architecture"]);
+ Ok("amd64\n".to_owned())
+ } else {
+ assert_eq!(
+ cmd,
+ &[
+ "dpkg-query",
+ "--showformat",
+ "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
+ "--show",
+ "proxmox-kernel-[0-9]*",
+ ]
+ );
+ Ok(r#"ii |all|proxmox-kernel-6.8
+un ||proxmox-kernel-6.8.8-2-pve
+ii |amd64|proxmox-kernel-6.8.8-2-pve-signed
+ "#
+ .to_owned())
+ }
+ };
+
+ assert_eq!(
+ find_kernel_package_name(&mocked_run_cmd).unwrap(),
+ "proxmox-kernel-6.8.8-2-pve-signed"
+ );
+ }
+
+ #[test]
+ fn finds_correct_kernel_package_name_arm64() {
+ let mocked_run_cmd = |cmd: &[&str]| {
+ if cmd[0] == "dpkg" {
+ assert_eq!(cmd, &["dpkg", "--print-architecture"]);
+ Ok("arm64\n".to_owned())
+ } else {
+ assert_eq!(
+ cmd,
+ &[
+ "dpkg-query",
+ "--showformat",
+ "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
+ "--show",
+ "proxmox-kernel-[0-9]*",
+ ]
+ );
+ Ok(r#"ii |all|proxmox-kernel-6.17
+un ||proxmox-kernel-6.17.2-1-pve
+ii |arm64|proxmox-kernel-6.17.2-1-pve-signed
+ "#
+ .to_owned())
+ }
+ };
+
+ assert_eq!(
+ find_kernel_package_name(&mocked_run_cmd).unwrap(),
+ "proxmox-kernel-6.17.2-1-pve-signed"
+ );
+ }
+
+ #[test]
+ fn find_kernel_package_name_fails_on_wrong_architecture() {
+ let mocked_run_cmd = |cmd: &[&str]| {
+ if cmd[0] == "dpkg" {
+ assert_eq!(cmd, &["dpkg", "--print-architecture"]);
+ Ok("arm64\n".to_owned())
+ } else {
+ assert_eq!(
+ cmd,
+ &[
+ "dpkg-query",
+ "--showformat",
+ "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
+ "--show",
+ "proxmox-kernel-[0-9]*",
+ ]
+ );
+ Ok(r#"ii |all|proxmox-kernel-6.8
+un ||proxmox-kernel-6.8.8-2-pve
+ii |amd64|proxmox-kernel-6.8.8-2-pve-signed
+ "#
+ .to_owned())
+ }
+ };
+
+ assert_eq!(
+ find_kernel_package_name(&mocked_run_cmd)
+ .unwrap_err()
+ .to_string(),
+ "failed to find installed kernel package"
+ );
+ }
+
+ #[test]
+ fn find_kernel_package_name_fails_on_missing_package() {
+ let mocked_run_cmd = |cmd: &[&str]| {
+ if cmd[0] == "dpkg" {
+ assert_eq!(cmd, &["dpkg", "--print-architecture"]);
+ Ok("amd64\n".to_owned())
+ } else {
+ assert_eq!(
+ cmd,
+ &[
+ "dpkg-query",
+ "--showformat",
+ "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
+ "--show",
+ "proxmox-kernel-[0-9]*",
+ ]
+ );
+ Ok(r#"ii |all|proxmox-kernel-6.8
+un ||proxmox-kernel-6.8.8-2-pve
+ "#
+ .to_owned())
+ }
+ };
+
+ assert_eq!(
+ find_kernel_package_name(&mocked_run_cmd)
+ .unwrap_err()
+ .to_string(),
+ "failed to find installed kernel package"
+ );
+ }
+
+ #[test]
+ fn finds_correct_absolute_kernel_image_path() {
+ let mocked_run_cmd = |cmd: &[&str]| {
+ if cmd[0] == "dpkg" {
+ assert_eq!(cmd, &["dpkg", "--print-architecture"]);
+ Ok("amd64\n".to_owned())
+ } else if cmd[0..=1] == ["dpkg-query", "--showformat"] {
+ assert_eq!(
+ cmd,
+ &[
+ "dpkg-query",
+ "--showformat",
+ "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
+ "--show",
+ "proxmox-kernel-[0-9]*",
+ ]
+ );
+ Ok(r#"ii |all|proxmox-kernel-6.8
+un ||proxmox-kernel-6.8.8-2-pve
+ii |amd64|proxmox-kernel-6.8.8-2-pve-signed
+ "#
+ .to_owned())
+ } else {
+ assert_eq!(
+ cmd,
+ [
+ "dpkg-query",
+ "--listfiles",
+ "proxmox-kernel-6.8.8-2-pve-signed"
+ ]
+ );
+ Ok(r#"
+/.
+/boot
+/boot/System.map-6.8.8-2-pve
+/boot/config-6.8.8-2-pve
+/boot/vmlinuz-6.8.8-2-pve
+/lib
+/lib/modules
+/lib/modules/6.8.8-2-pve
+/lib/modules/6.8.8-2-pve/kernel
+/lib/modules/6.8.8-2-pve/kernel/arch
+/lib/modules/6.8.8-2-pve/kernel/arch/x86
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aegis128-aesni.ko
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aesni-intel.ko
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx-x86_64.ko
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx2-x86_64.ko
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-gfni-avx512-x86_64.ko
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/blowfish-x86_64.ko
+/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/camellia-aesni-avx-x86_64.ko
+ "#
+ .to_owned())
+ }
+ };
+
+ assert_eq!(
+ find_kernel_image_path(&mocked_run_cmd).unwrap(),
+ "/boot/vmlinuz-6.8.8-2-pve"
+ );
+ }
+ }
}
/// Runs the specified callback with the mounted chroot, passing along the
@@ -704,7 +744,9 @@ fn with_chroot<R, F: FnOnce(&str) -> Result<R>>(callback: F) -> Result<R> {
/// optional certificate fingerprint for HTTPS). If configured, retrieves all relevant information
/// about the installed system and sends them to the given endpoint.
fn do_main() -> Result<()> {
- let answer = Answer::try_from_reader(std::io::stdin().lock())?;
+ let mut raw_toml = String::new();
+ std::io::stdin().read_to_string(&mut raw_toml)?;
+ let answer: AutoInstallerConfig = toml::from_str(&raw_toml)?;
if let Some(PostNotificationHookInfo {
url,
@@ -713,7 +755,7 @@ fn do_main() -> Result<()> {
{
println!("Found post-installation-webhook; sending POST request to '{url}'.");
- let info = with_chroot(|target_path| PostHookInfo::gather(target_path, &answer))?;
+ let info = with_chroot(|target_path| detail::gather(target_path, &answer))?;
if let Err(err) = fs::write(
"/run/proxmox-installer/post-hook-data.json",
@@ -747,197 +789,3 @@ fn main() -> ExitCode {
}
}
}
-
-#[cfg(test)]
-mod tests {
- use crate::PostHookInfo;
-
- #[test]
- fn finds_correct_kernel_package_name() {
- let mocked_run_cmd = |cmd: &[&str]| {
- if cmd[0] == "dpkg" {
- assert_eq!(cmd, &["dpkg", "--print-architecture"]);
- Ok("amd64\n".to_owned())
- } else {
- assert_eq!(
- cmd,
- &[
- "dpkg-query",
- "--showformat",
- "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
- "--show",
- "proxmox-kernel-[0-9]*",
- ]
- );
- Ok(r#"ii |all|proxmox-kernel-6.8
-un ||proxmox-kernel-6.8.8-2-pve
-ii |amd64|proxmox-kernel-6.8.8-2-pve-signed
- "#
- .to_owned())
- }
- };
-
- assert_eq!(
- PostHookInfo::find_kernel_package_name(&mocked_run_cmd).unwrap(),
- "proxmox-kernel-6.8.8-2-pve-signed"
- );
- }
-
- #[test]
- fn finds_correct_kernel_package_name_arm64() {
- let mocked_run_cmd = |cmd: &[&str]| {
- if cmd[0] == "dpkg" {
- assert_eq!(cmd, &["dpkg", "--print-architecture"]);
- Ok("arm64\n".to_owned())
- } else {
- assert_eq!(
- cmd,
- &[
- "dpkg-query",
- "--showformat",
- "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
- "--show",
- "proxmox-kernel-[0-9]*",
- ]
- );
- Ok(r#"ii |all|proxmox-kernel-6.17
-un ||proxmox-kernel-6.17.2-1-pve
-ii |arm64|proxmox-kernel-6.17.2-1-pve-signed
- "#
- .to_owned())
- }
- };
-
- assert_eq!(
- PostHookInfo::find_kernel_package_name(&mocked_run_cmd).unwrap(),
- "proxmox-kernel-6.17.2-1-pve-signed"
- );
- }
-
- #[test]
- fn find_kernel_package_name_fails_on_wrong_architecture() {
- let mocked_run_cmd = |cmd: &[&str]| {
- if cmd[0] == "dpkg" {
- assert_eq!(cmd, &["dpkg", "--print-architecture"]);
- Ok("arm64\n".to_owned())
- } else {
- assert_eq!(
- cmd,
- &[
- "dpkg-query",
- "--showformat",
- "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
- "--show",
- "proxmox-kernel-[0-9]*",
- ]
- );
- Ok(r#"ii |all|proxmox-kernel-6.8
-un ||proxmox-kernel-6.8.8-2-pve
-ii |amd64|proxmox-kernel-6.8.8-2-pve-signed
- "#
- .to_owned())
- }
- };
-
- assert_eq!(
- PostHookInfo::find_kernel_package_name(&mocked_run_cmd)
- .unwrap_err()
- .to_string(),
- "failed to find installed kernel package"
- );
- }
-
- #[test]
- fn find_kernel_package_name_fails_on_missing_package() {
- let mocked_run_cmd = |cmd: &[&str]| {
- if cmd[0] == "dpkg" {
- assert_eq!(cmd, &["dpkg", "--print-architecture"]);
- Ok("amd64\n".to_owned())
- } else {
- assert_eq!(
- cmd,
- &[
- "dpkg-query",
- "--showformat",
- "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
- "--show",
- "proxmox-kernel-[0-9]*",
- ]
- );
- Ok(r#"ii |all|proxmox-kernel-6.8
-un ||proxmox-kernel-6.8.8-2-pve
- "#
- .to_owned())
- }
- };
-
- assert_eq!(
- PostHookInfo::find_kernel_package_name(&mocked_run_cmd)
- .unwrap_err()
- .to_string(),
- "failed to find installed kernel package"
- );
- }
-
- #[test]
- fn finds_correct_absolute_kernel_image_path() {
- let mocked_run_cmd = |cmd: &[&str]| {
- if cmd[0] == "dpkg" {
- assert_eq!(cmd, &["dpkg", "--print-architecture"]);
- Ok("amd64\n".to_owned())
- } else if cmd[0..=1] == ["dpkg-query", "--showformat"] {
- assert_eq!(
- cmd,
- &[
- "dpkg-query",
- "--showformat",
- "${db:Status-Abbrev}|${Architecture}|${Package}\\n",
- "--show",
- "proxmox-kernel-[0-9]*",
- ]
- );
- Ok(r#"ii |all|proxmox-kernel-6.8
-un ||proxmox-kernel-6.8.8-2-pve
-ii |amd64|proxmox-kernel-6.8.8-2-pve-signed
- "#
- .to_owned())
- } else {
- assert_eq!(
- cmd,
- [
- "dpkg-query",
- "--listfiles",
- "proxmox-kernel-6.8.8-2-pve-signed"
- ]
- );
- Ok(r#"
-/.
-/boot
-/boot/System.map-6.8.8-2-pve
-/boot/config-6.8.8-2-pve
-/boot/vmlinuz-6.8.8-2-pve
-/lib
-/lib/modules
-/lib/modules/6.8.8-2-pve
-/lib/modules/6.8.8-2-pve/kernel
-/lib/modules/6.8.8-2-pve/kernel/arch
-/lib/modules/6.8.8-2-pve/kernel/arch/x86
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aegis128-aesni.ko
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aesni-intel.ko
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx-x86_64.ko
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-aesni-avx2-x86_64.ko
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/aria-gfni-avx512-x86_64.ko
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/blowfish-x86_64.ko
-/lib/modules/6.8.8-2-pve/kernel/arch/x86/crypto/camellia-aesni-avx-x86_64.ko
- "#
- .to_owned())
- }
- };
-
- assert_eq!(
- PostHookInfo::find_kernel_image_path(&mocked_run_cmd).unwrap(),
- "/boot/vmlinuz-6.8.8-2-pve"
- );
- }
-}
diff --git a/proxmox-tui-installer/Cargo.toml b/proxmox-tui-installer/Cargo.toml
index 1ca91cb..56395a4 100644
--- a/proxmox-tui-installer/Cargo.toml
+++ b/proxmox-tui-installer/Cargo.toml
@@ -10,6 +10,7 @@ homepage = "https://www.proxmox.com"
[dependencies]
proxmox-installer-common.workspace = true
proxmox-network-types.workspace = true
+proxmox-installer-types.workspace = true
anyhow.workspace = true
serde_json.workspace = true
diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs
index c80877f..ff15fa0 100644
--- a/proxmox-tui-installer/src/options.rs
+++ b/proxmox-tui-installer/src/options.rs
@@ -2,29 +2,10 @@ use crate::SummaryOption;
use proxmox_installer_common::{
EMAIL_DEFAULT_PLACEHOLDER,
- options::{
- BootdiskOptions, BtrfsRaidLevel, FsType, NetworkOptions, TimezoneOptions, ZfsRaidLevel,
- },
+ options::{BootdiskOptions, NetworkOptions, TimezoneOptions},
setup::LocaleInfo,
};
-pub const FS_TYPES: &[FsType] = {
- use FsType::*;
- &[
- Ext4,
- Xfs,
- Zfs(ZfsRaidLevel::Raid0),
- Zfs(ZfsRaidLevel::Raid1),
- Zfs(ZfsRaidLevel::Raid10),
- Zfs(ZfsRaidLevel::RaidZ),
- Zfs(ZfsRaidLevel::RaidZ2),
- Zfs(ZfsRaidLevel::RaidZ3),
- Btrfs(BtrfsRaidLevel::Raid0),
- Btrfs(BtrfsRaidLevel::Raid1),
- Btrfs(BtrfsRaidLevel::Raid10),
- ]
-};
-
#[derive(Clone)]
pub struct PasswordOptions {
pub email: String,
diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs
index 5ec3e83..ed3936f 100644
--- a/proxmox-tui-installer/src/views/bootdisk.rs
+++ b/proxmox-tui-installer/src/views/bootdisk.rs
@@ -16,7 +16,6 @@ use cursive::{
use super::{DiskSizeEditView, FormView, IntegerEditView, TabbedView};
use crate::InstallerState;
-use crate::options::FS_TYPES;
use proxmox_installer_common::{
disk_checks::{
@@ -24,11 +23,12 @@ use proxmox_installer_common::{
},
options::{
AdvancedBootdiskOptions, BTRFS_COMPRESS_OPTIONS, BootdiskOptions, BtrfsBootdiskOptions,
- Disk, FsType, LvmBootdiskOptions, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS,
+ Disk, LvmBootdiskOptions, RaidLevel, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS,
ZfsBootdiskOptions,
},
setup::{BootType, ProductConfig, ProxmoxProduct, RuntimeInfo},
};
+use proxmox_installer_types::answer::{FILESYSTEM_TYPE_OPTIONS, FilesystemType};
/// OpenZFS specifies 64 MiB as the absolute minimum:
/// <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max>
@@ -125,19 +125,19 @@ impl AdvancedBootdiskOptionsView {
product_conf: ProductConfig,
) -> Self {
let filter_btrfs =
- |fstype: &&FsType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() };
+ |fstype: &&FilesystemType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() };
let options = options_ref.lock().unwrap();
let fstype_select = SelectView::new()
.popup()
.with_all(
- FS_TYPES
+ FILESYSTEM_TYPE_OPTIONS
.iter()
.filter(filter_btrfs)
.map(|t| (t.to_string(), *t)),
)
.selected(
- FS_TYPES
+ FILESYSTEM_TYPE_OPTIONS
.iter()
.filter(filter_btrfs)
.position(|t| *t == options.fstype)
@@ -185,7 +185,11 @@ impl AdvancedBootdiskOptionsView {
/// * `fstype` - The chosen filesystem type by the user, for which the UI should be
/// updated accordingly
/// * `options_ref` - [`BootdiskOptionsRef`] where advanced disk options should be saved to
- fn fstype_on_submit(siv: &mut Cursive, fstype: &FsType, options_ref: BootdiskOptionsRef) {
+ fn fstype_on_submit(
+ siv: &mut Cursive,
+ fstype: &FilesystemType,
+ options_ref: BootdiskOptionsRef,
+ ) {
let state = siv.user_data::<InstallerState>().unwrap();
let runinfo = state.runtime_info.clone();
let product_conf = state.setup_info.config.clone();
@@ -208,16 +212,16 @@ impl AdvancedBootdiskOptionsView {
{
view.remove_child(3);
match fstype {
- FsType::Ext4 | FsType::Xfs => {
+ FilesystemType::Ext4 | FilesystemType::Xfs => {
view.add_child(LvmBootdiskOptionsView::new_with_defaults(
&selected_lvm_disk,
&product_conf,
))
}
- FsType::Zfs(_) => {
+ FilesystemType::Zfs(_) => {
view.add_child(ZfsBootdiskOptionsView::new_with_defaults(&runinfo))
}
- FsType::Btrfs(_) => {
+ FilesystemType::Btrfs(_) => {
view.add_child(BtrfsBootdiskOptionsView::new_with_defaults(&runinfo))
}
}
@@ -236,7 +240,7 @@ impl AdvancedBootdiskOptionsView {
siv.call_on_name(
"bootdisk-options-target-disk",
move |view: &mut FormView| match fstype {
- FsType::Ext4 | FsType::Xfs => {
+ FilesystemType::Ext4 | FilesystemType::Xfs => {
view.replace_child(
0,
target_bootdisk_selectview(&runinfo.disks, options_ref, &selected_lvm_disk),
@@ -252,7 +256,7 @@ impl AdvancedBootdiskOptionsView {
.view
.get_child(1)
.and_then(|v| v.downcast_ref::<FormView>())
- .and_then(|v| v.get_value::<SelectView<FsType>, _>(0))
+ .and_then(|v| v.get_value::<SelectView<FilesystemType>, _>(0))
.ok_or("Failed to retrieve filesystem type".to_owned())?;
let advanced = self
@@ -279,7 +283,7 @@ impl AdvancedBootdiskOptionsView {
.get_values()
.ok_or("Failed to retrieve advanced bootdisk options")?;
- if let FsType::Zfs(level) = fstype {
+ if let FilesystemType::Zfs(level) = fstype {
level
.check_raid_disks_setup(&disks)
.map_err(|err| format!("{fstype}: {err}"))?;
@@ -295,7 +299,7 @@ impl AdvancedBootdiskOptionsView {
.get_values()
.ok_or("Failed to retrieve advanced bootdisk options")?;
- if let FsType::Btrfs(level) = fstype {
+ if let FilesystemType::Btrfs(level) = fstype {
level
.check_raid_disks_setup(&disks)
.map_err(|err| format!("{fstype}: {err}"))?;
--
2.53.0
next prev parent reply other threads:[~2026-04-30 12:50 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-30 12:46 [PATCH datacenter-manager/installer/proxmox/yew-comp v4 00/40] add auto-installer integration Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 01/40] api-macro: allow $ in identifier name Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 02/40] schema: oneOf: allow single string variant Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 03/40] schema: implement UpdaterType for HashMap and BTreeMap Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 04/40] network-types: move `Fqdn` type from proxmox-installer-common Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 05/40] network-types: implement api type for Fqdn Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 06/40] network-types: add api wrapper type for std::net::IpAddr Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 07/40] network-types: cidr: implement generic `IpAddr::new` constructor Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 08/40] network-types: fqdn: implement standard library Error for Fqdn Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 09/40] node-status: make KernelVersionInformation Clone + PartialEq Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 10/40] installer-types: add common types used by the installer Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 11/40] installer-types: add types used by the auto-installer Christoph Heiss
2026-04-30 12:46 ` [PATCH proxmox v4 12/40] installer-types: implement api type for all externally-used types Christoph Heiss
2026-04-30 12:46 ` [PATCH yew-comp v4 13/40] widget: kvlist: add widget for user-modifiable data tables Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 14/40] api-types, cli: use ReturnType::new() instead of constructing it manually Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 15/40] api-types: add api types for auto-installer integration Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 16/40] config: add auto-installer configuration module Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 17/40] acl: wire up new /system/auto-installation acl path Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 18/40] server: api: add auto-installer integration module Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 19/40] server: api: auto-installer: add access token management endpoints Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 20/40] client: add bindings for auto-installer endpoints Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 21/40] ui: auto-installer: add installations overview panel Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 22/40] ui: auto-installer: add prepared answer configuration panel Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 23/40] ui: auto-installer: add access token " Christoph Heiss
2026-04-30 12:46 ` [PATCH datacenter-manager v4 24/40] docs: add documentation for auto-installer integration Christoph Heiss
2026-04-30 12:46 ` [PATCH installer v4 25/40] install: iso env: use JSON boolean literals for product config Christoph Heiss
2026-04-30 12:46 ` [PATCH installer v4 26/40] common: http: allow passing custom headers to post() Christoph Heiss
2026-04-30 12:46 ` [PATCH installer v4 27/40] common: http: retrieve error message from body on post() Christoph Heiss
2026-04-30 12:46 ` [PATCH installer v4 28/40] common: options: move regex construction out of loop Christoph Heiss
2026-04-30 12:46 ` [PATCH installer v4 29/40] assistant: support adding an authorization token for HTTP-based answers Christoph Heiss
2026-04-30 12:46 ` [PATCH installer v4 30/40] post-hook: run cargo fmt Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 31/40] tree-wide: used moved `Fqdn` type to proxmox-network-types Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 32/40] tree-wide: use `Cidr` type from proxmox-network-types Christoph Heiss
2026-04-30 12:47 ` Christoph Heiss [this message]
2026-04-30 12:47 ` [PATCH installer v4 34/40] auto: sysinfo: switch to types from proxmox-installer-types Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 35/40] fetch-answer: " Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 36/40] fetch-answer: http: prefer json over toml for answer format Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 37/40] fetch-answer: send auto-installer HTTP authorization token if set Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 38/40] fetch-answer: print full error messages when fetching failed Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 39/40] tree-wide: switch out `Answer` -> `AutoInstallerConfig` types Christoph Heiss
2026-04-30 12:47 ` [PATCH installer v4 40/40] auto: drop now-dead answer file definitions Christoph Heiss
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260430124712.1614305-34-c.heiss@proxmox.com \
--to=c.heiss@proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.