mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 10:47:36 +00:00
The nvidia driver installer now writes only `nvidia` and `nvidia_uvm` into `/etc/modules-load.d/nvidia-vfio.conf`, leaving the vfio-pci modules for `switch_gpu_mode.sh` to add and remove when the user explicitly transitions between LXC and VM mode. A host with residual `/etc/modprobe.d/vfio.conf` entries from an earlier VM passthrough setup no longer has vfio-pci racing nvidia at boot and capturing the GPU by ID. All three GPU-facing entry points also detect a stale `vfio.conf` entry whose vid:did belongs to a GPU currently bound to a non-vfio driver, and offer to clean it in place. `nvidia_installer.sh` runs the check as a pre-flight before install. `switch_gpu_mode.sh` presents an interactive dialog and rebuilds initramfs. `switch_gpu_mode_direct.sh` performs the same cleanup transparently for the Monitor web variant. Once cleaned, "Add GPU to LXC", the driver install and Switch Mode all proceed without any manual intervention. The two supporting helpers `_pci_driver_of` and `_clean_vfio_conf_ids` previously local to `add_gpu_lxc.sh` move to `scripts/global/pci_passthrough_helpers.sh`, so every GPU-related script resolves them from a single source without duplication.
1503 lines
52 KiB
Bash
1503 lines
52 KiB
Bash
#!/bin/bash
|
|
# ==========================================================
|
|
# ProxMenux - GPU Switch Mode (VM <-> LXC)
|
|
# ==========================================================
|
|
# Author : MacRimi
|
|
# Copyright : (c) 2024 MacRimi
|
|
# License : GPL-3.0
|
|
# Version : 1.0
|
|
# Last Updated: 05/04/2026
|
|
# ==========================================================
|
|
# Description:
|
|
# Moves an already-assigned GPU between the two modes it can
|
|
# live in on a Proxmox host:
|
|
# - VM mode (bound to vfio-pci, exclusive to one VM)
|
|
# - LXC mode (bound to the native driver, shared with CTs)
|
|
#
|
|
# Detects the current mode of each selected GPU and applies
|
|
# the host-side changes needed to switch (vfio.conf,
|
|
# blacklist.conf, /etc/modules, initramfs). Also handles the
|
|
# VM/LXC side so the switch doesn't leave dangling config
|
|
# pointing at a GPU the workload can no longer access.
|
|
#
|
|
# Features:
|
|
# - Multi-GPU selection (uniform current mode enforced)
|
|
# - SR-IOV guard (blocks VF / active-PF passthrough)
|
|
# - Blocked-ID policy list (e.g. Intel Arc A770)
|
|
# - IOMMU-group aware ID collection (sweeps siblings)
|
|
# - Conflict policy per affected VM/LXC
|
|
# (keep + disable onboot OR remove from config)
|
|
# - Orphan audio cascade: when a GPU leaves a VM, offer
|
|
# to remove companion audio hostpci entries and clean
|
|
# vfio.conf if no other VM still uses those IDs
|
|
# - Precise BDF regex for hostpci removal
|
|
# (no substring collision between unrelated GPUs)
|
|
# - NVIDIA stack sanitize/restore (udev, module-load,
|
|
# hard-blacklist) depending on target mode
|
|
# - Rebuilds initramfs only if host config actually changed
|
|
# - Reboot prompt at the end
|
|
# ==========================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
LOCAL_SCRIPTS_LOCAL="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
LOCAL_SCRIPTS_DEFAULT="/usr/local/share/proxmenux/scripts"
|
|
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_DEFAULT"
|
|
BASE_DIR="/usr/local/share/proxmenux"
|
|
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
|
if [[ -f "$LOCAL_SCRIPTS_LOCAL/utils.sh" ]]; then
|
|
LOCAL_SCRIPTS="$LOCAL_SCRIPTS_LOCAL"
|
|
UTILS_FILE="$LOCAL_SCRIPTS/utils.sh"
|
|
elif [[ ! -f "$UTILS_FILE" ]]; then
|
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
|
fi
|
|
|
|
LOG_FILE="/tmp/proxmenux_gpu_switch_mode.log"
|
|
screen_capture="/tmp/proxmenux_gpu_switch_mode_screen_$$.txt"
|
|
|
|
if [[ -f "$UTILS_FILE" ]]; then
|
|
source "$UTILS_FILE"
|
|
fi
|
|
# Both helper libraries are required for the SR-IOV guard and the audio
|
|
# orphan cascade to work. Surface a loud warning if neither path resolves
|
|
# — the previous behaviour evaluated `declare -F` later and silently
|
|
# disabled the validations, leaving the user thinking they were
|
|
# protected. Audit Tier 6 — `switch_gpu_mode.sh` silent helper loss.
|
|
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
|
|
source "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh"
|
|
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh" ]]; then
|
|
source "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh"
|
|
else
|
|
msg_warn "$(translate 'pci_passthrough_helpers.sh missing — SR-IOV / orphan-audio guards will be skipped')"
|
|
fi
|
|
load_language
|
|
initialize_cache
|
|
|
|
declare -a ALL_GPU_PCIS=()
|
|
declare -a ALL_GPU_TYPES=()
|
|
declare -a ALL_GPU_NAMES=()
|
|
declare -a ALL_GPU_DRIVERS=()
|
|
declare -a ALL_GPU_VIDDID=()
|
|
declare -a SELECTED_GPU_IDX=()
|
|
|
|
declare -a SELECTED_IOMMU_IDS=()
|
|
declare -a SELECTED_PCI_SLOTS=()
|
|
|
|
declare -a LXC_AFFECTED_CTIDS=()
|
|
declare -a LXC_AFFECTED_NAMES=()
|
|
declare -a LXC_AFFECTED_RUNNING=()
|
|
declare -a LXC_AFFECTED_ONBOOT=()
|
|
|
|
declare -a VM_AFFECTED_IDS=()
|
|
declare -a VM_AFFECTED_NAMES=()
|
|
declare -a VM_AFFECTED_RUNNING=()
|
|
declare -a VM_AFFECTED_ONBOOT=()
|
|
|
|
TARGET_MODE="" # vm | lxc
|
|
CURRENT_MODE="" # vm | lxc | mixed
|
|
LXC_ACTION="" # keep_gpu_disable_onboot | remove_gpu_keep_onboot
|
|
VM_ACTION="" # keep_gpu_disable_onboot | remove_gpu_keep_onboot
|
|
GPU_COUNT=0
|
|
HOST_CONFIG_CHANGED=false
|
|
|
|
_set_title() {
|
|
show_proxmenux_logo
|
|
case "$TARGET_MODE" in
|
|
vm) msg_title "GPU Switch Mode (GPU -> VM)" ;;
|
|
lxc) msg_title "GPU Switch Mode (GPU -> LXC)" ;;
|
|
*) msg_title "GPU Switch Mode (VM <-> LXC)" ;;
|
|
esac
|
|
}
|
|
|
|
_add_line_if_missing() {
|
|
local line="$1"
|
|
local file="$2"
|
|
touch "$file"
|
|
if ! grep -qFx "$line" "$file" 2>/dev/null; then
|
|
echo "$line" >>"$file"
|
|
HOST_CONFIG_CHANGED=true
|
|
fi
|
|
}
|
|
|
|
_get_pci_driver() {
|
|
local pci_full="$1"
|
|
local driver_link="/sys/bus/pci/devices/${pci_full}/driver"
|
|
if [[ -L "$driver_link" ]]; then
|
|
basename "$(readlink "$driver_link")"
|
|
else
|
|
echo "none"
|
|
fi
|
|
}
|
|
|
|
_ct_is_running() {
|
|
local ctid="$1"
|
|
pct status "$ctid" 2>/dev/null | grep -q "status: running"
|
|
}
|
|
|
|
_ct_onboot_enabled() {
|
|
local ctid="$1"
|
|
pct config "$ctid" 2>/dev/null | grep -qE "^onboot:\s*1"
|
|
}
|
|
|
|
_vm_is_running() {
|
|
local vmid="$1"
|
|
qm status "$vmid" 2>/dev/null | grep -q "status: running"
|
|
}
|
|
|
|
_vm_onboot_enabled() {
|
|
local vmid="$1"
|
|
qm config "$vmid" 2>/dev/null | grep -qE "^onboot:\s*1"
|
|
}
|
|
|
|
_get_iommu_group_ids() {
|
|
local pci_full="$1"
|
|
local group_link="/sys/bus/pci/devices/${pci_full}/iommu_group"
|
|
[[ ! -L "$group_link" ]] && return
|
|
|
|
local group_dir
|
|
group_dir="/sys/kernel/iommu_groups/$(basename "$(readlink "$group_link")")/devices"
|
|
for dev_path in "${group_dir}/"*; do
|
|
[[ -e "$dev_path" ]] || continue
|
|
local dev dev_class vid did
|
|
dev=$(basename "$dev_path")
|
|
dev_class=$(cat "/sys/bus/pci/devices/${dev}/class" 2>/dev/null)
|
|
[[ "$dev_class" == 0x0604* || "$dev_class" == 0x0600* ]] && continue
|
|
vid=$(cat "/sys/bus/pci/devices/${dev}/vendor" 2>/dev/null | sed 's/0x//')
|
|
did=$(cat "/sys/bus/pci/devices/${dev}/device" 2>/dev/null | sed 's/0x//')
|
|
[[ -n "$vid" && -n "$did" ]] && echo "${vid}:${did}"
|
|
done
|
|
}
|
|
|
|
_read_vfio_ids() {
|
|
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
|
local ids_line ids_part
|
|
ids_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
|
[[ -z "$ids_line" ]] && return
|
|
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
|
[[ -z "$ids_part" ]] && return
|
|
tr ',' '\n' <<< "$ids_part" | sed '/^$/d'
|
|
}
|
|
|
|
_write_vfio_ids() {
|
|
local -a ids=("$@")
|
|
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
|
touch "$vfio_conf"
|
|
|
|
local current_line new_line ids_str
|
|
current_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
|
sed -i '/^options vfio-pci ids=/d' "$vfio_conf"
|
|
|
|
if [[ ${#ids[@]} -gt 0 ]]; then
|
|
ids_str=$(IFS=','; echo "${ids[*]}")
|
|
new_line="options vfio-pci ids=${ids_str} disable_vga=1"
|
|
echo "$new_line" >>"$vfio_conf"
|
|
[[ "$current_line" != "$new_line" ]] && HOST_CONFIG_CHANGED=true
|
|
else
|
|
[[ -n "$current_line" ]] && HOST_CONFIG_CHANGED=true
|
|
fi
|
|
}
|
|
|
|
_contains_in_array() {
|
|
local needle="$1"
|
|
shift
|
|
local x
|
|
for x in "$@"; do
|
|
[[ "$x" == "$needle" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
_remove_gpu_blacklist() {
|
|
local gpu_type="$1"
|
|
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
|
[[ ! -f "$blacklist_file" ]] && return
|
|
local changed=false
|
|
case "$gpu_type" in
|
|
nvidia)
|
|
grep -qE '^blacklist (nouveau|nvidia|nvidiafb|nvidia_drm|nvidia_modeset|nvidia_uvm|lbm-nouveau)$|^options nouveau modeset=0$' "$blacklist_file" 2>/dev/null && changed=true
|
|
sed -i '/^blacklist nouveau$/d' "$blacklist_file"
|
|
sed -i '/^blacklist nvidia$/d' "$blacklist_file"
|
|
sed -i '/^blacklist nvidiafb$/d' "$blacklist_file"
|
|
sed -i '/^blacklist nvidia_drm$/d' "$blacklist_file"
|
|
sed -i '/^blacklist nvidia_modeset$/d' "$blacklist_file"
|
|
sed -i '/^blacklist nvidia_uvm$/d' "$blacklist_file"
|
|
sed -i '/^blacklist lbm-nouveau$/d' "$blacklist_file"
|
|
sed -i '/^options nouveau modeset=0$/d' "$blacklist_file"
|
|
;;
|
|
amd)
|
|
grep -qE '^blacklist (radeon|amdgpu)$' "$blacklist_file" 2>/dev/null && changed=true
|
|
sed -i '/^blacklist radeon$/d' "$blacklist_file"
|
|
sed -i '/^blacklist amdgpu$/d' "$blacklist_file"
|
|
;;
|
|
intel)
|
|
grep -qE '^blacklist i915$' "$blacklist_file" 2>/dev/null && changed=true
|
|
sed -i '/^blacklist i915$/d' "$blacklist_file"
|
|
;;
|
|
esac
|
|
$changed && HOST_CONFIG_CHANGED=true
|
|
$changed
|
|
}
|
|
|
|
_add_gpu_blacklist() {
|
|
local gpu_type="$1"
|
|
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
|
touch "$blacklist_file"
|
|
case "$gpu_type" in
|
|
nvidia)
|
|
_add_line_if_missing "blacklist nouveau" "$blacklist_file"
|
|
_add_line_if_missing "blacklist nvidia" "$blacklist_file"
|
|
_add_line_if_missing "blacklist nvidiafb" "$blacklist_file"
|
|
_add_line_if_missing "blacklist nvidia_drm" "$blacklist_file"
|
|
_add_line_if_missing "blacklist nvidia_modeset" "$blacklist_file"
|
|
_add_line_if_missing "blacklist nvidia_uvm" "$blacklist_file"
|
|
_add_line_if_missing "blacklist lbm-nouveau" "$blacklist_file"
|
|
_add_line_if_missing "options nouveau modeset=0" "$blacklist_file"
|
|
;;
|
|
amd)
|
|
_add_line_if_missing "blacklist radeon" "$blacklist_file"
|
|
_add_line_if_missing "blacklist amdgpu" "$blacklist_file"
|
|
;;
|
|
intel)
|
|
_add_line_if_missing "blacklist i915" "$blacklist_file"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_sanitize_nvidia_host_stack_for_vfio() {
|
|
local changed=false
|
|
local state_dir="/var/lib/proxmenux"
|
|
local state_file="${state_dir}/nvidia-host-services.state"
|
|
local svc
|
|
local -a services=(
|
|
"nvidia-persistenced.service"
|
|
"nvidia-powerd.service"
|
|
"nvidia-fabricmanager.service"
|
|
)
|
|
|
|
mkdir -p "$state_dir" >/dev/null 2>&1 || true
|
|
: > "$state_file"
|
|
|
|
for svc in "${services[@]}"; do
|
|
local was_enabled=0 was_active=0
|
|
if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
|
|
was_enabled=1
|
|
fi
|
|
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
|
was_active=1
|
|
fi
|
|
if (( was_enabled == 1 || was_active == 1 )); then
|
|
echo "${svc} enabled=${was_enabled} active=${was_active}" >>"$state_file"
|
|
fi
|
|
|
|
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
|
systemctl stop "$svc" >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
|
|
systemctl disable "$svc" >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
done
|
|
|
|
[[ -s "$state_file" ]] || rm -f "$state_file"
|
|
|
|
if [[ -f /etc/modules-load.d/nvidia-vfio.conf ]]; then
|
|
mv /etc/modules-load.d/nvidia-vfio.conf /etc/modules-load.d/nvidia-vfio.conf.proxmenux-disabled-vfio >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
|
|
if grep -qE '^(nvidia|nvidia_uvm|nvidia_drm|nvidia_modeset)$' /etc/modules 2>/dev/null; then
|
|
sed -i '/^nvidia$/d;/^nvidia_uvm$/d;/^nvidia_drm$/d;/^nvidia_modeset$/d' /etc/modules
|
|
changed=true
|
|
fi
|
|
|
|
# Disable NVIDIA udev rules that trigger nvidia-smi (causes conflict with vfio-pci)
|
|
local udev_rules="/etc/udev/rules.d/70-nvidia.rules"
|
|
if [[ -f "$udev_rules" ]]; then
|
|
mv "$udev_rules" "${udev_rules}.proxmenux-disabled" >>"$LOG_FILE" 2>&1 || true
|
|
udevadm control --reload-rules >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
|
|
# Create hard blacklist to prevent ANY nvidia module loading (even via modprobe/nvidia-smi)
|
|
local nvidia_blacklist="/etc/modprobe.d/nvidia-blacklist.conf"
|
|
if [[ ! -f "$nvidia_blacklist" ]]; then
|
|
cat > "$nvidia_blacklist" <<'EOF'
|
|
# ProxMenux: Hard blacklist to prevent ANY nvidia module loading in VFIO mode
|
|
# This prevents nvidia-smi and other tools from triggering module load attempts
|
|
install nvidia /bin/false
|
|
install nvidia_uvm /bin/false
|
|
install nvidia_drm /bin/false
|
|
install nvidia_modeset /bin/false
|
|
EOF
|
|
changed=true
|
|
fi
|
|
|
|
if $changed; then
|
|
HOST_CONFIG_CHANGED=true
|
|
msg_ok "$(translate 'NVIDIA host services/autoload disabled for VFIO mode')" | tee -a "$screen_capture"
|
|
else
|
|
msg_ok "$(translate 'NVIDIA host services/autoload already aligned for VFIO mode')" | tee -a "$screen_capture"
|
|
fi
|
|
|
|
# Sync components_status.json — the host driver stays on disk but is
|
|
# not in use because the GPU now belongs to a VM. Prevents the update
|
|
# notification path (and any future logic gated on nvidia_driver.status)
|
|
# from acting on a state that no longer matches reality.
|
|
if declare -F update_component_status >/dev/null 2>&1; then
|
|
local _nvd_ver
|
|
_nvd_ver=$(jq -r '.nvidia_driver.version // ""' \
|
|
/usr/local/share/proxmenux/components_status.json 2>/dev/null)
|
|
update_component_status "nvidia_driver" "vfio_passthrough" \
|
|
"${_nvd_ver:-}" "gpu" '{"patched":false}' >>"$LOG_FILE" 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
_restore_nvidia_host_stack_for_lxc() {
|
|
local changed=false
|
|
local state_file="/var/lib/proxmenux/nvidia-host-services.state"
|
|
local disabled_file="/etc/modules-load.d/nvidia-vfio.conf.proxmenux-disabled-vfio"
|
|
local active_file="/etc/modules-load.d/nvidia-vfio.conf"
|
|
|
|
# New per-BDF model: drop every NVIDIA BDF from the initramfs binder so
|
|
# the nvidia module reclaims the GPU after the next reboot. Idempotent:
|
|
# no-op if no NVIDIA BDFs are tracked. Vendor 10de = NVIDIA.
|
|
if declare -F _proxmenux_vfio_bind_purge_vendor >/dev/null 2>&1; then
|
|
_proxmenux_vfio_bind_purge_vendor "10de" && changed=true
|
|
fi
|
|
|
|
# Remove hard blacklist that was preventing nvidia module loading
|
|
local nvidia_blacklist="/etc/modprobe.d/nvidia-blacklist.conf"
|
|
if [[ -f "$nvidia_blacklist" ]]; then
|
|
rm -f "$nvidia_blacklist" >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
|
|
# Restore NVIDIA udev rules if they were disabled
|
|
local udev_disabled="/etc/udev/rules.d/70-nvidia.rules.proxmenux-disabled"
|
|
local udev_rules="/etc/udev/rules.d/70-nvidia.rules"
|
|
if [[ -f "$udev_disabled" ]]; then
|
|
mv "$udev_disabled" "$udev_rules" >>"$LOG_FILE" 2>&1 || true
|
|
udevadm control --reload-rules >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
|
|
# Restore previous modules-load policy if ProxMenux disabled it in VM mode.
|
|
if [[ -f "$disabled_file" ]]; then
|
|
mv "$disabled_file" "$active_file" >>"$LOG_FILE" 2>&1 || true
|
|
changed=true
|
|
fi
|
|
|
|
# Best effort: load NVIDIA kernel modules now that we are back in native mode.
|
|
# If not installed, these calls simply fail silently.
|
|
modprobe nvidia >/dev/null 2>&1 || true
|
|
modprobe nvidia_uvm >/dev/null 2>&1 || true
|
|
modprobe nvidia_modeset >/dev/null 2>&1 || true
|
|
modprobe nvidia_drm >/dev/null 2>&1 || true
|
|
|
|
if [[ -f "$state_file" ]]; then
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
local svc enabled active
|
|
svc=$(echo "$line" | awk '{print $1}')
|
|
enabled=$(echo "$line" | awk -F'enabled=' '{print $2}' | awk '{print $1}')
|
|
active=$(echo "$line" | awk -F'active=' '{print $2}' | awk '{print $1}')
|
|
[[ "$enabled" == "1" ]] && systemctl enable "$svc" >>"$LOG_FILE" 2>&1 || true
|
|
[[ "$active" == "1" ]] && systemctl start "$svc" >>"$LOG_FILE" 2>&1 || true
|
|
done <"$state_file"
|
|
rm -f "$state_file"
|
|
changed=true
|
|
fi
|
|
|
|
if $changed; then
|
|
HOST_CONFIG_CHANGED=true
|
|
msg_ok "$(translate 'NVIDIA host services/autoload restored for native mode')" | tee -a "$screen_capture"
|
|
else
|
|
msg_ok "$(translate 'NVIDIA host services/autoload already aligned for native mode')" | tee -a "$screen_capture"
|
|
fi
|
|
|
|
# Sync components_status.json back to installed — the host has reclaimed
|
|
# the GPU and the nvidia stack is being reloaded. Restores the state to
|
|
# what it was before the VFIO switch so the update notification path and
|
|
# the auto-reinstall gate see the driver as active on the host again.
|
|
if declare -F update_component_status >/dev/null 2>&1; then
|
|
local _nvd_ver
|
|
_nvd_ver=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1)
|
|
if [[ -z "$_nvd_ver" ]]; then
|
|
_nvd_ver=$(jq -r '.nvidia_driver.version // ""' \
|
|
/usr/local/share/proxmenux/components_status.json 2>/dev/null)
|
|
fi
|
|
update_component_status "nvidia_driver" "installed" \
|
|
"${_nvd_ver:-}" "gpu" '{"patched":false}' >>"$LOG_FILE" 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
_add_amd_softdep() {
|
|
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
|
_add_line_if_missing "softdep radeon pre: vfio-pci" "$vfio_conf"
|
|
_add_line_if_missing "softdep amdgpu pre: vfio-pci" "$vfio_conf"
|
|
_add_line_if_missing "softdep snd_hda_intel pre: vfio-pci" "$vfio_conf"
|
|
}
|
|
|
|
_remove_amd_softdep() {
|
|
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
|
[[ ! -f "$vfio_conf" ]] && return
|
|
local changed=false
|
|
grep -qE '^softdep (radeon|amdgpu|snd_hda_intel) pre: vfio-pci$' "$vfio_conf" 2>/dev/null && changed=true
|
|
sed -i '/^softdep radeon pre: vfio-pci$/d' "$vfio_conf"
|
|
sed -i '/^softdep amdgpu pre: vfio-pci$/d' "$vfio_conf"
|
|
sed -i '/^softdep snd_hda_intel pre: vfio-pci$/d' "$vfio_conf"
|
|
$changed && HOST_CONFIG_CHANGED=true
|
|
$changed
|
|
}
|
|
|
|
_add_vfio_modules() {
|
|
local modules=("vfio" "vfio_iommu_type1" "vfio_pci")
|
|
local kernel_major kernel_minor
|
|
kernel_major=$(uname -r | cut -d. -f1)
|
|
kernel_minor=$(uname -r | cut -d. -f2)
|
|
if (( kernel_major < 6 || ( kernel_major == 6 && kernel_minor < 2 ) )); then
|
|
modules+=("vfio_virqfd")
|
|
fi
|
|
local mod
|
|
for mod in "${modules[@]}"; do
|
|
_add_line_if_missing "$mod" /etc/modules
|
|
done
|
|
}
|
|
|
|
_remove_vfio_modules_if_unused() {
|
|
local vfio_count
|
|
vfio_count=$(_read_vfio_ids | wc -l | tr -d '[:space:]')
|
|
[[ "$vfio_count" != "0" ]] && return 1
|
|
local modules_file="/etc/modules"
|
|
[[ ! -f "$modules_file" ]] && return 1
|
|
local had_any=false
|
|
grep -qE '^vfio$|^vfio_iommu_type1$|^vfio_pci$|^vfio_virqfd$' "$modules_file" 2>/dev/null && had_any=true
|
|
sed -i '/^vfio$/d' "$modules_file"
|
|
sed -i '/^vfio_iommu_type1$/d' "$modules_file"
|
|
sed -i '/^vfio_pci$/d' "$modules_file"
|
|
sed -i '/^vfio_virqfd$/d' "$modules_file"
|
|
if $had_any; then
|
|
HOST_CONFIG_CHANGED=true
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
_configure_iommu_options() {
|
|
_add_line_if_missing "options vfio_iommu_type1 allow_unsafe_interrupts=1" /etc/modprobe.d/iommu_unsafe_interrupts.conf
|
|
_add_line_if_missing "options kvm ignore_msrs=1" /etc/modprobe.d/kvm.conf
|
|
}
|
|
|
|
_selected_types_unique() {
|
|
local -a out=()
|
|
local idx t
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
t="${ALL_GPU_TYPES[$idx]}"
|
|
_contains_in_array "$t" "${out[@]}" || out+=("$t")
|
|
done
|
|
printf '%s\n' "${out[@]}"
|
|
}
|
|
|
|
detect_host_gpus() {
|
|
ALL_GPU_PCIS=()
|
|
ALL_GPU_TYPES=()
|
|
ALL_GPU_NAMES=()
|
|
ALL_GPU_DRIVERS=()
|
|
ALL_GPU_VIDDID=()
|
|
|
|
while IFS= read -r line; do
|
|
local pci_short pci_full name type driver viddid pci_info
|
|
pci_short=$(echo "$line" | awk '{print $1}')
|
|
pci_full="0000:${pci_short}"
|
|
pci_info=$(lspci -nn -s "${pci_short}" 2>/dev/null | sed 's/^[^ ]* //')
|
|
name="${pci_info#*: }"
|
|
[[ "$name" == "$pci_info" ]] && name="$pci_info"
|
|
name=$(echo "$name" | sed -E 's/ \(rev [^)]+\)$//' | cut -c1-72)
|
|
[[ -z "$name" ]] && name="$(translate 'Unknown GPU')"
|
|
if echo "$line" | grep -qi "Intel"; then
|
|
type="intel"
|
|
elif echo "$line" | grep -qiE "AMD|Advanced Micro|Radeon"; then
|
|
type="amd"
|
|
elif echo "$line" | grep -qi "NVIDIA"; then
|
|
type="nvidia"
|
|
else
|
|
continue
|
|
fi
|
|
driver=$(_get_pci_driver "$pci_full")
|
|
viddid=$(echo "$line" | grep -oE '\[[0-9a-f]{4}:[0-9a-f]{4}\]' | tr -d '[]')
|
|
ALL_GPU_PCIS+=("$pci_full")
|
|
ALL_GPU_TYPES+=("$type")
|
|
ALL_GPU_NAMES+=("$name")
|
|
ALL_GPU_DRIVERS+=("$driver")
|
|
ALL_GPU_VIDDID+=("$viddid")
|
|
done < <(lspci -nn | grep -iE "VGA compatible controller|3D controller|Display controller" | grep -iv "Ethernet\|Network\|Audio")
|
|
|
|
GPU_COUNT=${#ALL_GPU_PCIS[@]}
|
|
if [[ "$GPU_COUNT" -eq 0 ]]; then
|
|
dialog --backtitle "ProxMenux" \
|
|
--title "$(translate 'GPU Switch Mode')" \
|
|
--msgbox "\n$(translate 'No compatible GPUs were detected on this host.')" 8 64
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
_selected_gpu_current_mode() {
|
|
local mode=""
|
|
local idx drv cur
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
drv="${ALL_GPU_DRIVERS[$idx]}"
|
|
if [[ "$drv" == "vfio-pci" ]]; then
|
|
cur="vm"
|
|
else
|
|
cur="lxc"
|
|
fi
|
|
|
|
if [[ -z "$mode" ]]; then
|
|
mode="$cur"
|
|
elif [[ "$mode" != "$cur" ]]; then
|
|
echo "mixed"
|
|
return
|
|
fi
|
|
done
|
|
[[ -z "$mode" ]] && mode="lxc"
|
|
echo "$mode"
|
|
}
|
|
|
|
check_stale_vfio_config_switch_mode() {
|
|
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
|
[[ ! -f "$vfio_conf" ]] && return 0
|
|
|
|
local ids_line ids_part
|
|
ids_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
|
[[ -z "$ids_line" ]] && return 0
|
|
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
|
[[ -z "$ids_part" ]] && return 0
|
|
|
|
local -a legacy_ids=()
|
|
local legacy_list=""
|
|
local idx viddid drv pci name
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
viddid="${ALL_GPU_VIDDID[$idx]}"
|
|
drv="${ALL_GPU_DRIVERS[$idx]}"
|
|
pci="${ALL_GPU_PCIS[$idx]}"
|
|
name="${ALL_GPU_NAMES[$idx]}"
|
|
[[ -z "$viddid" ]] && continue
|
|
[[ "$drv" == "vfio-pci" ]] && continue
|
|
if echo ",${ids_part}," | grep -q ",${viddid},"; then
|
|
legacy_ids+=("$viddid")
|
|
legacy_list+=" • ${name} (${pci}) [${viddid}]\n"
|
|
fi
|
|
done
|
|
|
|
[[ ${#legacy_ids[@]} -eq 0 ]] && return 0
|
|
|
|
local msg
|
|
msg="\n$(translate 'The following selected GPU(s) still have a VFIO passthrough entry in') /etc/modprobe.d/vfio.conf:\n\n"
|
|
msg+="${legacy_list}\n"
|
|
msg+="$(translate 'The active kernel driver is not vfio-pci, but the entry will rebind the GPU to vfio-pci on the next reboot.')\n\n"
|
|
msg+="\Z1\Zb$(translate 'Do you want to remove the stale entry from vfio.conf?')\Zn"
|
|
|
|
dialog --colors --backtitle "ProxMenux" \
|
|
--title "$(translate 'Stale VFIO Config Detected')" \
|
|
--yesno "$msg" 18 80 || return 0
|
|
|
|
if declare -F _clean_vfio_conf_ids >/dev/null 2>&1 \
|
|
&& _clean_vfio_conf_ids "${legacy_ids[@]}"; then
|
|
msg_info "$(translate 'Rebuilding initramfs after vfio.conf cleanup...')"
|
|
update-initramfs -u >/dev/null 2>&1 || true
|
|
dialog --backtitle "ProxMenux" \
|
|
--title "$(translate 'Stale VFIO Config Cleaned')" \
|
|
--msgbox "\n$(translate 'The vfio.conf entries have been removed and initramfs rebuilt.')\n\n$(translate 'A reboot is recommended before the GPU is guaranteed to stay on the native driver.')" \
|
|
12 78
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
select_target_mode() {
|
|
CURRENT_MODE=$(_selected_gpu_current_mode)
|
|
check_stale_vfio_config_switch_mode
|
|
|
|
if [[ "$CURRENT_MODE" == "mixed" ]]; then
|
|
local msg idx mode_label
|
|
msg="\n$(translate 'Mixed current mode detected in selected GPU(s).')\n\n"
|
|
msg+="$(translate 'Please select GPU(s) that are currently in the same mode and try again.')\n\n"
|
|
msg+="$(translate 'Selected GPU(s):')\n"
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
if [[ "${ALL_GPU_DRIVERS[$idx]}" == "vfio-pci" ]]; then
|
|
mode_label="GPU -> VM"
|
|
else
|
|
mode_label="GPU -> LXC"
|
|
fi
|
|
msg+=" • ${ALL_GPU_NAMES[$idx]} (${ALL_GPU_PCIS[$idx]}) [${mode_label}]\n"
|
|
done
|
|
dialog --backtitle "ProxMenux" \
|
|
--title "$(translate 'Mixed GPU Modes')" \
|
|
--msgbox "$msg" 20 94
|
|
return 2
|
|
fi
|
|
|
|
local menu_title menu_option_tag menu_option_desc current_mode_label current_mode_highlight
|
|
if [[ "$CURRENT_MODE" == "vm" ]]; then
|
|
TARGET_MODE="lxc"
|
|
current_mode_label="GPU -> VM (VFIO passthrough mode)"
|
|
menu_option_tag="lxc"
|
|
menu_option_desc="$(translate 'Switch to GPU -> LXC (native driver mode)')"
|
|
else
|
|
TARGET_MODE="vm"
|
|
current_mode_label="GPU -> LXC (native driver mode)"
|
|
menu_option_tag="vm"
|
|
menu_option_desc="$(translate 'Switch to GPU -> VM (VFIO passthrough mode)')"
|
|
fi
|
|
|
|
current_mode_highlight="\\Zb\\Z4${current_mode_label}\\Zn"
|
|
menu_title="\n$(translate 'Select target mode for selected GPU(s):')\n\n$(translate 'Current mode'): ${current_mode_highlight}\n\n$(translate 'Available action'):"
|
|
local selected
|
|
selected=$(dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'GPU Switch Mode')" \
|
|
--menu "$menu_title" 16 80 6 \
|
|
"$menu_option_tag" "$menu_option_desc" \
|
|
2>&1 >/dev/tty) || exit 0
|
|
|
|
[[ "$selected" != "$menu_option_tag" ]] && exit 0
|
|
return 0
|
|
}
|
|
|
|
# Return codes:
|
|
# 0 = compatible
|
|
# 1 = blocked and should exit
|
|
# 2 = blocked but user can reselect GPUs
|
|
validate_vm_mode_blocked_ids() {
|
|
[[ "$TARGET_MODE" != "vm" ]] && return 0
|
|
|
|
local -a blocked_lines=()
|
|
local idx viddid name pci
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
viddid="${ALL_GPU_VIDDID[$idx]}"
|
|
name="${ALL_GPU_NAMES[$idx]}"
|
|
pci="${ALL_GPU_PCIS[$idx]}"
|
|
|
|
case "$viddid" in
|
|
8086:5a84|8086:5a85)
|
|
blocked_lines+=(" • ${name} (${pci}) [ID: ${viddid}]")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[[ ${#blocked_lines[@]} -eq 0 ]] && return 0
|
|
|
|
local msg
|
|
msg="\n\Zb\Z1$(translate 'Blocked GPU ID for VM Mode')\Zn\n\n"
|
|
msg+="$(translate 'At least one selected GPU is blocked by policy for GPU -> VM mode due to passthrough instability risk.')\n\n"
|
|
msg+="$(translate 'Blocked device(s):')\n"
|
|
local line
|
|
for line in "${blocked_lines[@]}"; do
|
|
msg+="${line}\n"
|
|
done
|
|
msg+="\n$(translate 'Recommended: use GPU -> LXC mode for these devices.')\n"
|
|
|
|
if [[ "$GPU_COUNT" -gt 1 ]]; then
|
|
msg+="\n$(translate 'Please reselect GPU(s) and choose only compatible devices for VM mode.')"
|
|
dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'GPU Switch Mode Blocked')" \
|
|
--msgbox "$msg" 20 88
|
|
return 2
|
|
fi
|
|
|
|
dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'GPU Switch Mode Blocked')" \
|
|
--msgbox "$msg" 19 84
|
|
return 1
|
|
}
|
|
|
|
select_gpus() {
|
|
SELECTED_GPU_IDX=()
|
|
if [[ "$GPU_COUNT" -eq 1 ]]; then
|
|
SELECTED_GPU_IDX=(0)
|
|
return 0
|
|
fi
|
|
|
|
local -a menu_items=()
|
|
local i
|
|
for i in "${!ALL_GPU_PCIS[@]}"; do
|
|
menu_items+=("$i" "${ALL_GPU_NAMES[$i]} [${ALL_GPU_DRIVERS[$i]}] — ${ALL_GPU_PCIS[$i]}" "off")
|
|
done
|
|
|
|
local raw sel
|
|
raw=$(dialog --backtitle "ProxMenux" \
|
|
--title "$(translate 'Select GPU(s)')" \
|
|
--checklist "\n$(translate 'Select one or more GPU(s) to switch mode:')" 20 96 12 \
|
|
"${menu_items[@]}" \
|
|
2>&1 >/dev/tty) || exit 0
|
|
|
|
sel=$(echo "$raw" | tr -d '"')
|
|
if [[ -z "$sel" ]]; then
|
|
dialog --backtitle "ProxMenux" \
|
|
--title "$(translate 'Select GPU(s)')" \
|
|
--msgbox "\n$(translate 'No GPU selected.')" 7 52
|
|
exit 0
|
|
fi
|
|
read -ra SELECTED_GPU_IDX <<< "$sel"
|
|
}
|
|
|
|
# ==========================================================
|
|
# SR-IOV guard — abort mode switch when SR-IOV is active
|
|
# ==========================================================
|
|
# Intel i915-sriov-dkms and AMD MxGPU split a Physical Function (PF) into
|
|
# multiple Virtual Functions (VFs). Switching the PF's driver destroys
|
|
# every VF; switching a VF's driver affects only that VF. ProxMenux does
|
|
# not yet manage the SR-IOV lifecycle (create/destroy VFs, track per-VF
|
|
# ownership), so operating on a PF with active VFs — or on a VF itself —
|
|
# would leave the user's virtualization stack in an inconsistent state.
|
|
# We detect the situation early and hand the user back to the Proxmox
|
|
# web UI, which understands VFs as first-class PCI devices.
|
|
check_sriov_and_block_if_needed() {
|
|
declare -F _pci_sriov_role >/dev/null 2>&1 || return 0
|
|
|
|
local idx pci role first_word pf_bdf active_count
|
|
local -a vf_list=()
|
|
local -a pf_list=()
|
|
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
pci="${ALL_GPU_PCIS[$idx]}"
|
|
role=$(_pci_sriov_role "$pci")
|
|
first_word="${role%% *}"
|
|
case "$first_word" in
|
|
vf)
|
|
pf_bdf="${role#vf }"
|
|
vf_list+=("${pci}|${pf_bdf}")
|
|
;;
|
|
pf-active)
|
|
active_count="${role#pf-active }"
|
|
pf_list+=("${pci}|${active_count}")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[[ ${#vf_list[@]} -eq 0 && ${#pf_list[@]} -eq 0 ]] && return 0
|
|
|
|
local title msg entry bdf parent cnt
|
|
title="$(translate 'SR-IOV Configuration Detected')"
|
|
msg="\n"
|
|
|
|
if [[ ${#vf_list[@]} -gt 0 ]]; then
|
|
msg+="$(translate 'The following selected device(s) are SR-IOV Virtual Functions (VFs):')\n\n"
|
|
for entry in "${vf_list[@]}"; do
|
|
bdf="${entry%%|*}"
|
|
parent="${entry#*|}"
|
|
msg+=" • ${bdf} $(translate '(parent PF:') ${parent})\n"
|
|
done
|
|
msg+="\n"
|
|
fi
|
|
|
|
if [[ ${#pf_list[@]} -gt 0 ]]; then
|
|
msg+="$(translate 'The following selected device(s) are Physical Functions with active Virtual Functions:')\n\n"
|
|
for entry in "${pf_list[@]}"; do
|
|
bdf="${entry%%|*}"
|
|
cnt="${entry#*|}"
|
|
msg+=" • ${bdf} — ${cnt} $(translate 'active VF(s)')\n"
|
|
done
|
|
msg+="\n"
|
|
fi
|
|
|
|
msg+="$(translate 'To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.')"
|
|
|
|
dialog --backtitle "ProxMenux" \
|
|
--title "$title" \
|
|
--msgbox "$msg" 20 80
|
|
|
|
exit 0
|
|
}
|
|
|
|
collect_selected_iommu_ids() {
|
|
SELECTED_IOMMU_IDS=()
|
|
SELECTED_PCI_SLOTS=()
|
|
|
|
local idx pci viddid slot
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
pci="${ALL_GPU_PCIS[$idx]}"
|
|
viddid="${ALL_GPU_VIDDID[$idx]}"
|
|
slot="${pci#0000:}"
|
|
slot="${slot%.*}"
|
|
SELECTED_PCI_SLOTS+=("$slot")
|
|
|
|
local -a group_ids=()
|
|
mapfile -t group_ids < <(_get_iommu_group_ids "$pci")
|
|
if [[ ${#group_ids[@]} -gt 0 ]]; then
|
|
local gid
|
|
for gid in "${group_ids[@]}"; do
|
|
_contains_in_array "$gid" "${SELECTED_IOMMU_IDS[@]}" || SELECTED_IOMMU_IDS+=("$gid")
|
|
done
|
|
elif [[ -n "$viddid" ]]; then
|
|
_contains_in_array "$viddid" "${SELECTED_IOMMU_IDS[@]}" || SELECTED_IOMMU_IDS+=("$viddid")
|
|
fi
|
|
done
|
|
}
|
|
|
|
_lxc_conf_uses_type() {
|
|
local conf="$1"
|
|
local gpu_type="$2"
|
|
case "$gpu_type" in
|
|
nvidia) grep -qE "dev[0-9]+:.*(/dev/nvidia|/dev/nvidia-caps)" "$conf" 2>/dev/null ;;
|
|
amd) grep -qE "dev[0-9]+:.*(/dev/dri|/dev/kfd)|lxc\.mount\.entry:.*dev/dri" "$conf" 2>/dev/null ;;
|
|
intel) grep -qE "dev[0-9]+:.*(/dev/dri)|lxc\.mount\.entry:.*dev/dri" "$conf" 2>/dev/null ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
detect_affected_lxc_for_selected() {
|
|
LXC_AFFECTED_CTIDS=()
|
|
LXC_AFFECTED_NAMES=()
|
|
LXC_AFFECTED_RUNNING=()
|
|
LXC_AFFECTED_ONBOOT=()
|
|
|
|
local -a types=()
|
|
mapfile -t types < <(_selected_types_unique)
|
|
|
|
local conf
|
|
for conf in /etc/pve/lxc/*.conf; do
|
|
[[ -f "$conf" ]] || continue
|
|
local matched=false
|
|
local t
|
|
for t in "${types[@]}"; do
|
|
_lxc_conf_uses_type "$conf" "$t" && matched=true && break
|
|
done
|
|
$matched || continue
|
|
|
|
local ctid ct_name run onb
|
|
ctid=$(basename "$conf" .conf)
|
|
ct_name=$(pct config "$ctid" 2>/dev/null | awk '/^hostname:/ {print $2}')
|
|
[[ -z "$ct_name" ]] && ct_name="CT-${ctid}"
|
|
run=0; onb=0
|
|
_ct_is_running "$ctid" && run=1
|
|
_ct_onboot_enabled "$ctid" && onb=1
|
|
|
|
LXC_AFFECTED_CTIDS+=("$ctid")
|
|
LXC_AFFECTED_NAMES+=("$ct_name")
|
|
LXC_AFFECTED_RUNNING+=("$run")
|
|
LXC_AFFECTED_ONBOOT+=("$onb")
|
|
done
|
|
}
|
|
|
|
prompt_lxc_action_for_vm_mode() {
|
|
[[ ${#LXC_AFFECTED_CTIDS[@]} -eq 0 ]] && return 0
|
|
|
|
local running_count=0 onboot_count=0 i
|
|
for i in "${!LXC_AFFECTED_CTIDS[@]}"; do
|
|
[[ "${LXC_AFFECTED_RUNNING[$i]}" == "1" ]] && running_count=$((running_count + 1))
|
|
[[ "${LXC_AFFECTED_ONBOOT[$i]}" == "1" ]] && onboot_count=$((onboot_count + 1))
|
|
done
|
|
|
|
local msg choice
|
|
msg="\n$(translate 'The selected GPU(s) are used in these LXC container(s):')\n\n"
|
|
for i in "${!LXC_AFFECTED_CTIDS[@]}"; do
|
|
local st ob
|
|
st="$(translate 'stopped')"; ob="onboot=0"
|
|
[[ "${LXC_AFFECTED_RUNNING[$i]}" == "1" ]] && st="$(translate 'running')"
|
|
[[ "${LXC_AFFECTED_ONBOOT[$i]}" == "1" ]] && ob="onboot=1"
|
|
msg+=" • CT ${LXC_AFFECTED_CTIDS[$i]} (${LXC_AFFECTED_NAMES[$i]}) [${st}, ${ob}]\n"
|
|
done
|
|
msg+="\n$(translate 'Switching to GPU -> VM mode requires exclusive VFIO binding.')\n"
|
|
[[ "$running_count" -gt 0 ]] && msg+="\Z1$(translate 'Running containers detected'): ${running_count}\Zn\n"
|
|
[[ "$onboot_count" -gt 0 ]] && msg+="\Z1\Zb$(translate 'Start on boot enabled'): ${onboot_count}\Zn\n"
|
|
msg+="\n$(translate 'Choose conflict policy:')"
|
|
|
|
choice=$(dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'LXC Conflict Policy')" \
|
|
--default-item "2" \
|
|
--menu "$msg" 24 80 8 \
|
|
"1" "$(translate 'Keep GPU in LXC config (disable Start on boot)')" \
|
|
"2" "$(translate 'Remove GPU from LXC config (keep Start on boot)')" \
|
|
2>&1 >/dev/tty) || exit 0
|
|
|
|
case "$choice" in
|
|
1) LXC_ACTION="keep_gpu_disable_onboot" ;;
|
|
2) LXC_ACTION="remove_gpu_keep_onboot" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
}
|
|
|
|
_remove_type_from_lxc_conf() {
|
|
local conf="$1"
|
|
local gpu_type="$2"
|
|
case "$gpu_type" in
|
|
nvidia)
|
|
sed -i '/dev[0-9]\+:.*\/dev\/nvidia/d' "$conf"
|
|
;;
|
|
amd)
|
|
sed -i '/dev[0-9]\+:.*\/dev\/dri/d' "$conf"
|
|
sed -i '/dev[0-9]\+:.*\/dev\/kfd/d' "$conf"
|
|
sed -i '/lxc\.mount\.entry:.*dev\/dri/d' "$conf"
|
|
sed -i '/lxc\.cgroup2\.devices\.allow:.*226/d' "$conf"
|
|
;;
|
|
intel)
|
|
sed -i '/dev[0-9]\+:.*\/dev\/dri/d' "$conf"
|
|
sed -i '/lxc\.mount\.entry:.*dev\/dri/d' "$conf"
|
|
sed -i '/lxc\.cgroup2\.devices\.allow:.*226/d' "$conf"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
apply_lxc_action_for_vm_mode() {
|
|
[[ ${#LXC_AFFECTED_CTIDS[@]} -eq 0 ]] && return 0
|
|
local -a types=()
|
|
mapfile -t types < <(_selected_types_unique)
|
|
|
|
local i
|
|
for i in "${!LXC_AFFECTED_CTIDS[@]}"; do
|
|
local ctid conf
|
|
ctid="${LXC_AFFECTED_CTIDS[$i]}"
|
|
conf="/etc/pve/lxc/${ctid}.conf"
|
|
|
|
if [[ "${LXC_AFFECTED_RUNNING[$i]}" == "1" ]]; then
|
|
msg_info "$(translate 'Stopping LXC') ${ctid}..."
|
|
# _pmx_stop_lxc: unlock + graceful shutdown with forceStop+timeout,
|
|
# fallback to pct stop. Prevents the indefinite hang that raw
|
|
# `pct stop` triggers on locked / stuck containers.
|
|
if _pmx_stop_lxc "$ctid" "$LOG_FILE"; then
|
|
msg_ok "$(translate 'LXC stopped') ${ctid}" | tee -a "$screen_capture"
|
|
else
|
|
msg_warn "$(translate 'Could not stop LXC') ${ctid}" | tee -a "$screen_capture"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$LXC_ACTION" == "keep_gpu_disable_onboot" && "${LXC_AFFECTED_ONBOOT[$i]}" == "1" ]]; then
|
|
if pct set "$ctid" -onboot 0 >>"$LOG_FILE" 2>&1; then
|
|
msg_warn "$(translate 'Start on boot disabled for LXC') ${ctid}" | tee -a "$screen_capture"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$LXC_ACTION" == "remove_gpu_keep_onboot" && -f "$conf" ]]; then
|
|
local t
|
|
for t in "${types[@]}"; do
|
|
_remove_type_from_lxc_conf "$conf" "$t"
|
|
done
|
|
msg_ok "$(translate 'GPU access removed from LXC') ${ctid}" | tee -a "$screen_capture"
|
|
fi
|
|
done
|
|
}
|
|
|
|
detect_affected_vms_for_selected() {
|
|
VM_AFFECTED_IDS=()
|
|
VM_AFFECTED_NAMES=()
|
|
VM_AFFECTED_RUNNING=()
|
|
VM_AFFECTED_ONBOOT=()
|
|
|
|
local conf
|
|
for conf in /etc/pve/qemu-server/*.conf; do
|
|
[[ -f "$conf" ]] || continue
|
|
local matched=false slot
|
|
for slot in "${SELECTED_PCI_SLOTS[@]}"; do
|
|
if grep -qE "hostpci[0-9]+:.*(0000:)?${slot}(\\.[0-7])?([,[:space:]]|$)" "$conf"; then
|
|
matched=true
|
|
break
|
|
fi
|
|
done
|
|
$matched || continue
|
|
|
|
local vmid vm_name run onb
|
|
vmid=$(basename "$conf" .conf)
|
|
vm_name=$(grep "^name:" "$conf" 2>/dev/null | awk '{print $2}')
|
|
[[ -z "$vm_name" ]] && vm_name="VM-${vmid}"
|
|
run=0; onb=0
|
|
_vm_is_running "$vmid" && run=1
|
|
_vm_onboot_enabled "$vmid" && onb=1
|
|
|
|
VM_AFFECTED_IDS+=("$vmid")
|
|
VM_AFFECTED_NAMES+=("$vm_name")
|
|
VM_AFFECTED_RUNNING+=("$run")
|
|
VM_AFFECTED_ONBOOT+=("$onb")
|
|
done
|
|
}
|
|
|
|
prompt_vm_action_for_lxc_mode() {
|
|
[[ ${#VM_AFFECTED_IDS[@]} -eq 0 ]] && return 0
|
|
|
|
local running_count=0 onboot_count=0 i
|
|
for i in "${!VM_AFFECTED_IDS[@]}"; do
|
|
[[ "${VM_AFFECTED_RUNNING[$i]}" == "1" ]] && running_count=$((running_count + 1))
|
|
[[ "${VM_AFFECTED_ONBOOT[$i]}" == "1" ]] && onboot_count=$((onboot_count + 1))
|
|
done
|
|
|
|
local msg choice
|
|
msg="\n$(translate 'The selected GPU(s) are configured in these VM(s):')\n\n"
|
|
for i in "${!VM_AFFECTED_IDS[@]}"; do
|
|
local st ob
|
|
st="$(translate 'stopped')"; ob="onboot=0"
|
|
[[ "${VM_AFFECTED_RUNNING[$i]}" == "1" ]] && st="$(translate 'running')"
|
|
[[ "${VM_AFFECTED_ONBOOT[$i]}" == "1" ]] && ob="onboot=1"
|
|
msg+=" • VM ${VM_AFFECTED_IDS[$i]} (${VM_AFFECTED_NAMES[$i]}) [${st}, ${ob}]\n"
|
|
done
|
|
msg+="\n$(translate 'Switching to GPU -> LXC mode removes VFIO exclusivity.')\n"
|
|
[[ "$running_count" -gt 0 ]] && msg+="\Z1$(translate 'Running VM detected'): ${running_count}\Zn\n"
|
|
[[ "$onboot_count" -gt 0 ]] && msg+="\Z1\Zb$(translate 'Start on boot enabled'): ${onboot_count}\Zn\n"
|
|
msg+="\n$(translate 'Choose conflict policy:')"
|
|
|
|
choice=$(dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'VM Conflict Policy')" \
|
|
--default-item "1" \
|
|
--menu "$msg" 24 80 8 \
|
|
"1" "$(translate 'Keep GPU in VM config (disable Start on boot)')" \
|
|
"2" "$(translate 'Remove GPU from VM config (keep Start on boot)')" \
|
|
2>&1 >/dev/tty) || exit 0
|
|
|
|
case "$choice" in
|
|
1) VM_ACTION="keep_gpu_disable_onboot" ;;
|
|
2) VM_ACTION="remove_gpu_keep_onboot" ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
}
|
|
|
|
apply_vm_action_for_lxc_mode() {
|
|
[[ ${#VM_AFFECTED_IDS[@]} -eq 0 ]] && return 0
|
|
|
|
local i
|
|
for i in "${!VM_AFFECTED_IDS[@]}"; do
|
|
local vmid conf
|
|
vmid="${VM_AFFECTED_IDS[$i]}"
|
|
conf="/etc/pve/qemu-server/${vmid}.conf"
|
|
|
|
if [[ "${VM_AFFECTED_RUNNING[$i]}" == "1" ]]; then
|
|
msg_info "$(translate 'Stopping VM') ${vmid}..."
|
|
qm stop "$vmid" >>"$LOG_FILE" 2>&1 || true
|
|
msg_ok "$(translate 'VM stopped') ${vmid}" | tee -a "$screen_capture"
|
|
fi
|
|
|
|
if [[ "$VM_ACTION" == "keep_gpu_disable_onboot" && "${VM_AFFECTED_ONBOOT[$i]}" == "1" ]]; then
|
|
if qm set "$vmid" -onboot 0 >>"$LOG_FILE" 2>&1; then
|
|
msg_warn "$(translate 'Start on boot disabled for VM') ${vmid}" | tee -a "$screen_capture"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$VM_ACTION" == "remove_gpu_keep_onboot" && -f "$conf" ]]; then
|
|
# Primary cleanup: strip hostpci lines whose BDF matches any of
|
|
# the GPU's selected slots. Matches both the PF function (.0) and
|
|
# any sibling audio or HDMI codec that shares the slot (typical
|
|
# for discrete NVIDIA/AMD cards where .1 is the HDMI audio).
|
|
#
|
|
# Precise regex: the slot must be followed by ".<function>" and
|
|
# either a delimiter or end-of-line. A looser ".*${slot}" would
|
|
# match by pure substring and delete unrelated hostpci entries —
|
|
# e.g. slot "00:02" would match inside "0000:02:00.0" (a dGPU at
|
|
# 02:00) and wipe both the iGPU and the unrelated dGPU.
|
|
local slot
|
|
for slot in "${SELECTED_PCI_SLOTS[@]}"; do
|
|
sed -E -i "/^hostpci[0-9]+:[[:space:]]*(0000:)?${slot}\.[0-7]([,[:space:]]|$)/d" "$conf"
|
|
done
|
|
msg_ok "$(translate 'GPU removed from VM config') ${vmid}" | tee -a "$screen_capture"
|
|
|
|
# Cascade cleanup: Intel iGPU passthrough typically pairs the GPU
|
|
# at 00:02.0 with chipset audio at 00:1f.3, which lives at a
|
|
# different slot and therefore survives the sed above. If it
|
|
# stays in the VM config after the GPU is gone, the VM either
|
|
# fails to start (vfio-pci no longer claims 8086:51c8 after the
|
|
# switch-back) or it steals host audio unnecessarily. Enumerate
|
|
# orphan audio hostpci entries and ask the user what to do.
|
|
if declare -F _vm_list_orphan_audio_hostpci >/dev/null 2>&1; then
|
|
# Concatenate orphan-audio entries across ALL selected GPUs.
|
|
# The previous code only checked `SELECTED_PCI_SLOTS[0]`, so when
|
|
# the user switched 2 dGPUs at once and each had its own audio
|
|
# companion, the second GPU's audio was left dangling in the VM
|
|
# config. Audit Tier 6 — orphan audio solo del primer slot.
|
|
local _orphan_audio=""
|
|
local _slot
|
|
for _slot in "${SELECTED_PCI_SLOTS[@]}"; do
|
|
local _piece
|
|
_piece=$(_vm_list_orphan_audio_hostpci "$vmid" "$_slot")
|
|
if [[ -n "$_piece" ]]; then
|
|
[[ -n "$_orphan_audio" ]] && _orphan_audio+=$'\n'
|
|
_orphan_audio+="$_piece"
|
|
fi
|
|
done
|
|
if [[ -n "$_orphan_audio" ]]; then
|
|
local -a _orph_items=()
|
|
local _line _o_idx _o_bdf _o_name
|
|
while IFS= read -r _line; do
|
|
[[ -z "$_line" ]] && continue
|
|
_o_idx="${_line%%|*}"
|
|
_line="${_line#*|}"
|
|
_o_bdf="${_line%%|*}"
|
|
_o_name="${_line#*|}"
|
|
_orph_items+=("$_o_idx" "${_o_bdf} ${_o_name}" "on")
|
|
done <<< "$_orphan_audio"
|
|
|
|
local _prompt _selected
|
|
_prompt="\n$(translate 'The GPU is being detached from VM') \Zb${vmid}\Zn.\n\n"
|
|
_prompt+="$(translate 'The VM also has these audio devices assigned via PCI passthrough — typically added together with the GPU. Remove them too?')\n\n"
|
|
_prompt+="$(translate '(Checked entries will be removed. Uncheck to keep in VM.)')"
|
|
|
|
_selected=$(dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'Associated Audio Devices')" \
|
|
--checklist "$_prompt" 20 84 "$(( ${#_orph_items[@]} / 3 ))" \
|
|
"${_orph_items[@]}" \
|
|
2>&1 >/dev/tty) || _selected=""
|
|
_selected=$(echo "$_selected" | tr -d '"')
|
|
|
|
# Cross-reference table so we can recover each selected idx's
|
|
# original BDF (we need it for vendor:device lookup below).
|
|
declare -A _orphan_bdf_by_idx=()
|
|
local _o_line _o_i _o_b
|
|
while IFS= read -r _o_line; do
|
|
[[ -z "$_o_line" ]] && continue
|
|
_o_i="${_o_line%%|*}"
|
|
_o_line="${_o_line#*|}"
|
|
_o_b="${_o_line%%|*}"
|
|
_orphan_bdf_by_idx["$_o_i"]="$_o_b"
|
|
done <<< "$_orphan_audio"
|
|
|
|
local _sel _removed_audio="" _rem_bdf _vd_hex _dd_hex _vd_id
|
|
for _sel in $_selected; do
|
|
_rem_bdf="${_orphan_bdf_by_idx[$_sel]:-}"
|
|
if _vm_remove_hostpci_index "$vmid" "$_sel" "$LOG_FILE"; then
|
|
_removed_audio+=" hostpci${_sel}"
|
|
|
|
# Fix B: if the removed audio BDF is not referenced by any
|
|
# OTHER VM, its vendor:device can safely come out of
|
|
# /etc/modprobe.d/vfio.conf too. Without this step,
|
|
# SELECTED_IOMMU_IDS only held the GPU's own IOMMU group
|
|
# (e.g. 8086:46a3 for Intel iGPU) and the companion audio
|
|
# id (e.g. 8086:51c8 for chipset audio) survived in
|
|
# vfio.conf, so vfio-pci kept claiming it at next boot
|
|
# even though nothing used it.
|
|
[[ -z "$_rem_bdf" ]] && continue
|
|
if ! _pci_bdf_in_any_vm "$_rem_bdf" "${VM_AFFECTED_IDS[@]}"; then
|
|
_vd_hex=$(cat "/sys/bus/pci/devices/${_rem_bdf}/vendor" 2>/dev/null | sed 's/^0x//')
|
|
_dd_hex=$(cat "/sys/bus/pci/devices/${_rem_bdf}/device" 2>/dev/null | sed 's/^0x//')
|
|
if [[ -n "$_vd_hex" && -n "$_dd_hex" ]]; then
|
|
_vd_id="${_vd_hex}:${_dd_hex}"
|
|
if ! _contains_in_array "$_vd_id" "${SELECTED_IOMMU_IDS[@]}"; then
|
|
SELECTED_IOMMU_IDS+=("$_vd_id")
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
unset _orphan_bdf_by_idx
|
|
if [[ -n "$_removed_audio" ]]; then
|
|
msg_ok "$(translate 'Associated audio removed from VM'): ${_removed_audio# }" \
|
|
| tee -a "$screen_capture"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
_register_iommu_tool() {
|
|
local tools_json="${BASE_DIR:-/usr/local/share/proxmenux}/installed_tools.json"
|
|
command -v jq >/dev/null 2>&1 || return 0
|
|
[[ -f "$tools_json" ]] || echo "{}" > "$tools_json"
|
|
jq '.vfio_iommu=true' "$tools_json" > "$tools_json.tmp" \
|
|
&& mv "$tools_json.tmp" "$tools_json" || true
|
|
}
|
|
|
|
_enable_iommu_cmdline() {
|
|
local cpu_vendor
|
|
cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | awk '{print $3}')
|
|
|
|
local iommu_param
|
|
if [[ "$cpu_vendor" == "GenuineIntel" ]]; then
|
|
iommu_param="intel_iommu=on"
|
|
elif [[ "$cpu_vendor" == "AuthenticAMD" ]]; then
|
|
iommu_param="amd_iommu=on"
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
local cmdline_file="/etc/kernel/cmdline"
|
|
local grub_file="/etc/default/grub"
|
|
|
|
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file" 2>/dev/null; then
|
|
if ! grep -q "$iommu_param" "$cmdline_file"; then
|
|
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
|
sed -i "s|\\s*$| ${iommu_param} iommu=pt|" "$cmdline_file"
|
|
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
|
fi
|
|
elif [[ -f "$grub_file" ]]; then
|
|
if ! grep -q "$iommu_param" "$grub_file"; then
|
|
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
|
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|" "$grub_file"
|
|
update-grub >>"$LOG_FILE" 2>&1 || true
|
|
fi
|
|
else
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
switch_to_vm_mode() {
|
|
detect_affected_lxc_for_selected
|
|
prompt_lxc_action_for_vm_mode
|
|
|
|
_set_title
|
|
collect_selected_iommu_ids
|
|
apply_lxc_action_for_vm_mode
|
|
|
|
msg_info "$(translate 'Configuring host for GPU -> VM mode...')"
|
|
|
|
if declare -F _pci_is_iommu_active >/dev/null 2>&1 && _pci_is_iommu_active; then
|
|
_register_iommu_tool
|
|
msg_ok "$(translate 'IOMMU is already active on this system')" | tee -a "$screen_capture"
|
|
elif grep -qE 'intel_iommu=on|amd_iommu=on' /etc/kernel/cmdline 2>/dev/null || \
|
|
grep -qE 'intel_iommu=on|amd_iommu=on' /etc/default/grub 2>/dev/null; then
|
|
# Cross-check that IOMMU is *actually* active in the running kernel.
|
|
# The kernel parameter alone doesn't guarantee functional IOMMU —
|
|
# if the BIOS toggle is off, /sys/kernel/iommu_groups/ is empty even
|
|
# though intel_iommu=on is in cmdline. Without this gate we'd write
|
|
# vfio.conf and after reboot the GPU never gets claimed by VFIO.
|
|
# Audit Tier 6 — IOMMU check optimista.
|
|
if ! find /sys/kernel/iommu_groups -mindepth 1 -maxdepth 1 -name '[0-9]*' 2>/dev/null | grep -q .; then
|
|
msg_warn "$(translate 'intel_iommu/amd_iommu is set in cmdline but no IOMMU groups exist — IOMMU appears disabled in BIOS. Enable VT-d / AMD-Vi in firmware before continuing.')"
|
|
fi
|
|
_register_iommu_tool
|
|
HOST_CONFIG_CHANGED=true
|
|
msg_ok "$(translate 'IOMMU already configured in kernel parameters')" | tee -a "$screen_capture"
|
|
else
|
|
if _enable_iommu_cmdline; then
|
|
_register_iommu_tool
|
|
HOST_CONFIG_CHANGED=true
|
|
msg_ok "$(translate 'IOMMU kernel parameters configured')" | tee -a "$screen_capture"
|
|
else
|
|
msg_warn "$(translate 'Could not configure IOMMU kernel parameters automatically. Configure manually and reboot.')" | tee -a "$screen_capture"
|
|
fi
|
|
fi
|
|
|
|
_add_vfio_modules
|
|
msg_ok "$(translate 'VFIO modules configured in /etc/modules')" | tee -a "$screen_capture"
|
|
_configure_iommu_options
|
|
msg_ok "$(translate 'IOMMU interrupt remapping configured')" | tee -a "$screen_capture"
|
|
|
|
local -a current_ids=()
|
|
mapfile -t current_ids < <(_read_vfio_ids)
|
|
local id
|
|
for id in "${SELECTED_IOMMU_IDS[@]}"; do
|
|
_contains_in_array "$id" "${current_ids[@]}" || current_ids+=("$id")
|
|
done
|
|
_write_vfio_ids "${current_ids[@]}"
|
|
if [[ ${#SELECTED_IOMMU_IDS[@]} -gt 0 ]]; then
|
|
local ids_label
|
|
ids_label=$(IFS=','; echo "${SELECTED_IOMMU_IDS[*]}")
|
|
msg_ok "$(translate 'vfio-pci IDs configured') (${ids_label})" | tee -a "$screen_capture"
|
|
fi
|
|
|
|
local -a selected_types=()
|
|
mapfile -t selected_types < <(_selected_types_unique)
|
|
local t
|
|
for t in "${selected_types[@]}"; do
|
|
_add_gpu_blacklist "$t"
|
|
done
|
|
msg_ok "$(translate 'GPU host driver blacklisted in /etc/modprobe.d/blacklist.conf')" | tee -a "$screen_capture"
|
|
_contains_in_array "nvidia" "${selected_types[@]}" && _sanitize_nvidia_host_stack_for_vfio
|
|
_contains_in_array "amd" "${selected_types[@]}" && _add_amd_softdep
|
|
|
|
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
|
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
|
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
|
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
|
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
|
fi
|
|
}
|
|
|
|
_type_has_remaining_vfio_ids() {
|
|
local gpu_type="$1"
|
|
local -a remaining_ids=("$@")
|
|
remaining_ids=("${remaining_ids[@]:1}")
|
|
local idx viddid
|
|
for idx in "${!ALL_GPU_TYPES[@]}"; do
|
|
[[ "${ALL_GPU_TYPES[$idx]}" != "$gpu_type" ]] && continue
|
|
viddid="${ALL_GPU_VIDDID[$idx]}"
|
|
_contains_in_array "$viddid" "${remaining_ids[@]}" && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
switch_to_lxc_mode() {
|
|
collect_selected_iommu_ids
|
|
detect_affected_vms_for_selected
|
|
prompt_vm_action_for_lxc_mode
|
|
|
|
_set_title
|
|
apply_vm_action_for_lxc_mode
|
|
|
|
msg_info "$(translate 'Removing VFIO ownership for selected GPU(s)...')"
|
|
|
|
local -a current_ids=() remaining_ids=() removed_ids=()
|
|
mapfile -t current_ids < <(_read_vfio_ids)
|
|
local id remove
|
|
for id in "${current_ids[@]}"; do
|
|
remove=false
|
|
_contains_in_array "$id" "${SELECTED_IOMMU_IDS[@]}" && remove=true
|
|
if $remove; then
|
|
removed_ids+=("$id")
|
|
else
|
|
remaining_ids+=("$id")
|
|
fi
|
|
done
|
|
_write_vfio_ids "${remaining_ids[@]}"
|
|
if [[ ${#removed_ids[@]} -gt 0 ]]; then
|
|
local ids_label
|
|
ids_label=$(IFS=','; echo "${removed_ids[*]}")
|
|
msg_ok "$(translate 'VFIO device IDs removed from /etc/modprobe.d/vfio.conf') (${ids_label})" | tee -a "$screen_capture"
|
|
fi
|
|
|
|
local -a selected_types=()
|
|
mapfile -t selected_types < <(_selected_types_unique)
|
|
local t
|
|
for t in "${selected_types[@]}"; do
|
|
if ! _type_has_remaining_vfio_ids "$t" "${remaining_ids[@]}"; then
|
|
if _remove_gpu_blacklist "$t"; then
|
|
msg_ok "$(translate 'Driver blacklist removed for') ${t}" | tee -a "$screen_capture"
|
|
fi
|
|
if [[ "$t" == "nvidia" ]]; then
|
|
_restore_nvidia_host_stack_for_lxc
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if ! _type_has_remaining_vfio_ids "amd" "${remaining_ids[@]}"; then
|
|
_remove_amd_softdep || true
|
|
fi
|
|
|
|
if _remove_vfio_modules_if_unused; then
|
|
msg_ok "$(translate 'VFIO modules removed from /etc/modules')" | tee -a "$screen_capture"
|
|
fi
|
|
|
|
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
|
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
|
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
|
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
|
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
|
fi
|
|
}
|
|
|
|
# ==========================================================
|
|
# Send notification when GPU mode switch completes
|
|
# ==========================================================
|
|
_send_gpu_mode_notification() {
|
|
local new_mode="$1"
|
|
local old_mode="$2"
|
|
local notify_script="/usr/bin/notification_manager.py"
|
|
|
|
[[ ! -f "$notify_script" ]] && return 0
|
|
|
|
local hostname_short
|
|
hostname_short=$(hostname -s)
|
|
|
|
# Build GPU list for notification
|
|
local gpu_list=""
|
|
local idx
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
gpu_list+="${ALL_GPU_NAMES[$idx]} (${ALL_GPU_PCIS[$idx]}), "
|
|
done
|
|
gpu_list="${gpu_list%, }"
|
|
|
|
local mode_label details
|
|
if [[ "$new_mode" == "vm" ]]; then
|
|
mode_label="GPU -> VM (VFIO passthrough)"
|
|
details="GPU(s) ready for VM passthrough. A host reboot may be required."
|
|
else
|
|
mode_label="GPU -> LXC (native driver)"
|
|
details="GPU(s) available for LXC containers with native drivers."
|
|
fi
|
|
|
|
python3 "$notify_script" --action send-raw --severity INFO \
|
|
--title "${hostname_short}: GPU mode changed to ${mode_label}" \
|
|
--message "GPU passthrough mode switched.
|
|
GPU(s): ${gpu_list}
|
|
Previous: ${old_mode}
|
|
New: ${mode_label}
|
|
${details}" 2>/dev/null || true
|
|
}
|
|
|
|
confirm_plan() {
|
|
local msg mode_line
|
|
if [[ "$TARGET_MODE" == "vm" ]]; then
|
|
mode_line="$(translate 'Target mode'): GPU -> VM (VFIO)"
|
|
else
|
|
mode_line="$(translate 'Target mode'): GPU -> LXC (native driver)"
|
|
fi
|
|
|
|
msg="\n${mode_line}\n\n$(translate 'Selected GPU(s)'):\n"
|
|
local idx
|
|
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
|
msg+=" • ${ALL_GPU_NAMES[$idx]} (${ALL_GPU_PCIS[$idx]}) [${ALL_GPU_DRIVERS[$idx]}]\n"
|
|
done
|
|
msg+="\n$(translate 'Do you want to proceed?')"
|
|
|
|
dialog --backtitle "ProxMenux" --colors \
|
|
--title "$(translate 'Confirm GPU Switch Mode')" \
|
|
--yesno "$msg" 18 88
|
|
[[ $? -ne 0 ]] && exit 0
|
|
}
|
|
|
|
final_summary() {
|
|
_set_title
|
|
cat "$screen_capture"
|
|
echo
|
|
echo -e "${TAB}${BL}Log: ${LOG_FILE}${CL}"
|
|
|
|
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
|
echo -e "${TAB}${DGN}- $(translate 'Host GPU binding changed — reboot required.')${CL}"
|
|
whiptail --title "$(translate 'Reboot Required')" \
|
|
--yesno "$(translate 'A reboot is required to apply the new GPU mode. Do you want to restart now?')" 10 74
|
|
if [[ $? -eq 0 ]]; then
|
|
msg_warn "$(translate 'Rebooting the system...')"
|
|
reboot
|
|
else
|
|
msg_info2 "$(translate 'Please reboot manually to complete the switch.')"
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
fi
|
|
else
|
|
echo -e "${TAB}${DGN}- $(translate 'No host VFIO/native binding changes were required.')${CL}"
|
|
msg_success "$(translate 'Press Enter to continue...')"
|
|
read -r
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
: >"$LOG_FILE"
|
|
: >"$screen_capture"
|
|
|
|
detect_host_gpus
|
|
while true; do
|
|
select_gpus
|
|
check_sriov_and_block_if_needed
|
|
select_target_mode
|
|
[[ $? -eq 2 ]] && continue
|
|
validate_vm_mode_blocked_ids
|
|
case $? in
|
|
2) continue ;;
|
|
1) exit 0 ;;
|
|
esac
|
|
break
|
|
done
|
|
confirm_plan
|
|
|
|
clear
|
|
_set_title
|
|
echo
|
|
|
|
# Determine old mode before switch for notification
|
|
local old_mode_label
|
|
if [[ "$CURRENT_MODE" == "vm" ]]; then
|
|
old_mode_label="GPU -> VM (VFIO)"
|
|
else
|
|
old_mode_label="GPU -> LXC (native)"
|
|
fi
|
|
|
|
if [[ "$TARGET_MODE" == "vm" ]]; then
|
|
switch_to_vm_mode
|
|
msg_success "$(translate 'GPU switch complete: VM mode prepared.')"
|
|
_send_gpu_mode_notification "vm" "$old_mode_label"
|
|
else
|
|
switch_to_lxc_mode
|
|
msg_success "$(translate 'GPU switch complete: LXC mode prepared.')"
|
|
_send_gpu_mode_notification "lxc" "$old_mode_label"
|
|
fi
|
|
|
|
final_summary
|
|
rm -f "$screen_capture"
|
|
}
|
|
|
|
main "$@"
|