public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Lukas Wagner" <l.wagner@proxmox.com>
To: "Proxmox Datacenter Manager development discussion"
	<pdm-devel@lists.proxmox.com>,
	"Christoph Heiss" <c.heiss@proxmox.com>
Subject: Re: [pdm-devel] [PATCH proxmox v2 06/14] installer-types: add types used by the auto-installer
Date: Tue, 09 Dec 2025 10:44:16 +0100	[thread overview]
Message-ID: <DETL7QHIT8JC.25VUU2JHBCXD7@proxmox.com> (raw)
In-Reply-To: <20251205112528.373387-7-c.heiss@proxmox.com>

Mostly skimmed the code, since these were pre-existing type definitions
with not much logic, but looks good from what I can tell.

Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>

On Fri Dec 5, 2025 at 12:25 PM CET, Christoph Heiss wrote:
> Moving them over from proxmox-auto-installer and proxmox-post-hook, to
> allow re-use in other places.
>
> The network configuration and disk setup has been restructured slightly,
> making its typing a bit more ergonomic to work with. No functional
> changes though, still parses from/into the same format.
>
> Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
> ---
> Changes v1 -> v2:
>   * no changes
>
>  proxmox-installer-types/Cargo.toml       |   1 +
>  proxmox-installer-types/debian/control   |   2 +
>  proxmox-installer-types/src/answer.rs    | 898 +++++++++++++++++++++++
>  proxmox-installer-types/src/lib.rs       |   3 +
>  proxmox-installer-types/src/post_hook.rs | 183 +++++
>  5 files changed, 1087 insertions(+)
>  create mode 100644 proxmox-installer-types/src/answer.rs
>  create mode 100644 proxmox-installer-types/src/post_hook.rs
>
> diff --git a/proxmox-installer-types/Cargo.toml b/proxmox-installer-types/Cargo.toml
> index 62df7f4e..96413fe1 100644
> --- a/proxmox-installer-types/Cargo.toml
> +++ b/proxmox-installer-types/Cargo.toml
> @@ -12,6 +12,7 @@ exclude.workspace = true
>  rust-version.workspace = true
>  
>  [dependencies]
> +anyhow.workspace = true
>  serde = { workspace = true, features = ["derive"] }
>  serde_plain.workspace = true
>  proxmox-network-types.workspace = true
> diff --git a/proxmox-installer-types/debian/control b/proxmox-installer-types/debian/control
> index 902977af..d208a014 100644
> --- a/proxmox-installer-types/debian/control
> +++ b/proxmox-installer-types/debian/control
> @@ -6,6 +6,7 @@ Build-Depends: debhelper-compat (= 13),
>  Build-Depends-Arch: cargo:native <!nocheck>,
>   rustc:native (>= 1.82) <!nocheck>,
>   libstd-rust-dev <!nocheck>,
> + librust-anyhow-1+default-dev <!nocheck>,
>   librust-proxmox-network-types-0.1+api-types-dev <!nocheck>,
>   librust-proxmox-network-types-0.1+default-dev <!nocheck>,
>   librust-serde-1+default-dev <!nocheck>,
> @@ -23,6 +24,7 @@ Architecture: any
>  Multi-Arch: same
>  Depends:
>   ${misc:Depends},
> + librust-anyhow-1+default-dev,
>   librust-proxmox-network-types-0.1+api-types-dev,
>   librust-proxmox-network-types-0.1+default-dev,
>   librust-serde-1+default-dev,
> diff --git a/proxmox-installer-types/src/answer.rs b/proxmox-installer-types/src/answer.rs
> new file mode 100644
> index 00000000..7129a941
> --- /dev/null
> +++ b/proxmox-installer-types/src/answer.rs
> @@ -0,0 +1,898 @@
> +//! Defines API types for the answer file format used by proxmox-auto-installer.
> +//!
> +//! **NOTE**: New answer file properties must use kebab-case, but should allow
> +//! snake_case for backwards compatibility.
> +//!
> +//! TODO: Remove the snake_case'd variants in a future major version (e.g.
> +//! PVE 10).
> +
> +use anyhow::{anyhow, bail, Result};
> +use serde::{Deserialize, Serialize};
> +use std::{
> +    collections::{BTreeMap, HashMap},
> +    fmt::{self, Display},
> +    str::FromStr,
> +};
> +
> +use proxmox_network_types::{fqdn::Fqdn, ip_address::Cidr};
> +type IpAddr = std::net::IpAddr;
> +
> +/// Defines API types used by proxmox-fetch-answer, the first part of the
> +/// auto-installer.
> +pub mod fetch {
> +    use serde::{Deserialize, Serialize};
> +
> +    use crate::SystemInfo;
> +
> +    #[derive(Deserialize, Serialize)]
> +    #[serde(rename_all = "kebab-case")]
> +    /// Metadata of the HTTP POST payload, such as schema version of the document.
> +    pub struct AnswerFetchDataSchema {
> +        /// 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.
> +        pub version: String,
> +    }
> +
> +    impl AnswerFetchDataSchema {
> +        const SCHEMA_VERSION: &str = "1.0";
> +    }
> +
> +    impl Default for AnswerFetchDataSchema {
> +        fn default() -> Self {
> +            Self {
> +                version: Self::SCHEMA_VERSION.to_owned(),
> +            }
> +        }
> +    }
> +
> +    #[derive(Deserialize, Serialize)]
> +    #[serde(rename_all = "kebab-case")]
> +    /// Data sent in the body of POST request when retrieving the answer file via HTTP(S).
> +    ///
> +    /// 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.
> +    pub struct AnswerFetchData {
> +        /// Metadata for the answer file fetch payload
> +        // 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")]
> +        pub schema: AnswerFetchDataSchema,
> +        /// Information about the running system, flattened into this structure directly.
> +        #[serde(flatten)]
> +        pub sysinfo: SystemInfo,
> +    }
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Top-level answer file structure, describing all possible options for an
> +/// automated installation.
> +pub struct AutoInstallerConfig {
> +    /// General target system options for setting up the system in an automated
> +    /// installation.
> +    pub global: GlobalOptions,
> +    /// Network configuration to set up inside the target installation.
> +    pub network: NetworkConfig,
> +    #[serde(rename = "disk-setup")]
> +    /// Disk configuration for the target installation.
> +    pub disks: DiskSetup,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Optional webhook to hit after a successful installation with information
> +    /// about the provisioned system.
> +    pub post_installation_webhook: Option<PostNotificationHookInfo>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Optional one-time hook to run on the first boot into the newly provisioned
> +    /// system.
> +    pub first_boot: Option<FirstBootHookInfo>,
> +}
> +
> +#[derive(Clone, Default, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// General target system options for setting up the system in an automated
> +/// installation.
> +pub struct GlobalOptions {
> +    /// Country to use for apt mirrors.
> +    pub country: String,
> +    /// FQDN to set for the installed system.
> +    pub fqdn: FqdnConfig,
> +    /// Keyboard layout to set.
> +    pub keyboard: KeyboardLayout,
> +    /// Mail address for `root@pam`.
> +    pub mailto: String,
> +    /// Timezone to set on the new system.
> +    pub timezone: String,
> +    #[serde(alias = "root_password", skip_serializing_if = "Option::is_none")]
> +    /// Password to set for the `root` PAM account in plain text. Mutual
> +    /// exclusive with the `root-password-hashed` option.
> +    pub root_password: Option<String>,
> +    #[cfg_attr(feature = "legacy", serde(alias = "root_password_hashed"))]
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Password to set for the `root` PAM account as hash, created using e.g.
> +    /// mkpasswd(8). Mutual exclusive with the `root-password` option.
> +    pub root_password_hashed: Option<String>,
> +    #[serde(default)]
> +    #[cfg_attr(feature = "legacy", serde(alias = "reboot_on_error"))]
> +    /// Whether to reboot the machine if an error occurred during the
> +    /// installation.
> +    pub reboot_on_error: bool,
> +    #[serde(default)]
> +    #[cfg_attr(feature = "legacy", serde(alias = "reboot_mode"))]
> +    /// Action to take after the installation completed successfully.
> +    pub reboot_mode: RebootMode,
> +    #[serde(default)]
> +    #[cfg_attr(feature = "legacy", serde(alias = "root_ssh_keys"))]
> +    /// Public SSH keys to set up for the `root` PAM account.
> +    pub root_ssh_keys: Vec<String>,
> +}
> +
> +#[derive(Copy, Clone, Deserialize, Serialize, Debug, Default, PartialEq, Eq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Action to take after the installation completed successfully.
> +pub enum RebootMode {
> +    #[default]
> +    /// Reboot the machine.
> +    Reboot,
> +    /// Power off and halt the machine.
> +    PowerOff,
> +}
> +
> +serde_plain::derive_fromstr_from_deserialize!(RebootMode);
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(
> +    untagged,
> +    expecting = "either a fully-qualified domain name or extendend configuration for usage with DHCP must be specified"
> +)]
> +/// Allow the user to either set the FQDN of the installation to either some
> +/// fixed value or retrieve it dynamically via e.g.DHCP.
> +pub enum FqdnConfig {
> +    /// Sets the FQDN to the exact value.
> +    Simple(Fqdn),
> +    /// Extended configuration, e.g. to use hostname and domain from DHCP.
> +    FromDhcp(FqdnFromDhcpConfig),
> +}
> +
> +impl Default for FqdnConfig {
> +    fn default() -> Self {
> +        Self::FromDhcp(FqdnFromDhcpConfig::default())
> +    }
> +}
> +
> +impl FqdnConfig {
> +    /// Constructs a new "simple" FQDN configuration, i.e. a fixed hostname.
> +    pub fn simple<S: Into<String>>(fqdn: S) -> Result<Self> {
> +        Ok(Self::Simple(
> +            fqdn.into()
> +                .parse::<Fqdn>()
> +                .map_err(|err| anyhow!("{err}"))?,
> +        ))
> +    }
> +
> +    /// Constructs an extended FQDN configuration, in particular instructing the
> +    /// auto-installer to use the FQDN from DHCP lease information.
> +    pub fn from_dhcp(domain: Option<String>) -> Self {
> +        Self::FromDhcp(FqdnFromDhcpConfig {
> +            source: FqdnSourceMode::FromDhcp,
> +            domain,
> +        })
> +    }
> +}
> +
> +#[derive(Clone, Default, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Extended configuration for retrieving the FQDN from external sources.
> +pub struct FqdnFromDhcpConfig {
> +    /// Source to gather the FQDN from.
> +    #[serde(default)]
> +    pub source: FqdnSourceMode,
> +    /// Domain to use if none is received via DHCP.
> +    #[serde(default, deserialize_with = "deserialize_non_empty_string_maybe")]
> +    pub domain: Option<String>,
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Default, PartialEq, Serialize)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Describes the source to retrieve the FQDN of the installation.
> +pub enum FqdnSourceMode {
> +    #[default]
> +    /// Use the FQDN as provided by the DHCP server, if any.
> +    FromDhcp,
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Configuration for the post-installation hook, which runs after an
> +/// installation has completed successfully.
> +pub struct PostNotificationHookInfo {
> +    /// URL to send a POST request to
> +    pub url: String,
> +    /// SHA256 cert fingerprint if certificate pinning should be used.
> +    #[serde(skip_serializing_if = "Option::is_none", alias = "cert_fingerprint")]
> +    pub cert_fingerprint: Option<String>,
> +}
> +
> +#[derive(Clone, Deserialize, Debug, PartialEq, Serialize)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Possible sources for the optional first-boot hook script/executable file.
> +pub enum FirstBootHookSourceMode {
> +    /// Fetch the executable file from an URL, specified in the parent.
> +    FromUrl,
> +    /// The executable file has been baked into the ISO at a known location,
> +    /// and should be retrieved from there.
> +    FromIso,
> +}
> +
> +#[derive(Clone, Default, Deserialize, Debug, PartialEq, Serialize)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Possible orderings for the `proxmox-first-boot` systemd service.
> +///
> +/// Determines the final value of `Unit.Before` and `Unit.Wants` in the service
> +/// file.
> +// Must be kept in sync with Proxmox::Install::Config and the service files in the
> +// proxmox-first-boot package.
> +pub enum FirstBootHookServiceOrdering {
> +    /// Needed for bringing up the network itself, runs before any networking is attempted.
> +    BeforeNetwork,
> +    /// Network needs to be already online, runs after networking was brought up.
> +    NetworkOnline,
> +    /// Runs after the system has successfully booted up completely.
> +    #[default]
> +    FullyUp,
> +}
> +
> +impl FirstBootHookServiceOrdering {
> +    /// Maps the enum to the appropriate systemd target name, without the '.target' suffix.
> +    pub fn as_systemd_target_name(&self) -> &str {
> +        match self {
> +            FirstBootHookServiceOrdering::BeforeNetwork => "network-pre",
> +            FirstBootHookServiceOrdering::NetworkOnline => "network-online",
> +            FirstBootHookServiceOrdering::FullyUp => "multi-user",
> +        }
> +    }
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Describes from where to fetch the first-boot hook script, either being baked into the ISO or
> +/// from a URL.
> +pub struct FirstBootHookInfo {
> +    /// Mode how to retrieve the first-boot executable file, either from an URL or from the ISO if
> +    /// it has been baked-in.
> +    pub source: FirstBootHookSourceMode,
> +    /// Determines the service order when the hook will run on first boot.
> +    #[serde(default)]
> +    pub ordering: FirstBootHookServiceOrdering,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Retrieve the post-install script from a URL, if source == "from-url".
> +    pub url: Option<String>,
> +    /// SHA256 cert fingerprint if certificate pinning should be used, if source == "from-url".
> +    #[cfg_attr(feature = "legacy", serde(alias = "cert_fingerprint"))]
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    pub cert_fingerprint: Option<String>,
> +}
> +
> +/// Options controlling the behaviour of the network interface pinning (by
> +/// creating appropriate systemd.link files) during the installation.
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +pub struct NetworkInterfacePinningOptionsAnswer {
> +    /// Whether interfaces should be pinned during the installation.
> +    pub enabled: bool,
> +    /// Maps MAC address to custom name
> +    #[serde(default)]
> +    pub mapping: HashMap<String, String>,
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Static network configuration given by the user.
> +pub struct NetworkConfigFromAnswer {
> +    /// CIDR of the machine.
> +    pub cidr: Cidr,
> +    /// DNS nameserver host to use.
> +    pub dns: IpAddr,
> +    /// Gateway to set.
> +    pub gateway: IpAddr,
> +    #[serde(default)]
> +    /// Filter for network devices, to select a specific management interface.
> +    pub filter: BTreeMap<String, String>,
> +    /// Controls network interface pinning behaviour during installation.
> +    /// Off by default. Allowed for both `from-dhcp` and `from-answer` modes.
> +    #[serde(default)]
> +    pub interface_name_pinning: Option<NetworkInterfacePinningOptionsAnswer>,
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Use the network configuration received from the DHCP server.
> +pub struct NetworkConfigFromDhcp {
> +    /// Controls network interface pinning behaviour during installation.
> +    /// Off by default. Allowed for both `from-dhcp` and `from-answer` modes.
> +    #[serde(default)]
> +    pub interface_name_pinning: Option<NetworkInterfacePinningOptionsAnswer>,
> +}
> +
> +#[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields, tag = "source")]
> +/// Network configuration to set up inside the target installation.
> +/// It can either be given statically or taken from the DHCP lease.
> +pub enum NetworkConfig {
> +    /// Use the configuration from the DHCP lease.
> +    FromDhcp(NetworkConfigFromDhcp),
> +    /// Static configuration to apply.
> +    FromAnswer(NetworkConfigFromAnswer),
> +}
> +
> +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", tag = "filesystem")]
> +/// Filesystem-specific options to set on the root disk.
> +pub enum FilesystemOptions {
> +    /// LVM-specific options, used when the selected filesystem is ext4 or xfs.
> +    Lvm(LvmOptions),
> +    /// ZFS-specific options.
> +    Zfs(ZfsOptions),
> +    /// Btrfs-specific options.
> +    Btrfs(BtrfsOptions),
> +}
> +
> +#[derive(Clone, Debug, Serialize)]
> +/// Defines the disks to use for the installation. Can either be a fixed list
> +/// of disk names or a dynamic filter list.
> +pub enum DiskSelection {
> +    /// Fixed list of disk names to use for the installation.
> +    Selection(Vec<String>),
> +    /// Select disks dynamically by filtering them by udev properties.
> +    Filter(BTreeMap<String, String>),
> +}
> +
> +impl Display for DiskSelection {
> +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
> +        match self {
> +            Self::Selection(disks) => write!(f, "{}", disks.join(", ")),
> +            Self::Filter(map) => write!(
> +                f,
> +                "{}",
> +                map.iter()
> +                    .fold(String::new(), |acc, (k, v)| format!("{acc}{k}: {v}\n"))
> +                    .trim_end()
> +            ),
> +        }
> +    }
> +}
> +
> +#[derive(Copy, Clone, Default, Deserialize, Debug, PartialEq, Serialize)]
> +#[serde(rename_all = "lowercase", deny_unknown_fields)]
> +/// Whether the associated filters must all match for a device or if any one
> +/// is enough.
> +pub enum FilterMatch {
> +    /// Device must match any filter.
> +    #[default]
> +    Any,
> +    /// Device must match all given filters.
> +    All,
> +}
> +
> +serde_plain::derive_fromstr_from_deserialize!(FilterMatch);
> +
> +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Disk configuration for the target installation.
> +pub struct DiskSetup {
> +    /// Filesystem to use on the root disk.
> +    pub filesystem: Filesystem,
> +    #[serde(default)]
> +    #[cfg_attr(feature = "legacy", serde(alias = "disk_list"))]
> +    /// List of raw disk identifiers to use for the root filesystem.
> +    pub disk_list: Vec<String>,
> +    #[serde(default)]
> +    /// Filter against udev properties to select the disks for the installation,
> +    /// to allow dynamic selection of disks.
> +    pub filter: BTreeMap<String, String>,
> +    #[cfg_attr(feature = "legacy", serde(alias = "filter_match"))]
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Set whether it is enough that any filter matches on a disk or all given
> +    /// filters must match to select a disk.
> +    pub filter_match: Option<FilterMatch>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// ZFS-specific filesystem options.
> +    pub zfs: Option<ZfsOptions>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// LVM-specific filesystem options, when using ext4 or xfs as filesystem.
> +    pub lvm: Option<LvmOptions>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Btrfs-specific filesystem options.
> +    pub btrfs: Option<BtrfsOptions>,
> +}
> +
> +impl DiskSetup {
> +    /// Returns the concrete disk selection made in the setup.
> +    pub fn disk_selection(&self) -> Result<DiskSelection> {
> +        if self.disk_list.is_empty() && self.filter.is_empty() {
> +            bail!("Need either 'disk-list' or 'filter' set");
> +        }
> +        if !self.disk_list.is_empty() && !self.filter.is_empty() {
> +            bail!("Cannot use both, 'disk-list' and 'filter'");
> +        }
> +
> +        if !self.disk_list.is_empty() {
> +            Ok(DiskSelection::Selection(self.disk_list.clone()))
> +        } else {
> +            Ok(DiskSelection::Filter(self.filter.clone()))
> +        }
> +    }
> +
> +    /// Returns the concrete filesystem type and corresponding options selected
> +    /// in the setup.
> +    pub fn filesystem_details(&self) -> Result<(FilesystemType, FilesystemOptions)> {
> +        let lvm_checks = || -> Result<()> {
> +            if self.zfs.is_some() || self.btrfs.is_some() {
> +                bail!("make sure only 'lvm' options are set");
> +            }
> +            if self.disk_list.len() > 1 {
> +                bail!("make sure to define only one disk for ext4 and xfs");
> +            }
> +            Ok(())
> +        };
> +
> +        match self.filesystem {
> +            Filesystem::Xfs => {
> +                lvm_checks()?;
> +                Ok((
> +                    FilesystemType::Xfs,
> +                    FilesystemOptions::Lvm(self.lvm.unwrap_or_default()),
> +                ))
> +            }
> +            Filesystem::Ext4 => {
> +                lvm_checks()?;
> +                Ok((
> +                    FilesystemType::Ext4,
> +                    FilesystemOptions::Lvm(self.lvm.unwrap_or_default()),
> +                ))
> +            }
> +            Filesystem::Zfs => {
> +                if self.lvm.is_some() || self.btrfs.is_some() {
> +                    bail!("make sure only 'zfs' options are set");
> +                }
> +                match self.zfs {
> +                    None | Some(ZfsOptions { raid: None, .. }) => {
> +                        bail!("ZFS raid level 'zfs.raid' must be set");
> +                    }
> +                    Some(opts) => Ok((
> +                        FilesystemType::Zfs(opts.raid.unwrap()),
> +                        FilesystemOptions::Zfs(opts),
> +                    )),
> +                }
> +            }
> +            Filesystem::Btrfs => {
> +                if self.zfs.is_some() || self.lvm.is_some() {
> +                    bail!("make sure only 'btrfs' options are set");
> +                }
> +                match self.btrfs {
> +                    None | Some(BtrfsOptions { raid: None, .. }) => {
> +                        bail!("Btrfs raid level 'btrfs.raid' must be set");
> +                    }
> +                    Some(opts) => Ok((
> +                        FilesystemType::Btrfs(opts.raid.unwrap()),
> +                        FilesystemOptions::Btrfs(opts),
> +                    )),
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
> +#[serde(rename_all = "lowercase", deny_unknown_fields)]
> +/// Available filesystem during installation.
> +pub enum Filesystem {
> +    /// Fourth extended filesystem
> +    Ext4,
> +    /// XFS
> +    Xfs,
> +    /// ZFS
> +    Zfs,
> +    /// Btrfs
> +    Btrfs,
> +}
> +
> +#[derive(Clone, Copy, Default, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// ZFS-specific filesystem options.
> +pub struct ZfsOptions {
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// RAID level to use.
> +    pub raid: Option<ZfsRaidLevel>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// `ashift` value to create the zpool with.
> +    pub ashift: Option<u32>,
> +    #[cfg_attr(feature = "legacy", serde(alias = "arc_max"))]
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Maximum ARC size that ZFS should use, in MiB.
> +    pub arc_max: Option<u32>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Checksumming algorithm to create the zpool with.
> +    pub checksum: Option<ZfsChecksumOption>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Compression algorithm to set on the zpool.
> +    pub compress: Option<ZfsCompressOption>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// `copies` value to create the zpool with.
> +    pub copies: Option<u32>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Size of the root disk to use, can be used to reserve free space on the
> +    /// hard disk for further partitioning after the installation. Optional,
> +    /// will be heuristically determined if unset.
> +    pub hdsize: Option<f64>,
> +}
> +
> +#[derive(Clone, Copy, Default, Deserialize, Serialize, Debug, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// LVM-specific filesystem options, when using ext4 or xfs as filesystem.
> +pub struct LvmOptions {
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Size of the root disk to use, can be used to reserve free space on the
> +    /// hard disk for further partitioning after the installation. Optional,
> +    /// will be heuristically determined if unset.
> +    pub hdsize: Option<f64>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Size of the swap volume. Optional, will be heuristically determined if
> +    /// unset.
> +    pub swapsize: Option<f64>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Maximum size the `root` volume. Optional, will be heuristically determined
> +    /// if unset.
> +    pub maxroot: Option<f64>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Maximum size the `data` volume. Optional, will be heuristically determined
> +    /// if unset.
> +    pub maxvz: Option<f64>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Minimum amount of free space that should be left in the LVM volume group.
> +    /// Optional, will be heuristically determined if unset.
> +    pub minfree: Option<f64>,
> +}
> +
> +#[derive(Clone, Copy, Default, Deserialize, Debug, Serialize, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Btrfs-specific filesystem options.
> +pub struct BtrfsOptions {
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Size of the root partition. Optional, will be heuristically determined if
> +    /// unset.
> +    pub hdsize: Option<f64>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// RAID level to use.
> +    pub raid: Option<BtrfsRaidLevel>,
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    /// Whether to enable filesystem-level compression and what type.
> +    pub compress: Option<BtrfsCompressOption>,
> +}
> +
> +#[derive(Copy, Clone, Deserialize, Serialize, Debug, Default, PartialEq)]
> +#[serde(rename_all = "kebab-case", deny_unknown_fields)]
> +/// Keyboard layout of the system.
> +pub enum KeyboardLayout {
> +    /// German
> +    De,
> +    /// Swiss-German
> +    DeCh,
> +    /// Danish
> +    Dk,
> +    /// United Kingdom English
> +    EnGb,
> +    #[default]
> +    /// U.S. English
> +    EnUs,
> +    /// Spanish
> +    Es,
> +    /// Finnish
> +    Fi,
> +    /// French
> +    Fr,
> +    /// Belgium-French
> +    FrBe,
> +    /// Canada-French
> +    FrCa,
> +    /// Swiss-French
> +    FrCh,
> +    /// Hungarian
> +    Hu,
> +    /// Icelandic
> +    Is,
> +    /// Italian
> +    It,
> +    /// Japanese
> +    Jp,
> +    /// Lithuanian
> +    Lt,
> +    /// Macedonian
> +    Mk,
> +    /// Dutch
> +    Nl,
> +    /// Norwegian
> +    No,
> +    /// Polish
> +    Pl,
> +    /// Portuguese
> +    Pt,
> +    /// Brazil-Portuguese
> +    PtBr,
> +    /// Swedish
> +    Se,
> +    /// Slovenian
> +    Si,
> +    /// Turkish
> +    Tr,
> +}
> +
> +impl Display for KeyboardLayout {
> +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
> +        match self {
> +            Self::Dk => write!(f, "Danish"),
> +            Self::De => write!(f, "German"),
> +            Self::DeCh => write!(f, "Swiss-German"),
> +            Self::EnGb => write!(f, "United Kingdom"),
> +            Self::EnUs => write!(f, "U.S. English"),
> +            Self::Es => write!(f, "Spanish"),
> +            Self::Fi => write!(f, "Finnish"),
> +            Self::Fr => write!(f, "French"),
> +            Self::FrBe => write!(f, "Belgium-French"),
> +            Self::FrCa => write!(f, "Canada-French"),
> +            Self::FrCh => write!(f, "Swiss-French"),
> +            Self::Hu => write!(f, "Hungarian"),
> +            Self::Is => write!(f, "Icelandic"),
> +            Self::It => write!(f, "Italian"),
> +            Self::Jp => write!(f, "Japanese"),
> +            Self::Lt => write!(f, "Lithuanian"),
> +            Self::Mk => write!(f, "Macedonian"),
> +            Self::Nl => write!(f, "Dutch"),
> +            Self::No => write!(f, "Norwegian"),
> +            Self::Pl => write!(f, "Polish"),
> +            Self::Pt => write!(f, "Portuguese"),
> +            Self::PtBr => write!(f, "Brazil-Portuguese"),
> +            Self::Si => write!(f, "Slovenian"),
> +            Self::Se => write!(f, "Swedish"),
> +            Self::Tr => write!(f, "Turkish"),
> +        }
> +    }
> +}
> +
> +serde_plain::derive_fromstr_from_deserialize!(KeyboardLayout);
> +
> +#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
> +#[serde(rename_all(deserialize = "lowercase", serialize = "UPPERCASE"))]
> +/// Available Btrfs RAID levels.
> +pub enum BtrfsRaidLevel {
> +    #[serde(alias = "RAID0")]
> +    /// RAID 0, aka. single or striped.
> +    Raid0,
> +    #[serde(alias = "RAID1")]
> +    /// RAID 1, aka. mirror.
> +    Raid1,
> +    #[serde(alias = "RAID10")]
> +    /// RAID 10, combining stripe and mirror.
> +    Raid10,
> +}
> +
> +serde_plain::derive_display_from_serialize!(BtrfsRaidLevel);
> +
> +#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
> +#[serde(rename_all = "lowercase")]
> +/// Possible compression algorithms usable with Btrfs. See the accompanying
> +/// mount option in btrfs(5).
> +pub enum BtrfsCompressOption {
> +    /// Enable compression, chooses the default algorithm set by Btrfs.
> +    On,
> +    #[default]
> +    /// Disable compression.
> +    Off,
> +    /// Use zlib for compression.
> +    Zlib,
> +    /// Use zlo for compression.
> +    Lzo,
> +    /// Use Zstandard for compression.
> +    Zstd,
> +}
> +
> +serde_plain::derive_display_from_serialize!(BtrfsCompressOption);
> +serde_plain::derive_fromstr_from_deserialize!(BtrfsCompressOption);
> +
> +/// List of all available Btrfs compression options.
> +pub const BTRFS_COMPRESS_OPTIONS: &[BtrfsCompressOption] = {
> +    use BtrfsCompressOption::*;
> +    &[On, Off, Zlib, Lzo, Zstd]
> +};
> +
> +#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
> +#[serde(rename_all = "UPPERCASE")]
> +/// Available ZFS RAID levels.
> +pub enum ZfsRaidLevel {
> +    #[serde(alias = "raid0")]
> +    /// RAID 0, aka. single or striped.
> +    Raid0,
> +    #[serde(alias = "raid1")]
> +    /// RAID 1, aka. mirror.
> +    Raid1,
> +    #[serde(alias = "raid10")]
> +    /// RAID 10, combining stripe and mirror.
> +    Raid10,
> +    #[serde(alias = "raidz-1", rename = "RAIDZ-1")]
> +    /// ZFS-specific RAID level, provides fault tolerance for one disk.
> +    RaidZ,
> +    #[serde(alias = "raidz-2", rename = "RAIDZ-2")]
> +    /// ZFS-specific RAID level, provides fault tolerance for two disks.
> +    RaidZ2,
> +    #[serde(alias = "raidz-3", rename = "RAIDZ-3")]
> +    /// ZFS-specific RAID level, provides fault tolerance for three disks.
> +    RaidZ3,
> +}
> +
> +serde_plain::derive_display_from_serialize!(ZfsRaidLevel);
> +
> +#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
> +#[serde(rename_all = "lowercase")]
> +/// Possible compression algorithms usable with ZFS.
> +pub enum ZfsCompressOption {
> +    #[default]
> +    /// Enable compression, chooses the default algorithm set by ZFS.
> +    On,
> +    /// Disable compression.
> +    Off,
> +    /// Use lzjb for compression.
> +    Lzjb,
> +    /// Use lz4 for compression.
> +    Lz4,
> +    /// Use zle for compression.
> +    Zle,
> +    /// Use gzip for compression.
> +    Gzip,
> +    /// Use Zstandard for compression.
> +    Zstd,
> +}
> +
> +serde_plain::derive_display_from_serialize!(ZfsCompressOption);
> +serde_plain::derive_fromstr_from_deserialize!(ZfsCompressOption);
> +
> +/// List of all available ZFS compression options.
> +pub const ZFS_COMPRESS_OPTIONS: &[ZfsCompressOption] = {
> +    use ZfsCompressOption::*;
> +    &[On, Off, Lzjb, Lz4, Zle, Gzip, Zstd]
> +};
> +
> +#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
> +#[serde(rename_all = "kebab-case")]
> +/// Possible checksum algorithms usable with ZFS.
> +pub enum ZfsChecksumOption {
> +    #[default]
> +    /// Enable compression, chooses the default algorithm set by ZFS.
> +    On,
> +    /// Use Fletcher4 for checksumming.
> +    Fletcher4,
> +    /// Use SHA256 for checksumming.
> +    Sha256,
> +}
> +
> +serde_plain::derive_display_from_serialize!(ZfsChecksumOption);
> +serde_plain::derive_fromstr_from_deserialize!(ZfsChecksumOption);
> +
> +/// List of all available ZFS checksumming options.
> +pub const ZFS_CHECKSUM_OPTIONS: &[ZfsChecksumOption] = {
> +    use ZfsChecksumOption::*;
> +    &[On, Fletcher4, Sha256]
> +};
> +
> +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
> +/// The filesystem to use for the installation.
> +pub enum FilesystemType {
> +    #[default]
> +    /// Fourth extended filesystem.
> +    Ext4,
> +    /// XFS.
> +    Xfs,
> +    /// ZFS, with a given RAID level.
> +    Zfs(ZfsRaidLevel),
> +    /// Btrfs, with a given RAID level.
> +    Btrfs(BtrfsRaidLevel),
> +}
> +
> +impl FilesystemType {
> +    /// Returns whether this filesystem is Btrfs.
> +    pub fn is_btrfs(&self) -> bool {
> +        matches!(self, FilesystemType::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, FilesystemType::Ext4 | FilesystemType::Xfs)
> +    }
> +}
> +
> +impl fmt::Display for FilesystemType {
> +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
> +        // Values displayed to the user in the installer UI
> +        match self {
> +            FilesystemType::Ext4 => write!(f, "ext4"),
> +            FilesystemType::Xfs => write!(f, "XFS"),
> +            FilesystemType::Zfs(level) => write!(f, "ZFS ({level})"),
> +            FilesystemType::Btrfs(level) => write!(f, "BTRFS ({level})"),
> +        }
> +    }
> +}
> +
> +impl Serialize for FilesystemType {
> +    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
> +            FilesystemType::Ext4 => "ext4",
> +            FilesystemType::Xfs => "xfs",
> +            // proxinstall::get_zfs_raid_setup()
> +            FilesystemType::Zfs(level) => &format!("zfs ({level})"),
> +            // proxinstall::get_btrfs_raid_setup()
> +            FilesystemType::Btrfs(level) => &format!("btrfs ({level})"),
> +        };
> +
> +        serializer.collect_str(value)
> +    }
> +}
> +
> +impl FromStr for FilesystemType {
> +    type Err = String;
> +
> +    fn from_str(s: &str) -> Result<Self, Self::Err> {
> +        match s {
> +            "ext4" => Ok(FilesystemType::Ext4),
> +            "xfs" => Ok(FilesystemType::Xfs),
> +            "zfs (RAID0)" => Ok(FilesystemType::Zfs(ZfsRaidLevel::Raid0)),
> +            "zfs (RAID1)" => Ok(FilesystemType::Zfs(ZfsRaidLevel::Raid1)),
> +            "zfs (RAID10)" => Ok(FilesystemType::Zfs(ZfsRaidLevel::Raid10)),
> +            "zfs (RAIDZ-1)" => Ok(FilesystemType::Zfs(ZfsRaidLevel::RaidZ)),
> +            "zfs (RAIDZ-2)" => Ok(FilesystemType::Zfs(ZfsRaidLevel::RaidZ2)),
> +            "zfs (RAIDZ-3)" => Ok(FilesystemType::Zfs(ZfsRaidLevel::RaidZ3)),
> +            "btrfs (RAID0)" => Ok(FilesystemType::Btrfs(BtrfsRaidLevel::Raid0)),
> +            "btrfs (RAID1)" => Ok(FilesystemType::Btrfs(BtrfsRaidLevel::Raid1)),
> +            "btrfs (RAID10)" => Ok(FilesystemType::Btrfs(BtrfsRaidLevel::Raid10)),
> +            _ => Err(format!("Could not find file system: {s}")),
> +        }
> +    }
> +}
> +
> +serde_plain::derive_deserialize_from_fromstr!(FilesystemType, "valid filesystem");
> +
> +/// List of all available filesystem types.
> +pub const FILESYSTEM_TYPE_OPTIONS: &[FilesystemType] = {
> +    use FilesystemType::*;
> +    &[
> +        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),
> +    ]
> +};
> +
> +fn deserialize_non_empty_string_maybe<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
> +where
> +    D: serde::Deserializer<'de>,
> +{
> +    let val: Option<String> = Deserialize::deserialize(deserializer)?;
> +
> +    match val {
> +        Some(s) if !s.is_empty() => Ok(Some(s)),
> +        _ => Ok(None),
> +    }
> +}
> diff --git a/proxmox-installer-types/src/lib.rs b/proxmox-installer-types/src/lib.rs
> index 07927cb0..12679bdc 100644
> --- a/proxmox-installer-types/src/lib.rs
> +++ b/proxmox-installer-types/src/lib.rs
> @@ -7,6 +7,9 @@
>  #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
>  #![deny(unsafe_code, missing_docs)]
>  
> +pub mod answer;
> +pub mod post_hook;
> +
>  use serde::{Deserialize, Serialize};
>  use std::{
>      collections::{BTreeMap, HashMap},
> diff --git a/proxmox-installer-types/src/post_hook.rs b/proxmox-installer-types/src/post_hook.rs
> new file mode 100644
> index 00000000..8fbe54f8
> --- /dev/null
> +++ b/proxmox-installer-types/src/post_hook.rs
> @@ -0,0 +1,183 @@
> +//! Defines API types for the proxmox-auto-installer post-installation hook.
> +
> +use serde::{Deserialize, Serialize};
> +
> +use proxmox_network_types::ip_address::Cidr;
> +
> +use crate::{
> +    answer::{FilesystemType, RebootMode},
> +    BootType, IsoInfo, ProxmoxProduct, SystemDMI, UdevProperties,
> +};
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +/// Information about the system boot status.
> +pub struct BootInfo {
> +    /// Whether the system is booted using UEFI or legacy BIOS.
> +    pub mode: BootType,
> +    /// Whether SecureBoot is enabled for the installation.
> +    #[serde(default, skip_serializing_if = "bool_is_false")]
> +    secureboot: bool,
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +/// Holds all the public keys for the different algorithms available.
> +pub struct SshPublicHostKeys {
> +    /// ECDSA-based public host key
> +    pub ecdsa: String,
> +    /// ED25519-based public host key
> +    pub ed25519: String,
> +    /// RSA-based public host key
> +    pub rsa: String,
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +#[serde(rename_all = "kebab-case")]
> +/// Holds information about a single disk in the system.
> +pub struct DiskInfo {
> +    /// Size in bytes
> +    pub size: u64,
> +    /// Set to true if the disk is used for booting.
> +    #[serde(default, skip_serializing_if = "bool_is_false")]
> +    pub is_bootdisk: bool,
> +    /// Properties about the device as given by udev.
> +    pub udev_properties: UdevProperties,
> +}
> +
> +/// Holds information about the management network interface.
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +#[serde(rename_all = "kebab-case")]
> +pub struct NetworkInterfaceInfo {
> +    /// Name of the interface
> +    name: String,
> +    /// MAC address of the interface
> +    pub mac: String,
> +    /// (Designated) IP address of the interface
> +    #[serde(skip_serializing_if = "Option::is_none")]
> +    pub address: Option<Cidr>,
> +    /// Set to true if the interface is the chosen management interface during
> +    /// installation.
> +    #[serde(default, skip_serializing_if = "bool_is_false")]
> +    pub is_management: bool,
> +    /// Set to true if the network interface name was pinned based on the MAC
> +    /// address during the installation.
> +    #[serde(default, skip_serializing_if = "bool_is_false")]
> +    is_pinned: bool,
> +    /// Properties about the device as given by udev.
> +    pub udev_properties: UdevProperties,
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +#[serde(rename_all = "kebab-case")]
> +/// Information about the installed product itself.
> +pub struct ProductInfo {
> +    /// Full name of the product
> +    pub fullname: String,
> +    /// Product abbreviation
> +    pub short: ProxmoxProduct,
> +    /// Version of the installed product
> +    pub version: String,
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +/// The current kernel version.
> +/// Aligns with the format as used by the `/nodes/<node>/status` API of each product.
> +pub 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,
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +/// Information about the CPU(s) installed in the system
> +pub struct CpuInfo {
> +    /// Number of physical CPU cores.
> +    pub cores: u32,
> +    /// Number of logical CPU cores aka. threads.
> +    pub cpus: u32,
> +    /// CPU feature flag set as a space-delimited list.
> +    pub flags: String,
> +    /// Whether hardware-accelerated virtualization is supported.
> +    pub hvm: bool,
> +    /// Reported model of the CPU(s)
> +    pub model: String,
> +    /// Number of physical CPU sockets
> +    pub sockets: u32,
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +#[serde(rename_all = "kebab-case")]
> +/// Metadata of the hook, such as schema version of the document.
> +pub 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.
> +    pub version: String,
> +}
> +
> +impl PostHookInfoSchema {
> +    const SCHEMA_VERSION: &str = "1.2";
> +}
> +
> +impl Default for PostHookInfoSchema {
> +    fn default() -> Self {
> +        Self {
> +            version: Self::SCHEMA_VERSION.to_owned(),
> +        }
> +    }
> +}
> +
> +#[derive(Clone, Serialize, Deserialize, PartialEq)]
> +#[serde(rename_all = "kebab-case")]
> +/// 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.
> +pub 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 version information for this struct instance.
> +    pub schema: PostHookInfoSchema,
> +    /// major.minor version of Debian as installed, retrieved from /etc/debian_version
> +    pub debian_version: String,
> +    /// PVE/PMG/PBS/PDM version as reported by `pveversion`, `pmgversion`,
> +    /// `proxmox-backup-manager version` or `proxmox-datacenter-manager version`, respectively.
> +    pub product: ProductInfo,
> +    /// Release information for the ISO used for the installation.
> +    pub iso: IsoInfo,
> +    /// Installed kernel version
> +    pub kernel_version: KernelVersionInformation,
> +    /// Describes the boot mode of the machine and the SecureBoot status.
> +    pub boot_info: BootInfo,
> +    /// Information about the installed CPU(s)
> +    pub cpu_info: CpuInfo,
> +    /// DMI information about the system
> +    pub dmi: SystemDMI,
> +    /// Filesystem used for boot disk(s)
> +    pub filesystem: FilesystemType,
> +    /// Fully qualified domain name of the installed system
> +    pub fqdn: String,
> +    /// Unique systemd-id128 identifier of the installed system (128-bit, 16 bytes)
> +    pub machine_id: String,
> +    /// All disks detected on the system.
> +    pub disks: Vec<DiskInfo>,
> +    /// All network interfaces detected on the system.
> +    pub network_interfaces: Vec<NetworkInterfaceInfo>,
> +    /// Public parts of SSH host keys of the installed system
> +    pub ssh_public_host_keys: SshPublicHostKeys,
> +    /// Action to will be performed, i.e. either reboot or power off the machine.
> +    pub reboot_mode: RebootMode,
> +}
> +
> +fn bool_is_false(value: &bool) -> bool {
> +    !value
> +}



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel


  reply	other threads:[~2025-12-09  9:44 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-05 11:25 [pdm-devel] [PATCH proxmox/datacenter-manager v2 00/14] initial auto-installer integration Christoph Heiss
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 01/14] api-macro: allow $ in identifier name Christoph Heiss
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 02/14] network-types: move `Fqdn` type from proxmox-installer-common Christoph Heiss
2025-12-09  9:13   ` Lukas Wagner
2025-12-09 12:26     ` Christoph Heiss
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 03/14] network-types: implement api type for Fqdn Christoph Heiss
2025-12-09  9:13   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 04/14] network-types: add api wrapper type for std::net::IpAddr Christoph Heiss
2025-12-09  9:16   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 05/14] installer-types: add common types used by the installer Christoph Heiss
2025-12-09  9:35   ` Lukas Wagner
2025-12-09 12:17     ` Christoph Heiss
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 06/14] installer-types: add types used by the auto-installer Christoph Heiss
2025-12-09  9:44   ` Lukas Wagner [this message]
2025-12-05 11:25 ` [pdm-devel] [PATCH proxmox v2 07/14] installer-types: implement api type for all externally-used types Christoph Heiss
2025-12-09  9:52   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 08/14] api-types: add api types for auto-installer integration Christoph Heiss
2025-12-09 10:03   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 09/14] config: add auto-installer configuration module Christoph Heiss
2025-12-09 10:22   ` Lukas Wagner
2025-12-09 12:10     ` Christoph Heiss
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 10/14] acl: wire up new /system/auto-installation acl path Christoph Heiss
2025-12-09 10:23   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 11/14] server: api: add auto-installer integration module Christoph Heiss
2025-12-09 11:01   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 12/14] ui: auto-installer: add installations overview panel Christoph Heiss
2025-12-09 12:35   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 13/14] ui: auto-installer: add prepared answer configuration panel Christoph Heiss
2025-12-09 13:01   ` Lukas Wagner
2025-12-05 11:25 ` [pdm-devel] [PATCH datacenter-manager v2 14/14] docs: add documentation for auto-installer integration Christoph Heiss
2025-12-09 13:12   ` Lukas Wagner
2025-12-05 11:53 ` [pdm-devel] [PATCH proxmox/datacenter-manager v2 00/14] initial " Thomas Lamprecht
2025-12-05 15:50   ` Christoph Heiss
2025-12-05 15:57     ` Thomas Lamprecht
2025-12-09 13:38 ` Lukas Wagner

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=DETL7QHIT8JC.25VUU2JHBCXD7@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal