all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations
@ 2026-11-06 13:14 Christoph Heiss
  2026-11-06 13:14 ` [PATCH proxmox v2 1/7] installer-types: derive Display for RebootMode and FilterMatch Christoph Heiss
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

Prerequisites: [0] for the installer site.

This adds a new advanced section in the "Network" tab of the prepared
answer add/edit dialog.

With this, the user can the VLAN tag and/or bonding for newly installed
system.
Bond member selection uses udev filters, to provide a way to dynamically
select network interfaces for target systems.

For more details w.r.t. the implementation, see the corresponding
proxmox-installer-types/pve-installer changes in the patch series link above.

[0] https://lore.proxmox.com/pve-devel/20261106131058.804870-1-c.heiss@proxmox.com

History
=======
v1: https://lore.proxmox.com/pdm-devel/20261102124939.1023342-1-c.heiss@proxmox.com/

Notable changes v1 -> v2:
  * rebased on latest applied version of proxmox-installer-types

Diffstat
========

proxmox:

Christoph Heiss (2):
  installer-types: derive Display for RebootMode and FilterMatch
  network-types: derive Display and FromStr for network bond types

 proxmox-installer-types/src/answer.rs |  2 ++
 proxmox-network-types/src/bond.rs     | 28 ++++-----------------------
 2 files changed, 6 insertions(+), 24 deletions(-)

proxmox-datacenter-manager:

Christoph Heiss (5):
  config: auto-install: add new options for configuring vlan and bond
  api-types: auto-installer: add new options for configuring vlan and
    bond
  api: auto-installer: wire up new vlan and bond network options
  ui: auto-installer: use .to_string() instead of manual serde variant
  ui: auto-installer: add vlan and bond config as advanced options

 Cargo.toml                                    |   2 +-
 debian/control                                |   4 +-
 lib/pdm-api-types/Cargo.toml                  |   1 +
 lib/pdm-api-types/src/auto_installer.rs       |  49 ++++
 lib/pdm-config/Cargo.toml                     |   1 +
 lib/pdm-config/src/auto_install.rs            | 138 ++++++++++
 server/src/api/auto_installer/mod.rs          | 237 ++++++++++++++++++
 ui/Cargo.toml                                 |   3 +-
 ui/debian/control                             |   2 +-
 .../prepared_answer_add_wizard.rs             |   6 +
 .../prepared_answer_edit_window.rs            |   5 +
 .../auto_installer/prepared_answer_form.rs    | 176 ++++++++++---
 12 files changed, 586 insertions(+), 38 deletions(-)





^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH proxmox v2 1/7] installer-types: derive Display for RebootMode and FilterMatch
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  2026-11-06 13:14 ` [PATCH proxmox v2 2/7] network-types: derive Display and FromStr for network bond types Christoph Heiss
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Extracted from the original pve-installer series, as the rest for
`proxmox` has been applied.

Changes v1 -> v2:
  * new patch

 proxmox-installer-types/src/answer.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/proxmox-installer-types/src/answer.rs b/proxmox-installer-types/src/answer.rs
index 1ceadd6c..c50da050 100644
--- a/proxmox-installer-types/src/answer.rs
+++ b/proxmox-installer-types/src/answer.rs
@@ -266,6 +266,7 @@ pub enum RebootMode {
 }
 
 serde_plain::derive_fromstr_from_deserialize!(RebootMode);
+serde_plain::derive_display_from_serialize!(RebootMode);
 
 #[derive(Clone, Deserialize, Debug, Serialize, PartialEq)]
 #[cfg_attr(feature = "api-types", derive(Updater))]
@@ -893,6 +894,7 @@ pub enum FilterMatch {
 }
 
 serde_plain::derive_fromstr_from_deserialize!(FilterMatch);
+serde_plain::derive_display_from_serialize!(FilterMatch);
 
 #[cfg_attr(feature = "api-types", api(
     properties: {
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH proxmox v2 2/7] network-types: derive Display and FromStr for network bond types
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
  2026-11-06 13:14 ` [PATCH proxmox v2 1/7] installer-types: derive Display for RebootMode and FilterMatch Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 3/7] config: auto-install: add new options for configuring vlan and bond Christoph Heiss
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

Since the names implemented manually through the Display trait are equal
to the serde-serialized variants, use serde_plain for that too.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Extracted and rebased from the original pve-installer series, as the
rest for `proxmox` has been applied.

Changes v1 -> v2:
  * new patch

 proxmox-network-types/src/bond.rs | 28 ++++------------------------
 1 file changed, 4 insertions(+), 24 deletions(-)

diff --git a/proxmox-network-types/src/bond.rs b/proxmox-network-types/src/bond.rs
index b31e3984..a49de317 100644
--- a/proxmox-network-types/src/bond.rs
+++ b/proxmox-network-types/src/bond.rs
@@ -1,7 +1,5 @@
 //! Types for Linux bonding interfaces.
 
-use std::fmt;
-
 use serde::{Deserialize, Serialize};
 
 #[cfg(feature = "api-types")]
@@ -30,19 +28,8 @@ pub enum LinuxBondMode {
     BalanceAlb = 6,
 }
 
-impl fmt::Display for LinuxBondMode {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        f.write_str(match self {
-            LinuxBondMode::BalanceRr => "balance-rr",
-            LinuxBondMode::ActiveBackup => "active-backup",
-            LinuxBondMode::BalanceXor => "balance-xor",
-            LinuxBondMode::Broadcast => "broadcast",
-            LinuxBondMode::Ieee802_3ad => "802.3ad",
-            LinuxBondMode::BalanceTlb => "balance-tlb",
-            LinuxBondMode::BalanceAlb => "balance-alb",
-        })
-    }
-}
+serde_plain::derive_display_from_serialize!(LinuxBondMode);
+serde_plain::derive_fromstr_from_deserialize!(LinuxBondMode);
 
 #[cfg_attr(feature = "api-types", api())]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -60,12 +47,5 @@ pub enum BondXmitHashPolicy {
     Layer3_4 = 2,
 }
 
-impl fmt::Display for BondXmitHashPolicy {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        f.write_str(match self {
-            BondXmitHashPolicy::Layer2 => "layer2",
-            BondXmitHashPolicy::Layer2_3 => "layer2+3",
-            BondXmitHashPolicy::Layer3_4 => "layer3+4",
-        })
-    }
-}
+serde_plain::derive_display_from_serialize!(BondXmitHashPolicy);
+serde_plain::derive_fromstr_from_deserialize!(BondXmitHashPolicy);
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH datacenter-manager v2 3/7] config: auto-install: add new options for configuring vlan and bond
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
  2026-11-06 13:14 ` [PATCH proxmox v2 1/7] installer-types: derive Display for RebootMode and FilterMatch Christoph Heiss
  2026-11-06 13:14 ` [PATCH proxmox v2 2/7] network-types: derive Display and FromStr for network bond types Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 4/7] api-types: auto-installer: " Christoph Heiss
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

This mostly mirror the the options available from
`proxmox_installer_types::NetworkVlanOptions` and
`proxmox_installer_types::NetworkBondOptions`.

For bonding, an additional, explicit on/off setting is required to
properly keep UI state, and only udev filters are supported, not fixed
member lists.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * no changes

 Cargo.toml                         |   2 +-
 debian/control                     |   4 +-
 lib/pdm-config/Cargo.toml          |   1 +
 lib/pdm-config/src/auto_install.rs | 138 +++++++++++++++++++++++++++++
 4 files changed, 142 insertions(+), 3 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index d9e7dece..b8450a1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,7 +68,7 @@ proxmox-tfa = { version = "6", features = [ "api-types" ], default-features = fa
 proxmox-time = "2"
 proxmox-upgrade-checks = "1.1.1"
 proxmox-uuid = "1"
-proxmox-installer-types = "0.2"
+proxmox-installer-types = "0.3"
 proxmox-network-types = "1.1"
 
 # other proxmox crates
diff --git a/debian/control b/debian/control
index 527594e3..1e2f06fe 100644
--- a/debian/control
+++ b/debian/control
@@ -66,8 +66,8 @@ Build-Depends: debhelper-compat (= 13),
                librust-proxmox-http-1+proxmox-async-dev (>= 1.0.4-~~),
                librust-proxmox-http-1+websocket-dev (>= 1.0.4-~~),
                librust-proxmox-human-byte-1+default-dev,
-               librust-proxmox-installer-types-0.2+api-types-dev,
-               librust-proxmox-installer-types-0.2+default-dev,
+               librust-proxmox-installer-types-0.3+api-types-dev,
+               librust-proxmox-installer-types-0.3+default-dev,
                librust-proxmox-lang-1+default-dev (>= 1.1-~~),
                librust-proxmox-ldap-1+default-dev (>= 1.1-~~),
                librust-proxmox-ldap-1+sync-dev (>= 1.1-~~),
diff --git a/lib/pdm-config/Cargo.toml b/lib/pdm-config/Cargo.toml
index 8c1b46da..af077a62 100644
--- a/lib/pdm-config/Cargo.toml
+++ b/lib/pdm-config/Cargo.toml
@@ -26,6 +26,7 @@ proxmox-sys = { workspace = true, features = [ "acl", "crypt", "timer" ] }
 proxmox-acme-api.workspace = true
 proxmox-serde.workspace = true
 proxmox-installer-types = { workspace = true, features = [ "api-types" ] }
+proxmox-network-api.workspace = true
 proxmox-network-types.workspace = true
 pdm-api-types.workspace = true
 pdm-buildcfg.workspace = true
diff --git a/lib/pdm-config/src/auto_install.rs b/lib/pdm-config/src/auto_install.rs
index 8ac48fb6..ccb9eaa6 100644
--- a/lib/pdm-config/src/auto_install.rs
+++ b/lib/pdm-config/src/auto_install.rs
@@ -38,6 +38,7 @@ pub mod types {
         },
     };
     use proxmox_installer_types::answer::SUBSCRIPTION_KEY_SCHEMA;
+    use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
     use proxmox_schema::{ApiStringFormat, ApiType, PropertyString, api};
 
     #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
@@ -124,6 +125,19 @@ pub mod types {
                 type: String,
                 optional: true,
             },
+            "bond-enabled": {
+                optional: true,
+            },
+            "bond-members-filter": {
+                type: String,
+                optional: true,
+            },
+            "vlan-id": {
+                type: Integer,
+                optional: true,
+                minimum: 2,
+                maximum: 4094,
+            },
             filesystem: {
                 type: String,
             },
@@ -256,6 +270,26 @@ pub mod types {
         /// Whether to enable network interface name pinning.
         pub netif_name_pinning_enabled: bool,
 
+        /// Whether to enable interface bonding for the management network.
+        #[serde(default)]
+        pub bond_enabled: bool,
+        /// Netdev filter for selecting management network bond members
+        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
+        pub bond_members_filter: PropertyString<BTreeMapWrapper<String>>,
+        /// Whether it is enough that any filter matches on a network interface or all given
+        /// filters must match to select an interface as bond member.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub bond_members_filter_match: Option<answer::FilterMatch>,
+        /// Mode of the management interface bond.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub bond_mode: Option<LinuxBondMode>,
+        /// Hash policy of the management interface bond.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub bond_hash_policy: Option<BondXmitHashPolicy>,
+        /// VLAN tag to configure on the management interface.
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        pub vlan_id: Option<u16>,
+
         /// Root filesystem options.
         pub filesystem: PropertyString<answer::FilesystemOptions>,
 
@@ -319,6 +353,12 @@ pub mod types {
                 dns: conf.dns,
                 netdev_filter: PropertyString::new(BTreeMapWrapper(conf.netdev_filter)),
                 netif_name_pinning_enabled: conf.netif_name_pinning_enabled,
+                bond_enabled: conf.bond_enabled,
+                bond_members_filter: PropertyString::new(BTreeMapWrapper(conf.bond_members_filter)),
+                bond_members_filter_match: conf.bond_members_filter_match,
+                bond_mode: conf.bond_mode,
+                bond_hash_policy: conf.bond_hash_policy,
+                vlan_id: conf.vlan_id,
                 // disk options
                 filesystem: PropertyString::new(conf.filesystem),
                 disk_mode: conf.disk_mode,
@@ -372,6 +412,12 @@ pub mod types {
                 dns: self.dns,
                 netdev_filter: self.netdev_filter.into_inner().0,
                 netif_name_pinning_enabled: self.netif_name_pinning_enabled,
+                bond_enabled: self.bond_enabled,
+                bond_members_filter: self.bond_members_filter.into_inner().0,
+                bond_members_filter_match: self.bond_members_filter_match,
+                bond_mode: self.bond_mode,
+                bond_hash_policy: self.bond_hash_policy,
+                vlan_id: self.vlan_id,
                 // disk options
                 filesystem: self.filesystem.into_inner(),
                 disk_mode: self.disk_mode,
@@ -660,3 +706,95 @@ fn write_token_shadow(data: HashMap<String, String>) -> Result<()> {
     let raw = serde_json::to_string(&data)?;
     replace_config(TOKENS_SHADOW_FILE, raw.as_bytes())
 }
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::types::*;
+    use super::*;
+    use pdm_api_types::auto_installer::PreparedInstallationConfig;
+
+    fn prepared_config(extra: serde_json::Value) -> PreparedInstallationConfig {
+        let mut value = json!({
+            "id": "test",
+            "is-default": true,
+            "country": "at",
+            "fqdn": "test.example.com",
+            "use-dhcp-fqdn": false,
+            "keyboard": "en-us",
+            "mailto": "root@example.com",
+            "timezone": "UTC",
+            "reboot-on-error": false,
+            "reboot-mode": "reboot",
+            "use-dhcp-network": false,
+            "cidr": "192.168.100.2/24",
+            "gateway": "192.168.100.1",
+            "dns": "192.168.100.254",
+            "netif-name-pinning-enabled": false,
+            "filesystem": { "filesystem": "ext4" },
+            "disk-mode": "fixed",
+        });
+
+        for (key, v) in extra.as_object().unwrap() {
+            value
+                .as_object_mut()
+                .unwrap()
+                .insert(key.clone(), v.clone());
+        }
+
+        serde_json::from_value(value).unwrap()
+    }
+
+    fn round_trip(conf: PreparedInstallationConfig) -> PreparedInstallationConfig {
+        let mut data: SectionConfigData<PreparedInstallationSectionConfigWrapper> =
+            HashMap::new().into();
+        data.insert(conf.id.clone(), conf.clone().try_into().unwrap());
+
+        let raw = PreparedInstallationSectionConfigWrapper::write_section_config(
+            PREPARED_CONF_FILE,
+            &data,
+        )
+        .unwrap();
+        let parsed = PreparedInstallationSectionConfigWrapper::parse_section_config(
+            PREPARED_CONF_FILE,
+            &raw,
+        )
+        .unwrap();
+
+        match parsed.get(&conf.id).expect("entry survived the round trip") {
+            PreparedInstallationSectionConfigWrapper::PreparedConfig(section) => {
+                section.clone().try_into().unwrap()
+            }
+        }
+    }
+
+    #[test]
+    fn bond_and_vlan_survive_the_config_file_round_trip() {
+        let conf = prepared_config(json!({
+            "bond-members": ["bc:24:11:49:8d:75", "bc:24:11:49:8d:76"],
+            "bond-mode": "802.3ad",
+            "bond-hash-policy": "layer2+3",
+            "vlan-id": 100,
+        }));
+
+        let back = round_trip(conf.clone());
+
+        assert_eq!(conf.bond_members_filter, back.bond_members_filter);
+        assert_eq!(conf.bond_mode, back.bond_mode);
+        assert_eq!(conf.bond_hash_policy, back.bond_hash_policy);
+        assert_eq!(conf.vlan_id, back.vlan_id);
+        assert_eq!(conf, back);
+    }
+
+    #[test]
+    fn absent_bond_and_vlan_stay_absent() {
+        let conf = prepared_config(json!({}));
+        let back = round_trip(conf.clone());
+
+        assert!(back.bond_members_filter.is_empty());
+        assert_eq!(back.bond_mode, None);
+        assert_eq!(back.vlan_id, None);
+        assert_eq!(conf, back);
+    }
+}
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH datacenter-manager v2 4/7] api-types: auto-installer: add new options for configuring vlan and bond
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
                   ` (2 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 3/7] config: auto-install: add new options for configuring vlan and bond Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 5/7] api: auto-installer: wire up new vlan and bond network options Christoph Heiss
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

Effectively mirroring the available options from
`pdm_config::auto_install::types::PreparedInstallationSectionConfig`.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * no changes

 lib/pdm-api-types/Cargo.toml            |  1 +
 lib/pdm-api-types/src/auto_installer.rs | 48 +++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/lib/pdm-api-types/Cargo.toml b/lib/pdm-api-types/Cargo.toml
index f9e3d07e..0a133889 100644
--- a/lib/pdm-api-types/Cargo.toml
+++ b/lib/pdm-api-types/Cargo.toml
@@ -23,6 +23,7 @@ proxmox-lang.workspace = true
 proxmox-config-digest.workspace = true
 proxmox-installer-types = { workspace = true, features = ["api-types"] }
 proxmox-network-types = { workspace = true, features = ["api-types"] }
+proxmox-network-api.workspace = true
 proxmox-schema = { workspace = true, features = ["api-macro"] }
 proxmox-section-config.workspace = true
 proxmox-dns-api.workspace = true
diff --git a/lib/pdm-api-types/src/auto_installer.rs b/lib/pdm-api-types/src/auto_installer.rs
index 4b5027fb..4a2dce13 100644
--- a/lib/pdm-api-types/src/auto_installer.rs
+++ b/lib/pdm-api-types/src/auto_installer.rs
@@ -7,6 +7,7 @@ use proxmox_auth_api::types::Userid;
 use proxmox_installer_types::{
     SystemInfo, answer::SUBSCRIPTION_KEY_SCHEMA, post_hook::PostHookInfo,
 };
+use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
 use proxmox_schema::{
     ApiStringFormat, Schema, StringSchema, Updater, api,
     api_types::{CERT_FINGERPRINT_SHA256_SCHEMA, COMMENT_SCHEMA, HTTP_URL_SCHEMA, UUID_FORMAT},
@@ -144,6 +145,19 @@ pub const PREPARED_INSTALL_CONFIG_ID_SCHEMA: proxmox_schema::Schema =
             additional_properties: true,
             optional: true,
         },
+        "bond-members-filter": {
+            type: Object,
+            properties: {},
+            additional_properties: true,
+            optional: true,
+        },
+        // 0 and 4095 are reserved, 1 is the untagged default PVID of VLAN-aware bridges on Linux
+        "vlan-id": {
+            type: Integer,
+            optional: true,
+            minimum: 2,
+            maximum: 4094,
+        },
         "disk-mode": {
             type: String,
         },
@@ -298,6 +312,32 @@ pub struct PreparedInstallationConfig {
     #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
     pub netif_name_pinning_enabled: bool,
 
+    /// Whether to enable interface bonding for the management network.
+    #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
+    pub bond_enabled: bool,
+    /// Netdev filter for selecting management network bond members
+    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
+    #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
+    pub bond_members_filter: BTreeMap<String, String>,
+    /// Whether it is enough that any filter matches on a network interface or all given
+    /// filters must match to select an interface as bond member.
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
+    pub bond_members_filter_match: Option<answer::FilterMatch>,
+    /// Mode of the management interface bond, defaults to `active-backup`.
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
+    pub bond_mode: Option<LinuxBondMode>,
+    /// Hash policy of the management interface bond, only used with the `802.3ad` and
+    /// `balance-xor` modes.
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
+    pub bond_hash_policy: Option<BondXmitHashPolicy>,
+    /// Optional VLAN tag to configure on the management interface.
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
+    pub vlan_id: Option<u16>,
+
     /// Root filesystem options.
     #[updater(serde(default, skip_serializing_if = "Option::is_none"))]
     pub filesystem: answer::FilesystemOptions,
@@ -353,6 +393,14 @@ pub enum DeletablePreparedInstallationConfigProperty {
     TargetFilter,
     /// Delete all udev property filters for the management network device
     NetdevFilter,
+    /// Delete all udev property filters for selecting management network bond members
+    BondMembersFilter,
+    /// Delete the management interface bond mode.
+    BondMode,
+    /// Delete the management interface bond hash policy.
+    BondHashPolicy,
+    /// Delete the management interface VLAN tag.
+    VlanId,
     /// Delete all udev property filters for disks
     DiskFilter,
     /// Delete all `root` user public ssh keys.
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH datacenter-manager v2 5/7] api: auto-installer: wire up new vlan and bond network options
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
                   ` (3 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 4/7] api-types: auto-installer: " Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 6/7] ui: auto-installer: use .to_string() instead of manual serde variant Christoph Heiss
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 7/7] ui: auto-installer: add vlan and bond config as advanced options Christoph Heiss
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

Overall pretty straight-forward; adds the required handling in the
update and configuration rendering path.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * rebased on applied `proxmox-{installer,network}-types` patches

 server/src/api/auto_installer/mod.rs | 237 +++++++++++++++++++++++++++
 1 file changed, 237 insertions(+)

diff --git a/server/src/api/auto_installer/mod.rs b/server/src/api/auto_installer/mod.rs
index 7c8ef0fb..97d22f5a 100644
--- a/server/src/api/auto_installer/mod.rs
+++ b/server/src/api/auto_installer/mod.rs
@@ -25,6 +25,7 @@ use proxmox_installer_types::{
     },
     post_hook::PostHookInfo,
 };
+use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
 use proxmox_network_types::fqdn::Fqdn;
 use proxmox_router::{
     ApiHandler, ApiMethod, ApiResponseFuture, Permission, Router, RpcEnvironment, SubdirMap,
@@ -427,6 +428,7 @@ async fn create_prepared_answer(
 
     validate_udev_filter_map(&config.netdev_filter)?;
     validate_udev_filter_map(&config.disk_filter)?;
+    validate_udev_filter_map(&config.bond_members_filter)?;
     validate_template_map(&config.template_counters)?;
 
     prepared.insert(config.id.clone(), config.clone().try_into()?);
@@ -546,6 +548,18 @@ async fn update_prepared_answer(
                 DeletablePreparedInstallationConfigProperty::NetdevFilter => {
                     p.netdev_filter.clear();
                 }
+                DeletablePreparedInstallationConfigProperty::BondMembersFilter => {
+                    p.bond_members_filter.clear();
+                }
+                DeletablePreparedInstallationConfigProperty::BondMode => {
+                    p.bond_mode = None;
+                }
+                DeletablePreparedInstallationConfigProperty::BondHashPolicy => {
+                    p.bond_hash_policy = None;
+                }
+                DeletablePreparedInstallationConfigProperty::VlanId => {
+                    p.vlan_id = None;
+                }
                 DeletablePreparedInstallationConfigProperty::DiskFilter => {
                     p.disk_filter.clear();
                 }
@@ -589,6 +603,12 @@ async fn update_prepared_answer(
         dns,
         netdev_filter,
         netif_name_pinning_enabled,
+        bond_enabled,
+        bond_members_filter,
+        bond_members_filter_match,
+        bond_mode,
+        bond_hash_policy,
+        vlan_id,
         filesystem,
         disk_mode,
         disk_list,
@@ -687,6 +707,31 @@ async fn update_prepared_answer(
         p.netif_name_pinning_enabled = enabled;
     }
 
+    if let Some(enabled) = bond_enabled {
+        p.bond_enabled = enabled;
+    }
+
+    if let Some(filter) = bond_members_filter {
+        validate_udev_filter_map(&filter)?;
+        **p.bond_members_filter = filter;
+    }
+
+    if let Some(filter_match) = bond_members_filter_match {
+        p.bond_members_filter_match = Some(filter_match);
+    }
+
+    if let Some(mode) = bond_mode {
+        p.bond_mode = Some(mode);
+    }
+
+    if let Some(hash_policy) = bond_hash_policy {
+        p.bond_hash_policy = Some(hash_policy);
+    }
+
+    if let Some(vid) = vlan_id {
+        p.vlan_id = Some(vid);
+    }
+
     if let Some(fs) = filesystem {
         *p.filesystem = fs;
     }
@@ -1168,6 +1213,40 @@ fn find_config(
     default_answer.map(|a| a.try_into()).transpose()
 }
 
+/// Collects both bond and VLAN options of a prepared configuration into the corresponding
+/// answer types and doing some sanity checks.
+///
+/// Bonding is considered off if no members are set, mirroring the meaning of an empty member list
+/// in the low-level installer.
+fn render_prepared_advanced_network_config(
+    conf: &PreparedInstallationConfig,
+) -> Result<(
+    Option<answer::NetworkBondOptions>,
+    Option<answer::NetworkVlanOptions>,
+)> {
+    let bond = if conf.bond_enabled {
+        let bond = answer::NetworkBondOptions {
+            members: vec![],
+            filter: conf.bond_members_filter.clone(),
+            filter_match: conf.bond_members_filter_match,
+            mode: conf.bond_mode.unwrap_or(LinuxBondMode::ActiveBackup),
+            hash_policy: conf.bond_hash_policy.unwrap_or(BondXmitHashPolicy::Layer2),
+            primary_interface: None,
+        };
+        bond.verify()?;
+        Some(bond)
+    } else {
+        None
+    };
+
+    let vlan = conf.vlan_id.map(|vid| answer::NetworkVlanOptions { vid });
+    if let Some(vlan) = &vlan {
+        vlan.verify()?;
+    }
+
+    Ok((bond, vlan))
+}
+
 /// Renders a given [`PreparedInstallationConfig`] into the target [`AutoInstallerConfig`] struct.
 ///
 /// Converts all types as needed and renders out MiniJinja templates in applicable fields.
@@ -1237,9 +1316,12 @@ fn render_prepared_config(
             },
         );
 
+        let (bond, vlan) = render_prepared_advanced_network_config(conf)?;
+
         if conf.use_dhcp_network {
             answer::NetworkConfig::FromDhcp(answer::NetworkConfigFromDhcp {
                 interface_name_pinning,
+                bond,
             })
         } else {
             let cidr = conf
@@ -1284,6 +1366,8 @@ fn render_prepared_config(
                 gateway,
                 filter: conf.netdev_filter.clone(),
                 interface_name_pinning,
+                bond,
+                vlan,
             })
         }
     };
@@ -1367,3 +1451,156 @@ fn generate_token(
         rpcenv,
     )
 }
+
+#[cfg(test)]
+mod tests {
+    use pdm_api_types::auto_installer::DiskSelectionMode;
+    use proxmox_installer_types::{
+        IsoInfo, NetworkInterface, ProductConfig, ProxmoxProduct, SystemDMI,
+        answer::NetworkVlanOptions,
+    };
+
+    use super::*;
+
+    fn config() -> PreparedInstallationConfig {
+        PreparedInstallationConfig {
+            id: "test".to_owned(),
+            authorized_tokens: Vec::new(),
+            // target filter
+            is_default: false,
+            target_filter: BTreeMap::new(),
+            // global options
+            country: "at".to_owned(),
+            fqdn: "test.example.com".to_owned(),
+            use_dhcp_fqdn: false,
+            keyboard: answer::KeyboardLayout::default(),
+            mailto: String::new(),
+            timezone: "Europe/Vienna".to_owned(),
+            root_password_hashed: None,
+            reboot_on_error: false,
+            reboot_mode: answer::RebootMode::default(),
+            root_ssh_keys: Vec::new(),
+            // network options
+            use_dhcp_network: true,
+            cidr: None,
+            gateway: None,
+            dns: None,
+            netdev_filter: BTreeMap::new(),
+            netif_name_pinning_enabled: true,
+            bond_enabled: false,
+            bond_members_filter: BTreeMap::new(),
+            bond_members_filter_match: None,
+            bond_mode: None,
+            bond_hash_policy: None,
+            vlan_id: None,
+            // disk options
+            filesystem: answer::FilesystemOptions::Ext4(answer::LvmOptions::default()),
+            disk_mode: DiskSelectionMode::default(),
+            disk_list: Vec::new(),
+            disk_filter: BTreeMap::new(),
+            disk_filter_match: None,
+            // post hook
+            post_hook_base_url: None,
+            post_hook_cert_fp: None,
+            // templating
+            template_counters: BTreeMap::new(),
+            // subscription
+            subscription_key: None,
+        }
+    }
+
+    fn sysinfo() -> SystemInfo {
+        SystemInfo {
+            product: ProductConfig {
+                fullname: "Proxmox VE".to_owned(),
+                product: ProxmoxProduct::Pve,
+                enable_btrfs: true,
+            },
+            iso: IsoInfo {
+                release: "9.1".to_owned(),
+                isorelease: "1".to_owned(),
+            },
+            dmi: SystemDMI {
+                baseboard: HashMap::new(),
+                chassis: HashMap::new(),
+                system: HashMap::new(),
+            },
+            network_interfaces: vec![
+                NetworkInterface {
+                    link: "ens18".to_owned(),
+                    mac: "ab:cd:ef:12:34:56".parse().unwrap(),
+                },
+                NetworkInterface {
+                    link: "ens19".to_owned(),
+                    mac: "12:34:56:ab:cd:ef".parse().unwrap(),
+                },
+            ],
+        }
+    }
+
+    #[test]
+    fn netwoork_bond_and_vlan_unset_stays_disabled() {
+        let prepared = render_prepared_config(&config(), &sysinfo()).unwrap();
+        assert!(prepared.network.bond().is_none());
+        assert!(prepared.network.vlan().is_none());
+    }
+
+    #[test]
+    fn network_bond_renders_default_options() {
+        let mut config = config();
+        config.bond_enabled = true;
+        config
+            .bond_members_filter
+            .insert("ID_NET_DRIVER".to_owned(), "igc".to_owned());
+
+        let prepared = render_prepared_config(&config, &sysinfo()).unwrap();
+        let bond = prepared.network.bond().unwrap();
+        assert_eq!(bond.mode, LinuxBondMode::ActiveBackup);
+        assert_eq!(bond.hash_policy, BondXmitHashPolicy::Layer2);
+    }
+
+    #[test]
+    fn bond_options_without_members_filter_are_rejected() {
+        let mut config = config();
+        config.bond_enabled = true;
+
+        let prepared = render_prepared_config(&config, &sysinfo());
+        assert!(prepared.is_err());
+        assert_eq!(
+            prepared.unwrap_err().to_string(),
+            "bond setup requires either fixed list of members or udev filter list"
+        );
+    }
+
+    #[test]
+    fn vlan_id_is_validated() {
+        let mut config = config();
+        let sysinfo = sysinfo();
+
+        assert_eq!(
+            render_prepared_config(&config, &sysinfo)
+                .unwrap()
+                .network
+                .vlan(),
+            None
+        );
+
+        config.vlan_id = Some(7);
+        assert_eq!(
+            render_prepared_config(&config, &sysinfo)
+                .unwrap()
+                .network
+                .vlan(),
+            Some(&NetworkVlanOptions { vid: 7 }),
+        );
+
+        // 1 is the untagged default PVID, thus not accepted
+        config.vlan_id = Some(1);
+        assert_eq!(
+            render_prepared_config(&config, &sysinfo)
+                .unwrap_err()
+                .to_string(),
+            "VLAN id must be between 2 and 4094 inclusive, got 1"
+        );
+    }
+}
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH datacenter-manager v2 6/7] ui: auto-installer: use .to_string() instead of manual serde variant
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
                   ` (4 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 5/7] api: auto-installer: wire up new vlan and bond network options Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 7/7] ui: auto-installer: add vlan and bond config as advanced options Christoph Heiss
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

No functional changes.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * rebased on applied `proxmox-{installer,network}-types` patches

 lib/pdm-api-types/src/auto_installer.rs       |  1 +
 ui/Cargo.toml                                 |  2 +-
 ui/debian/control                             |  2 +-
 .../auto_installer/prepared_answer_form.rs    | 41 +++++++------------
 4 files changed, 17 insertions(+), 29 deletions(-)

diff --git a/lib/pdm-api-types/src/auto_installer.rs b/lib/pdm-api-types/src/auto_installer.rs
index 4a2dce13..858bd0c2 100644
--- a/lib/pdm-api-types/src/auto_installer.rs
+++ b/lib/pdm-api-types/src/auto_installer.rs
@@ -87,6 +87,7 @@ pub enum DiskSelectionMode {
 }
 
 serde_plain::derive_fromstr_from_deserialize!(DiskSelectionMode);
+serde_plain::derive_display_from_serialize!(DiskSelectionMode);
 
 const_regex! {
     /// Regex for matching the key of udev filters.
diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index 99ff5619..d3053802 100644
--- a/ui/Cargo.toml
+++ b/ui/Cargo.toml
@@ -38,7 +38,7 @@ proxmox-acme-api = "1"
 proxmox-deb-version = "0.1"
 proxmox-client = "1"
 proxmox-human-byte = "1"
-proxmox-installer-types = "0.2"
+proxmox-installer-types = "0.3"
 proxmox-login = "1"
 proxmox-network-types = "1.1"
 proxmox-schema = "5"
diff --git a/ui/debian/control b/ui/debian/control
index 916f2c90..8ceb881a 100644
--- a/ui/debian/control
+++ b/ui/debian/control
@@ -23,7 +23,7 @@ Build-Depends: debhelper-compat (= 13),
                librust-proxmox-client-1+default-dev,
                librust-proxmox-deb-version-0.1-dev,
                librust-proxmox-human-byte-1+default-dev (>= 0.1.3-~~),
-               librust-proxmox-installer-types-0.2+default-dev,
+               librust-proxmox-installer-types-0.3+default-dev,
                librust-proxmox-login-1+default-dev,
                librust-proxmox-network-types-1+default-dev (>= 1.1-~~),
                librust-proxmox-rrd-api-types-1+default-dev,
diff --git a/ui/src/remotes/auto_installer/prepared_answer_form.rs b/ui/src/remotes/auto_installer/prepared_answer_form.rs
index 960decae..0b01baf1 100644
--- a/ui/src/remotes/auto_installer/prepared_answer_form.rs
+++ b/ui/src/remotes/auto_installer/prepared_answer_form.rs
@@ -2,7 +2,7 @@
 //! edit window, as well as some utility to collect and prepare the form data for submission.
 
 use anyhow::{Result, anyhow, bail};
-use serde::{Deserialize, Serialize};
+use serde::Deserialize;
 use serde_json::{Value, json};
 use std::{collections::BTreeMap, ops::Deref, rc::Rc, sync::LazyLock};
 
@@ -248,7 +248,7 @@ pub fn render_global_options_form(
                 .items(Rc::new(
                     KEYBOARD_LAYOUTS
                         .iter()
-                        .map(|l| serde_variant_name(l).expect("valid variant").into())
+                        .map(|l| l.to_string().into())
                         .collect(),
                 ))
                 .render_value(|v: &AttrValue| {
@@ -257,7 +257,7 @@ pub fn render_global_options_form(
                         .unwrap_or_default()
                         .into()
                 })
-                .value(serde_variant_name(config.keyboard))
+                .value(config.keyboard.to_string())
                 .filter(|item: &AttrValue, query: &str| {
                     let query = query.to_string().to_lowercase();
                     let item = item.to_string();
@@ -317,7 +317,7 @@ pub fn render_global_options_form(
                 .items(Rc::new(
                     [RebootMode::Reboot, RebootMode::PowerOff]
                         .iter()
-                        .map(|opt| serde_variant_name(opt).expect("valid variant").into())
+                        .map(|m| m.to_string().into())
                         .collect(),
                 ))
                 .render_value(|v: &AttrValue| match v.parse::<RebootMode>() {
@@ -325,7 +325,7 @@ pub fn render_global_options_form(
                     Ok(RebootMode::PowerOff) => tr!("Power off").into(),
                     _ => v.into(),
                 })
-                .value(serde_variant_name(config.reboot_mode))
+                .value(config.reboot_mode.to_string())
                 .required(true),
         )
         .with_field(
@@ -482,7 +482,7 @@ pub fn render_disk_setup_form(
                 .items(Rc::new(
                     FILESYSTEM_TYPE_OPTIONS
                         .iter()
-                        .map(|opt| serde_variant_name(opt).expect("valid variant").into())
+                        .map(|opt| opt.to_string().into())
                         .collect(),
                 ))
                 .render_value(|v: &AttrValue| {
@@ -491,7 +491,7 @@ pub fn render_disk_setup_form(
                         .unwrap_or_default()
                         .into()
                 })
-                .value(serde_variant_name(config.filesystem.to_type()))
+                .value(config.filesystem.to_type().to_string())
                 .required(true)
                 .show_filter(false),
         )
@@ -508,7 +508,7 @@ pub fn render_disk_setup_form(
                     _ => v.into(),
                 })
                 .required(true)
-                .value(serde_variant_name(config.disk_mode)),
+                .value(config.disk_mode.to_string()),
         )
         .with_field(
             tr!("Disk Names"),
@@ -528,7 +528,7 @@ pub fn render_disk_setup_form(
                 .items(Rc::new(
                     [FilterMatch::Any, FilterMatch::All]
                         .iter()
-                        .map(|opt| serde_variant_name(opt).expect("valid variant").into())
+                        .map(|opt| opt.to_string().into())
                         .collect(),
                 ))
                 .render_value(|v: &AttrValue| match v.parse::<FilterMatch>() {
@@ -536,8 +536,8 @@ pub fn render_disk_setup_form(
                     Ok(FilterMatch::All) => tr!("Match All Filters").into(),
                     _ => v.into(),
                 })
-                .default(serde_variant_name(FilterMatch::default()))
-                .value(config.disk_filter_match.and_then(serde_variant_name))
+                .default(FilterMatch::default().to_string())
+                .value(config.disk_filter_match.map(|v| v.to_string()))
                 .disabled(disk_mode != DiskSelectionMode::Filter),
         )
         .with_large_field(
@@ -729,7 +729,7 @@ fn add_zfs_advanced_form_fields(panel: &mut InputPanel, fs_opts: &ZfsOptions) {
             .items(Rc::new(
                 ZFS_CHECKSUM_OPTIONS
                     .iter()
-                    .map(|opt| serde_variant_name(opt).expect("valid variant").into())
+                    .map(|opt| opt.to_string().into())
                     .collect(),
             ))
             .render_value(|v: &AttrValue| {
@@ -751,7 +751,7 @@ fn add_zfs_advanced_form_fields(panel: &mut InputPanel, fs_opts: &ZfsOptions) {
             .items(Rc::new(
                 ZFS_COMPRESS_OPTIONS
                     .iter()
-                    .map(|opt| serde_variant_name(opt).expect("valid variant").into())
+                    .map(|opt| opt.to_string().into())
                     .collect(),
             ))
             .render_value(|v: &AttrValue| {
@@ -789,7 +789,7 @@ fn add_btrfs_advanced_form_fields(panel: &mut InputPanel, fs_opts: &BtrfsOptions
             .items(Rc::new(
                 BTRFS_COMPRESS_OPTIONS
                     .iter()
-                    .map(|opt| serde_variant_name(opt).expect("valid variant").into())
+                    .map(|opt| opt.to_string().into())
                     .collect(),
             ))
             .render_value(|v: &AttrValue| {
@@ -1223,19 +1223,6 @@ fn kv_list_to_template_counter_map_validate(v: &Vec<(String, Value)>) -> Result<
     Ok(serde_json::to_value(map)?)
 }
 
-fn serde_variant_name<T: Serialize>(ty: T) -> Option<String> {
-    match serde_json::to_value(ty) {
-        Ok(Value::String(s)) => Some(s),
-        other => {
-            log::warn!(
-                "expected string of type {}, got {other:?}",
-                std::any::type_name::<T>()
-            );
-            None
-        }
-    }
-}
-
 const KEYBOARD_LAYOUTS: &[KeyboardLayout] = {
     use KeyboardLayout::*;
     &[
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH datacenter-manager v2 7/7] ui: auto-installer: add vlan and bond config as advanced options
  2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
                   ` (5 preceding siblings ...)
  2026-11-06 13:14 ` [PATCH datacenter-manager v2 6/7] ui: auto-installer: use .to_string() instead of manual serde variant Christoph Heiss
@ 2026-11-06 13:14 ` Christoph Heiss
  6 siblings, 0 replies; 8+ messages in thread
From: Christoph Heiss @ 2026-11-06 13:14 UTC (permalink / raw)
  To: pdm-devel

This adds a new advanced section in the "Network" tab of the prepared
answer add/edit dialog.

With this, the user can the VLAN tag and/or bonding for newly installed
system.
Bond member selection uses udev filters, to provide a way to dynamically
select network interfaces for target systems.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v1 -> v2:
  * no changes

 ui/Cargo.toml                                 |   1 +
 .../prepared_answer_add_wizard.rs             |   6 +
 .../prepared_answer_edit_window.rs            |   5 +
 .../auto_installer/prepared_answer_form.rs    | 135 +++++++++++++++++-
 4 files changed, 141 insertions(+), 6 deletions(-)

diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index d3053802..0b00914c 100644
--- a/ui/Cargo.toml
+++ b/ui/Cargo.toml
@@ -40,6 +40,7 @@ proxmox-client = "1"
 proxmox-human-byte = "1"
 proxmox-installer-types = "0.3"
 proxmox-login = "1"
+proxmox-network-api = "1"
 proxmox-network-types = "1.1"
 proxmox-schema = "5"
 proxmox-subscription = { version = "1.0.1", features = ["api-types"], default-features = false }
diff --git a/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs b/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs
index 03d78be8..63f10dcf 100644
--- a/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs
+++ b/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs
@@ -72,6 +72,12 @@ impl AddAnswerWizardProperties {
             dns: None,
             netdev_filter: BTreeMap::new(),
             netif_name_pinning_enabled: true,
+            bond_enabled: false,
+            bond_members_filter: BTreeMap::new(),
+            bond_members_filter_match: None,
+            bond_mode: None,
+            bond_hash_policy: None,
+            vlan_id: None,
             // disk options
             filesystem: answer::FilesystemOptions::Ext4(answer::LvmOptions::default()),
             disk_mode: DiskSelectionMode::default(),
diff --git a/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs b/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs
index 33a9ce00..5b4108ba 100644
--- a/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs
+++ b/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs
@@ -157,6 +157,11 @@ async fn submit(
             "post-hook-cert-fp",
             "disk-filter",
             "netdev-filter",
+            "bond-members-filter",
+            "bond-mode",
+            "bond-hash-policy",
+            "bond-primary-interface",
+            "vlan-id",
             "target-filter",
             "subscription-key",
             "template-counters",
diff --git a/ui/src/remotes/auto_installer/prepared_answer_form.rs b/ui/src/remotes/auto_installer/prepared_answer_form.rs
index 0b01baf1..d1a20577 100644
--- a/ui/src/remotes/auto_installer/prepared_answer_form.rs
+++ b/ui/src/remotes/auto_installer/prepared_answer_form.rs
@@ -18,11 +18,12 @@ use proxmox_installer_types::{
     answer::{
         BTRFS_COMPRESS_OPTIONS, BtrfsCompressOption, BtrfsOptions, DiskSetup,
         FILESYSTEM_TYPE_OPTIONS, Filesystem, FilesystemOptions, FilesystemType, FilterMatch,
-        KeyboardLayout, LvmOptions, ROOT_PASSWORD_SCHEMA, RebootMode, SUBSCRIPTION_KEY_SCHEMA,
-        ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, ZfsChecksumOption, ZfsCompressOption,
-        ZfsOptions, email_validate,
+        KeyboardLayout, LvmOptions, NetworkBondOptions, ROOT_PASSWORD_SCHEMA, RebootMode,
+        SUBSCRIPTION_KEY_SCHEMA, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, ZfsChecksumOption,
+        ZfsCompressOption, ZfsOptions, email_validate,
     },
 };
+use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
 use proxmox_network_types::fqdn::Fqdn;
 use proxmox_schema::api_types::{CIDR_SCHEMA, IP_SCHEMA};
 use proxmox_yew_comp::{KeyValueList, SchemaValidation, utils::copy_text_to_clipboard};
@@ -355,7 +356,20 @@ pub fn render_network_options_form(
         .and_then(|v| v.as_bool())
         .unwrap_or(true);
 
-    InputPanel::new()
+    let bond_enabled = form_ctx
+        .read()
+        .get_field_value("bond-enabled")
+        .and_then(|v| v.as_bool())
+        .unwrap_or(!config.bond_members_filter.is_empty());
+
+    let bond_mode = form_ctx
+        .read()
+        .get_field_value("bond-mode")
+        .and_then(|v| v.as_str().and_then(|s| s.parse().ok()))
+        .or(config.bond_mode)
+        .unwrap_or(LinuxBondMode::ActiveBackup);
+
+    let panel = InputPanel::new()
         .class(Flex::Fill)
         .class(Overflow::Auto)
         .padding(4)
@@ -451,7 +465,100 @@ pub fn render_network_options_form(
                 .disabled(use_dhcp_network)
                 .required(!use_dhcp_network),
         )
-        .into()
+        // Bonding and VLAN tagging are supported for both the DHCP and the static
+        // configuration, so they are independent of the 'Use DHCP' setting.
+        .with_advanced_spacer()
+        .with_advanced_field(
+            tr!("VLAN ID"),
+            Number::<u16>::new()
+                .name("vlan-id")
+                .min(2)
+                .max(4094)
+                .step(1)
+                .submit_empty(false)
+                .placeholder(tr!("Optional, 2-4094. Disabled when empty."))
+                .value(config.vlan_id.map(|v| v.to_string()))
+                .disabled(use_dhcp_network),
+        )
+        .with_advanced_field(
+            tr!("Enable Bonding"),
+            Checkbox::new()
+                .name("bond-enabled")
+                .tip(tr!(
+                    "Bond the member interfaces for the management network."
+                ))
+                .default(config.bond_enabled),
+        )
+        .with_field(
+            tr!("Bond Members udev Filter Mode"),
+            filter_match_combobox()
+                .name("bond-members-filter-match")
+                .value(config.bond_members_filter_match.map(|v| v.to_string()))
+                .disabled(!bond_enabled),
+        )
+        .with_advanced_field(
+            tr!("Bond Members udev Filters"),
+            KeyValueList::new()
+                .name("bond-members-filter")
+                .class(FlexFit)
+                .value(
+                    config
+                        .bond_members_filter
+                        .iter()
+                        .map(|(k, v)| (k.clone(), Value::String(v.clone())))
+                        .collect(),
+                )
+                .key_label(tr!("Property Name"))
+                .value_label(tr!("Value To Match"))
+                .key_placeholder(tr!("udev Property Name, e.g. ID_MODEL"))
+                .value_renderer(render_udev_filter_value.into())
+                .submit_validate(kv_list_to_udev_filter_map_validate)
+                .submit_empty(false)
+                .disabled(!bond_enabled)
+                .required(bond_enabled),
+        )
+        .with_advanced_field(
+            tr!("Bond Mode"),
+            Combobox::new()
+                .name("bond-mode")
+                .items(Rc::new(
+                    NetworkBondOptions::MODES
+                        .iter()
+                        .map(|mode| mode.to_string().into())
+                        .collect(),
+                ))
+                .default(LinuxBondMode::ActiveBackup.to_string())
+                .value(config.bond_mode.map(|v| v.to_string()))
+                .disabled(!bond_enabled)
+                .required(bond_enabled)
+                .show_filter(false),
+        )
+        .with_advanced_field(
+            tr!("Bond Hash Policy"),
+            Combobox::new()
+                .name("bond-hash-policy")
+                .items(Rc::new(
+                    NetworkBondOptions::HASH_POLICIES
+                        .iter()
+                        .map(|policy| policy.to_string().into())
+                        .collect(),
+                ))
+                .default(BondXmitHashPolicy::Layer2.to_string())
+                .value(config.bond_hash_policy.map(|v| v.to_string()))
+                .disabled(
+                    !bond_enabled
+                        || !matches!(
+                            bond_mode,
+                            LinuxBondMode::Ieee802_3ad | LinuxBondMode::BalanceXor
+                        ),
+                )
+                .show_filter(false),
+        );
+
+    // the bond and VLAN options live in the advanced section, report that to the dialog so it can
+    // offer the toggle
+    form_ctx.set_has_advanced(panel.has_advanced());
+    panel.into()
 }
 
 pub fn render_disk_setup_form(
@@ -523,7 +630,7 @@ pub fn render_disk_setup_form(
         .with_spacer()
         .with_field(
             tr!("Disk udev Filter Mode"),
-            Combobox::new()
+            filter_match_combobox()
                 .name("disk-filter-match")
                 .items(Rc::new(
                     [FilterMatch::Any, FilterMatch::All]
@@ -1147,6 +1254,22 @@ fn render_template_counter_value(
         .into()
 }
 
+fn filter_match_combobox() -> Combobox {
+    Combobox::new()
+        .items(Rc::new(
+            [FilterMatch::Any, FilterMatch::All]
+                .iter()
+                .map(|opt| opt.to_string().into())
+                .collect(),
+        ))
+        .render_value(|v: &AttrValue| match v.parse::<FilterMatch>() {
+            Ok(FilterMatch::Any) => tr!("Match Any Filter").into(),
+            Ok(FilterMatch::All) => tr!("Match All Filters").into(),
+            _ => v.into(),
+        })
+        .default(FilterMatch::default().to_string())
+}
+
 /// Validate against a schema, but accept any value carrying a MiniJinja placeholder, as the
 /// backend substitutes those before parsing the field.
 fn templated_schema_validate(schema: &'static proxmox_schema::Schema) -> ValidateFn<String> {
-- 
2.56.0





^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-11-06 13:15 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-06 13:14 [PATCH datacenter-manager/proxmox v2 0/7] auto-install: add support for VLAN and bond configurations Christoph Heiss
2026-11-06 13:14 ` [PATCH proxmox v2 1/7] installer-types: derive Display for RebootMode and FilterMatch Christoph Heiss
2026-11-06 13:14 ` [PATCH proxmox v2 2/7] network-types: derive Display and FromStr for network bond types Christoph Heiss
2026-11-06 13:14 ` [PATCH datacenter-manager v2 3/7] config: auto-install: add new options for configuring vlan and bond Christoph Heiss
2026-11-06 13:14 ` [PATCH datacenter-manager v2 4/7] api-types: auto-installer: " Christoph Heiss
2026-11-06 13:14 ` [PATCH datacenter-manager v2 5/7] api: auto-installer: wire up new vlan and bond network options Christoph Heiss
2026-11-06 13:14 ` [PATCH datacenter-manager v2 6/7] ui: auto-installer: use .to_string() instead of manual serde variant Christoph Heiss
2026-11-06 13:14 ` [PATCH datacenter-manager v2 7/7] ui: auto-installer: add vlan and bond config as advanced options Christoph Heiss

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal