* [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.