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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox