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:
MacRimi
2026-08-26 17:23:09 +02:00
parent b71dd65898
commit fcfe8da765
106 changed files with 3376 additions and 1358 deletions
+246 -86
View File
@@ -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)
}
}
+1
View File
@@ -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
View File
@@ -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
}
+72 -44
View File
@@ -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
View File
@@ -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
+99 -203
View File
@@ -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
+16 -6
View File
@@ -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"
}
+143 -50
View File
@@ -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=(
+151 -37
View File
@@ -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
View File
@@ -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=$!
}