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