mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/Mapleawaa/PVE-Tools-9.git
synced 2026-09-20 08:03:35 +08:00
feat(网络): 添加基于 MAC 地址的网卡命名绑定功能
实现通过 systemd .link 文件按 MAC 地址固定网卡命名,避免重启或硬件变更后网卡顺序变化导致网络配置失效。新增功能包括: - 支持创建、删除和查看 MAC 绑定 - 自动检测并清理旧式 udev 规则 - 提供冲突检测与依赖检查 - 更新 README 和界面截图
This commit is contained in:
558
PVE-Tools.sh
558
PVE-Tools.sh
@@ -8635,6 +8635,536 @@ host_network_validate_mtu() {
|
||||
fi
|
||||
}
|
||||
|
||||
host_network_get_iface_mac() {
|
||||
local iface="$1"
|
||||
local mac
|
||||
mac="$(cat "/sys/class/net/${iface}/address" 2>/dev/null || true)"
|
||||
[[ "$mac" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]] && echo "$mac" || echo ""
|
||||
}
|
||||
|
||||
host_network_get_physical_ifaces_with_mac() {
|
||||
local iface mac
|
||||
for iface in $(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | cut -d'@' -f1 | sort -u); do
|
||||
[[ "$iface" == "lo" ]] && continue
|
||||
# 跳过 bridge、bond、vlan 等虚拟接口
|
||||
[[ -f "/sys/class/net/${iface}/device/vendor" ]] || continue
|
||||
mac="$(host_network_get_iface_mac "$iface")"
|
||||
[[ -n "$mac" ]] || continue
|
||||
printf '%s|%s\n' "$iface" "$mac"
|
||||
done
|
||||
}
|
||||
|
||||
host_network_validate_mac() {
|
||||
local mac="$1"
|
||||
[[ "$mac" =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]
|
||||
}
|
||||
|
||||
host_network_mac_to_iface() {
|
||||
local target_mac="$1"
|
||||
target_mac="$(echo "$target_mac" | tr '[:upper:]' '[:lower:]')"
|
||||
local iface mac
|
||||
for iface in $(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | cut -d'@' -f1 | sort -u); do
|
||||
[[ "$iface" == "lo" ]] && continue
|
||||
mac="$(host_network_get_iface_mac "$iface")"
|
||||
[[ "$(echo "$mac" | tr '[:upper:]' '[:lower:]')" == "$target_mac" ]] && { echo "$iface"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
host_network_validate_systemd_link_name() {
|
||||
local name="$1"
|
||||
[[ -n "$name" && ${#name} -le 15 ]] || return 1
|
||||
[[ "$name" != "." && "$name" != ".." && "$name" != "all" && "$name" != "default" ]] || return 1
|
||||
[[ "$name" =~ ^[A-Za-z0-9_.-]+$ ]] || return 1
|
||||
[[ "$name" =~ ^[0-9]+$ ]] && return 1
|
||||
}
|
||||
|
||||
host_network_systemd_link_dir() {
|
||||
echo "/etc/systemd/network"
|
||||
}
|
||||
|
||||
host_network_systemd_link_file_for_mac() {
|
||||
local mac="$1"
|
||||
local lower_mac
|
||||
lower_mac="$(echo "$mac" | tr '[:upper:]' '[:lower:]')"
|
||||
echo "$(host_network_systemd_link_dir)/10-pve-tools-${lower_mac//:/-}.link"
|
||||
}
|
||||
|
||||
host_network_systemd_link_get_value() {
|
||||
local file="$1"
|
||||
local section="$2"
|
||||
local key="$3"
|
||||
|
||||
[[ -f "$file" ]] || return 1
|
||||
|
||||
awk -v section="$section" -v key="$key" '
|
||||
BEGIN { in_section=0 }
|
||||
{
|
||||
line=$0
|
||||
sub(/[[:space:]]*#.*/, "", line)
|
||||
sub(/^[[:space:]]+/, "", line)
|
||||
sub(/[[:space:]]+$/, "", line)
|
||||
if (line == "") next
|
||||
if (line ~ /^\[[^]]+\]$/) {
|
||||
in_section = (line == "[" section "]")
|
||||
next
|
||||
}
|
||||
if (in_section && line ~ ("^" key "[[:space:]]*=[[:space:]]*")) {
|
||||
sub("^" key "[[:space:]]*=[[:space:]]*", "", line)
|
||||
gsub(/^"/, "", line)
|
||||
gsub(/"$/, "", line)
|
||||
print line
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
host_network_systemd_link_file_has_binding() {
|
||||
local file="$1"
|
||||
local mac="$2"
|
||||
local name="$3"
|
||||
local lower_mac file_mac file_name
|
||||
|
||||
file_mac="$(host_network_systemd_link_get_value "$file" "Match" "MACAddress" 2>/dev/null || true)"
|
||||
file_name="$(host_network_systemd_link_get_value "$file" "Link" "Name" 2>/dev/null || true)"
|
||||
lower_mac="$(echo "$mac" | tr '[:upper:]' '[:lower:]')"
|
||||
file_mac="$(echo "$file_mac" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
[[ "$file_mac" == "$lower_mac" && "$file_name" == "$name" ]]
|
||||
}
|
||||
|
||||
host_network_systemd_link_list_managed_files() {
|
||||
local dir
|
||||
dir="$(host_network_systemd_link_dir)"
|
||||
[[ -d "$dir" ]] || return 0
|
||||
find "$dir" -maxdepth 1 -type f -name '10-pve-tools-*.link' 2>/dev/null | sort
|
||||
}
|
||||
|
||||
host_network_systemd_link_find_conflicts() {
|
||||
local mac="$1"
|
||||
local name="$2"
|
||||
local target_file="$3"
|
||||
local dir file file_mac file_name search_dirs=()
|
||||
|
||||
for dir in /etc/systemd/network /run/systemd/network /usr/local/lib/systemd/network /usr/lib/systemd/network; do
|
||||
[[ -d "$dir" ]] && search_dirs+=("$dir")
|
||||
done
|
||||
|
||||
(( ${#search_dirs[@]} > 0 )) || return 0
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
[[ "$file" == "$target_file" ]] && continue
|
||||
file_mac="$(host_network_systemd_link_get_value "$file" "Match" "MACAddress" 2>/dev/null || true)"
|
||||
file_name="$(host_network_systemd_link_get_value "$file" "Link" "Name" 2>/dev/null || true)"
|
||||
file_mac="$(echo "$file_mac" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$file_mac" == "$mac" ]]; then
|
||||
printf '%s|MACAddress=%s\n' "$file" "$file_mac"
|
||||
elif [[ "$file_name" == "$name" ]]; then
|
||||
printf '%s|Name=%s\n' "$file" "$file_name"
|
||||
fi
|
||||
done < <(find "${search_dirs[@]}" -maxdepth 1 -type f -name '*.link' -print0 2>/dev/null)
|
||||
}
|
||||
|
||||
host_network_systemd_link_write_binding() {
|
||||
local mac="$1"
|
||||
local name="$2"
|
||||
local lower_mac target_dir target_file tmp backup_path=""
|
||||
lower_mac="$(echo "$mac" | tr '[:upper:]' '[:lower:]')"
|
||||
target_dir="$(host_network_systemd_link_dir)"
|
||||
target_file="$(host_network_systemd_link_file_for_mac "$lower_mac")"
|
||||
|
||||
mkdir -p "$target_dir" 2>/dev/null || {
|
||||
log_error "无法创建 systemd .link 目录: $target_dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ -f "$target_file" ]]; then
|
||||
if ! backup_file "$target_file" backup_path >/dev/null 2>&1; then
|
||||
log_error "无法备份现有 systemd .link 文件: $target_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
tmp="$(mktemp "${target_dir}/.pve-tools-link.XXXXXX")" || {
|
||||
log_error "无法创建临时文件: $target_dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
cat > "$tmp" <<EOF
|
||||
# Generated by PVE-Tools
|
||||
[Match]
|
||||
MACAddress=$lower_mac
|
||||
|
||||
[Link]
|
||||
Name=$name
|
||||
EOF
|
||||
|
||||
chmod 0644 "$tmp" >/dev/null 2>&1 || true
|
||||
|
||||
if ! mv -f "$tmp" "$target_file"; then
|
||||
rm -f "$tmp" >/dev/null 2>&1 || true
|
||||
if [[ -n "$backup_path" && -f "$backup_path" ]]; then
|
||||
cp -a "$backup_path" "$target_file" >/dev/null 2>&1 || true
|
||||
else
|
||||
rm -f "$target_file" >/dev/null 2>&1 || true
|
||||
fi
|
||||
log_error "写入 systemd .link 文件失败: $target_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local verify_mac verify_name
|
||||
verify_mac="$(host_network_systemd_link_get_value "$target_file" "Match" "MACAddress" 2>/dev/null || true)"
|
||||
verify_name="$(host_network_systemd_link_get_value "$target_file" "Link" "Name" 2>/dev/null || true)"
|
||||
verify_mac="$(echo "$verify_mac" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$verify_mac" != "$lower_mac" || "$verify_name" != "$name" ]]; then
|
||||
if [[ -n "$backup_path" && -f "$backup_path" ]]; then
|
||||
cp -a "$backup_path" "$target_file" >/dev/null 2>&1 || rm -f "$target_file" >/dev/null 2>&1 || true
|
||||
else
|
||||
rm -f "$target_file" >/dev/null 2>&1 || true
|
||||
fi
|
||||
log_error "写入后的 systemd .link 规则校验失败: $target_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$target_file"
|
||||
}
|
||||
|
||||
host_network_systemd_link_remove_binding_by_mac() {
|
||||
local mac="$1"
|
||||
local target_file backup_path=""
|
||||
|
||||
target_file="$(host_network_systemd_link_file_for_mac "$mac")"
|
||||
[[ -f "$target_file" ]] || return 0
|
||||
|
||||
if ! backup_file "$target_file" backup_path >/dev/null 2>&1; then
|
||||
log_error "无法备份待删除的 systemd .link 文件: $target_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! rm -f "$target_file"; then
|
||||
log_error "删除 systemd .link 文件失败: $target_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ -e "$target_file" ]] && {
|
||||
log_error "systemd .link 文件仍然存在: $target_file"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
host_network_legacy_udev_rules_file() {
|
||||
echo "/etc/udev/rules.d/70-persistent-net.rules"
|
||||
}
|
||||
|
||||
host_network_legacy_udev_list_bindings() {
|
||||
local file line mac legacy_name
|
||||
file="$(host_network_legacy_udev_rules_file)"
|
||||
[[ -f "$file" ]] || return 0
|
||||
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] || continue
|
||||
mac="$(echo "$line" | sed -n 's/.*ATTR{address}=="\([^"]*\)".*/\1/p')"
|
||||
legacy_name="$(echo "$line" | sed -n 's/.*NAME="\([^"]*\)".*/\1/p')"
|
||||
[[ -n "$mac" && -n "$legacy_name" ]] || continue
|
||||
printf '%s|%s\n' "$mac" "$legacy_name"
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
host_network_legacy_udev_name_conflicts() {
|
||||
local mac="$1"
|
||||
local name="$2"
|
||||
local file line legacy_mac legacy_name lower_mac
|
||||
file="$(host_network_legacy_udev_rules_file)"
|
||||
[[ -f "$file" ]] || return 1
|
||||
|
||||
lower_mac="$(echo "$mac" | tr '[:upper:]' '[:lower:]')"
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] || continue
|
||||
legacy_mac="$(echo "$line" | sed -n 's/.*ATTR{address}=="\([^"]*\)".*/\1/p')"
|
||||
legacy_name="$(echo "$line" | sed -n 's/.*NAME="\([^"]*\)".*/\1/p')"
|
||||
[[ -n "$legacy_mac" && -n "$legacy_name" ]] || continue
|
||||
legacy_mac="$(echo "$legacy_mac" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$legacy_name" == "$name" && "$legacy_mac" != "$lower_mac" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
host_network_legacy_udev_prune_binding() {
|
||||
local mac="$1"
|
||||
local file tmp backup_path="" lower_mac
|
||||
|
||||
file="$(host_network_legacy_udev_rules_file)"
|
||||
[[ -f "$file" ]] || return 0
|
||||
|
||||
if ! backup_file "$file" backup_path >/dev/null 2>&1; then
|
||||
log_error "无法备份 legacy udev 规则文件: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
lower_mac="$(echo "$mac" | tr '[:upper:]' '[:lower:]')"
|
||||
tmp="$(mktemp)" || {
|
||||
log_error "无法创建临时文件用于清理 legacy udev 规则"
|
||||
return 1
|
||||
}
|
||||
|
||||
awk -v mac="$lower_mac" '
|
||||
{
|
||||
line=$0
|
||||
line_lc=tolower(line)
|
||||
if (index(line_lc, "ATTR{address}==\"" mac "\"") > 0) next
|
||||
print
|
||||
}
|
||||
' "$file" > "$tmp"
|
||||
|
||||
if ! mv -f "$tmp" "$file"; then
|
||||
rm -f "$tmp" >/dev/null 2>&1 || true
|
||||
if [[ -n "$backup_path" && -f "$backup_path" ]]; then
|
||||
cp -a "$backup_path" "$file" >/dev/null 2>&1 || true
|
||||
fi
|
||||
log_error "清理 legacy udev 规则失败: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
[[ -s "$file" ]] || rm -f "$file"
|
||||
}
|
||||
|
||||
host_network_interface_has_config_reference() {
|
||||
local iface_name="$1"
|
||||
awk -v iface_name="$iface_name" '
|
||||
{
|
||||
line=$0
|
||||
sub(/[[:space:]]*#.*/, "", line)
|
||||
sub(/^[[:space:]]+/, "", line)
|
||||
sub(/[[:space:]]+$/, "", line)
|
||||
if (line == "") next
|
||||
|
||||
n=split(line, fields, /[[:space:]]+/)
|
||||
if (fields[1] == "iface" && n >= 2 && fields[2] == iface_name) {
|
||||
print line
|
||||
exit
|
||||
}
|
||||
if (fields[1] == "auto" || fields[1] ~ /^allow-/ || fields[1] == "bridge-ports" || fields[1] == "bond-slaves" || fields[1] == "vlan-raw-device") {
|
||||
for (i=2; i<=n; i++) {
|
||||
if (fields[i] == iface_name) {
|
||||
print line
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
' "$HOST_NETWORK_INTERFACES_FILE" 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
host_network_mac_binding_has_dependency() {
|
||||
local iface_name="$1"
|
||||
host_network_interface_has_master_dependency "$iface_name" && return 0
|
||||
host_network_interface_has_config_reference "$iface_name"
|
||||
}
|
||||
|
||||
host_network_show_mac_bindings() {
|
||||
local file iface mac link_name
|
||||
echo -e "${CYAN}当前 MAC 地址绑定(systemd .link 规则):${NC}"
|
||||
if host_network_systemd_link_list_managed_files | grep -q .; then
|
||||
while IFS= read -r file; do
|
||||
[[ -n "$file" ]] || continue
|
||||
mac="$(host_network_systemd_link_get_value "$file" "Match" "MACAddress" 2>/dev/null || true)"
|
||||
link_name="$(host_network_systemd_link_get_value "$file" "Link" "Name" 2>/dev/null || true)"
|
||||
[[ -n "$mac" && -n "$link_name" ]] || continue
|
||||
iface="$(host_network_mac_to_iface "$mac" 2>/dev/null || true)"
|
||||
if [[ -n "$iface" ]]; then
|
||||
printf ' %s → %s (当前接口: %s)\n' "$mac" "$link_name" "$iface"
|
||||
else
|
||||
printf ' %s → %s (当前未识别)\n' "$mac" "$link_name"
|
||||
fi
|
||||
done < <(host_network_systemd_link_list_managed_files)
|
||||
else
|
||||
echo " (无)"
|
||||
fi
|
||||
echo "$UI_DIVIDER"
|
||||
echo -e "${YELLOW}旧式 udev 绑定(兼容清理中):${NC}"
|
||||
if host_network_legacy_udev_list_bindings | grep -q .; then
|
||||
while IFS='|' read -r mac link_name; do
|
||||
[[ -n "$mac" ]] || continue
|
||||
printf ' %s → %s\n' "$mac" "$link_name"
|
||||
done < <(host_network_legacy_udev_list_bindings)
|
||||
else
|
||||
echo " (无)"
|
||||
fi
|
||||
echo "$UI_DIVIDER"
|
||||
echo -e "${CYAN}物理网卡 MAC 地址列表:${NC}"
|
||||
local entry iface_name iface_mac
|
||||
while IFS='|' read -r iface_name iface_mac; do
|
||||
[[ -n "$iface_name" ]] || continue
|
||||
link_name="$(host_network_systemd_link_get_value "$(host_network_systemd_link_file_for_mac "$iface_mac")" "Link" "Name" 2>/dev/null || true)"
|
||||
if [[ -n "$link_name" ]]; then
|
||||
printf ' [%s] %s (已绑定为 %s)\n' "$iface_name" "$iface_mac" "$link_name"
|
||||
else
|
||||
printf ' [%s] %s\n' "$iface_name" "$iface_mac"
|
||||
fi
|
||||
done < <(host_network_get_physical_ifaces_with_mac)
|
||||
}
|
||||
|
||||
host_network_create_mac_binding() {
|
||||
local entries=()
|
||||
local entry iface_name iface_mac idx pick fixed_name target_file backup_path link_name
|
||||
|
||||
while IFS='|' read -r iface_name iface_mac; do
|
||||
[[ -n "$iface_name" ]] || continue
|
||||
entries+=("$iface_name|$iface_mac")
|
||||
done < <(host_network_get_physical_ifaces_with_mac)
|
||||
|
||||
if (( ${#entries[@]} == 0 )); then
|
||||
display_error "未发现物理网卡" "请确认系统存在带有 PCI 设备的网络接口。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}可用物理网卡(按 MAC 地址固定命名):${NC}"
|
||||
idx=1
|
||||
for entry in "${entries[@]}"; do
|
||||
iface_name="${entry%%|*}"
|
||||
iface_mac="${entry##*|}"
|
||||
link_name="$(host_network_systemd_link_get_value "$(host_network_systemd_link_file_for_mac "$iface_mac")" "Link" "Name" 2>/dev/null || true)"
|
||||
if [[ -n "$link_name" ]]; then
|
||||
printf ' [%d] %s | %s (已绑定: %s)\n' "$idx" "$iface_name" "$iface_mac" "$link_name"
|
||||
else
|
||||
printf ' [%d] %s | %s\n' "$idx" "$iface_name" "$iface_mac"
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
echo "$UI_DIVIDER"
|
||||
|
||||
read -p "请选择要固定命名的网卡序号 (0 返回): " pick
|
||||
[[ "$pick" == "0" ]] && return 0
|
||||
[[ "$pick" =~ ^[0-9]+$ ]] || { display_error "无效选择"; return 1; }
|
||||
if (( pick < 1 || pick > ${#entries[@]} )); then
|
||||
display_error "选择超出范围"
|
||||
return 1
|
||||
fi
|
||||
|
||||
entry="${entries[$((pick - 1))]}"
|
||||
iface_name="${entry%%|*}"
|
||||
iface_mac="${entry##*|}"
|
||||
target_file="$(host_network_systemd_link_file_for_mac "$iface_mac")"
|
||||
|
||||
read -r -p "请输入固定接口名称(如 lan0、pve-eth0): " fixed_name
|
||||
[[ -n "$fixed_name" ]] || { display_error "接口名称不能为空"; return 1; }
|
||||
host_network_validate_systemd_link_name "$fixed_name" || {
|
||||
display_error "接口名称不合法: $fixed_name" "名称需为 1-15 位,只允许字母、数字、_、.、-,且不能是纯数字或保留名。"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "$fixed_name" != "$iface_name" ]] && host_network_iface_exists "$fixed_name"; then
|
||||
display_error "目标接口名称已被占用: $fixed_name" "请改用未被现有接口使用的名称。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if host_network_mac_binding_has_dependency "$iface_name"; then
|
||||
display_error "该网卡已被宿主机网络配置引用" "请先迁移或删除依赖该接口的 bridge / bond / VLAN / direct iface 配置,再修改命名。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if host_network_legacy_udev_name_conflicts "$iface_mac" "$fixed_name"; then
|
||||
display_error "旧式 udev 规则中已存在同名绑定" "请先人工处理 /etc/udev/rules.d/70-persistent-net.rules 中的同名规则,再继续迁移。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mapfile -t conflicts < <(host_network_systemd_link_find_conflicts "$iface_mac" "$fixed_name" "$target_file")
|
||||
if (( ${#conflicts[@]} > 0 )); then
|
||||
local conflict_report="检测到其他 systemd .link 文件已经匹配相同 MAC 或同名接口:"
|
||||
local conflict
|
||||
for conflict in "${conflicts[@]}"; do
|
||||
conflict_report+=$'\n - '"${conflict%%|*} (${conflict##*|})"
|
||||
done
|
||||
display_error "检测到 systemd .link 规则冲突" "$conflict_report"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f "$target_file" ]] && host_network_systemd_link_file_has_binding "$target_file" "$iface_mac" "$fixed_name"; then
|
||||
display_success "固定命名规则已存在" "文件: $target_file;重启宿主机后仍会保持该命名。"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! confirm_high_risk_action "为 MAC 地址 ${iface_mac} 写入固定命名 ${fixed_name}" "将生成 ${target_file},让 systemd 根据 MAC 在启动时重命名接口。" "固定名与现有接口或其他 .link 文件冲突时,接口命名可能变得不可预测。" "请确认名称唯一,并准备好控制台或带外管理手段以便回滚。" "BIND-LINK"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! host_network_legacy_udev_prune_binding "$iface_mac"; then
|
||||
display_error "清理旧式 udev 绑定失败" "请先处理 /etc/udev/rules.d/70-persistent-net.rules 后重试。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! target_file="$(host_network_systemd_link_write_binding "$iface_mac" "$fixed_name")"; then
|
||||
display_error "MAC 地址固定命名写入失败" "请先处理冲突提示,或检查 /etc/systemd/network 目录权限。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
display_success "MAC 地址固定命名规则已写入" "文件: $target_file;建议重启宿主机后生效。"
|
||||
}
|
||||
|
||||
host_network_delete_mac_binding() {
|
||||
local file mac link_name entries=() idx pick file_list
|
||||
file_list="$(host_network_systemd_link_list_managed_files)" || file_list=""
|
||||
|
||||
while IFS= read -r file; do
|
||||
[[ -n "$file" ]] || continue
|
||||
mac="$(host_network_systemd_link_get_value "$file" "Match" "MACAddress" 2>/dev/null || true)"
|
||||
link_name="$(host_network_systemd_link_get_value "$file" "Link" "Name" 2>/dev/null || true)"
|
||||
[[ -n "$mac" && -n "$link_name" ]] || continue
|
||||
entries+=("$file|$mac|$link_name")
|
||||
done <<< "$file_list"
|
||||
|
||||
if (( ${#entries[@]} == 0 )); then
|
||||
display_error "没有可删除的 MAC 绑定规则" "当前没有 PVE-Tools 管理的 systemd .link 文件。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}已存在的 MAC 地址绑定:${NC}"
|
||||
idx=1
|
||||
for entry in "${entries[@]}"; do
|
||||
file="${entry%%|*}"
|
||||
mac="${entry#*|}"
|
||||
mac="${mac%%|*}"
|
||||
link_name="${entry##*|}"
|
||||
printf ' [%d] %s → %s (%s)\n' "$idx" "$mac" "$link_name" "$(basename "$file")"
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
echo "$UI_DIVIDER"
|
||||
|
||||
read -p "请选择要删除的绑定序号 (0 返回): " pick
|
||||
[[ "$pick" == "0" ]] && return 0
|
||||
[[ "$pick" =~ ^[0-9]+$ ]] || { display_error "无效选择"; return 1; }
|
||||
if (( pick < 1 || pick > ${#entries[@]} )); then
|
||||
display_error "选择超出范围"
|
||||
return 1
|
||||
fi
|
||||
|
||||
entry="${entries[$((pick - 1))]}"
|
||||
file="${entry%%|*}"
|
||||
mac="${entry#*|}"
|
||||
mac="${mac%%|*}"
|
||||
link_name="${entry##*|}"
|
||||
|
||||
if ! confirm_action "删除 MAC 地址固定命名 ${mac} → ${link_name}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! host_network_legacy_udev_prune_binding "$mac"; then
|
||||
display_error "清理旧式 udev 绑定失败" "请检查 /etc/udev/rules.d/70-persistent-net.rules 的权限与内容。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! host_network_systemd_link_remove_binding_by_mac "$mac"; then
|
||||
display_error "删除 MAC 地址固定命名失败" "请检查文件权限或磁盘状态。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
display_success "MAC 地址固定命名已删除: ${mac} → ${link_name}" "如需恢复,请重新创建对应 .link 文件。"
|
||||
}
|
||||
|
||||
host_network_validate_ipv4() {
|
||||
local ip="$1"
|
||||
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
|
||||
@@ -9279,13 +9809,39 @@ host_network_bridge_menu() {
|
||||
show_menu_option "1" "列出当前网卡与桥接"
|
||||
show_menu_option "2" "创建桥接"
|
||||
show_menu_option "3" "删除桥接"
|
||||
show_menu_option "4" "MAC 地址绑定管理(防网卡顺序变化)"
|
||||
show_menu_option "0" "返回"
|
||||
show_menu_footer
|
||||
read -p "请选择操作 [0-3]: " choice
|
||||
read -p "请选择操作 [0-4]: " choice
|
||||
case "$choice" in
|
||||
1) host_network_show_current_overview ;;
|
||||
2) host_network_create_bridge ;;
|
||||
3) host_network_delete_bridge ;;
|
||||
4) host_network_mac_binding_menu ;;
|
||||
0) return ;;
|
||||
*) log_error "无效选择" ;;
|
||||
esac
|
||||
pause_function
|
||||
done
|
||||
}
|
||||
|
||||
host_network_mac_binding_menu() {
|
||||
while true; do
|
||||
clear
|
||||
show_menu_header "MAC 地址绑定管理"
|
||||
echo -e "${YELLOW}说明:通过 MAC 地址而不是接口名来固定网卡命名,避免重启或硬件变更后网卡顺序变化导致网络配置失效。${NC}"
|
||||
echo -e "${YELLOW}绑定后会在 /etc/systemd/network/ 中生成 .link 规则;若系统残留旧式 udev 规则,脚本会尝试同步清理。${NC}"
|
||||
echo "$UI_DIVIDER"
|
||||
host_network_show_mac_bindings
|
||||
echo "$UI_DIVIDER"
|
||||
show_menu_option "1" "创建 MAC 地址绑定"
|
||||
show_menu_option "2" "删除 MAC 地址绑定"
|
||||
show_menu_option "0" "返回"
|
||||
show_menu_footer
|
||||
read -p "请选择操作 [0-2]: " choice
|
||||
case "$choice" in
|
||||
1) host_network_create_mac_binding ;;
|
||||
2) host_network_delete_mac_binding ;;
|
||||
0) return ;;
|
||||
*) log_error "无效选择" ;;
|
||||
esac
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[](https://www.proxmox.com/)
|
||||
[-A81D33?logo=debian&logoColor=white)](https://www.debian.org/)
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"upcoming_features": [
|
||||
{
|
||||
"title": "支持按 MAC 地址绑定网卡到 Bridge",
|
||||
"status": "in-progress",
|
||||
"description": "通过 MAC 地址而不是 PCIe 地址来识别网卡,避免重启或硬件变更后网卡顺序变化,导致网络配置失效甚至失联。"
|
||||
"status": "completed",
|
||||
"description": "通过 systemd .link 按 MAC 地址固定网卡命名,避免重启或硬件变更后网卡顺序变化,导致网络配置失效甚至失联。"
|
||||
},
|
||||
{
|
||||
"title": "提供 Intel 核显 ROM 自动编译与下载服务",
|
||||
@@ -88,4 +88,4 @@
|
||||
"description": "PVE-Tools 1.0 发布,奠定了一键优化与维护脚本的基础。"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
images/cover.svg
Normal file
1
images/cover.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.1 KiB |
BIN
images/main.png
Normal file
BIN
images/main.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 288 KiB |
Reference in New Issue
Block a user