This commit is contained in:
David Schroeder
2026-07-29 17:17:56 -05:00
parent dd7a66e1e4
commit 8bd60e2f52
5 changed files with 224 additions and 7 deletions
+4 -3
View File
@@ -80,9 +80,10 @@ ISO repository.
The legacy Dell OMSA installer is limited to supported PowerEdge x30/x40
systems running Proxmox VE 9 on Debian 13 (Trixie), amd64.
CPU compatibility detection previews cluster-wide QEMU VM and template
changes before applying the ProxCLMC recommendation through the Proxmox CLI.
Running VMs are not restarted automatically.
CPU compatibility analysis compares every cluster host's physical processor,
processor generation/family, and supported generic VM CPU baseline. It then previews
QEMU VM and template changes before applying the highest baseline shared by
all nodes through the Proxmox CLI. Running VMs are not restarted automatically.
The native TAPM host configuration workflow replaces the former ProxMenux
post-install dependency. It can audit a host, apply the recommended PVE 9
+1 -1
View File
@@ -3,7 +3,7 @@
action="${1:-}"
FOLDER='/opt/idssys/ta-proxmenu'
VERS='2026.7.28-10'
VERS='2026.7.28-11'
noupdate=' '
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Parse the per-node rows printed by ProxCLMC. Fields are separated with an
# ASCII unit separator so processor descriptions can safely contain spaces.
TAPM_PROXCLMC_HOST_ROWS() {
awk -F '|' '
function trim(value) {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
return value
}
{
node = trim($1)
address = trim($2)
level = trim($3)
if (node ~ /^[[:alnum:]_.-]+$/ && level ~ /^x86-64-v(1|2-AES|3|4)$/) {
printf "%s\034%s\034%s\n", node, address, level
}
}
'
}
# Produce a conservative generation label from the processor name exposed by
# Proxmox. Ambiguous products are described as a family instead of being given
# a potentially incorrect codename.
TAPM_CPU_GENERATION_FROM_NAME() {
local cpu_name="${1:-}"
local normalized
local generation=""
local model_number=""
normalized="$(printf '%s' "$cpu_name" | tr '[:upper:]' '[:lower:]')"
if [[ "$normalized" =~ xeon.*e5-[[:digit:]]+[[:space:]]+v([1-4]) ]]; then
generation="${BASH_REMATCH[1]}"
case "$generation" in
1) printf '%s\n' "Sandy Bridge-EP" ;;
2) printf '%s\n' "Ivy Bridge-EP" ;;
3) printf '%s\n' "Haswell-EP" ;;
4) printf '%s\n' "Broadwell-EP" ;;
esac
return 0
fi
if [[ "$normalized" =~ xeon.*(bronze|silver|gold|platinum)[[:space:]]+([[:digit:]]{4}) ]]; then
model_number="${BASH_REMATCH[2]}"
generation="${model_number:1:1}"
case "$generation" in
1) printf '%s\n' "1st Gen Xeon Scalable (Skylake-SP)" ;;
2) printf '%s\n' "2nd Gen Xeon Scalable (Cascade Lake)" ;;
3) printf '%s\n' "3rd Gen Xeon Scalable (Ice Lake)" ;;
4) printf '%s\n' "4th Gen Xeon Scalable (Sapphire Rapids)" ;;
5) printf '%s\n' "5th Gen Xeon Scalable (Emerald Rapids)" ;;
*) printf '%s\n' "Intel Xeon Scalable family" ;;
esac
return 0
fi
if [[ "$normalized" =~ epyc[[:space:]]+([[:digit:]]{4}) ]]; then
model_number="${BASH_REMATCH[1]}"
generation="${model_number:3:1}"
case "$generation" in
1) printf '%s\n' "1st Gen EPYC (Naples / Zen)" ;;
2) printf '%s\n' "2nd Gen EPYC (Rome / Zen 2)" ;;
3) printf '%s\n' "3rd Gen EPYC (Milan / Zen 3)" ;;
4) printf '%s\n' "4th Gen EPYC (Zen 4 family)" ;;
5) printf '%s\n' "5th Gen EPYC (Zen 5 family)" ;;
*) printf '%s\n' "AMD EPYC family" ;;
esac
return 0
fi
if [[ "$normalized" =~ core.*i[3579]-([[:digit:]]{4,5}) ]]; then
model_number="${BASH_REMATCH[1]}"
if (( ${#model_number} == 4 )); then
generation="${model_number:0:1}"
else
generation="${model_number:0:2}"
fi
printf '%s\n' "Intel Core Gen ${generation}"
return 0
fi
if [[ "$normalized" =~ ryzen.*[[:space:]]([[:digit:]]{4,5}) ]]; then
model_number="${BASH_REMATCH[1]}"
printf '%s\n' "AMD Ryzen ${model_number:0:1}000 family"
return 0
fi
case "$normalized" in
*intel*) printf '%s\n' "Intel generation unknown" ;;
*amd*) printf '%s\n' "AMD generation unknown" ;;
*) printf '%s\n' "Generation unknown" ;;
esac
}
+76 -3
View File
@@ -10,6 +10,7 @@ source /opt/idssys/ta-proxmenu/inc/header-info.inc
source /opt/idssys/ta-proxmenu/inc/post-install.inc
source /opt/idssys/ta-proxmenu/inc/rmm.inc
source /opt/idssys/ta-proxmenu/inc/secure-input.inc
source /opt/idssys/ta-proxmenu/inc/cpu-compat.inc
source /opt/idssys/ta-proxmenu/inc/deploy-iso-nfs-lxc.sh
source /opt/idssys/ta-proxmenu/inc/deploy-pulse-lxc.sh
source /opt/idssys/ta-proxmenu/inc/virtio-helpers.inc
@@ -1225,6 +1226,73 @@ TAPM_SET_QEMU_CPU_MODEL() {
--cpu "$cpu_model"
}
TAPM_NODE_CPU_NAME() {
local node="${1:?node is required}"
local status_json
status_json="$(
pvesh get "/nodes/${node}/status" --output-format json 2>/dev/null
)" || return 1
TAPM_NODE_STATUS_JSON="$status_json" python3 -c '
import json
import os
try:
status = json.loads(os.environ["TAPM_NODE_STATUS_JSON"])
except (KeyError, TypeError, ValueError):
raise SystemExit(1)
cpu_info = status.get("cpuinfo") or {}
model = str(cpu_info.get("model") or "").strip()
if not model:
raise SystemExit(1)
print(" ".join(model.split()))
' 2>/dev/null
}
TAPM_SHOW_CLUSTER_CPU_TABLE() {
local proxclmc_output
local host_rows
local node
local address
local host_max
local cpu_name
local generation
proxclmc_output="$(proxclmc 2>/dev/null)" || proxclmc_output=""
host_rows="$(printf '%s\n' "$proxclmc_output" | TAPM_PROXCLMC_HOST_ROWS)"
printf '\nCluster host CPU capabilities:\n\n'
if [[ -z "$host_rows" ]]; then
echo -e " ${idsCL[Yellow]}Per-host CPU details were unavailable from ProxCLMC.${idsCL[Default]}"
return 0
fi
printf ' %-16s %-42s %-34s %-14s\n' \
'HOST' 'PHYSICAL CPU' 'GENERATION / FAMILY' 'BEST VM MODEL'
printf ' %-16s %-42s %-34s %-14s\n' \
'----------------' '------------------------------------------' \
'----------------------------------' '--------------'
while IFS=$'\034' read -r node address host_max; do
[[ -n "$node" ]] || continue
cpu_name="$(TAPM_NODE_CPU_NAME "$node" 2>/dev/null || true)"
if [[ -n "$cpu_name" ]]; then
generation="$(TAPM_CPU_GENERATION_FROM_NAME "$cpu_name")"
else
cpu_name='Unavailable'
generation='Unavailable'
fi
printf ' %-16.16s %-42.42s %-34.34s %-14s\n' \
"$node" "$cpu_name" "$generation" "$host_max"
done <<< "$host_rows"
}
DETECT_CPU() {
local answer
local cpu_model
@@ -1266,6 +1334,12 @@ DETECT_CPU() {
return
fi
TAPM_SHOW_CLUSTER_CPU_TABLE
printf '\nRecommended cluster-wide VM CPU baseline: %s\n' "$cpu_model"
printf '%s\n' \
'This is the highest generic model shared by every node for HA placement,' \
'load balancing, and moving workloads between cluster hosts.'
guest_output="$(TAPM_CLUSTER_QEMU_GUESTS)" || {
echo -e "${idsCL[LightRed]}Could not read cluster QEMU resources.${idsCL[Default]}"
FINISH_FAILED_ACTION
@@ -1296,7 +1370,6 @@ DETECT_CPU() {
return
fi
printf '\nRecommended cluster CPU model: %s\n' "$cpu_model"
printf '\n%-7s %-28s %-20s %-10s %-24s %s\n' \
'VMID' 'NAME' 'NODE' 'TEMPLATE' 'CURRENT CPU' 'PROPOSED CPU'
printf '%-7s %-28s %-20s %-10s %-24s %s\n' \
@@ -1308,7 +1381,7 @@ DETECT_CPU() {
"$vmid" "${name:-unnamed}" "$node" "$template" "$current_cpu" "$cpu_model"
done
echo -en "\n${idsCL[LightCyan]}Apply this CPU model to ${#changes[@]} VM(s) and template(s) (y/N)?${idsCL[Default]} "
echo -en "\n${idsCL[LightCyan]}Apply cluster-wide baseline ${cpu_model} to ${#changes[@]} VM(s) and template(s) (y/N)?${idsCL[Default]} "
read -r -n 1 answer
echo
[[ "$answer" =~ ^[Yy]$ ]] || return
@@ -2269,7 +2342,7 @@ HOST_SETUP_MENU() {
values=("post_install")
TAPM_POST_PROXMENUX_DETECTED &&
labels[0]="TAPM Host Configuration — ProxMenux migration recommended"
labels+=("Detect CPU model for live migrations")
labels+=("Analyze VM CPU compatibility for HA and workload mobility")
values+=("cpu")
# Discover ISO storage only after the VirtIO submenu is selected.
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "${TEST_DIR}/.." && pwd)"
source "${REPO_DIR}/inc/cpu-compat.inc"
assert_equal() {
local expected="${1:?expected value is required}"
local actual="${2:?actual value is required}"
local description="${3:?description is required}"
if [[ "$actual" != "$expected" ]]; then
printf 'FAIL: %s\nExpected: %s\nActual: %s\n' \
"$description" "$expected" "$actual" >&2
exit 1
fi
}
assert_equal \
"Broadwell-EP" \
"$(TAPM_CPU_GENERATION_FROM_NAME "Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz")" \
"identifies Xeon E5 v4"
assert_equal \
"3rd Gen Xeon Scalable (Ice Lake)" \
"$(TAPM_CPU_GENERATION_FROM_NAME "Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz")" \
"identifies third-generation Xeon Scalable"
assert_equal \
"3rd Gen EPYC (Milan / Zen 3)" \
"$(TAPM_CPU_GENERATION_FROM_NAME "AMD EPYC 7543 32-Core Processor")" \
"identifies EPYC Milan"
proxclmc_fixture='node-a | 10.0.0.1 | x86-64-v4
node-b | 10.0.0.2 | x86-64-v3
Cluster CPU type: x86-64-v3'
expected_rows=$'node-a\03410.0.0.1\034x86-64-v4\nnode-b\03410.0.0.2\034x86-64-v3'
actual_rows="$(printf '%s\n' "$proxclmc_fixture" | TAPM_PROXCLMC_HOST_ROWS)"
assert_equal "$expected_rows" "$actual_rows" "parses ProxCLMC host rows"
printf 'CPU compatibility tests passed.\n'