From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH installer v4 20/30] auto: pass through management network bond options to low-level installer
Date: Mon, 2 Nov 2026 13:45:26 +0100 [thread overview]
Message-ID: <20261102124539.1008075-21-c.heiss@proxmox.com> (raw)
In-Reply-To: <20261102124539.1008075-1-c.heiss@proxmox.com>
Partially fixes #2164 [0].
The auto-installer gains a new section under [network] in its answer
file for setting up a management bond:
[network.bond]
members = ["ab:cd:ef:12:34:56", "ab:cd:ef:12:34:57"]
mode = "active-backup"
hash-policy = "layer2+3"
primary-interface = "ab:cd:ef:12:34:57"
This then just passes through the given bond options in the answer file
to the low-level installation configuration, verifying them beforehand
and printing a status message that bonding is enabled.
[0] https://bugzilla.proxmox.com/show_bug.cgi?id=2164
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
Changes v3 -> v4:
* skip serializing disabled bond options to low-level config
* adapt to updated proxmox-installer-types, add support for
`members-filter`
Changes v2 -> v3:
* fixed typo in subject
* fixed patch section in Cargo.toml
Changes v1 -> v2:
* no changes
Cargo.toml | 2 +
debian/control | 1 +
proxmox-auto-installer/Cargo.toml | 1 +
proxmox-auto-installer/src/utils.rs | 101 +++++++++++++++---
proxmox-auto-installer/tests/parse-answer.rs | 7 +-
.../resources/parse_answer/network_bond.json | 25 +++++
.../resources/parse_answer/network_bond.toml | 20 ++++
.../network_bond_with_filter.json | 24 +++++
.../network_bond_with_filter.toml | 22 ++++
.../network_bond_invalid_hash_policy.json | 3 +
.../network_bond_invalid_hash_policy.toml | 20 ++++
.../network_bond_invalid_mode.json | 3 +
.../network_bond_invalid_mode.toml | 18 ++++
.../network_bond_primary_not_member.json | 3 +
.../network_bond_primary_not_member.toml | 19 ++++
.../network_bond_single_interface.json | 3 +
.../network_bond_single_interface.toml | 18 ++++
proxmox-installer-common/Cargo.toml | 1 +
proxmox-installer-common/src/options.rs | 38 +++++--
proxmox-installer-common/src/setup.rs | 45 +++++++-
20 files changed, 346 insertions(+), 28 deletions(-)
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/network_bond.json
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/network_bond.toml
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.json
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.toml
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.json
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.toml
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.json
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.toml
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.json
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.toml
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.json
create mode 100644 proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.toml
diff --git a/Cargo.toml b/Cargo.toml
index 747608e..75b5628 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,11 +27,13 @@ serde_plain = "1.0"
toml = "0.8"
proxmox-auto-installer.path = "./proxmox-auto-installer"
proxmox-installer-common.path = "./proxmox-installer-common"
+proxmox-network-api = "1.0"
proxmox-network-types = "1.1"
proxmox-installer-types = { version = "0.3", features = ["legacy"] }
# Local path overrides
# NOTE: You must run `cargo update` after changing this for it to take effect!
[patch.crates-io]
+# proxmox-network-api.path = "../proxmox/proxmox-network-api"
# proxmox-network-types.path = "../proxmox/proxmox-network-types"
# proxmox-installer-types.path = "../proxmox/proxmox-installer-types"
diff --git a/debian/control b/debian/control
index e860819..0f09c02 100644
--- a/debian/control
+++ b/debian/control
@@ -20,6 +20,7 @@ Build-Depends: cargo:native,
librust-pico-args-0.5-dev,
librust-pretty-assertions-1.4-dev,
librust-proxmox-installer-types-0.3+legacy-dev,
+ librust-proxmox-network-api-1-dev,
librust-proxmox-network-types-1-dev (>= 1.1-~~),
librust-proxmox-sys+crypt-dev,
librust-regex-1+default-dev (>= 1.7~~),
diff --git a/proxmox-auto-installer/Cargo.toml b/proxmox-auto-installer/Cargo.toml
index 5ef2f4f..b7ca85d 100644
--- a/proxmox-auto-installer/Cargo.toml
+++ b/proxmox-auto-installer/Cargo.toml
@@ -15,6 +15,7 @@ anyhow.workspace = true
log.workspace = true
proxmox-installer-common = { workspace = true, features = ["http"] }
proxmox-network-types.workspace = true
+proxmox-network-api.workspace = true
proxmox-installer-types.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
diff --git a/proxmox-auto-installer/src/utils.rs b/proxmox-auto-installer/src/utils.rs
index 9d27086..b9b3a8d 100644
--- a/proxmox-auto-installer/src/utils.rs
+++ b/proxmox-auto-installer/src/utils.rs
@@ -1,31 +1,30 @@
use anyhow::{Context, Result, bail};
use glob::Pattern;
use log::{info, warn};
+use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashSet},
process::Command,
};
-use proxmox_installer_types::{
- UdevInfo,
- answer::{
- AutoInstallerConfig, DiskSelection, Filesystem, FilesystemOptions, FilesystemType,
- FilterMatch, FirstBootHookSourceMode, FqdnConfig, FqdnFromDhcpConfig, FqdnSourceMode,
- NetworkConfig,
- },
-};
-
use proxmox_installer_common::{
ROOT_PASSWORD_MIN_LENGTH,
disk_checks::check_swapsize,
options::{FilesystemDiskInfo, NetworkInterfacePinningOptions, NetworkOptions, email_validate},
setup::{
- InstallBtrfsOption, InstallConfig, InstallFirstBootSetup, InstallRootPassword,
- InstallZfsOption, LocaleInfo, RuntimeInfo, SetupInfo,
+ InstallBtrfsOption, InstallConfig, InstallFirstBootSetup, InstallNetworkBondOptions,
+ InstallRootPassword, InstallZfsOption, LocaleInfo, RuntimeInfo, SetupInfo,
},
};
-
-use serde::{Deserialize, Serialize};
+use proxmox_installer_types::{
+ UdevInfo,
+ answer::{
+ AutoInstallerConfig, DiskSelection, Filesystem, FilesystemOptions, FilesystemType,
+ FilterMatch, FirstBootHookSourceMode, FqdnConfig, FqdnFromDhcpConfig, FqdnSourceMode,
+ NetworkBondMemberSelection, NetworkBondOptions, NetworkConfig,
+ },
+};
+use proxmox_network_types::MacAddress;
fn get_network_settings(
answer: &AutoInstallerConfig,
@@ -48,6 +47,7 @@ fn get_network_settings(
&runtime_info.network,
None,
pinning_opts.as_ref(),
+ answer.network.bond(),
);
opts.fqdn = name.to_owned();
opts
@@ -76,6 +76,7 @@ fn get_network_settings(
&runtime_info.network,
domain.as_deref(),
pinning_opts.as_ref(),
+ answer.network.bond(),
)
}
};
@@ -92,6 +93,37 @@ fn get_network_settings(
opts.verify()?;
}
+ if let Some(opts) = &network_options.bond_opts {
+ let members = match_selected_network_bond_members(opts, udev_info, runtime_info)?
+ .iter()
+ .fold(String::new(), |acc, mac| {
+ // Resolve either to the pinned name, if enabled, otherwise to the kernel-assigned one.
+ let name = network_options
+ .pinning_opts
+ .as_ref()
+ .and_then(|o| o.mapping.get(mac).cloned())
+ .or_else(|| {
+ runtime_info
+ .network
+ .interfaces
+ .iter()
+ .find_map(|(_, iface)| {
+ (iface.mac == *mac).then_some(iface.name.to_owned())
+ })
+ })
+ .unwrap_or("<unknown>".to_owned());
+
+ if acc.is_empty() {
+ name
+ } else {
+ format!("{acc}, {name}")
+ }
+ });
+
+ info!("Network interface bonding enabled on: {members}");
+ opts.verify()?;
+ }
+
info!("Network interface used is '{}'", &network_options.ifname);
Ok(network_options)
}
@@ -192,7 +224,7 @@ pub fn get_matched_udev_indexes(
let mut did_match_all = true;
for (filter_key, filter_value) in filter {
let filter_pattern =
- Pattern::new(filter_value).context("invalid glob in disk selection")?;
+ Pattern::new(filter_value).context("invalid glob in filter selection")?;
for (udev_key, udev_value) in dev_values {
if udev_key == filter_key && filter_pattern.matches(udev_value) {
did_match_once = true;
@@ -328,6 +360,32 @@ fn set_selected_disks(
Ok(())
}
+fn match_selected_network_bond_members(
+ bond: &NetworkBondOptions,
+ udev_info: &UdevInfo,
+ runtime_info: &RuntimeInfo,
+) -> Result<Vec<MacAddress>> {
+ match bond.members()? {
+ None => Ok(Vec::new()),
+ Some(NetworkBondMemberSelection::Fixed(list)) => Ok(list),
+ Some(NetworkBondMemberSelection::Filter(filter)) => {
+ let filter_match = bond.members_filter_match.unwrap_or_default();
+ // list of kernel-assigned names
+ let selected = get_matched_udev_indexes(
+ &filter,
+ &udev_info.nics,
+ filter_match == FilterMatch::All,
+ )?
+ .into_iter()
+ .filter_map(|ifname| runtime_info.network.interfaces.get(&ifname))
+ .map(|iface| iface.mac)
+ .collect();
+
+ Ok(selected)
+ }
+ }
+}
+
fn get_first_selected_disk(config: &InstallConfig) -> usize {
config
.disk_selection
@@ -493,6 +551,10 @@ pub fn verify_network_settings(
}
}
+ if let Some(opts) = network.bond() {
+ opts.verify()?;
+ }
+
Ok(())
}
@@ -552,6 +614,17 @@ pub fn parse_answer(
subscription_key: answer.global.subscription_key.clone(),
mngmt_nic: network_settings.ifname,
+ mngmt_bond: network_settings
+ .bond_opts
+ .map(|bond| -> Result<InstallNetworkBondOptions> {
+ Ok(InstallNetworkBondOptions {
+ members: match_selected_network_bond_members(&bond, udev_info, runtime_info)?,
+ mode: bond.mode,
+ hash_policy: bond.hash_policy,
+ primary_interface: bond.primary_interface,
+ })
+ })
+ .unwrap_or_else(|| Ok(InstallNetworkBondOptions::default()))?,
network_interface_pin_map: network_settings
.pinning_opts
.map(|o| o.mapping)
diff --git a/proxmox-auto-installer/tests/parse-answer.rs b/proxmox-auto-installer/tests/parse-answer.rs
index ae5b87d..0320bba 100644
--- a/proxmox-auto-installer/tests/parse-answer.rs
+++ b/proxmox-auto-installer/tests/parse-answer.rs
@@ -127,6 +127,7 @@ mod tests {
full_fqdn_from_dhcp_with_default_domain,
hashed_root_password,
minimal,
+ network_bond,
network_interface_pinning,
network_interface_pinning_mixed_case_mac_addresses,
nic_matching,
@@ -150,11 +151,15 @@ mod tests {
fqdn_hostname_only,
ipv4_and_subnet_mask_33,
lvm_swapsize_greater_than_hdsize,
+ network_bond_invalid_hash_policy,
+ network_bond_invalid_mode,
+ network_bond_primary_not_member,
+ network_bond_single_interface,
network_interface_pinning_overlong_interface_name,
no_fqdn_from_dhcp,
no_root_password_set,
short_password,
- zfs_raid_single_disk
+ zfs_raid_single_disk,
);
}
}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/network_bond.json b/proxmox-auto-installer/tests/resources/parse_answer/network_bond.json
new file mode 100644
index 0000000..cf0aa98
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer/network_bond.json
@@ -0,0 +1,25 @@
+{
+ "autoreboot": 1,
+ "cidr": "192.168.1.114/24",
+ "country": "at",
+ "dns": "192.168.1.254",
+ "domain": "testinstall",
+ "filesys": "ext4",
+ "gateway": "192.168.1.1",
+ "hdsize": 223.57088470458984,
+ "existing_storage_auto_rename": 1,
+ "hostname": "pveauto",
+ "keymap": "de",
+ "mailto": "mail@no.invalid",
+ "mngmt_nic": "bond0",
+ "mngmt_bond": {
+ "members": ["B4:2E:99:AC:AD:B4", "B4:2E:99:AC:AD:B5"],
+ "mode": "active-backup",
+ "hash-policy": "layer3+4",
+ "primary-interface": "B4:2E:99:AC:AD:B5"
+ },
+ "root_password": { "plain": "12345678" },
+ "target_hd": "/dev/sda",
+ "timezone": "Europe/Vienna",
+ "first_boot": { "enabled": 0 }
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/network_bond.toml b/proxmox-auto-installer/tests/resources/parse_answer/network_bond.toml
new file mode 100644
index 0000000..31d748c
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer/network_bond.toml
@@ -0,0 +1,20 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root-password = "12345678"
+
+[network]
+source = "from-dhcp"
+
+[network.bond]
+members-list = ["b4:2e:99:ac:ad:b4", "b4:2e:99:ac:ad:b5"]
+mode = "active-backup"
+hash-policy = "layer3+4"
+primary-interface = "b4:2e:99:ac:ad:b5"
+
+[disk-setup]
+filesystem = "ext4"
+disk-list = ["sda"]
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.json b/proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.json
new file mode 100644
index 0000000..91a4723
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.json
@@ -0,0 +1,24 @@
+{
+ "autoreboot": 1,
+ "cidr": "192.168.1.114/24",
+ "country": "at",
+ "dns": "192.168.1.254",
+ "domain": "testinstall",
+ "filesys": "ext4",
+ "gateway": "192.168.1.1",
+ "hdsize": 223.57088470458984,
+ "existing_storage_auto_rename": 1,
+ "hostname": "pveauto",
+ "keymap": "de",
+ "mailto": "mail@no.invalid",
+ "mngmt_nic": "bond0",
+ "mngmt_bond": {
+ "members": ["B4:2E:99:AC:AD:B4", "B4:2E:99:AC:AD:B5"],
+ "mode": "active-backup",
+ "hash-policy": "layer2"
+ },
+ "root_password": { "plain": "12345678" },
+ "target_hd": "/dev/sda",
+ "timezone": "Europe/Vienna",
+ "first_boot": { "enabled": 0 }
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.toml b/proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.toml
new file mode 100644
index 0000000..a1af3a1
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer/network_bond_with_filter.toml
@@ -0,0 +1,22 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root-password = "12345678"
+
+[network]
+source = "from-dhcp"
+
+[network.bond]
+# matches `eno1` and `eno2`
+members-filter.ID_NET_DRIVER = "igb"
+members-filter.ID_PATH = "pci-0000:c2:00.*"
+members-filter-match = "all"
+mode = "active-backup"
+hash-policy = "layer2"
+
+[disk-setup]
+filesystem = "ext4"
+disk-list = ["sda"]
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.json
new file mode 100644
index 0000000..b76c9d6
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.json
@@ -0,0 +1,3 @@
+{
+ "parse-error": "error parsing answer.toml: unknown variant `vlan+srcmac`, expected one of `layer2`, `layer2+3`, `layer3+4`"
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.toml b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.toml
new file mode 100644
index 0000000..af2789b
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_hash_policy.toml
@@ -0,0 +1,20 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root-password = "12345678"
+
+[network]
+source = "from-dhcp"
+
+[network.bond]
+members = ["b4:2e:99:ac:ad:b4", "b4:2e:99:ac:ad:b5"]
+mode = "active-backup"
+hash-policy = "vlan+srcmac"
+primary-interface = "b4:2e:99:ac:ad:b5"
+
+[disk-setup]
+filesystem = "ext4"
+disk-list = ["sda"]
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.json
new file mode 100644
index 0000000..95461dc
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.json
@@ -0,0 +1,3 @@
+{
+ "parse-error": "error parsing answer.toml: unknown variant `non-existent-mode`, expected one of `balance-rr`, `active-backup`, `balance-xor`, `broadcast`, `802.3ad`, `balance-tlb`, `balance-alb`"
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.toml b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.toml
new file mode 100644
index 0000000..79695da
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_invalid_mode.toml
@@ -0,0 +1,18 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root-password = "12345678"
+
+[network]
+source = "from-dhcp"
+
+[network.bond]
+members = ["b4:2e:99:ac:ad:b4", "b4:2e:99:ac:ad:b5"]
+mode = "non-existent-mode"
+
+[disk-setup]
+filesystem = "ext4"
+disk-list = ["sda"]
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.json
new file mode 100644
index 0000000..7e64e92
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.json
@@ -0,0 +1,3 @@
+{
+ "error": "primary bond interface B4:2E:99:AC:AD:B6 must be a member of the bond (members: B4:2E:99:AC:AD:B4, B4:2E:99:AC:AD:B5)"
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.toml b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.toml
new file mode 100644
index 0000000..f285828
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_primary_not_member.toml
@@ -0,0 +1,19 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root-password = "12345678"
+
+[network]
+source = "from-dhcp"
+
+[network.bond]
+members-list = ["b4:2e:99:ac:ad:b4", "b4:2e:99:ac:ad:b5"]
+mode = "active-backup"
+primary-interface = "b4:2e:99:ac:ad:b6"
+
+[disk-setup]
+filesystem = "ext4"
+disk-list = ["sda"]
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.json b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.json
new file mode 100644
index 0000000..730fad6
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.json
@@ -0,0 +1,3 @@
+{
+ "error": "bond setup requires at least 2 interfaces, got 1"
+}
diff --git a/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.toml b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.toml
new file mode 100644
index 0000000..7ec6204
--- /dev/null
+++ b/proxmox-auto-installer/tests/resources/parse_answer_fail/network_bond_single_interface.toml
@@ -0,0 +1,18 @@
+[global]
+keyboard = "de"
+country = "at"
+fqdn = "pveauto.testinstall"
+mailto = "mail@no.invalid"
+timezone = "Europe/Vienna"
+root-password = "12345678"
+
+[network]
+source = "from-dhcp"
+
+[network.bond]
+members-list = ["b4:2e:99:ac:ad:b4"]
+mode = "balance-rr"
+
+[disk-setup]
+filesystem = "ext4"
+disk-list = ["sda"]
diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml
index 7682680..a283094 100644
--- a/proxmox-installer-common/Cargo.toml
+++ b/proxmox-installer-common/Cargo.toml
@@ -13,6 +13,7 @@ regex.workspace = true
serde = { workspace = true, features = [ "derive" ] }
serde_json.workspace = true
serde_plain.workspace = true
+proxmox-network-api.workspace = true
proxmox-network-types.workspace = true
proxmox-installer-types.workspace = true
diff --git a/proxmox-installer-common/src/options.rs b/proxmox-installer-common/src/options.rs
index fbbfe77..1add63b 100644
--- a/proxmox-installer-common/src/options.rs
+++ b/proxmox-installer-common/src/options.rs
@@ -15,8 +15,8 @@ use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo};
use proxmox_installer_types::{
EMAIL_DEFAULT_PLACEHOLDER,
answer::{
- BtrfsCompressOption, BtrfsRaidLevel, FilesystemType, NetworkInterfacePinningOptionsAnswer,
- ZfsChecksumOption, ZfsCompressOption, ZfsRaidLevel,
+ BtrfsCompressOption, BtrfsRaidLevel, FilesystemType, NetworkBondOptions,
+ NetworkInterfacePinningOptionsAnswer, ZfsChecksumOption, ZfsCompressOption, ZfsRaidLevel,
},
};
use proxmox_network_types::{MacAddress, fqdn::Fqdn, ip_address::Cidr};
@@ -395,6 +395,7 @@ pub struct NetworkOptions {
pub gateway: IpAddr,
pub dns_server: IpAddr,
pub pinning_opts: Option<NetworkInterfacePinningOptions>,
+ pub bond_opts: Option<NetworkBondOptions>,
}
impl NetworkOptions {
@@ -405,6 +406,7 @@ impl NetworkOptions {
network: &NetworkInfo,
default_domain: Option<&str>,
pinning_opts: Option<&NetworkInterfacePinningOptions>,
+ bond_opts: Option<&NetworkBondOptions>,
) -> Self {
// Sets up sensible defaults as much as possible, such that even in the
// worse case nothing breaks down *completely*.
@@ -417,6 +419,7 @@ impl NetworkOptions {
gateway: Ipv4Addr::new(192, 168, 100, 1).into(),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: pinning_opts.cloned(),
+ bond_opts: bond_opts.cloned(),
};
let iface = if let Some(gw) = &network.routes.gateway4
@@ -479,6 +482,10 @@ impl NetworkOptions {
}
}
+ if this.bond_opts.is_some() {
+ this.ifname = "bond0".to_owned();
+ }
+
this
}
@@ -632,7 +639,7 @@ mod tests {
let (setup, mut info) = mock_setup_network();
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("foo.bar.com").unwrap(),
@@ -640,12 +647,13 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
info.hostname = None;
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("pve.bar.com").unwrap(),
@@ -653,12 +661,13 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
info.dns.domain = None;
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("pve.example.invalid").unwrap(),
@@ -666,12 +675,13 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
info.hostname = Some("foo".to_owned());
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("foo.example.invalid").unwrap(),
@@ -679,6 +689,7 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
}
@@ -726,7 +737,7 @@ mod tests {
let (setup, info) = mock_setup_network_v6_only();
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("foo.bar.com").unwrap(),
@@ -734,6 +745,7 @@ mod tests {
gateway: IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),
dns_server: IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x53)),
pinning_opts: None,
+ bond_opts: None,
}
);
}
@@ -743,7 +755,7 @@ mod tests {
let (setup, mut info) = mock_setup_network();
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("foo.bar.com").unwrap(),
@@ -751,12 +763,13 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
info.dns.domain = None;
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, Some("custom.local"), None),
+ NetworkOptions::defaults_from(&setup, &info, Some("custom.local"), None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("foo.custom.local").unwrap(),
@@ -764,12 +777,13 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
info.dns.domain = Some("some.domain.local".to_owned());
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, Some("custom.local"), None),
+ NetworkOptions::defaults_from(&setup, &info, Some("custom.local"), None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("foo.custom.local").unwrap(),
@@ -777,6 +791,7 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
}
@@ -813,7 +828,7 @@ mod tests {
let setup = SetupInfo::mocked();
pretty_assertions::assert_eq!(
- NetworkOptions::defaults_from(&setup, &info, None, None),
+ NetworkOptions::defaults_from(&setup, &info, None, None, None),
NetworkOptions {
ifname: "eth0".to_owned(),
fqdn: Fqdn::from("pve.example.invalid").unwrap(),
@@ -821,6 +836,7 @@ mod tests {
gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 100, 1)),
dns_server: Ipv4Addr::new(192, 168, 100, 1).into(),
pinning_opts: None,
+ bond_opts: None,
}
);
}
diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs
index 14882bc..31a1195 100644
--- a/proxmox-installer-common/src/setup.rs
+++ b/proxmox-installer-common/src/setup.rs
@@ -16,8 +16,12 @@ use crate::options::{
};
use proxmox_installer_types::{
BootType, IsoInfo, ProductConfig,
- answer::{BtrfsCompressOption, FilesystemType, ZfsChecksumOption, ZfsCompressOption},
+ answer::{
+ BtrfsCompressOption, FilesystemType, ZfsChecksumOption,
+ ZfsCompressOption,
+ },
};
+use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
use proxmox_network_types::{Cidr, MacAddress};
/// Paths in the ISO environment containing installer data.
@@ -326,7 +330,8 @@ pub struct NetworkInfo {
pub dns: Dns,
pub routes: Routes,
- /// Maps devices to their configuration, if it has a usable configuration.
+ /// Maps devices to their configuration, if it has a usable configuration, keyed by their
+ /// kernel-assigned interface name.
/// (Contains no entries for devices with only link-local addresses.)
#[serde(default)]
pub interfaces: BTreeMap<String, Interface>,
@@ -488,6 +493,39 @@ pub fn spawn_low_level_installer(test_mode: bool) -> io::Result<process::Child>
.spawn()
}
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Options for creating a bond for the management interface with the given
+/// physical interfaces and mode.
+pub struct InstallNetworkBondOptions {
+ /// List of interface MAC addresses to add as bond members.
+ pub members: Vec<MacAddress>,
+ /// Mode for the bond to set.
+ pub mode: LinuxBondMode,
+ /// Bond hash policy. Only used with '802.3ad' and 'balance-xor' mode.
+ pub hash_policy: BondXmitHashPolicy,
+ /// Primary bond interface. Only used with 'active-backup' mode.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub primary_interface: Option<MacAddress>,
+}
+
+impl Default for InstallNetworkBondOptions {
+ fn default() -> Self {
+ Self {
+ members: Vec::new(),
+ mode: LinuxBondMode::ActiveBackup,
+ hash_policy: BondXmitHashPolicy::Layer2,
+ primary_interface: None,
+ }
+ }
+}
+
+impl InstallNetworkBondOptions {
+ fn is_disabled(&self) -> bool {
+ self.members.is_empty()
+ }
+}
+
/// See Proxmox::Install::Config
#[derive(Debug, Deserialize, Serialize)]
pub struct InstallConfig {
@@ -530,6 +568,9 @@ pub struct InstallConfig {
pub subscription_key: Option<String>,
pub mngmt_nic: String,
+ #[serde(skip_serializing_if = "InstallNetworkBondOptions::is_disabled")]
+ pub mngmt_bond: InstallNetworkBondOptions,
+
// Maps MAC addresses -> custom name. If set, enables pinning for all
// interfaces present.
#[serde(
--
2.55.0
next prev parent reply other threads:[~2026-11-02 12:49 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 12:45 [PATCH installer/proxmox v4 00/30] fix #2164: add bond setup across all installers Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 01/30] installer-types: clippy: use .is_multiple_of() instead of manual modulo Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 02/30] installer-types: use `MacAddress` type where applicable Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 03/30] installer-types: derive Display for RebootMode and FilterMatch Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 04/30] installer-types: answer: add options for configuring management bond Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 05/30] installer-types: answer: add options for configuring management vlan Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 06/30] installer-types: bump fetch schema for bond and VLAN answer options Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 07/30] installer-types: bump version to 0.3.0-1 Christoph Heiss
2026-11-02 12:45 ` [PATCH proxmox v4 08/30] network-api: derive Display and FromStr for network bond types Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 09/30] install: run make tidy Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 10/30] tree-wide: use `MacAddress` type instead of string for MAC addresses Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 11/30] install: factor out ifupdown2 config generation into own subroutine Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 12/30] install: do not rely on interface name map to be fully populated Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 13/30] install: config: add option for setting up bond on management interface Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 14/30] install: config: add option for setting up vlan " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 15/30] install: network: set up management bond if requested Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 16/30] install: network: set up vlan on management interface " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 17/30] gui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 18/30] gui: network: add bond setup options to " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 19/30] gui: network: add vlan setup options to advanced network dialog Christoph Heiss
2026-11-02 12:45 ` Christoph Heiss [this message]
2026-11-02 12:45 ` [PATCH installer v4 21/30] auto: pass through management network vlan options to low-level installer Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 22/30] tui: drop long obsolete comment about logo formatting Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 23/30] tui: views: add widget for displaying a group of checkboxes Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 24/30] tui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 25/30] tui: network: add bond setup options to advanced network dialog Christoph Heiss
2026-11-02 14:55 ` Lukas Sichert
2026-11-02 12:45 ` [PATCH installer v4 26/30] tui: network: add vlan " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 27/30] gui: network: split advanced options dialog into tabs Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 28/30] tui: " Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 29/30] gui: network: list bond member candidates sharing a MAC address once Christoph Heiss
2026-11-02 12:45 ` [PATCH installer v4 30/30] tui: " Christoph Heiss
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261102124539.1008075-21-c.heiss@proxmox.com \
--to=c.heiss@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox