mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 10:47:36 +00:00
refine post-install and hardware GPU docs, Monitor UX and CLI styling
- rewrite the 15 post-install pages and the 3 hardware GPU pages so they reflect the current scripts (reversibility, tracked-tool counts, kernel parameters, per-tool commands, Alpine LXC propagation flow) - migrate the legacy step-badge helper on post-install/optional and create-vm/synology to the canonical pill component, with the stepLabel key added in each locale - fix rich-text i18n calls missing helpers across network, automated, optional, security, customization and the post-install landing pages, and escape the `<iface>` placeholder in automated so intl no longer parses it as a tag - remove the mouse-follow blue overlay from the docs landing layout - reposition the App-tab Edit button and stack the Search and Register controls vertically on mobile - move the Bulk update Configure/Edit control into the section header so it behaves the same on desktop and mobile - show a spinner during the final autoremove/autoclean pass of update-pve-safe so the cleanup step reads as active instead of silent - restyle the shell spinner and msg_info in a distinctive purple and drop the unused msg_lang duplicate - add a web-docs i18n build script and its CI workflow, plus tests for the pushover notification channel
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ -n "${__PROXMENUX_PCI_PASSTHROUGH_HELPERS__}" ]]; then
|
||||
if [[ -n "${__PROXMENUX_PCI_PASSTHROUGH_HELPERS__:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
__PROXMENUX_PCI_PASSTHROUGH_HELPERS__=1
|
||||
@@ -378,14 +378,23 @@ function _pci_sriov_role() {
|
||||
# PCI subsystem ADD event, which is exactly when we need them.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
PROXMENUX_VFIO_BIND_STATE="/etc/proxmenux/vfio-bind.bdfs"
|
||||
PROXMENUX_VFIO_BIND_UDEV_RULE="/etc/udev/rules.d/10-proxmenux-vfio-bind.rules"
|
||||
PROXMENUX_SYSFS_ROOT="${PROXMENUX_SYSFS_ROOT:-/sys}"
|
||||
PROXMENUX_ETC_ROOT="${PROXMENUX_ETC_ROOT:-/etc}"
|
||||
PROXMENUX_STATE_ROOT="${PROXMENUX_STATE_ROOT:-${BASE_DIR:-/usr/local/share/proxmenux}}"
|
||||
PROXMENUX_VFIO_BIND_STATE="${PROXMENUX_VFIO_BIND_STATE:-${PROXMENUX_ETC_ROOT}/proxmenux/vfio-bind.bdfs}"
|
||||
PROXMENUX_VFIO_BIND_UDEV_RULE="${PROXMENUX_VFIO_BIND_UDEV_RULE:-${PROXMENUX_ETC_ROOT}/udev/rules.d/10-proxmenux-vfio-bind.rules}"
|
||||
PROXMENUX_VFIO_CONF="${PROXMENUX_VFIO_CONF:-${PROXMENUX_ETC_ROOT}/modprobe.d/vfio.conf}"
|
||||
# Auto-managed blacklist applied only when *every* NVIDIA GPU on the host
|
||||
# is in passthrough. Removed when any NVIDIA GPU goes back to the host.
|
||||
PROXMENUX_NVIDIA_VFIO_BLACKLIST="/etc/modprobe.d/proxmenux-nvidia-vfio-blacklist.conf"
|
||||
PROXMENUX_NVIDIA_VFIO_BLACKLIST="${PROXMENUX_NVIDIA_VFIO_BLACKLIST:-${PROXMENUX_ETC_ROOT}/modprobe.d/proxmenux-nvidia-vfio-blacklist.conf}"
|
||||
PROXMENUX_NVIDIA_SERVICE_STATE="${PROXMENUX_NVIDIA_SERVICE_STATE:-${PROXMENUX_STATE_ROOT}/nvidia-host-services.state}"
|
||||
# A short-lived implementation stored this state under /var/lib. Keep a
|
||||
# one-way migration so upgraded hosts restore the exact service state that was
|
||||
# captured there, then remove the provisional file.
|
||||
PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE="${PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE:-/var/lib/proxmenux/nvidia-host-services.state}"
|
||||
# Legacy artifact paths from a previous attempt — kept here so we can
|
||||
# remove them when migrating a host that ran the older init-top hook.
|
||||
PROXMENUX_VFIO_BIND_LEGACY_HOOK="/etc/initramfs-tools/scripts/init-top/proxmenux-vfio-bind"
|
||||
PROXMENUX_VFIO_BIND_LEGACY_HOOK="${PROXMENUX_VFIO_BIND_LEGACY_HOOK:-${PROXMENUX_ETC_ROOT}/initramfs-tools/scripts/init-top/proxmenux-vfio-bind}"
|
||||
|
||||
_proxmenux_vfio_bind_write_udev_rule() {
|
||||
# Always nuke the obsolete init-top hook from earlier attempts (if it
|
||||
@@ -428,6 +437,38 @@ _proxmenux_vfio_bind_cleanup_legacy() {
|
||||
fi
|
||||
}
|
||||
|
||||
_proxmenux_mark_host_config_changed() {
|
||||
[[ -n "${HOST_CONFIG_CHANGED+x}" ]] && HOST_CONFIG_CHANGED=true
|
||||
}
|
||||
|
||||
_proxmenux_vfio_bind_has_bdf() {
|
||||
local bdf="$1"
|
||||
[[ -n "$bdf" && -f "$PROXMENUX_VFIO_BIND_STATE" ]] || return 1
|
||||
[[ "$bdf" == 0000:* ]] || bdf="0000:${bdf}"
|
||||
grep -qxF "$bdf" "$PROXMENUX_VFIO_BIND_STATE" 2>/dev/null
|
||||
}
|
||||
|
||||
_proxmenux_vfio_bind_has_entries() {
|
||||
[[ -s "$PROXMENUX_VFIO_BIND_STATE" ]] \
|
||||
&& grep -qEv '^[[:space:]]*(#|$)' "$PROXMENUX_VFIO_BIND_STATE" 2>/dev/null
|
||||
}
|
||||
|
||||
_proxmenux_vfio_bind_state_has_vendor() {
|
||||
local target_vendor="${1,,}"
|
||||
[[ -n "$target_vendor" && -f "$PROXMENUX_VFIO_BIND_STATE" ]] || return 1
|
||||
|
||||
local bdf full vendor_hex
|
||||
while IFS= read -r bdf; do
|
||||
[[ -z "$bdf" || "$bdf" == \#* ]] && continue
|
||||
full="$bdf"
|
||||
[[ "$full" == 0000:* ]] || full="0000:${full}"
|
||||
vendor_hex=$(cat "${PROXMENUX_SYSFS_ROOT}/bus/pci/devices/${full}/vendor" 2>/dev/null \
|
||||
| sed 's/^0x//' | tr '[:upper:]' '[:lower:]')
|
||||
[[ "$vendor_hex" == "$target_vendor" ]] && return 0
|
||||
done < "$PROXMENUX_VFIO_BIND_STATE"
|
||||
return 1
|
||||
}
|
||||
|
||||
_proxmenux_vfio_bind_add_bdfs() {
|
||||
# Args: any number of BDFs ("01:00.0" or "0000:01:00.0")
|
||||
mkdir -p "$(dirname "$PROXMENUX_VFIO_BIND_STATE")"
|
||||
@@ -450,8 +491,8 @@ _proxmenux_vfio_bind_add_bdfs() {
|
||||
done
|
||||
if $changed; then
|
||||
_proxmenux_vfio_bind_write_udev_rule
|
||||
_proxmenux_nvidia_vfio_blacklist_sync || true
|
||||
[[ -n "${HOST_CONFIG_CHANGED+x}" ]] && HOST_CONFIG_CHANGED=true
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
_proxmenux_mark_host_config_changed
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -475,10 +516,10 @@ _proxmenux_vfio_bind_remove_bdfs() {
|
||||
if ! cmp -s "$tmp" "$PROXMENUX_VFIO_BIND_STATE"; then
|
||||
mv "$tmp" "$PROXMENUX_VFIO_BIND_STATE"
|
||||
_proxmenux_vfio_bind_write_udev_rule
|
||||
_proxmenux_nvidia_vfio_blacklist_sync || true
|
||||
[[ -n "${HOST_CONFIG_CHANGED+x}" ]] && HOST_CONFIG_CHANGED=true
|
||||
# If empty, remove state file too (keeps host clean)
|
||||
[[ ! -s "$PROXMENUX_VFIO_BIND_STATE" ]] && rm -f "$PROXMENUX_VFIO_BIND_STATE"
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
_proxmenux_mark_host_config_changed
|
||||
else
|
||||
rm -f "$tmp"
|
||||
fi
|
||||
@@ -490,9 +531,10 @@ _proxmenux_vfio_bind_remove_bdfs() {
|
||||
# or whether the host still needs the nvidia driver loaded for at
|
||||
# least one GPU (multi-GPU mixed case).
|
||||
_proxmenux_all_nvidia_in_vfio() {
|
||||
local -a host_nvidia=() vfio_nvidia=()
|
||||
local d cls vendor
|
||||
for d in /sys/bus/pci/devices/*; do
|
||||
local -a host_nvidia=()
|
||||
local d cls vendor bdf
|
||||
for d in "${PROXMENUX_SYSFS_ROOT}/bus/pci/devices/"*; do
|
||||
[[ -d "$d" ]] || continue
|
||||
vendor=$(cat "$d/vendor" 2>/dev/null)
|
||||
[[ "$vendor" != "0x10de" ]] && continue
|
||||
cls=$(cat "$d/class" 2>/dev/null)
|
||||
@@ -502,23 +544,10 @@ _proxmenux_all_nvidia_in_vfio() {
|
||||
done
|
||||
(( ${#host_nvidia[@]} == 0 )) && return 1
|
||||
|
||||
if [[ -f "$PROXMENUX_VFIO_BIND_STATE" ]]; then
|
||||
local bdf full
|
||||
while IFS= read -r bdf; do
|
||||
[[ -z "$bdf" ]] && continue
|
||||
case "$bdf" in \#*) continue ;; esac
|
||||
full="$bdf"
|
||||
[[ "$full" != 0000:* ]] && full="0000:${full}"
|
||||
vendor=$(cat "/sys/bus/pci/devices/${full}/vendor" 2>/dev/null)
|
||||
[[ "$vendor" != "0x10de" ]] && continue
|
||||
cls=$(cat "/sys/bus/pci/devices/${full}/class" 2>/dev/null)
|
||||
case "$cls" in
|
||||
0x0300*|0x0302*) vfio_nvidia+=("$full") ;;
|
||||
esac
|
||||
done < "$PROXMENUX_VFIO_BIND_STATE"
|
||||
fi
|
||||
|
||||
(( ${#vfio_nvidia[@]} >= ${#host_nvidia[@]} ))
|
||||
for bdf in "${host_nvidia[@]}"; do
|
||||
_proxmenux_vfio_bind_has_bdf "$bdf" || return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# Apply or remove the auto-managed nvidia blacklist + the nvidia-smi
|
||||
@@ -526,11 +555,12 @@ _proxmenux_all_nvidia_in_vfio() {
|
||||
# passthrough. Returns 0 if anything changed (caller may want to
|
||||
# rebuild initramfs).
|
||||
_proxmenux_nvidia_vfio_blacklist_sync() {
|
||||
local nvidia_udev_rule="/etc/udev/rules.d/70-nvidia.rules"
|
||||
local nvidia_udev_rule="${PROXMENUX_ETC_ROOT}/udev/rules.d/70-nvidia.rules"
|
||||
local changed=1
|
||||
|
||||
if _proxmenux_all_nvidia_in_vfio; then
|
||||
if [[ ! -f "$PROXMENUX_NVIDIA_VFIO_BLACKLIST" ]]; then
|
||||
mkdir -p "$(dirname "$PROXMENUX_NVIDIA_VFIO_BLACKLIST")"
|
||||
cat > "$PROXMENUX_NVIDIA_VFIO_BLACKLIST" <<'EOF'
|
||||
# ProxMenux: every NVIDIA GPU on this host is in VFIO passthrough.
|
||||
# Block the nvidia module so it doesn't loop trying to claim devices
|
||||
@@ -567,55 +597,179 @@ EOF
|
||||
return $changed
|
||||
}
|
||||
|
||||
# Returns the BDF of a PCI bridge sharing the IOMMU group of $1, if any.
|
||||
# The kernel refuses to bind vfio-pci to root ports, so when a GPU shares
|
||||
# its IOMMU group with the upstream root port the VFIO setup silently
|
||||
# does nothing — the GPU keeps its native driver and the host can also
|
||||
# end up with a stuck boot if other devices behind the bridge were
|
||||
# expected to come up under the original driver. Detecting this lets
|
||||
# callers warn the operator and bail out before writing host config.
|
||||
_proxmenux_vfio_bind_group_bridge() {
|
||||
local target="$1"
|
||||
[[ "$target" != 0000:* ]] && target="0000:${target}"
|
||||
local group_link
|
||||
group_link=$(readlink "/sys/bus/pci/devices/${target}/iommu_group" 2>/dev/null) || return 1
|
||||
local group_num
|
||||
group_num=$(basename "$group_link")
|
||||
local member bdf cls
|
||||
for member in "/sys/kernel/iommu_groups/${group_num}/devices/"*; do
|
||||
bdf=$(basename "$member")
|
||||
[[ "$bdf" == "$target" ]] && continue
|
||||
cls=$(cat "$member/class" 2>/dev/null)
|
||||
# PCI bridge class is 0x0604xx (Normal bridge 0x060400, Subtractive 0x060401).
|
||||
if [[ "$cls" == 0x0604* ]]; then
|
||||
echo "$bdf"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
_proxmenux_nvidia_vfio_softdeps_sync() {
|
||||
local changed=1
|
||||
mkdir -p "$(dirname "$PROXMENUX_VFIO_CONF")"
|
||||
touch "$PROXMENUX_VFIO_CONF"
|
||||
|
||||
local -a softdeps=(
|
||||
"softdep nvidia pre: vfio-pci"
|
||||
"softdep nvidia_drm pre: vfio-pci"
|
||||
"softdep nvidia_modeset pre: vfio-pci"
|
||||
"softdep nvidia_uvm pre: vfio-pci"
|
||||
)
|
||||
local line
|
||||
if _proxmenux_vfio_bind_state_has_vendor "10de"; then
|
||||
for line in "${softdeps[@]}"; do
|
||||
if ! grep -qFx "$line" "$PROXMENUX_VFIO_CONF" 2>/dev/null; then
|
||||
echo "$line" >> "$PROXMENUX_VFIO_CONF"
|
||||
changed=0
|
||||
fi
|
||||
done
|
||||
else
|
||||
for line in "${softdeps[@]}"; do
|
||||
if grep -qFx "$line" "$PROXMENUX_VFIO_CONF" 2>/dev/null; then
|
||||
sed -i "\|^${line}$|d" "$PROXMENUX_VFIO_CONF"
|
||||
changed=0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
return $changed
|
||||
}
|
||||
|
||||
_proxmenux_vfio_bind_purge_vendor() {
|
||||
# Removes every BDF from the binder state whose PCI vendor matches $1
|
||||
# (hex, e.g. "10de" for NVIDIA, "1002" for AMD, "8086" for Intel).
|
||||
# Used by switch_gpu_mode to drop all NVIDIA bindings when reverting
|
||||
# NVIDIA passthrough — the nvidia module reclaims the GPUs after the
|
||||
# next reboot.
|
||||
local target_vendor="${1,,}"
|
||||
[[ -z "$target_vendor" || ! -f "$PROXMENUX_VFIO_BIND_STATE" ]] && return 0
|
||||
# NVIDIA services are host-wide. They must only be stopped when every
|
||||
# NVIDIA display controller is assigned to VFIO; on a mixed host they stay
|
||||
# available for the GPU(s) that remain native. The first transition stores
|
||||
# the previous service state and later transitions do not overwrite it.
|
||||
_proxmenux_nvidia_host_services_sync() {
|
||||
command -v systemctl >/dev/null 2>&1 || return 1
|
||||
|
||||
local -a to_remove=()
|
||||
local bdf vendor_hex
|
||||
while IFS= read -r bdf; do
|
||||
[[ -z "$bdf" ]] && continue
|
||||
case "$bdf" in \#*) continue ;; esac
|
||||
local full="$bdf"
|
||||
[[ "$full" != 0000:* ]] && full="0000:${full}"
|
||||
vendor_hex=$(cat "/sys/bus/pci/devices/${full}/vendor" 2>/dev/null | sed 's/^0x//' | tr '[:upper:]' '[:lower:]')
|
||||
[[ "$vendor_hex" == "$target_vendor" ]] && to_remove+=("$full")
|
||||
done < "$PROXMENUX_VFIO_BIND_STATE"
|
||||
local changed=1 svc was_enabled was_active enabled active
|
||||
local -a services=(
|
||||
"nvidia-persistenced.service"
|
||||
"nvidia-powerd.service"
|
||||
"nvidia-fabricmanager.service"
|
||||
)
|
||||
|
||||
[[ ${#to_remove[@]} -gt 0 ]] && _proxmenux_vfio_bind_remove_bdfs "${to_remove[@]}"
|
||||
if [[ "$PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE" != "$PROXMENUX_NVIDIA_SERVICE_STATE" \
|
||||
&& -f "$PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE" ]]; then
|
||||
mkdir -p "$(dirname "$PROXMENUX_NVIDIA_SERVICE_STATE")"
|
||||
if [[ ! -f "$PROXMENUX_NVIDIA_SERVICE_STATE" ]]; then
|
||||
mv "$PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE" \
|
||||
"$PROXMENUX_NVIDIA_SERVICE_STATE" 2>/dev/null || true
|
||||
else
|
||||
rm -f "$PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE"
|
||||
fi
|
||||
rmdir "$(dirname "$PROXMENUX_NVIDIA_SERVICE_LEGACY_STATE")" \
|
||||
>/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if _proxmenux_all_nvidia_in_vfio; then
|
||||
mkdir -p "$(dirname "$PROXMENUX_NVIDIA_SERVICE_STATE")"
|
||||
if [[ ! -f "$PROXMENUX_NVIDIA_SERVICE_STATE" ]]; then
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
for svc in "${services[@]}"; do
|
||||
was_enabled=0
|
||||
was_active=0
|
||||
systemctl is-enabled --quiet "$svc" 2>/dev/null && was_enabled=1
|
||||
systemctl is-active --quiet "$svc" 2>/dev/null && was_active=1
|
||||
if (( was_enabled == 1 || was_active == 1 )); then
|
||||
echo "${svc} enabled=${was_enabled} active=${was_active}" >> "$tmp"
|
||||
fi
|
||||
done
|
||||
if [[ -s "$tmp" ]]; then
|
||||
mv "$tmp" "$PROXMENUX_NVIDIA_SERVICE_STATE"
|
||||
else
|
||||
rm -f "$tmp"
|
||||
fi
|
||||
fi
|
||||
|
||||
for svc in "${services[@]}"; do
|
||||
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
||||
systemctl stop "$svc" >/dev/null 2>&1 || true
|
||||
changed=0
|
||||
fi
|
||||
if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
|
||||
systemctl disable "$svc" >/dev/null 2>&1 || true
|
||||
changed=0
|
||||
fi
|
||||
done
|
||||
elif [[ -f "$PROXMENUX_NVIDIA_SERVICE_STATE" ]]; then
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
svc=${line%% *}
|
||||
enabled=$(printf '%s\n' "$line" | sed -nE 's/.*enabled=([01]).*/\1/p')
|
||||
active=$(printf '%s\n' "$line" | sed -nE 's/.*active=([01]).*/\1/p')
|
||||
[[ "$enabled" == "1" ]] && systemctl enable "$svc" >/dev/null 2>&1 || true
|
||||
[[ "$active" == "1" ]] && systemctl start "$svc" >/dev/null 2>&1 || true
|
||||
done < "$PROXMENUX_NVIDIA_SERVICE_STATE"
|
||||
rm -f "$PROXMENUX_NVIDIA_SERVICE_STATE"
|
||||
changed=0
|
||||
fi
|
||||
return $changed
|
||||
}
|
||||
|
||||
_proxmenux_nvidia_component_status_sync() {
|
||||
declare -F update_component_status >/dev/null 2>&1 || return 1
|
||||
|
||||
local status_file="${BASE_DIR:-/usr/local/share/proxmenux}/components_status.json"
|
||||
local version="" status="installed" patched=false metadata='{"patched":false}'
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
version=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null \
|
||||
| head -1 | tr -d '[:space:]')
|
||||
fi
|
||||
if [[ -z "$version" && -f "$status_file" ]] && command -v jq >/dev/null 2>&1; then
|
||||
version=$(jq -r '.nvidia_driver.version // ""' "$status_file" 2>/dev/null)
|
||||
fi
|
||||
if [[ -f "$status_file" ]] && command -v jq >/dev/null 2>&1; then
|
||||
patched=$(jq -r '.nvidia_driver.patched // false' "$status_file" 2>/dev/null)
|
||||
[[ "$patched" == "true" ]] && metadata='{"patched":true}'
|
||||
fi
|
||||
_proxmenux_all_nvidia_in_vfio && status="vfio_passthrough"
|
||||
update_component_status "nvidia_driver" "$status" "$version" "gpu" \
|
||||
"$metadata" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
_proxmenux_nvidia_vfio_policy_sync() {
|
||||
local changed=1
|
||||
_proxmenux_nvidia_vfio_blacklist_sync && changed=0
|
||||
_proxmenux_nvidia_vfio_softdeps_sync && changed=0
|
||||
_proxmenux_nvidia_host_services_sync && changed=0
|
||||
_proxmenux_nvidia_component_status_sync || true
|
||||
(( changed == 0 )) && _proxmenux_mark_host_config_changed
|
||||
return $changed
|
||||
}
|
||||
|
||||
# Convert legacy vendor:device NVIDIA entries into exact BDF entries before
|
||||
# removing the old IDs. This preserves the previous host state even when two
|
||||
# GPUs share the same model/PCI ID, while allowing subsequent selective
|
||||
# restore of one GPU without releasing the others.
|
||||
_proxmenux_vfio_bind_migrate_legacy_nvidia_ids() {
|
||||
[[ -f "$PROXMENUX_VFIO_CONF" ]] || return 1
|
||||
local ids_part
|
||||
ids_part=$(grep '^options vfio-pci ids=' "$PROXMENUX_VFIO_CONF" 2>/dev/null \
|
||||
| head -1 | grep -oE 'ids=[^[:space:]]+' | sed 's/^ids=//' | tr '[:upper:]' '[:lower:]')
|
||||
[[ -n "$ids_part" ]] || return 1
|
||||
|
||||
local -a ids=() matched_ids=() bdfs=()
|
||||
IFS=',' read -ra ids <<< "$ids_part"
|
||||
local path vendor device class token existing
|
||||
for path in "${PROXMENUX_SYSFS_ROOT}/bus/pci/devices/"*; do
|
||||
[[ -d "$path" ]] || continue
|
||||
vendor=$(cat "$path/vendor" 2>/dev/null | sed 's/^0x//' | tr '[:upper:]' '[:lower:]')
|
||||
[[ "$vendor" == "10de" ]] || continue
|
||||
class=$(cat "$path/class" 2>/dev/null)
|
||||
[[ "$class" == 0x0600* || "$class" == 0x0604* ]] && continue
|
||||
device=$(cat "$path/device" 2>/dev/null | sed 's/^0x//' | tr '[:upper:]' '[:lower:]')
|
||||
token="${vendor}:${device}"
|
||||
for existing in "${ids[@]}"; do
|
||||
[[ "$existing" == "$token" ]] || continue
|
||||
bdfs+=("$(basename "$path")")
|
||||
if [[ " ${matched_ids[*]} " != *" ${token} "* ]]; then
|
||||
matched_ids+=("$token")
|
||||
fi
|
||||
break
|
||||
done
|
||||
done
|
||||
(( ${#matched_ids[@]} > 0 )) || return 1
|
||||
|
||||
(( ${#bdfs[@]} > 0 )) && _proxmenux_vfio_bind_add_bdfs "${bdfs[@]}"
|
||||
if _clean_vfio_conf_ids "${matched_ids[@]}"; then
|
||||
_proxmenux_mark_host_config_changed
|
||||
fi
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
return 0
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
@@ -626,12 +780,12 @@ _proxmenux_vfio_bind_purge_vendor() {
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
_proxmenux_nvidia_migrate_legacy_blacklist() {
|
||||
local changed=false
|
||||
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
||||
local nvidia_blacklist="/etc/modprobe.d/nvidia-blacklist.conf"
|
||||
local udev_disabled="/etc/udev/rules.d/70-nvidia.rules.proxmenux-disabled"
|
||||
local udev_rules="/etc/udev/rules.d/70-nvidia.rules"
|
||||
local modules_load_disabled="/etc/modules-load.d/nvidia-vfio.conf.proxmenux-disabled-vfio"
|
||||
local modules_load_active="/etc/modules-load.d/nvidia-vfio.conf"
|
||||
local blacklist_file="${PROXMENUX_ETC_ROOT}/modprobe.d/blacklist.conf"
|
||||
local nvidia_blacklist="${PROXMENUX_ETC_ROOT}/modprobe.d/nvidia-blacklist.conf"
|
||||
local udev_disabled="${PROXMENUX_ETC_ROOT}/udev/rules.d/70-nvidia.rules.proxmenux-disabled"
|
||||
local udev_rules="${PROXMENUX_ETC_ROOT}/udev/rules.d/70-nvidia.rules"
|
||||
local modules_load_disabled="${PROXMENUX_ETC_ROOT}/modules-load.d/nvidia-vfio.conf.proxmenux-disabled-vfio"
|
||||
local modules_load_active="${PROXMENUX_ETC_ROOT}/modules-load.d/nvidia-vfio.conf"
|
||||
|
||||
if [[ -f "$blacklist_file" ]] && grep -qE '^blacklist (nvidia|nvidia_drm|nvidia_modeset|nvidia_uvm|nvidiafb)$' "$blacklist_file"; then
|
||||
sed -i \
|
||||
@@ -660,8 +814,14 @@ _proxmenux_nvidia_migrate_legacy_blacklist() {
|
||||
changed=true
|
||||
fi
|
||||
|
||||
if _proxmenux_vfio_bind_migrate_legacy_nvidia_ids; then
|
||||
changed=true
|
||||
fi
|
||||
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
|
||||
if $changed; then
|
||||
[[ -n "${HOST_CONFIG_CHANGED+x}" ]] && HOST_CONFIG_CHANGED=true
|
||||
_proxmenux_mark_host_config_changed
|
||||
if declare -F msg_ok >/dev/null 2>&1; then
|
||||
msg_ok "$(declare -F translate >/dev/null 2>&1 && translate 'Migrated legacy ProxMenux NVIDIA blacklist state — module will reload after reboot' || echo 'Migrated legacy ProxMenux NVIDIA blacklist state — module will reload after reboot')"
|
||||
else
|
||||
@@ -676,7 +836,7 @@ _pci_driver_of() {
|
||||
[[ -z "$pci" ]] && return
|
||||
local pci_full="$pci"
|
||||
[[ "$pci_full" != 0000:* ]] && pci_full="0000:${pci_full}"
|
||||
local link="/sys/bus/pci/devices/${pci_full}/driver"
|
||||
local link="${PROXMENUX_SYSFS_ROOT}/bus/pci/devices/${pci_full}/driver"
|
||||
[[ -L "$link" ]] && basename "$(readlink "$link")"
|
||||
}
|
||||
|
||||
@@ -684,7 +844,7 @@ _pci_driver_of() {
|
||||
# /etc/modprobe.d/vfio.conf. Preserves any remaining tokens and any
|
||||
# trailing options on the line. Returns 0 when the file changes.
|
||||
_clean_vfio_conf_ids() {
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
local vfio_conf="$PROXMENUX_VFIO_CONF"
|
||||
[[ ! -f "$vfio_conf" ]] && return 1
|
||||
local -a targets=("$@")
|
||||
[[ ${#targets[@]} -eq 0 ]] && return 1
|
||||
@@ -694,7 +854,7 @@ _clean_vfio_conf_ids() {
|
||||
awk -v targets="${targets[*]}" '
|
||||
BEGIN {
|
||||
n = split(targets, a, " ")
|
||||
for (i = 1; i <= n; i++) drop[a[i]] = 1
|
||||
for (i = 1; i <= n; i++) drop[tolower(a[i])] = 1
|
||||
}
|
||||
/^options vfio-pci ids=/ {
|
||||
pre = ""; ids = ""; post = ""
|
||||
@@ -707,7 +867,7 @@ _clean_vfio_conf_ids() {
|
||||
out = ""
|
||||
for (i = 1; i <= m; i++) {
|
||||
t = tok[i]
|
||||
if (!(t in drop)) {
|
||||
if (!(tolower(t) in drop)) {
|
||||
out = (out == "" ? t : out "," t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,7 @@ update_pve_safe() {
|
||||
fi
|
||||
|
||||
# ── 10. Final cleanup ──
|
||||
msg_info "$(translate "Running cleanup")"
|
||||
apt-get -y autoremove >/dev/null 2>&1 || true
|
||||
apt-get -y autoclean >/dev/null 2>&1 || true
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
|
||||
+50
-110
@@ -5,8 +5,8 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# Version : 1.0
|
||||
# Last Updated: 03/04/2026
|
||||
# Version : 1.1
|
||||
# Last Updated: 26/08/2026
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# Automates full GPU passthrough (VFIO) from Proxmox host to a VM.
|
||||
@@ -323,25 +323,40 @@ evaluate_host_reboot_requirement() {
|
||||
_file_has_exact_line "$mod" "$modules_file" || needs_change=true
|
||||
done
|
||||
|
||||
# vfio-pci ids
|
||||
# VFIO ownership. NVIDIA uses exact BDFs so another GPU with the same
|
||||
# vendor:device ID can remain native; AMD/Intel keep the legacy IDs list.
|
||||
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)
|
||||
if [[ -z "$ids_line" ]]; then
|
||||
needs_change=true
|
||||
else
|
||||
[[ "$ids_line" == *"disable_vga=1"* ]] || needs_change=true
|
||||
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
||||
local existing_ids=()
|
||||
IFS=',' read -ra existing_ids <<< "$ids_part"
|
||||
local required found existing
|
||||
for required in "${IOMMU_VFIO_IDS[@]}"; do
|
||||
found=false
|
||||
for existing in "${existing_ids[@]}"; do
|
||||
[[ "$existing" == "$required" ]] && found=true && break
|
||||
done
|
||||
$found || needs_change=true
|
||||
if [[ "$SELECTED_GPU" == "nvidia" ]]; then
|
||||
local required_bdf
|
||||
for required_bdf in "${IOMMU_DEVICES[@]}"; do
|
||||
if ! declare -F _proxmenux_vfio_bind_has_bdf >/dev/null 2>&1 \
|
||||
|| ! _proxmenux_vfio_bind_has_bdf "$required_bdf"; then
|
||||
needs_change=true
|
||||
fi
|
||||
done
|
||||
_file_has_exact_line "softdep nvidia pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
_file_has_exact_line "softdep nvidia_drm pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
_file_has_exact_line "softdep nvidia_modeset pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
_file_has_exact_line "softdep nvidia_uvm pre: vfio-pci" "$vfio_conf" || needs_change=true
|
||||
else
|
||||
ids_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
||||
if [[ -z "$ids_line" ]]; then
|
||||
needs_change=true
|
||||
else
|
||||
[[ "$ids_line" == *"disable_vga=1"* ]] || needs_change=true
|
||||
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
||||
local existing_ids=()
|
||||
IFS=',' read -ra existing_ids <<< "$ids_part"
|
||||
local required found existing
|
||||
for required in "${IOMMU_VFIO_IDS[@]}"; do
|
||||
found=false
|
||||
for existing in "${existing_ids[@]}"; do
|
||||
[[ "$existing" == "$required" ]] && found=true && break
|
||||
done
|
||||
$found || needs_change=true
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# modprobe options files
|
||||
@@ -362,21 +377,16 @@ evaluate_host_reboot_requirement() {
|
||||
case "$SELECTED_GPU" in
|
||||
nvidia)
|
||||
_file_has_exact_line "blacklist nouveau" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidia" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidia_drm" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidia_modeset" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidia_uvm" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist nvidiafb" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "blacklist lbm-nouveau" "$blacklist_file" || needs_change=true
|
||||
_file_has_exact_line "options nouveau modeset=0" "$blacklist_file" || needs_change=true
|
||||
[[ -f /etc/modules-load.d/nvidia-vfio.conf ]] && needs_change=true
|
||||
grep -qE '^(nvidia|nvidia_uvm|nvidia_drm|nvidia_modeset)$' /etc/modules 2>/dev/null && needs_change=true
|
||||
local svc
|
||||
for svc in nvidia-persistenced.service nvidia-persistenced nvidia-powerd.service nvidia-fabricmanager.service; do
|
||||
if systemctl is-active --quiet "$svc" 2>/dev/null || systemctl is-enabled --quiet "$svc" 2>/dev/null; then
|
||||
needs_change=true
|
||||
fi
|
||||
done
|
||||
# The managed global NVIDIA blacklist is required only when
|
||||
# every NVIDIA GPU is in VFIO. On a mixed host it must be absent.
|
||||
if declare -F _proxmenux_all_nvidia_in_vfio >/dev/null 2>&1 \
|
||||
&& _proxmenux_all_nvidia_in_vfio; then
|
||||
[[ -f /etc/modprobe.d/proxmenux-nvidia-vfio-blacklist.conf ]] || needs_change=true
|
||||
else
|
||||
[[ -f /etc/modprobe.d/proxmenux-nvidia-vfio-blacklist.conf ]] && needs_change=true
|
||||
fi
|
||||
;;
|
||||
amd)
|
||||
_file_has_exact_line "blacklist radeon" "$blacklist_file" || needs_change=true
|
||||
@@ -1611,8 +1621,8 @@ configure_vfio_pci_ids() {
|
||||
# NVIDIA: per-BDF binding (multi-GPU safe). The `options vfio-pci
|
||||
# ids=VENDOR:DEVICE` approach captures EVERY GPU with the same
|
||||
# vendor:device ID — fatal when two NVIDIA GPUs share a model.
|
||||
# Instead, we list the exact BDF(s) of the target GPU in the
|
||||
# initramfs hook, and add `softdep nvidia pre: vfio-pci` so vfio
|
||||
# Instead, we list the exact BDF(s) of the target GPU in an early
|
||||
# udev driver_override rule, and add `softdep nvidia pre: vfio-pci` so vfio
|
||||
# has a chance to claim the BDF before nvidia loads.
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
if [[ "$SELECTED_GPU" == "nvidia" ]]; then
|
||||
@@ -1649,7 +1659,7 @@ configure_vfio_pci_ids() {
|
||||
_add_line_if_missing "softdep nvidia_modeset pre: vfio-pci" "$vfio_conf"
|
||||
_add_line_if_missing "softdep nvidia_uvm pre: vfio-pci" "$vfio_conf"
|
||||
|
||||
# Per-BDF binder hook. IOMMU_DEVICES has the BDFs for the GPU
|
||||
# Per-BDF binder rule. IOMMU_DEVICES has the BDFs for the GPU
|
||||
# we're passing (and any same-group functions like the audio
|
||||
# function). Add all of them so the whole IOMMU group goes to
|
||||
# vfio-pci as Proxmox expects.
|
||||
@@ -1755,85 +1765,15 @@ blacklist_gpu_drivers() {
|
||||
}
|
||||
|
||||
sanitize_nvidia_host_stack_for_vfio() {
|
||||
# In the new per-BDF model we only stop systemd services that could
|
||||
# actively probe / lock GPUs at boot (persistenced) — but we DO NOT:
|
||||
# - blacklist the nvidia kernel module
|
||||
# - remove nvidia entries from /etc/modules
|
||||
# - rename /etc/modules-load.d/nvidia-vfio.conf
|
||||
# - rename /etc/udev/rules.d/70-nvidia.rules
|
||||
# - create /etc/modprobe.d/nvidia-blacklist.conf with install /bin/false
|
||||
# All of those were global and broke multi-GPU NVIDIA scenarios where
|
||||
# one GPU goes to a VM (vfio-pci) and another stays on the host
|
||||
# (nvidia driver). VFIO binding is now per-BDF via driver_override in
|
||||
# an initramfs hook — the nvidia module stays usable for any GPU not
|
||||
# explicitly targeted.
|
||||
# Host-wide NVIDIA services and module blacklisting are derived from
|
||||
# the complete per-BDF state. With two NVIDIA GPUs, assigning only one
|
||||
# to a VM keeps the native driver and services available for the other.
|
||||
msg_info "$(translate 'Sanitizing NVIDIA host services for VFIO mode...')"
|
||||
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 $changed; then
|
||||
HOST_CONFIG_CHANGED=true
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
if _proxmenux_all_nvidia_in_vfio; then
|
||||
msg_ok "$(translate 'NVIDIA host services disabled for VFIO mode')" | tee -a "$screen_capture"
|
||||
else
|
||||
msg_ok "$(translate 'NVIDIA host services 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 for this GPU because it now belongs to a VM. Per-BDF
|
||||
# model: on multi-GPU hosts where another NVIDIA card still uses the
|
||||
# nvidia driver, keep the status as "installed" — the driver is
|
||||
# genuinely in use elsewhere. Only flip to "vfio_passthrough" when no
|
||||
# NVIDIA GPU is bound to the host driver anymore.
|
||||
if declare -F update_component_status >/dev/null 2>&1; then
|
||||
local _nvd_ver _nvd_new_status
|
||||
_nvd_ver=$(jq -r '.nvidia_driver.version // ""' \
|
||||
/usr/local/share/proxmenux/components_status.json 2>/dev/null)
|
||||
_nvd_new_status="vfio_passthrough"
|
||||
# Any NVIDIA PCI device still using the nvidia driver on the host?
|
||||
if lspci -nnk 2>/dev/null | awk '
|
||||
/NVIDIA/{gpu=1; next}
|
||||
gpu && /Kernel driver in use: nvidia$/ {found=1; exit}
|
||||
/^[^\t]/{gpu=0}
|
||||
END{exit !found}
|
||||
'; then
|
||||
_nvd_new_status="installed"
|
||||
fi
|
||||
update_component_status "nvidia_driver" "$_nvd_new_status" \
|
||||
"${_nvd_ver:-}" "gpu" '{"patched":false}' >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'NVIDIA host services/autoload already aligned for native mode')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# Version : 1.2
|
||||
# Last Updated: 26/03/2026
|
||||
# Version : 1.3
|
||||
# Last Updated: 26/08/2026
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# Installs and manages the NVIDIA proprietary driver on a
|
||||
# Proxmox VE host. Detects hardware, picks a kernel-compatible
|
||||
# driver version and handles the full lifecycle
|
||||
# Proxmox VE host. Detects hardware, filters NVIDIA branches by
|
||||
# the installed GPU PCI IDs and handles the full lifecycle
|
||||
# (install / update / remove).
|
||||
#
|
||||
# Features:
|
||||
# - GPU detection + VFIO passthrough safety check
|
||||
# - Kernel-aware driver version filter (5.15 → 6.17+)
|
||||
# - GPU PCI-ID-aware branch filtering from NVIDIA supportedchips
|
||||
# - Nouveau blacklist + module unload
|
||||
# - DKMS-backed install (survives kernel upgrades)
|
||||
# - udev rules + nvidia-persistenced service
|
||||
@@ -36,6 +36,10 @@ screen_capture="/tmp/proxmenux_nvidia_screen_capture_$$.txt"
|
||||
|
||||
NVIDIA_BASE_URL="https://download.nvidia.com/XFree86/Linux-x86_64"
|
||||
NVIDIA_WORKDIR="/opt/nvidia"
|
||||
NVIDIA_NOUVEAU_BLACKLIST="/etc/modprobe.d/proxmenux-nouveau-blacklist.conf"
|
||||
NVIDIA_NOUVEAU_STATE="${BASE_DIR}/nvidia-nouveau-blacklist.state"
|
||||
NVIDIA_NOUVEAU_LEGACY_BLACKLIST="/etc/modprobe.d/nouveau-blacklist.conf"
|
||||
NVIDIA_GLOBAL_BLACKLIST="/etc/modprobe.d/blacklist.conf"
|
||||
|
||||
# LXC post-install update constants (used only when NVIDIA LXC passthrough
|
||||
# containers are detected and the user confirms updating them after the host
|
||||
@@ -541,16 +545,65 @@ ensure_repos_and_headers() {
|
||||
msg_ok "$(translate 'Kernel headers and build tools verified.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
_nouveau_legacy_file_is_proxmenux_shape() {
|
||||
[[ -f "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST" ]] || return 1
|
||||
local content
|
||||
content=$(sed '/^[[:space:]]*$/d' "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST" 2>/dev/null)
|
||||
[[ "$content" == $'blacklist nouveau\noptions nouveau modeset=0' ]]
|
||||
}
|
||||
|
||||
_nouveau_state_set() {
|
||||
local key="$1"
|
||||
mkdir -p "$(dirname "$NVIDIA_NOUVEAU_STATE")"
|
||||
touch "$NVIDIA_NOUVEAU_STATE"
|
||||
grep -qFx "${key}=1" "$NVIDIA_NOUVEAU_STATE" 2>/dev/null \
|
||||
|| echo "${key}=1" >> "$NVIDIA_NOUVEAU_STATE"
|
||||
}
|
||||
|
||||
restore_nouveau_after_uninstall() {
|
||||
local remove_global_line=false
|
||||
|
||||
if [[ -f "$NVIDIA_NOUVEAU_STATE" ]] \
|
||||
&& grep -qFx 'blacklist_conf_line_added=1' "$NVIDIA_NOUVEAU_STATE" 2>/dev/null; then
|
||||
remove_global_line=true
|
||||
fi
|
||||
|
||||
# Migration for installations made by older ProxMenux versions. That
|
||||
# version overwrote this exact two-line file and added the matching line
|
||||
# to blacklist.conf, but had no ownership state yet.
|
||||
if _nouveau_legacy_file_is_proxmenux_shape; then
|
||||
rm -f "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST"
|
||||
remove_global_line=true
|
||||
fi
|
||||
|
||||
rm -f "$NVIDIA_NOUVEAU_BLACKLIST"
|
||||
if $remove_global_line && [[ -f "$NVIDIA_GLOBAL_BLACKLIST" ]]; then
|
||||
sed -i '/^blacklist nouveau$/d' "$NVIDIA_GLOBAL_BLACKLIST"
|
||||
fi
|
||||
rm -f "$NVIDIA_NOUVEAU_STATE"
|
||||
}
|
||||
|
||||
blacklist_nouveau() {
|
||||
msg_info "$(translate 'Blacklisting nouveau driver...')"
|
||||
|
||||
# Write blacklist config files
|
||||
if ! grep -q '^blacklist nouveau' /etc/modprobe.d/blacklist.conf 2>/dev/null; then
|
||||
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
|
||||
local legacy_owned=false
|
||||
if _nouveau_legacy_file_is_proxmenux_shape; then
|
||||
rm -f "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST"
|
||||
legacy_owned=true
|
||||
_nouveau_state_set "legacy_migrated"
|
||||
fi
|
||||
|
||||
# Also write explicit options file to ensure it's fully disabled
|
||||
cat > /etc/modprobe.d/nouveau-blacklist.conf <<'EOF'
|
||||
if ! grep -q '^blacklist nouveau$' "$NVIDIA_GLOBAL_BLACKLIST" 2>/dev/null; then
|
||||
echo "blacklist nouveau" >> "$NVIDIA_GLOBAL_BLACKLIST"
|
||||
_nouveau_state_set "blacklist_conf_line_added"
|
||||
elif $legacy_owned; then
|
||||
# The legacy ProxMenux file proves ownership of the companion line.
|
||||
_nouveau_state_set "blacklist_conf_line_added"
|
||||
fi
|
||||
|
||||
# ProxMenux-owned file: uninstall can now remove only what we created.
|
||||
cat > "$NVIDIA_NOUVEAU_BLACKLIST" <<'EOF'
|
||||
# Managed by ProxMenux NVIDIA installer.
|
||||
blacklist nouveau
|
||||
options nouveau modeset=0
|
||||
EOF
|
||||
@@ -678,6 +731,7 @@ complete_nvidia_uninstall() {
|
||||
rm -f /etc/udev/rules.d/70-nvidia.rules
|
||||
rm -rf /usr/lib/modprobe.d/nvidia*.conf
|
||||
rm -rf /etc/modprobe.d/nvidia*.conf
|
||||
restore_nouveau_after_uninstall
|
||||
|
||||
if [[ -d "$NVIDIA_WORKDIR" ]]; then
|
||||
find "$NVIDIA_WORKDIR" -type d -name "nvidia-persistenced" -exec rm -rf {} + 2>/dev/null || true
|
||||
@@ -709,28 +763,14 @@ ensure_workdir() {
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Kernel + system detection
|
||||
# System detection
|
||||
# ==========================================================
|
||||
get_kernel_compatibility_info() {
|
||||
local kernel_version
|
||||
kernel_version=$(uname -r)
|
||||
|
||||
get_system_info() {
|
||||
if [[ -f /etc/pve/.version ]]; then
|
||||
PVE_VERSION=$(cat /etc/pve/.version)
|
||||
else
|
||||
PVE_VERSION="unknown"
|
||||
fi
|
||||
|
||||
KERNEL_MAJOR=$(echo "$kernel_version" | cut -d. -f1)
|
||||
KERNEL_MINOR=$(echo "$kernel_version" | cut -d. -f2)
|
||||
|
||||
MIN_DRIVER_VERSION=""
|
||||
RECOMMENDED_BRANCH=""
|
||||
COMPATIBILITY_NOTE=""
|
||||
}
|
||||
|
||||
is_version_compatible() {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1546,7 +1586,7 @@ show_version_menu() {
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate 'NVIDIA GPU Driver Installation')"
|
||||
msg_info "$(translate 'Fetching compatible driver versions for your kernel and GPU...')"
|
||||
msg_info "$(translate 'Fetching NVIDIA driver versions supported by your GPU...')"
|
||||
|
||||
latest=$(download_latest_version 2>/dev/null)
|
||||
versions_list=$(list_available_versions 2>/dev/null)
|
||||
@@ -1573,18 +1613,6 @@ show_version_menu() {
|
||||
latest=$(echo "$latest" | tr -d '[:space:]')
|
||||
|
||||
local current_list="$versions_list"
|
||||
|
||||
# Apply kernel compatibility filter if needed
|
||||
if [[ -n "$MIN_DRIVER_VERSION" ]]; then
|
||||
local filtered_list=""
|
||||
while IFS= read -r ver; do
|
||||
[[ -z "$ver" ]] && continue
|
||||
if is_version_compatible "$ver"; then
|
||||
filtered_list+="$ver"$'\n'
|
||||
fi
|
||||
done <<< "$current_list"
|
||||
current_list="$filtered_list"
|
||||
fi
|
||||
|
||||
if [[ -n "$current_list" ]]; then
|
||||
current_list=$(filter_option_c_branch "$current_list" "$CURRENT_DRIVER_VERSION" "")
|
||||
@@ -1636,7 +1664,7 @@ show_version_menu() {
|
||||
# 2. Fresh install (no current driver) → Production Branch head
|
||||
# from NVIDIA's Unix drivers page, when present in the list.
|
||||
# 3. Fallback → highest numeric in the list (Production may have
|
||||
# been filtered out by kernel-compat / GPU-compat / patch
|
||||
# been filtered out by maintained-branch / GPU PCI-ID / patch
|
||||
# awareness).
|
||||
latest=""
|
||||
if [[ -n "$CURRENT_DRIVER_VERSION" && -n "$current_list" ]]; then
|
||||
@@ -1665,7 +1693,7 @@ show_version_menu() {
|
||||
fi
|
||||
|
||||
local menu_text="$(translate 'Select the NVIDIA driver version to install:')\n\n"
|
||||
menu_text+="$(translate 'Versions shown are compatible with your kernel and your GPU. The recommended version keeps you on your current driver branch, or defaults to the NVIDIA Production Branch head on a fresh install.')"
|
||||
menu_text+="$(translate 'Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID. DKMS compilation is the final validation against the running kernel. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.')"
|
||||
if $patch_filtered; then
|
||||
menu_text+="\n\n$(translate 'NVENC patch detected — list narrowed to versions supported by keylase/nvidia-patch.')"
|
||||
elif [[ -n "$patch_filter_note" ]]; then
|
||||
@@ -1689,7 +1717,7 @@ show_version_menu() {
|
||||
choices+=("$ver" "$ver")
|
||||
done <<< "$current_list"
|
||||
else
|
||||
choices+=("" "$(translate 'No compatible versions found for your kernel')")
|
||||
choices+=("" "$(translate 'No supported NVIDIA versions found for this GPU')")
|
||||
fi
|
||||
|
||||
stop_spinner
|
||||
@@ -1741,7 +1769,7 @@ main() {
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_kernel_compatibility_info
|
||||
get_system_info
|
||||
|
||||
show_version_menu
|
||||
if [[ "$DRIVER_VERSION" == "cancel" || -z "$DRIVER_VERSION" ]]; then
|
||||
@@ -2008,4 +2036,4 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
exit $?
|
||||
fi
|
||||
main
|
||||
fi
|
||||
fi
|
||||
|
||||
+100
-208
@@ -5,8 +5,8 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# Version : 1.0
|
||||
# Last Updated: 05/04/2026
|
||||
# Version : 1.1
|
||||
# Last Updated: 26/08/2026
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# Moves an already-assigned GPU between the two modes it can
|
||||
@@ -67,7 +67,8 @@ if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
|
||||
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')"
|
||||
echo "ProxMenux: pci_passthrough_helpers.sh is required; refusing to change GPU ownership." >&2
|
||||
exit 1
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
@@ -81,6 +82,8 @@ declare -a SELECTED_GPU_IDX=()
|
||||
|
||||
declare -a SELECTED_IOMMU_IDS=()
|
||||
declare -a SELECTED_PCI_SLOTS=()
|
||||
declare -a SELECTED_NVIDIA_BDFS=()
|
||||
declare -a SELECTED_LEGACY_IOMMU_IDS=()
|
||||
|
||||
declare -a LXC_AFFECTED_CTIDS=()
|
||||
declare -a LXC_AFFECTED_NAMES=()
|
||||
@@ -167,12 +170,30 @@ _get_iommu_group_ids() {
|
||||
done
|
||||
}
|
||||
|
||||
_get_iommu_group_bdfs() {
|
||||
local pci_full="$1"
|
||||
local group_link="/sys/bus/pci/devices/${pci_full}/iommu_group"
|
||||
[[ -L "$group_link" ]] || return 0
|
||||
|
||||
local group_dir dev_path dev_class
|
||||
group_dir="/sys/kernel/iommu_groups/$(basename "$(readlink "$group_link")")/devices"
|
||||
for dev_path in "${group_dir}/"*; do
|
||||
[[ -e "$dev_path" ]] || continue
|
||||
dev_class=$(cat "$dev_path/class" 2>/dev/null)
|
||||
# Bridges belong to the isolation boundary, but vfio-pci does not
|
||||
# support PCI bridges. Proxmox passes the endpoint devices only.
|
||||
[[ "$dev_class" == 0x0604* || "$dev_class" == 0x0600* ]] && continue
|
||||
basename "$dev_path"
|
||||
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=//')
|
||||
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//' \
|
||||
| tr '[:upper:]' '[:lower:]')
|
||||
[[ -z "$ids_part" ]] && return
|
||||
tr ',' '\n' <<< "$ids_part" | sed '/^$/d'
|
||||
}
|
||||
@@ -213,15 +234,10 @@ _remove_gpu_blacklist() {
|
||||
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"
|
||||
# NVIDIA ownership is per BDF. Never alter the global blacklist here:
|
||||
# it may belong to the host-driver installer and another NVIDIA GPU may
|
||||
# still need the native driver.
|
||||
return 1
|
||||
;;
|
||||
amd)
|
||||
grep -qE '^blacklist (radeon|amdgpu)$' "$blacklist_file" 2>/dev/null && changed=true
|
||||
@@ -243,14 +259,8 @@ _add_gpu_blacklist() {
|
||||
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"
|
||||
# NVIDIA is handled exclusively by the shared per-BDF policy.
|
||||
return 0
|
||||
;;
|
||||
amd)
|
||||
_add_line_if_missing "blacklist radeon" "$blacklist_file"
|
||||
@@ -263,174 +273,18 @@ _add_gpu_blacklist() {
|
||||
}
|
||||
|
||||
_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
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
}
|
||||
|
||||
_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
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
if ! _proxmenux_all_nvidia_in_vfio; then
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
_add_amd_softdep() {
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
_add_line_if_missing "softdep radeon pre: vfio-pci" "$vfio_conf"
|
||||
@@ -468,6 +322,10 @@ _remove_vfio_modules_if_unused() {
|
||||
local vfio_count
|
||||
vfio_count=$(_read_vfio_ids | wc -l | tr -d '[:space:]')
|
||||
[[ "$vfio_count" != "0" ]] && return 1
|
||||
if declare -F _proxmenux_vfio_bind_has_entries >/dev/null 2>&1 \
|
||||
&& _proxmenux_vfio_bind_has_entries; then
|
||||
return 1
|
||||
fi
|
||||
local modules_file="/etc/modules"
|
||||
[[ ! -f "$modules_file" ]] && return 1
|
||||
local had_any=false
|
||||
@@ -811,25 +669,37 @@ check_sriov_and_block_if_needed() {
|
||||
collect_selected_iommu_ids() {
|
||||
SELECTED_IOMMU_IDS=()
|
||||
SELECTED_PCI_SLOTS=()
|
||||
SELECTED_NVIDIA_BDFS=()
|
||||
SELECTED_LEGACY_IOMMU_IDS=()
|
||||
|
||||
local idx pci viddid slot
|
||||
local idx pci viddid slot selected_type bdf vid did gid
|
||||
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
||||
pci="${ALL_GPU_PCIS[$idx]}"
|
||||
viddid="${ALL_GPU_VIDDID[$idx]}"
|
||||
selected_type="${ALL_GPU_TYPES[$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
|
||||
local -a group_bdfs=()
|
||||
mapfile -t group_bdfs < <(_get_iommu_group_bdfs "$pci")
|
||||
[[ ${#group_bdfs[@]} -gt 0 ]] || group_bdfs=("$pci")
|
||||
|
||||
for bdf in "${group_bdfs[@]}"; do
|
||||
[[ "$bdf" == 0000:* ]] || bdf="0000:${bdf}"
|
||||
vid=$(cat "/sys/bus/pci/devices/${bdf}/vendor" 2>/dev/null | sed 's/^0x//')
|
||||
did=$(cat "/sys/bus/pci/devices/${bdf}/device" 2>/dev/null | sed 's/^0x//')
|
||||
gid="${vid}:${did}"
|
||||
if [[ -n "$vid" && -n "$did" ]]; then
|
||||
_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
|
||||
if [[ "$selected_type" != "nvidia" ]]; then
|
||||
_contains_in_array "$gid" "${SELECTED_LEGACY_IOMMU_IDS[@]}" || SELECTED_LEGACY_IOMMU_IDS+=("$gid")
|
||||
fi
|
||||
fi
|
||||
if [[ "$selected_type" == "nvidia" ]]; then
|
||||
_contains_in_array "$bdf" "${SELECTED_NVIDIA_BDFS[@]}" || SELECTED_NVIDIA_BDFS+=("$bdf")
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
@@ -1167,6 +1037,9 @@ apply_vm_action_for_lxc_mode() {
|
||||
if ! _contains_in_array "$_vd_id" "${SELECTED_IOMMU_IDS[@]}"; then
|
||||
SELECTED_IOMMU_IDS+=("$_vd_id")
|
||||
fi
|
||||
if ! _contains_in_array "$_vd_id" "${SELECTED_LEGACY_IOMMU_IDS[@]}"; then
|
||||
SELECTED_LEGACY_IOMMU_IDS+=("$_vd_id")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -1234,6 +1107,12 @@ switch_to_vm_mode() {
|
||||
|
||||
msg_info "$(translate 'Configuring host for GPU -> VM mode...')"
|
||||
|
||||
local -a selected_types=()
|
||||
mapfile -t selected_types < <(_selected_types_unique)
|
||||
if _contains_in_array "nvidia" "${selected_types[@]}"; then
|
||||
_proxmenux_nvidia_migrate_legacy_blacklist
|
||||
fi
|
||||
|
||||
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"
|
||||
@@ -1269,24 +1148,30 @@ switch_to_vm_mode() {
|
||||
local -a current_ids=()
|
||||
mapfile -t current_ids < <(_read_vfio_ids)
|
||||
local id
|
||||
for id in "${SELECTED_IOMMU_IDS[@]}"; do
|
||||
for id in "${SELECTED_LEGACY_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
|
||||
if [[ ${#SELECTED_LEGACY_IOMMU_IDS[@]} -gt 0 ]]; then
|
||||
local ids_label
|
||||
ids_label=$(IFS=','; echo "${SELECTED_IOMMU_IDS[*]}")
|
||||
ids_label=$(IFS=','; echo "${SELECTED_LEGACY_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
|
||||
if [[ ${#SELECTED_NVIDIA_BDFS[@]} -gt 0 ]]; then
|
||||
_proxmenux_vfio_bind_add_bdfs "${SELECTED_NVIDIA_BDFS[@]}"
|
||||
msg_ok "$(translate 'NVIDIA per-BDF VFIO binding configured') (${SELECTED_NVIDIA_BDFS[*]})" | tee -a "$screen_capture"
|
||||
fi
|
||||
|
||||
local t legacy_blacklist_configured=false
|
||||
for t in "${selected_types[@]}"; do
|
||||
[[ "$t" == "nvidia" ]] && continue
|
||||
_add_gpu_blacklist "$t"
|
||||
legacy_blacklist_configured=true
|
||||
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
|
||||
$legacy_blacklist_configured \
|
||||
&& msg_ok "$(translate 'GPU host driver blacklisted in /etc/modprobe.d/blacklist.conf')" | tee -a "$screen_capture"
|
||||
_contains_in_array "nvidia" "${selected_types[@]}" && _proxmenux_nvidia_vfio_policy_sync || true
|
||||
_contains_in_array "amd" "${selected_types[@]}" && _add_amd_softdep
|
||||
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
@@ -1320,12 +1205,20 @@ switch_to_lxc_mode() {
|
||||
|
||||
msg_info "$(translate 'Removing VFIO ownership for selected GPU(s)...')"
|
||||
|
||||
local -a selected_types=()
|
||||
mapfile -t selected_types < <(_selected_types_unique)
|
||||
if _contains_in_array "nvidia" "${selected_types[@]}"; then
|
||||
_proxmenux_nvidia_migrate_legacy_blacklist
|
||||
[[ ${#SELECTED_NVIDIA_BDFS[@]} -gt 0 ]] \
|
||||
&& _proxmenux_vfio_bind_remove_bdfs "${SELECTED_NVIDIA_BDFS[@]}"
|
||||
fi
|
||||
|
||||
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
|
||||
_contains_in_array "$id" "${SELECTED_LEGACY_IOMMU_IDS[@]}" && remove=true
|
||||
if $remove; then
|
||||
removed_ids+=("$id")
|
||||
else
|
||||
@@ -1339,17 +1232,16 @@ switch_to_lxc_mode() {
|
||||
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 [[ "$t" == "nvidia" ]]; then
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
continue
|
||||
fi
|
||||
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
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# Version : 1.0
|
||||
# Last Updated: 09/04/2026
|
||||
# Version : 1.1
|
||||
# Last Updated: 26/08/2026
|
||||
# ==========================================================
|
||||
# This script is a hybrid version for ProxMenux Monitor.
|
||||
# It accepts parameters to skip GPU selection and uses
|
||||
@@ -36,6 +36,9 @@ 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
|
||||
echo "ProxMenux: pci_passthrough_helpers.sh is required; refusing to change GPU ownership." >&2
|
||||
exit 1
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
@@ -52,6 +55,8 @@ declare -a SELECTED_GPU_IDX=()
|
||||
|
||||
declare -a SELECTED_IOMMU_IDS=()
|
||||
declare -a SELECTED_PCI_SLOTS=()
|
||||
declare -a SELECTED_NVIDIA_BDFS=()
|
||||
declare -a SELECTED_LEGACY_IOMMU_IDS=()
|
||||
|
||||
declare -a LXC_AFFECTED_CTIDS=()
|
||||
declare -a LXC_AFFECTED_NAMES=()
|
||||
@@ -145,12 +150,28 @@ _get_iommu_group_ids() {
|
||||
done
|
||||
}
|
||||
|
||||
_get_iommu_group_bdfs() {
|
||||
local pci_full="$1"
|
||||
local group_link="/sys/bus/pci/devices/${pci_full}/iommu_group"
|
||||
[[ -L "$group_link" ]] || return 0
|
||||
|
||||
local group_dir dev_path dev_class
|
||||
group_dir="/sys/kernel/iommu_groups/$(basename "$(readlink "$group_link")")/devices"
|
||||
for dev_path in "${group_dir}/"*; do
|
||||
[[ -e "$dev_path" ]] || continue
|
||||
dev_class=$(cat "$dev_path/class" 2>/dev/null)
|
||||
[[ "$dev_class" == 0x0604* || "$dev_class" == 0x0600* ]] && continue
|
||||
basename "$dev_path"
|
||||
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=//')
|
||||
ids_part=$(echo "$ids_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//' \
|
||||
| tr '[:upper:]' '[:lower:]')
|
||||
[[ -z "$ids_part" ]] && return
|
||||
tr ',' '\n' <<< "$ids_part" | sed '/^$/d'
|
||||
}
|
||||
@@ -191,15 +212,10 @@ _remove_gpu_blacklist() {
|
||||
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"
|
||||
# NVIDIA ownership is per BDF. Never alter the global blacklist here:
|
||||
# it may belong to the host-driver installer and another NVIDIA GPU may
|
||||
# still need the native driver.
|
||||
return 1
|
||||
;;
|
||||
amd)
|
||||
grep -qE '^blacklist (radeon|amdgpu)$' "$blacklist_file" 2>/dev/null && changed=true
|
||||
@@ -221,14 +237,8 @@ _add_gpu_blacklist() {
|
||||
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"
|
||||
# NVIDIA is handled exclusively by the shared per-BDF policy.
|
||||
return 0
|
||||
;;
|
||||
amd)
|
||||
_add_line_if_missing "blacklist radeon" "$blacklist_file"
|
||||
@@ -241,170 +251,18 @@ _add_gpu_blacklist() {
|
||||
}
|
||||
|
||||
_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
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
}
|
||||
|
||||
_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.
|
||||
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
|
||||
|
||||
if [[ -f "$disabled_file" ]]; then
|
||||
mv "$disabled_file" "$active_file" >>"$LOG_FILE" 2>&1 || true
|
||||
changed=true
|
||||
fi
|
||||
|
||||
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
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
if ! _proxmenux_all_nvidia_in_vfio; then
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
_add_amd_softdep() {
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
_add_line_if_missing "softdep radeon pre: vfio-pci" "$vfio_conf"
|
||||
@@ -442,6 +300,10 @@ _remove_vfio_modules_if_unused() {
|
||||
local vfio_count
|
||||
vfio_count=$(_read_vfio_ids | wc -l | tr -d '[:space:]')
|
||||
[[ "$vfio_count" != "0" ]] && return 1
|
||||
if declare -F _proxmenux_vfio_bind_has_entries >/dev/null 2>&1 \
|
||||
&& _proxmenux_vfio_bind_has_entries; then
|
||||
return 1
|
||||
fi
|
||||
local modules_file="/etc/modules"
|
||||
[[ ! -f "$modules_file" ]] && return 1
|
||||
local had_any=false
|
||||
@@ -632,25 +494,37 @@ validate_vm_mode_blocked_ids() {
|
||||
collect_selected_iommu_ids() {
|
||||
SELECTED_IOMMU_IDS=()
|
||||
SELECTED_PCI_SLOTS=()
|
||||
SELECTED_NVIDIA_BDFS=()
|
||||
SELECTED_LEGACY_IOMMU_IDS=()
|
||||
|
||||
local idx pci viddid slot
|
||||
local idx pci viddid slot selected_type bdf vid did gid
|
||||
for idx in "${SELECTED_GPU_IDX[@]}"; do
|
||||
pci="${ALL_GPU_PCIS[$idx]}"
|
||||
viddid="${ALL_GPU_VIDDID[$idx]}"
|
||||
selected_type="${ALL_GPU_TYPES[$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
|
||||
local -a group_bdfs=()
|
||||
mapfile -t group_bdfs < <(_get_iommu_group_bdfs "$pci")
|
||||
[[ ${#group_bdfs[@]} -gt 0 ]] || group_bdfs=("$pci")
|
||||
|
||||
for bdf in "${group_bdfs[@]}"; do
|
||||
[[ "$bdf" == 0000:* ]] || bdf="0000:${bdf}"
|
||||
vid=$(cat "/sys/bus/pci/devices/${bdf}/vendor" 2>/dev/null | sed 's/^0x//')
|
||||
did=$(cat "/sys/bus/pci/devices/${bdf}/device" 2>/dev/null | sed 's/^0x//')
|
||||
gid="${vid}:${did}"
|
||||
if [[ -n "$vid" && -n "$did" ]]; then
|
||||
_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
|
||||
if [[ "$selected_type" != "nvidia" ]]; then
|
||||
_contains_in_array "$gid" "${SELECTED_LEGACY_IOMMU_IDS[@]}" || SELECTED_LEGACY_IOMMU_IDS+=("$gid")
|
||||
fi
|
||||
fi
|
||||
if [[ "$selected_type" == "nvidia" ]]; then
|
||||
_contains_in_array "$bdf" "${SELECTED_NVIDIA_BDFS[@]}" || SELECTED_NVIDIA_BDFS+=("$bdf")
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
@@ -948,6 +822,9 @@ apply_vm_action_for_lxc_mode() {
|
||||
if ! _contains_in_array "$_vd_id" "${SELECTED_IOMMU_IDS[@]}"; then
|
||||
SELECTED_IOMMU_IDS+=("$_vd_id")
|
||||
fi
|
||||
if ! _contains_in_array "$_vd_id" "${SELECTED_LEGACY_IOMMU_IDS[@]}"; then
|
||||
SELECTED_LEGACY_IOMMU_IDS+=("$_vd_id")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -1018,6 +895,12 @@ switch_to_vm_mode() {
|
||||
|
||||
msg_info "$(translate 'Configuring host for GPU -> VM mode...')"
|
||||
|
||||
local -a selected_types=()
|
||||
mapfile -t selected_types < <(_selected_types_unique)
|
||||
if _contains_in_array "nvidia" "${selected_types[@]}"; then
|
||||
_proxmenux_nvidia_migrate_legacy_blacklist
|
||||
fi
|
||||
|
||||
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"
|
||||
@@ -1044,24 +927,30 @@ switch_to_vm_mode() {
|
||||
local -a current_ids=()
|
||||
mapfile -t current_ids < <(_read_vfio_ids)
|
||||
local id
|
||||
for id in "${SELECTED_IOMMU_IDS[@]}"; do
|
||||
for id in "${SELECTED_LEGACY_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
|
||||
if [[ ${#SELECTED_LEGACY_IOMMU_IDS[@]} -gt 0 ]]; then
|
||||
local ids_label
|
||||
ids_label=$(IFS=','; echo "${SELECTED_IOMMU_IDS[*]}")
|
||||
ids_label=$(IFS=','; echo "${SELECTED_LEGACY_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
|
||||
if [[ ${#SELECTED_NVIDIA_BDFS[@]} -gt 0 ]]; then
|
||||
_proxmenux_vfio_bind_add_bdfs "${SELECTED_NVIDIA_BDFS[@]}"
|
||||
msg_ok "$(translate 'NVIDIA per-BDF VFIO binding configured') (${SELECTED_NVIDIA_BDFS[*]})" | tee -a "$screen_capture"
|
||||
fi
|
||||
|
||||
local t legacy_blacklist_configured=false
|
||||
for t in "${selected_types[@]}"; do
|
||||
[[ "$t" == "nvidia" ]] && continue
|
||||
_add_gpu_blacklist "$t"
|
||||
legacy_blacklist_configured=true
|
||||
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
|
||||
$legacy_blacklist_configured \
|
||||
&& msg_ok "$(translate 'GPU host driver blacklisted in /etc/modprobe.d/blacklist.conf')" | tee -a "$screen_capture"
|
||||
_contains_in_array "nvidia" "${selected_types[@]}" && _proxmenux_nvidia_vfio_policy_sync || true
|
||||
_contains_in_array "amd" "${selected_types[@]}" && _add_amd_softdep
|
||||
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
@@ -1095,12 +984,20 @@ switch_to_lxc_mode() {
|
||||
|
||||
msg_info "$(translate 'Removing VFIO ownership for selected GPU(s)...')"
|
||||
|
||||
local -a selected_types=()
|
||||
mapfile -t selected_types < <(_selected_types_unique)
|
||||
if _contains_in_array "nvidia" "${selected_types[@]}"; then
|
||||
_proxmenux_nvidia_migrate_legacy_blacklist
|
||||
[[ ${#SELECTED_NVIDIA_BDFS[@]} -gt 0 ]] \
|
||||
&& _proxmenux_vfio_bind_remove_bdfs "${SELECTED_NVIDIA_BDFS[@]}"
|
||||
fi
|
||||
|
||||
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
|
||||
_contains_in_array "$id" "${SELECTED_LEGACY_IOMMU_IDS[@]}" && remove=true
|
||||
if $remove; then
|
||||
removed_ids+=("$id")
|
||||
else
|
||||
@@ -1114,17 +1011,16 @@ switch_to_lxc_mode() {
|
||||
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 [[ "$t" == "nvidia" ]]; then
|
||||
_proxmenux_nvidia_vfio_policy_sync || true
|
||||
continue
|
||||
fi
|
||||
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
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# https://github.com/MacRimi/ProxMenux/blob/main/LICENSE
|
||||
# Version : 1.0
|
||||
# Version : 1.1
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# Applies a curated set of 14 safe optimizations to a fresh
|
||||
# Proxmox VE host without prompts. Every change is registered
|
||||
# in installed_tools.json so it can be reversed later from the
|
||||
# Uninstall Optimizations menu.
|
||||
# Proxmox VE host without prompts. Reversible changes are registered
|
||||
# in installed_tools.json so they can be restored later from the
|
||||
# Uninstall Optimizations menu; package upgrades are intentionally
|
||||
# excluded because they cannot be rolled back safely.
|
||||
#
|
||||
# Features:
|
||||
# - Zero-interaction baseline: repos, upgrade, banner, APT
|
||||
@@ -158,6 +159,8 @@ apt_upgrade() {
|
||||
|
||||
|
||||
remove_subscription_banner() {
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Patch the Proxmox web UI to suppress the subscription dialog and register a successful patch.
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
|
||||
@@ -176,15 +179,22 @@ remove_subscription_banner() {
|
||||
msg_warn "Banner removal cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"
|
||||
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"; then
|
||||
msg_error "$(translate "Subscription banner removal failed")"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
if ! whiptail --title "Proxmox VE 8.x Subscription Banner Removal" \
|
||||
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then
|
||||
msg_warn "Banner removal cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
bash "$LOCAL_SCRIPTS/global/remove-banner-pve8.sh"
|
||||
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve8.sh"; then
|
||||
msg_error "$(translate "Subscription banner removal failed")"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
register_tool "subscription_banner" true "$FUNC_VERSION"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# https://github.com/MacRimi/ProxMenux/blob/main/LICENSE
|
||||
# Version : 1.3
|
||||
# Version : 1.4
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# Interactive post-installation configurator for Proxmox VE.
|
||||
@@ -15,8 +15,9 @@
|
||||
# System, Virtualization, Network, Storage, Security,
|
||||
# Customization, Monitoring, Performance, Optional) and presents
|
||||
# a checklist per category so the user picks exactly what to
|
||||
# apply. Every change is registered in installed_tools.json for
|
||||
# later reversal from Uninstall Optimizations.
|
||||
# apply. Reversible changes are registered in installed_tools.json
|
||||
# for later restoration from Uninstall Optimizations; package upgrades
|
||||
# are intentionally excluded because they cannot be rolled back safely.
|
||||
#
|
||||
# Features:
|
||||
# - Checklist UI per category (10 categories, ~30 tools total).
|
||||
@@ -24,8 +25,8 @@
|
||||
# optimizations plus opt-in items (IOMMU/VFIO, Fastfetch,
|
||||
# Figurine, Ceph repo, HA, AMD fixes, pigz, ZFS ARC, …).
|
||||
# - Idempotent: safe to run repeatedly.
|
||||
# - Registration + rollback: every tool has a reverse function
|
||||
# in uninstall-tools.sh.
|
||||
# - Registration + rollback: every registered tool has a reverse
|
||||
# function in uninstall-tools.sh.
|
||||
#
|
||||
# Credits:
|
||||
# Incorporates ideas and snippets originally published under BSD
|
||||
@@ -155,7 +156,7 @@ $(translate "Do you want to continue anyway?")" 13 70
|
||||
|
||||
|
||||
enable_kexec() {
|
||||
local FUNC_VERSION="1.0"
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Install kexec-tools and add a Ctrl+Alt+K hotkey + systemd unit for fast reboots that skip BIOS/POST.
|
||||
msg_info2 "$(translate "Configuring kexec for quick reboots...")"
|
||||
NECESSARY_REBOOT=1
|
||||
@@ -169,7 +170,7 @@ enable_kexec() {
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install kexec-tools > /dev/null 2>&1
|
||||
msg_ok "$(translate "kexec-tools installed successfully")"
|
||||
else
|
||||
msg_ok "$(translate "kexec-tools installed successfully")"
|
||||
msg_ok "$(translate "kexec-tools is already installed")"
|
||||
fi
|
||||
|
||||
# Create systemd service file
|
||||
@@ -194,7 +195,7 @@ WantedBy=default.target
|
||||
EOF
|
||||
msg_ok "$(translate "kexec-pve service file created")"
|
||||
else
|
||||
msg_ok "$(translate "kexec-pve service file created")"
|
||||
msg_ok "$(translate "kexec-pve service file is already configured")"
|
||||
fi
|
||||
|
||||
# Enable the service
|
||||
@@ -202,7 +203,7 @@ EOF
|
||||
systemctl enable kexec-pve.service > /dev/null 2>&1
|
||||
msg_ok "$(translate "kexec-pve service enabled")"
|
||||
else
|
||||
msg_ok "$(translate "kexec-pve service enabled")"
|
||||
msg_ok "$(translate "kexec-pve service is already enabled")"
|
||||
fi
|
||||
|
||||
if [ ! -f /root/.bash_profile ]; then
|
||||
@@ -213,7 +214,7 @@ EOF
|
||||
echo "alias reboot-quick='systemctl kexec'" >> /root/.bash_profile
|
||||
msg_ok "$(translate "reboot-quick alias added")"
|
||||
else
|
||||
msg_ok "$(translate "reboot-quick alias added")"
|
||||
msg_ok "$(translate "reboot-quick alias is already configured")"
|
||||
fi
|
||||
|
||||
msg_success "$(translate "kexec configured successfully. Use the command: reboot-quick")"
|
||||
@@ -1026,7 +1027,7 @@ EOF
|
||||
|
||||
|
||||
install_ceph() {
|
||||
local FUNC_VERSION="1.0"
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Install Ceph (client + server packages) for distributed RBD/CephFS storage; PVE 8/9 aware repo selection.
|
||||
msg_info2 "$(translate "Installing Ceph support...")"
|
||||
|
||||
@@ -1055,6 +1056,11 @@ install_ceph() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -r /usr/share/keyrings/proxmox-archive-keyring.gpg ]]; then
|
||||
msg_error "$(translate "The Proxmox archive keyring is missing; Ceph installation cannot continue safely")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Configure Ceph repository based on version
|
||||
msg_info "$(translate "Configuring Ceph repository for PVE") $pve_version..."
|
||||
|
||||
@@ -1086,10 +1092,10 @@ EOF
|
||||
|
||||
# Use legacy format for PVE 8
|
||||
msg_info "$(translate "Creating Ceph repository for PVE 8 (legacy format)...")"
|
||||
echo "deb https://download.proxmox.com/debian/ceph-${ceph_version} ${target_codename} no-subscription" > /etc/apt/sources.list.d/ceph-${ceph_version}.list
|
||||
echo "deb [signed-by=/usr/share/keyrings/proxmox-archive-keyring.gpg] https://download.proxmox.com/debian/ceph-${ceph_version} ${target_codename} no-subscription" > /etc/apt/sources.list.d/ceph-${ceph_version}.list
|
||||
msg_ok "$(translate "Ceph repository configured for PVE 8")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Updating package lists...")"
|
||||
|
||||
@@ -1102,16 +1108,9 @@ EOF
|
||||
msg_warn "$(translate "Package update had issues, checking details...")"
|
||||
|
||||
|
||||
if echo "$update_output" | grep -q "NO_PUBKEY\|GPG error"; then
|
||||
msg_info "$(translate "Fixing GPG key issues...")"
|
||||
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $(echo "$update_output" | grep "NO_PUBKEY" | sed 's/.*NO_PUBKEY //' | head -1) 2>/dev/null
|
||||
|
||||
if apt-get update > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated after GPG fix")"
|
||||
else
|
||||
msg_warn "$(translate "Package update still has issues, continuing anyway...")"
|
||||
fi
|
||||
if echo "$update_output" | grep -Eqi 'NO_PUBKEY|GPG error|EXPKEYSIG|BADSIG|not signed|signatures? (could not|couldn.t) be verified'; then
|
||||
msg_error "$(translate "Ceph repository signature verification failed; installation has been stopped")"
|
||||
return 1
|
||||
elif echo "$update_output" | grep -q "404\|Failed to fetch"; then
|
||||
msg_warn "$(translate "Some repositories are not available, continuing with available ones...")"
|
||||
else
|
||||
@@ -1544,17 +1543,55 @@ update_snapshot_schedule() {
|
||||
|
||||
|
||||
disable_rpc() {
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Disable rpcbind service/socket while preserving their exact previous systemd state for rollback.
|
||||
local state_file="$BASE_DIR/rpcbind.state"
|
||||
local state_tmp="${state_file}.tmp.$$"
|
||||
local unit load_state enabled_state active_state
|
||||
|
||||
msg_info2 "$(translate "Disabling portmapper/rpcbind for security...")"
|
||||
|
||||
msg_info "$(translate "Disabling and stopping rpcbind service...")"
|
||||
mkdir -p "$BASE_DIR"
|
||||
if [[ ! -s "$state_file" ]]; then
|
||||
: > "$state_tmp"
|
||||
for unit in rpcbind.socket rpcbind.service; do
|
||||
load_state="$(systemctl show -p LoadState --value "$unit" 2>/dev/null || true)"
|
||||
[[ -z "$load_state" || "$load_state" == "not-found" ]] && continue
|
||||
enabled_state="$(systemctl is-enabled "$unit" 2>/dev/null || true)"
|
||||
active_state="$(systemctl is-active "$unit" 2>/dev/null || true)"
|
||||
printf '%s|%s|%s\n' "$unit" "${enabled_state:-unknown}" "${active_state:-unknown}" >> "$state_tmp"
|
||||
done
|
||||
|
||||
# Disable and stop rpcbind
|
||||
systemctl disable rpcbind > /dev/null 2>&1
|
||||
systemctl stop rpcbind > /dev/null 2>&1
|
||||
if [[ ! -s "$state_tmp" ]]; then
|
||||
rm -f "$state_tmp"
|
||||
msg_warn "$(translate "rpcbind units were not found; no changes were made")"
|
||||
return 0
|
||||
fi
|
||||
mv "$state_tmp" "$state_file"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "rpcbind service has been disabled and stopped")"
|
||||
# Register as soon as the original state is safely persisted. If a
|
||||
# later systemd operation fails, Uninstall Optimizations must still
|
||||
# expose the recovery path instead of leaving a hidden partial change.
|
||||
register_tool "rpc" true "$FUNC_VERSION"
|
||||
|
||||
msg_success "$(translate "portmapper/rpcbind has been disabled and removed")"
|
||||
msg_info "$(translate "Disabling and stopping rpcbind service and socket...")"
|
||||
|
||||
systemctl disable --now rpcbind.socket rpcbind.service > /dev/null 2>&1 || true
|
||||
|
||||
for unit in rpcbind.socket rpcbind.service; do
|
||||
active_state="$(systemctl is-active "$unit" 2>/dev/null || true)"
|
||||
enabled_state="$(systemctl is-enabled "$unit" 2>/dev/null || true)"
|
||||
if [[ "$active_state" == "active" || "$active_state" == "activating" ||
|
||||
"$enabled_state" == "enabled" || "$enabled_state" == "enabled-runtime" ]]; then
|
||||
msg_error "$(translate "rpcbind could not be disabled completely")"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate "rpcbind service and socket have been disabled and stopped")"
|
||||
|
||||
msg_success "$(translate "portmapper/rpcbind has been disabled")"
|
||||
}
|
||||
|
||||
|
||||
@@ -2008,26 +2045,49 @@ EOF
|
||||
|
||||
|
||||
setup_motd() {
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Add the ProxMenux MOTD banner while preserving the original file contents or absence for rollback.
|
||||
msg_info2 "$(translate "Configuring MOTD (Message of the Day) banner...")"
|
||||
|
||||
local motd_file="/etc/motd"
|
||||
local motd_file="${PROXMENUX_MOTD_FILE:-/etc/motd}"
|
||||
local custom_message=" This system is optimised by: ProxMenux"
|
||||
local state_file="$BASE_DIR/motd.state"
|
||||
local original_file="$BASE_DIR/motd.original"
|
||||
local changes_made=false
|
||||
|
||||
msg_info "$(translate "Checking MOTD configuration...")"
|
||||
|
||||
# Check if the custom message already exists
|
||||
if grep -q "$custom_message" "$motd_file"; then
|
||||
msg_ok "$(translate "Custom message added to MOTD")"
|
||||
else
|
||||
# Create a backup of the original MOTD file
|
||||
if [ ! -f "${motd_file}.bak" ]; then
|
||||
cp "$motd_file" "${motd_file}.bak"
|
||||
msg_ok "$(translate "Backup of original MOTD created")"
|
||||
mkdir -p "$BASE_DIR"
|
||||
if [[ ! -f "$state_file" ]]; then
|
||||
if grep -Fqx "$custom_message" "$motd_file" 2>/dev/null; then
|
||||
if [[ -f "${motd_file}.bak" ]]; then
|
||||
cp -a "${motd_file}.bak" "$original_file"
|
||||
printf 'present\n' > "$state_file"
|
||||
else
|
||||
printf 'legacy-marker\n' > "$state_file"
|
||||
fi
|
||||
elif [[ -e "$motd_file" ]]; then
|
||||
cp -a "$motd_file" "$original_file"
|
||||
printf 'present\n' > "$state_file"
|
||||
else
|
||||
printf 'absent\n' > "$state_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if the custom message already exists
|
||||
if grep -Fqx "$custom_message" "$motd_file" 2>/dev/null; then
|
||||
msg_ok "$(translate "Custom MOTD message is already configured")"
|
||||
else
|
||||
# Add the custom message at the beginning of the file
|
||||
echo -e "$custom_message\n\n$(cat $motd_file)" > "$motd_file"
|
||||
touch "$motd_file"
|
||||
local motd_tmp
|
||||
motd_tmp="$(mktemp)"
|
||||
{
|
||||
printf '%s\n\n' "$custom_message"
|
||||
cat "$motd_file"
|
||||
} > "$motd_tmp"
|
||||
cat "$motd_tmp" > "$motd_file"
|
||||
rm -f "$motd_tmp"
|
||||
changes_made=true
|
||||
msg_ok "$(translate "Custom message added to MOTD")"
|
||||
fi
|
||||
@@ -2037,8 +2097,9 @@ setup_motd() {
|
||||
if $changes_made; then
|
||||
msg_success "$(translate "MOTD configuration updated successfully")"
|
||||
else
|
||||
msg_success "$(translate "MOTD configuration updated successfully")"
|
||||
msg_success "$(translate "MOTD configuration was already up to date")"
|
||||
fi
|
||||
register_tool "motd" true "$FUNC_VERSION"
|
||||
}
|
||||
|
||||
|
||||
@@ -2094,7 +2155,7 @@ EOF
|
||||
|
||||
|
||||
remove_subscription_banner() {
|
||||
local FUNC_VERSION="1.0"
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Patch the Proxmox web UI to suppress the "no valid subscription" dialog (PVE 8 + 9 variants supported).
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
@@ -2105,11 +2166,15 @@ remove_subscription_banner() {
|
||||
fi
|
||||
|
||||
if [[ "$pve_version" -ge 9 ]]; then
|
||||
|
||||
bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"
|
||||
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"; then
|
||||
msg_error "$(translate "Subscription banner removal failed")"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
|
||||
bash "$LOCAL_SCRIPTS/global/remove-banner-pve8.sh"
|
||||
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve8.sh"; then
|
||||
msg_error "$(translate "Subscription banner removal failed")"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
register_tool "subscription_banner" true "$FUNC_VERSION"
|
||||
}
|
||||
@@ -3056,6 +3121,10 @@ setup_persistent_network() {
|
||||
|
||||
|
||||
install_system_utils() {
|
||||
local FUNC_VERSION="1.1"
|
||||
# description: Install selected system utilities and track only packages that were newly added by ProxMenux.
|
||||
local state_file="$BASE_DIR/system_utils.packages"
|
||||
local new_packages_tmp=""
|
||||
msg_info2 "$(translate "Installing system utilities...")"
|
||||
|
||||
# Build checklist from global PROXMENUX_UTILS array
|
||||
@@ -3087,6 +3156,8 @@ install_system_utils() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
new_packages_tmp="$(mktemp)"
|
||||
|
||||
local success=0 failed=0 warning=0
|
||||
local selected_array
|
||||
IFS=' ' read -ra selected_array <<< "$selected"
|
||||
@@ -3094,6 +3165,10 @@ install_system_utils() {
|
||||
for util in "${selected_array[@]}"; do
|
||||
util=$(echo "$util" | tr -d '"')
|
||||
local pkg_cmd="$util" pkg_desc="$util"
|
||||
local was_installed=false
|
||||
if dpkg-query -W -f='${Status}' "$util" 2>/dev/null | grep -q '^install ok installed$'; then
|
||||
was_installed=true
|
||||
fi
|
||||
for util_entry in "${PROXMENUX_UTILS[@]}"; do
|
||||
IFS=':' read -r epkg ecmd edesc <<< "$util_entry"
|
||||
if [[ "$epkg" == "$util" ]]; then
|
||||
@@ -3103,19 +3178,38 @@ install_system_utils() {
|
||||
fi
|
||||
done
|
||||
install_single_package "$util" "$pkg_cmd" "$pkg_desc"
|
||||
case $? in
|
||||
local install_result=$?
|
||||
case $install_result in
|
||||
0) success=$((success + 1)) ;;
|
||||
1) failed=$((failed + 1)) ;;
|
||||
2) warning=$((warning + 1)) ;;
|
||||
esac
|
||||
|
||||
if [[ "$was_installed" == false ]] &&
|
||||
dpkg-query -W -f='${Status}' "$util" 2>/dev/null | grep -q '^install ok installed$'; then
|
||||
printf '%s\n' "$util" >> "$new_packages_tmp"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -s "$new_packages_tmp" ]]; then
|
||||
mkdir -p "$BASE_DIR"
|
||||
{
|
||||
[[ -f "$state_file" ]] && cat "$state_file"
|
||||
cat "$new_packages_tmp"
|
||||
} | sort -u > "${state_file}.tmp"
|
||||
mv "${state_file}.tmp" "$state_file"
|
||||
fi
|
||||
rm -f "$new_packages_tmp"
|
||||
|
||||
hash -r 2>/dev/null
|
||||
echo
|
||||
msg_info2 "$(translate "Installation summary"):"
|
||||
[[ $success -gt 0 ]] && msg_ok "$(translate "Successful"): $success"
|
||||
[[ $warning -gt 0 ]] && msg_warn "$(translate "With warnings"): $warning"
|
||||
[[ $failed -gt 0 ]] && msg_error "$(translate "Failed"): $failed"
|
||||
if [[ -s "$state_file" ]]; then
|
||||
register_tool "system_utils" true "$FUNC_VERSION"
|
||||
fi
|
||||
msg_success "$(translate "Utilities installation completed")"
|
||||
}
|
||||
|
||||
@@ -3126,7 +3220,6 @@ custom_post_category_label() {
|
||||
case "$1" in
|
||||
"Basic Settings") translate "Basic Settings" ;;
|
||||
"System") translate "System" ;;
|
||||
"Hardware") translate "Hardware" ;;
|
||||
"Virtualization") translate "Virtualization" ;;
|
||||
"Network") translate "Network" ;;
|
||||
"Storage") translate "Storage" ;;
|
||||
@@ -3255,9 +3348,9 @@ main_menu() {
|
||||
HEADER="$(translate "Choose options to configure:")\n\n${header_line}"
|
||||
|
||||
declare -A category_order=(
|
||||
["Basic Settings"]=1 ["System"]=2 ["Hardware"]=3 ["Virtualization"]=4
|
||||
["Network"]=5 ["Storage"]=6 ["Security"]=7 ["Customization"]=8
|
||||
["Monitoring"]=9 ["Performance"]=10 ["Optional"]=11
|
||||
["Basic Settings"]=1 ["System"]=2 ["Virtualization"]=3
|
||||
["Network"]=4 ["Storage"]=5 ["Security"]=6 ["Customization"]=7
|
||||
["Monitoring"]=8 ["Performance"]=9 ["Optional"]=10
|
||||
)
|
||||
|
||||
local options=(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : GPL-3.0
|
||||
# https://github.com/MacRimi/ProxMenux/blob/main/LICENSE
|
||||
# Version : 1.0
|
||||
# Version : 1.1
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# Reverses post-install optimizations previously applied by
|
||||
@@ -17,8 +17,8 @@
|
||||
#
|
||||
# Features:
|
||||
# - Registry-driven: only shows tools currently applied.
|
||||
# - Per-tool reverse functions (one for each entry in the
|
||||
# auto / customizable scripts).
|
||||
# - Per-tool reverse functions for each registered reversible entry
|
||||
# in the auto / customizable scripts.
|
||||
# - Restores /etc configs from .bak backups when they exist.
|
||||
# - Reboot prompt for changes that require it (VFIO, kernel
|
||||
# cmdline, persistent NIC names, …).
|
||||
@@ -112,34 +112,6 @@ uninstall_kexec() {
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_apt_upgrade() {
|
||||
msg_info "$(translate "Restoring enterprise repositories...")"
|
||||
|
||||
# Re-enable enterprise repos
|
||||
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ]; then
|
||||
sed -i "s/^#deb/deb/g" /etc/apt/sources.list.d/pve-enterprise.list
|
||||
fi
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/ceph.list ]; then
|
||||
sed -i "s/^#deb/deb/g" /etc/apt/sources.list.d/ceph.list
|
||||
fi
|
||||
|
||||
# Remove public repo
|
||||
rm -f /etc/apt/sources.list.d/pve-public-repo.list
|
||||
|
||||
# Remove firmware warning config
|
||||
rm -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf
|
||||
|
||||
apt-get update > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Enterprise repositories restored")"
|
||||
register_tool "apt_upgrade" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
|
||||
|
||||
uninstall_subscription_banner() {
|
||||
msg_info "$(translate "Restoring subscription banner...")"
|
||||
|
||||
@@ -226,9 +198,8 @@ uninstall_subscription_banner() {
|
||||
|
||||
#systemctl restart pveproxy pvedaemon pvestatd 2>/dev/null || true
|
||||
|
||||
register_tool "subscription_banner" false
|
||||
|
||||
if [[ "$restored" == true ]]; then
|
||||
register_tool "subscription_banner" false
|
||||
msg_ok "$(translate "Subscription banner restored successfully (desktop and mobile)")"
|
||||
msg_ok "$(translate "Refresh your browser to see changes (server restart may be required)")"
|
||||
else
|
||||
@@ -241,6 +212,147 @@ uninstall_subscription_banner() {
|
||||
|
||||
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_rpc() {
|
||||
local state_file="$BASE_DIR/rpcbind.state"
|
||||
local unit enabled_state active_state
|
||||
local failed=0
|
||||
|
||||
if [[ ! -s "$state_file" ]]; then
|
||||
msg_error "$(translate "The original rpcbind state is unavailable; no service state was changed")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Restoring the original rpcbind service state...")"
|
||||
while IFS='|' read -r unit enabled_state active_state; do
|
||||
[[ "$unit" != "rpcbind.service" && "$unit" != "rpcbind.socket" ]] && continue
|
||||
|
||||
case "$enabled_state" in
|
||||
enabled)
|
||||
systemctl enable "$unit" >/dev/null 2>&1 || failed=1
|
||||
;;
|
||||
enabled-runtime)
|
||||
systemctl enable --runtime "$unit" >/dev/null 2>&1 || failed=1
|
||||
;;
|
||||
masked)
|
||||
systemctl mask "$unit" >/dev/null 2>&1 || failed=1
|
||||
;;
|
||||
masked-runtime)
|
||||
systemctl mask --runtime "$unit" >/dev/null 2>&1 || failed=1
|
||||
;;
|
||||
*)
|
||||
systemctl disable "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$active_state" in
|
||||
active|activating|reloading)
|
||||
systemctl start "$unit" >/dev/null 2>&1 || failed=1
|
||||
;;
|
||||
*)
|
||||
systemctl stop "$unit" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
done < "$state_file"
|
||||
|
||||
if [[ "$failed" -ne 0 ]]; then
|
||||
msg_error "$(translate "The original rpcbind state could not be restored completely")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$state_file"
|
||||
register_tool "rpc" false
|
||||
msg_ok "$(translate "The original rpcbind service state has been restored")"
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_motd() {
|
||||
local state_file="$BASE_DIR/motd.state"
|
||||
local original_file="$BASE_DIR/motd.original"
|
||||
local motd_file="${PROXMENUX_MOTD_FILE:-/etc/motd}"
|
||||
local custom_message=" This system is optimised by: ProxMenux"
|
||||
local original_state
|
||||
|
||||
if [[ ! -f "$state_file" ]]; then
|
||||
msg_error "$(translate "The original MOTD state is unavailable; no changes were made")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
original_state="$(head -n 1 "$state_file" 2>/dev/null)"
|
||||
case "$original_state" in
|
||||
present)
|
||||
if [[ ! -f "$original_file" ]]; then
|
||||
msg_error "$(translate "The original MOTD backup is unavailable; no changes were made")"
|
||||
return 1
|
||||
fi
|
||||
cp -a "$original_file" "$motd_file"
|
||||
;;
|
||||
absent)
|
||||
rm -f "$motd_file"
|
||||
;;
|
||||
legacy-marker)
|
||||
if [[ -f "$motd_file" ]]; then
|
||||
sed -i "\|^${custom_message}$|d" "$motd_file"
|
||||
sed -i '/./,$!d' "$motd_file"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
msg_error "$(translate "The saved MOTD state is invalid; no changes were made")"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -f "$state_file" "$original_file"
|
||||
register_tool "motd" false
|
||||
msg_ok "$(translate "The original MOTD configuration has been restored")"
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_system_utils() {
|
||||
local state_file="$BASE_DIR/system_utils.packages"
|
||||
local remaining_file="${state_file}.remaining.$$"
|
||||
local package
|
||||
local packages=()
|
||||
|
||||
if [[ ! -s "$state_file" ]]; then
|
||||
msg_error "$(translate "No ProxMenux-installed utility package list is available")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS= read -r package; do
|
||||
[[ "$package" =~ ^[a-z0-9][a-z0-9+.-]*(:[a-z0-9]+)?$ ]] || continue
|
||||
packages+=("$package")
|
||||
done < "$state_file"
|
||||
|
||||
if [[ ${#packages[@]} -eq 0 ]]; then
|
||||
msg_error "$(translate "The saved utility package list is invalid; no packages were removed")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Removing utilities installed by ProxMenux...")"
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}" >/dev/null 2>&1 || true
|
||||
|
||||
: > "$remaining_file"
|
||||
for package in "${packages[@]}"; do
|
||||
if dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q '^install ok installed$'; then
|
||||
printf '%s\n' "$package" >> "$remaining_file"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -s "$remaining_file" ]]; then
|
||||
mv "$remaining_file" "$state_file"
|
||||
msg_error "$(translate "Some utility packages could not be removed; the remaining list has been preserved")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$remaining_file" "$state_file"
|
||||
register_tool "system_utils" false
|
||||
msg_ok "$(translate "Utilities installed by ProxMenux have been removed")"
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_time_sync() {
|
||||
@@ -835,7 +947,9 @@ uninstall_ceph() {
|
||||
apt-get purge -y 'ceph-*' 'librados*' 'librbd*' 'libcephfs*' 'python3-ceph*' >/dev/null 2>&1 || true
|
||||
apt-get autoremove -y >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f /etc/apt/sources.list.d/ceph.list /etc/apt/sources.list.d/ceph.sources 2>/dev/null
|
||||
rm -f /etc/apt/sources.list.d/ceph.list \
|
||||
/etc/apt/sources.list.d/ceph-squid.list \
|
||||
/etc/apt/sources.list.d/ceph.sources 2>/dev/null
|
||||
rm -f /etc/apt/trusted.gpg.d/ceph.asc /etc/apt/trusted.gpg.d/ceph-release.gpg 2>/dev/null
|
||||
apt-get update -qq >/dev/null 2>&1 || true
|
||||
msg_ok "$(translate 'Ceph packages and repository removed')"
|
||||
@@ -1038,10 +1152,10 @@ show_uninstall_menu() {
|
||||
local menu_options=()
|
||||
for tool in "${tools_installed[@]}"; do
|
||||
case "$tool" in
|
||||
lvm_repair) desc="LVM PV Headers Repair";;
|
||||
repo_cleanup) desc="Repository Cleanup";;
|
||||
#apt_upgrade) desc="APT Upgrade & Repository Config";;
|
||||
subscription_banner) desc="Subscription Banner Removal";;
|
||||
rpc) desc="RPC / rpcbind Disable";;
|
||||
motd) desc="Custom MOTD Banner";;
|
||||
system_utils) desc="System Utilities installed by ProxMenux";;
|
||||
time_sync) desc="Time Synchronization";;
|
||||
apt_languages) desc="APT Language Skip";;
|
||||
journald) desc="Journald Optimization";;
|
||||
|
||||
+4
-12
@@ -51,6 +51,7 @@ DARK_GRAY="\033[38;5;244m"
|
||||
ORANGE="\033[38;5;208m"
|
||||
YW="\033[33m"
|
||||
YWB="\033[1;33m"
|
||||
MG="\033[35m"
|
||||
GN="\033[1;92m"
|
||||
RD="\033[01;31m"
|
||||
CL="\033[m"
|
||||
@@ -74,8 +75,8 @@ spinner() {
|
||||
local interval=0.1
|
||||
printf "\e[?25l"
|
||||
|
||||
local color="${YW}"
|
||||
|
||||
local color="${MG}"
|
||||
|
||||
while true; do
|
||||
printf "\r ${color}%s${CL}" "${frames[spin_i]}"
|
||||
spin_i=$(( (spin_i + 1) % ${#frames[@]} ))
|
||||
@@ -118,19 +119,10 @@ stop_spinner() {
|
||||
SPINNER_PID=""
|
||||
}
|
||||
|
||||
# Display trnaslate message with spinner
|
||||
msg_lang() {
|
||||
local msg="$1"
|
||||
echo -ne "${TAB}${YW}${HOLD}${msg}"
|
||||
spinner &
|
||||
SPINNER_PID=$!
|
||||
}
|
||||
|
||||
|
||||
# Display info message with spinner
|
||||
msg_info() {
|
||||
local msg="$1"
|
||||
echo -ne "${TAB}${YW}${HOLD}${msg}"
|
||||
echo -ne "${TAB}${MG}${HOLD}${msg}"
|
||||
spinner &
|
||||
SPINNER_PID=$!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user