From caeeb92e7c3b32d713bf7b7d3d4ecb6aec1e6767 Mon Sep 17 00:00:00 2001 From: Chris Moriarty <1413232+cwmoriarty@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:23:45 -0400 Subject: [PATCH 1/2] feat(tools/pve): add gpu-passthrough helper script --- tools/pve/gpu-passthrough.sh | 463 +++++++++++++++++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 tools/pve/gpu-passthrough.sh diff --git a/tools/pve/gpu-passthrough.sh b/tools/pve/gpu-passthrough.sh new file mode 100644 index 0000000000..a7a2104d2c --- /dev/null +++ b/tools/pve/gpu-passthrough.sh @@ -0,0 +1,463 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +set -Eeuo pipefail + +function header_info() { + clear + cat <<"EOF" + __________ __ __ ____ __ __ __ + / ____/ __ \/ / / / / __ \____ ______/ /_/ /_ _________ __ ______ _/ /_ + / / __/ /_/ / / / / / /_/ / __ `/ ___/ __/ __ \/ ___/ __ \/ / / / __ `/ __ \ +/ /_/ / ____/ /_/ / / ____/ /_/ (__ ) /_/ / / / / / /_/ / /_/ / /_/ / / / / +\____/_/ \____/ /_/ \__,_/____/\__/_/ /_/_/ \____/\__,_/\__, /_/ /_/ + /____/ +EOF +} + +RD="\033[01;31m" +YW="\033[33m" +GN="\033[1;92m" +BL="\033[36m" +CL="\033[m" +BFR="\r\033[K" +HOLD="-" +CM="${GN}✓${CL}" +CROSS="${RD}✗${CL}" +WARN="${YW}!${CL}" + +msg_info() { echo -e " ${HOLD} ${YW}$1...${CL}"; } +msg_ok() { echo -e "${BFR} ${CM} ${GN}$1${CL}"; } +msg_error() { echo -e "${BFR} ${CROSS} ${RD}$1${CL}"; } +msg_warn() { echo -e " ${WARN} ${YW}$1${CL}"; } + +# Telemetry +source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/api/api.func") 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "gpu-passthrough" "pve" + +VFIO_CONF="/etc/modprobe.d/vfio.conf" +GRUB_CONF="/etc/default/grub" +MODULES_CONF="/etc/modules" +BACKUP_SUFFIX=".bak.$(date +%Y%m%d%H%M%S)" + +# Check root and PVE environment +if [ "$(id -u)" -ne 0 ]; then + header_info + msg_error "This script must be run as root." + exit 1 +fi + +if ! command -v pveversion >/dev/null 2>&1; then + header_info + msg_error "No Proxmox VE Detected!" + exit 1 +fi + +backup_file() { + local f="$1" + if [[ -f "$f" ]]; then + cp "$f" "${f}${BACKUP_SUFFIX}" + msg_ok "Backed up $f -> ${f}${BACKUP_SUFFIX}" + fi +} + +ensure_vfio_conf() { + if [[ ! -f "$VFIO_CONF" ]]; then + touch "$VFIO_CONF" + fi +} + +grub_add_param() { + local param="$1" + if grep -q "GRUB_CMDLINE_LINUX_DEFAULT=" "$GRUB_CONF"; then + if ! grep -q "$param" "$GRUB_CONF"; then + sed -i "s/\(GRUB_CMDLINE_LINUX_DEFAULT=\"[^\"]*\)/\1 ${param}/" "$GRUB_CONF" + msg_ok "Added '$param' to GRUB cmdline" + else + msg_warn "'$param' already present in GRUB cmdline" + fi + else + msg_error "GRUB_CMDLINE_LINUX_DEFAULT not found in $GRUB_CONF" + return 1 + fi +} + +grub_remove_param() { + local param="$1" + if grep -q "$param" "$GRUB_CONF"; then + sed -i "s/ ${param}//" "$GRUB_CONF" + msg_ok "Removed '$param' from GRUB cmdline" + fi +} + +add_module() { + local mod="$1" + if ! grep -qx "$mod" "$MODULES_CONF"; then + echo "$mod" >> "$MODULES_CONF" + msg_ok "Added module $mod to $MODULES_CONF" + fi +} + +remove_module() { + local mod="$1" + sed -i "/^${mod}$/d" "$MODULES_CONF" +} + +add_softdep() { + local driver="$1" + local line="softdep ${driver} pre: vfio-pci" + ensure_vfio_conf + if ! grep -qxF "$line" "$VFIO_CONF"; then + echo "$line" >> "$VFIO_CONF" + fi +} + +remove_softdep() { + local driver="$1" + if [[ -f "$VFIO_CONF" ]]; then + sed -i "/^softdep ${driver} pre: vfio-pci$/d" "$VFIO_CONF" + fi +} + +check_virtualization() { + header_info + msg_info "Checking CPU Virtualization and IOMMU" + + local virt_type + virt_type=$(lscpu | grep -i virtualization || true) + + local virt_status="Not enabled" + if [[ "$virt_type" == *'VT-x'* ]]; then + virt_status="Intel VT-x enabled" + elif [[ "$virt_type" == *'AMD-V'* ]]; then + virt_status="AMD-V enabled" + fi + + local iommu_status="Not detected in dmesg" + if dmesg 2>/dev/null | grep -qE "DMAR-IR: Enabled IRQ remapping|AMD-Vi: Interrupt remapping enabled|IOMMU enabled|DMAR:.*IOMMU enabled"; then + iommu_status="IOMMU / Interrupt remapping enabled" + fi + + local group_count=0 + if [[ -d /sys/kernel/iommu_groups ]]; then + group_count=$(find /sys/kernel/iommu_groups -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l) + fi + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "Virtualization & IOMMU Status" --msgbox \ + "CPU Virtualization: ${virt_status}\nIOMMU Remapping: ${iommu_status}\nIOMMU Groups Found: ${group_count}\n\nNote: If IOMMU is missing, check BIOS/UEFI (VT-d / AMD-Vi) and ensure kernel IOMMU flags are active." 14 70 +} + +enable_passthrough() { + header_info + + # Clear warning about VM passthrough vs LXC container usage + local warning_text="This tool configures vfio-pci to dedicate your GPU exclusively to Virtual Machines.\n\n" + warning_text+="WARNING:\n" + warning_text+="Do NOT use this if you want to share your GPU with LXC containers (for example, running Ollama or Plex via LXC),\n" + warning_text+="as VFIO unbinds the host driver and disables access to /dev/dri and /dev/kfd.\n\n" + warning_text+="Do you want to proceed with enabling GPU passthrough?" + + if ! whiptail --backtitle "Proxmox VE Helper Scripts" --title "GPU Passthrough Warning" --yesno "$warning_text" 16 76; then + return + fi + + header_info + msg_info "Configuring GRUB parameters" + backup_file "$GRUB_CONF" + + if lscpu | grep -q Intel; then + grub_add_param "intel_iommu=on" + elif lscpu | grep -q AMD; then + grub_add_param "amd_iommu=on" + fi + grub_add_param "iommu=pt" + + msg_info "Updating GRUB" + /usr/sbin/update-grub >/dev/null 2>&1 + msg_ok "Updated GRUB" + + msg_info "Configuring VFIO kernel modules" + backup_file "$MODULES_CONF" + add_module "vfio" + add_module "vfio_iommu_type1" + add_module "vfio_pci" + if [[ -f "/lib/modules/$(uname -r)/kernel/drivers/vfio/vfio_virqfd.ko" ]] || \ + [[ -f "/lib/modules/$(uname -r)/kernel/drivers/vfio/vfio_virqfd.ko.zst" ]]; then + add_module "vfio_virqfd" + fi + msg_ok "VFIO modules configured in $MODULES_CONF" + + msg_info "Scanning GPU PCI devices" + ensure_vfio_conf + backup_file "$VFIO_CONF" + + local lspci_output + lspci_output=$(lspci -nn) + + local device_ids + device_ids=$(echo "$lspci_output" | grep -Ei "vga|3d|display|audio" \ + | grep -i -e 'nvidia' -e 'AMD/ATI' \ + | sed -n 's/.*\[\([0-9a-fA-F]*:[0-9a-fA-F]*\)\].*/\1/p' \ + | paste -sd ',' -) + + if [[ -n "$device_ids" ]]; then + msg_ok "Detected GPU PCI IDs: ${BL}${device_ids}${CL}" + sed -i "/^options vfio-pci ids=/d" "$VFIO_CONF" + echo "options vfio-pci ids=${device_ids}" >> "$VFIO_CONF" + else + msg_warn "No NVIDIA or AMD GPUs automatically detected" + fi + + if echo "$lspci_output" | grep -Ei "vga|3d|display|audio" | grep -qi 'nvidia'; then + msg_info "Adding NVIDIA driver softdeps" + for drv in nouveau nvidia nvidiafb nvidia_drm drm; do + remove_softdep "$drv" + add_softdep "$drv" + done + msg_ok "Added NVIDIA softdeps" + fi + + if echo "$lspci_output" | grep -Ei "vga|3d|display|audio" | grep -qi 'AMD/ATI'; then + msg_info "Adding AMD driver softdeps" + for drv in radeon amdgpu snd_hda_intel; do + remove_softdep "$drv" + add_softdep "$drv" + done + msg_ok "Added AMD softdeps" + fi + + msg_info "Updating initramfs (this may take a minute)" + /usr/sbin/update-initramfs -u >/dev/null 2>&1 + msg_ok "Updated initramfs" + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "Setup Complete" --msgbox \ + "GPU passthrough configuration is complete!\n\nDetected IDs: ${device_ids:-None}\nConfig file: $VFIO_CONF\n\nPlease reboot the Proxmox host for changes to take effect." 12 70 +} + +verify_passthrough() { + header_info + msg_info "Verifying VFIO driver bindings" + + local gpu_status + gpu_status=$(lspci -nnk 2>/dev/null | grep -A2 -e NVIDIA -e 'AMD/ATI' || true) + + if echo "$gpu_status" | grep -q 'vfio-pci'; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "GPU Passthrough Status: ACTIVE" --msgbox \ + "vfio-pci is bound to your GPU:\n\n${gpu_status}" 18 76 + else + whiptail --backtitle "Proxmox VE Helper Scripts" --title "GPU Passthrough Status: NOT BOUND" --msgbox \ + "vfio-pci is NOT currently bound to the GPU.\n\nCurrent status:\n${gpu_status:-No GPU detected}\n\nIf you recently ran setup, make sure you rebooted the host." 18 76 + fi +} + +compile_vbios() { + header_info + + if ! command -v gcc >/dev/null 2>&1; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "Missing Dependency" --msgbox \ + "gcc is required to compile the ACPI VBIOS extractor.\nPlease run: apt-get install -y build-essential" 10 65 + return + fi + + if [[ ! -f /sys/firmware/acpi/tables/VFCT ]]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "VFCT Table Missing" --msgbox \ + "/sys/firmware/acpi/tables/VFCT was not found.\nThis table is typically only available on systems with AMD GPUs." 10 68 + return + fi + + if ! whiptail --backtitle "Proxmox VE Helper Scripts" --title "Compile AMD VBIOS" --yesno \ + "Extract AMD VBIOS ROM from ACPI VFCT table to /usr/share/kvm/?" 10 65; then + return + fi + + local tmpdir + tmpdir=$(mktemp -d /tmp/vbios_extract.XXXXXX) + + cat << 'VBIOS_EOF' > "${tmpdir}/vbios.c" +#include +#include +#include + +typedef uint32_t ULONG; +typedef uint8_t UCHAR; +typedef uint16_t USHORT; + +typedef struct { + ULONG Signature; + ULONG TableLength; + UCHAR Revision; + UCHAR Checksum; + UCHAR OemId[6]; + UCHAR OemTableId[8]; + ULONG OemRevision; + ULONG CreatorId; + ULONG CreatorRevision; +} AMD_ACPI_DESCRIPTION_HEADER; + +typedef struct { + AMD_ACPI_DESCRIPTION_HEADER SHeader; + UCHAR TableUUID[16]; + ULONG VBIOSImageOffset; + ULONG Lib1ImageOffset; + ULONG Reserved[4]; +} UEFI_ACPI_VFCT; + +typedef struct { + ULONG PCIBus; + ULONG PCIDevice; + ULONG PCIFunction; + USHORT VendorID; + USHORT DeviceID; + USHORT SSVID; + USHORT SSID; + ULONG Revision; + ULONG ImageLength; +} VFCT_IMAGE_HEADER; + +typedef struct { + VFCT_IMAGE_HEADER VbiosHeader; + UCHAR VbiosContent[1]; +} GOP_VBIOS_CONTENT; + +int main(int argc, char** argv) { + FILE* fp_vfct; + FILE* fp_vbios; + UEFI_ACPI_VFCT* pvfct; + char vbios_name[0x400]; + + if (!(fp_vfct = fopen("/sys/firmware/acpi/tables/VFCT", "r"))) { + perror(argv[0]); + return -1; + } + if (!(pvfct = malloc(sizeof(UEFI_ACPI_VFCT)))) { + perror(argv[0]); + return -1; + } + if (sizeof(UEFI_ACPI_VFCT) != fread(pvfct, 1, sizeof(UEFI_ACPI_VFCT), fp_vfct)) { + fprintf(stderr, "%s: failed to read VFCT header!\n", argv[0]); + return -1; + } + + ULONG offset = pvfct->VBIOSImageOffset; + ULONG tbl_size = pvfct->SHeader.TableLength; + + if (!(pvfct = realloc(pvfct, tbl_size))) { + perror(argv[0]); + return -1; + } + if (tbl_size - sizeof(UEFI_ACPI_VFCT) != fread(pvfct + 1, 1, tbl_size - sizeof(UEFI_ACPI_VFCT), fp_vfct)) { + fprintf(stderr, "%s: failed to read VFCT body!\n", argv[0]); + return -1; + } + fclose(fp_vfct); + + while (offset < tbl_size) { + GOP_VBIOS_CONTENT* vbios = (GOP_VBIOS_CONTENT*)((char*)pvfct + offset); + VFCT_IMAGE_HEADER* vhdr = &vbios->VbiosHeader; + if (!vhdr->ImageLength) break; + + snprintf(vbios_name, sizeof(vbios_name), "vbios_%x_%x.bin", vhdr->VendorID, vhdr->DeviceID); + if (!(fp_vbios = fopen(vbios_name, "wb"))) { + perror(argv[0]); + return -1; + } + if (vhdr->ImageLength != fwrite(&vbios->VbiosContent, 1, vhdr->ImageLength, fp_vbios)) { + fprintf(stderr, "%s: failed to dump vbios %x:%x\n", argv[0], vhdr->VendorID, vhdr->DeviceID); + return -1; + } + fclose(fp_vbios); + printf("Dumped vbios %x:%x -> %s\n", vhdr->VendorID, vhdr->DeviceID, vbios_name); + offset += sizeof(VFCT_IMAGE_HEADER); + offset += vhdr->ImageLength; + } + return 0; +} +VBIOS_EOF + + msg_info "Compiling and extracting VBIOS" + gcc "${tmpdir}/vbios.c" -o "${tmpdir}/vbios" + pushd "$tmpdir" >/dev/null + ./vbios + popd >/dev/null + + local count=0 + for bin in "${tmpdir}"/vbios_*.bin; do + [[ -f "$bin" ]] || continue + local bname + bname=$(basename "$bin") + cp "$bin" "/usr/share/kvm/${bname}" + count=$((count + 1)) + done + rm -rf "$tmpdir" + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "VBIOS Extraction Complete" --msgbox \ + "Extracted ${count} VBIOS ROM file(s) into /usr/share/kvm/.\n\nYou can use them in Proxmox VM hardware options or with 'romfile='." 12 70 +} + +revert_changes() { + header_info + + if ! whiptail --backtitle "Proxmox VE Helper Scripts" --title "Revert Passthrough Changes" --yesno \ + "This will remove VFIO kernel parameters from GRUB, /etc/modules, and /etc/modprobe.d/vfio.conf.\n\nProceed?" 12 70; then + return + fi + + header_info + msg_info "Reverting GRUB parameters" + backup_file "$GRUB_CONF" + grub_remove_param "iommu=pt" + grub_remove_param "intel_iommu=on" + grub_remove_param "amd_iommu=on" + /usr/sbin/update-grub >/dev/null 2>&1 + msg_ok "Reverted GRUB" + + msg_info "Reverting modules" + backup_file "$MODULES_CONF" + remove_module "vfio" + remove_module "vfio_iommu_type1" + remove_module "vfio_pci" + remove_module "vfio_virqfd" + msg_ok "Reverted modules in $MODULES_CONF" + + if [[ -f "$VFIO_CONF" ]]; then + backup_file "$VFIO_CONF" + sed -i "/^options vfio-pci ids=/d" "$VFIO_CONF" + for drv in nouveau nvidia nvidiafb nvidia_drm drm radeon amdgpu snd_hda_intel; do + remove_softdep "$drv" + done + msg_ok "Cleaned $VFIO_CONF" + fi + + msg_info "Updating initramfs" + /usr/sbin/update-initramfs -u >/dev/null 2>&1 + msg_ok "Updated initramfs" + + whiptail --backtitle "Proxmox VE Helper Scripts" --title "Revert Complete" --msgbox \ + "GPU passthrough configuration has been reverted.\n\nPlease reboot the host for changes to take effect." 10 65 +} + +# Main interactive loop +while true; do + header_info + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE GPU Passthrough Helper" --menu \ + "Choose an option:" 16 70 6 \ + "1" "Check CPU Virtualization & IOMMU" \ + "2" "Enable GPU Passthrough (for VMs)" \ + "3" "Verify GPU Passthrough Status" \ + "4" "Compile AMD VBIOS ROM from ACPI" \ + "5" "Revert Passthrough Changes" \ + "6" "Exit" 3>&1 1>&2 2>&3) || exit 0 + + case "$CHOICE" in + 1) check_virtualization ;; + 2) enable_passthrough ;; + 3) verify_passthrough ;; + 4) compile_vbios ;; + 5) revert_changes ;; + 6) clear; exit 0 ;; + *) exit 0 ;; + esac +done From 40b03bf24856a4c1c70ee177562f9dde3d5de39a Mon Sep 17 00:00:00 2001 From: Chris Moriarty <1413232+cwmoriarty@users.noreply.github.com> Date: Mon, 9 Nov 2026 09:25:24 -0500 Subject: [PATCH 2/2] feat(json): add gpu-passthrough metadata --- json/gpu-passthrough.json | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 json/gpu-passthrough.json diff --git a/json/gpu-passthrough.json b/json/gpu-passthrough.json new file mode 100644 index 0000000000..1cc4157dbb --- /dev/null +++ b/json/gpu-passthrough.json @@ -0,0 +1,49 @@ +{ + "name": "GPU Passthrough", + "slug": "gpu-passthrough", + "categories": [ + 1 + ], + "date_created": "2026-10-17", + "type": "pve", + "updateable": false, + "privileged": false, + "interface_port": null, + "documentation": "https://pve.proxmox.com/wiki/PCI(e)_Passthrough", + "website": "https://community-scripts.org", + "repository": null, + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/proxmox.webp", + "description": "A helper script for configuring GPU passthrough (vfio-pci) for virtual machines on Proxmox VE. It checks CPU virtualization and IOMMU status, configures GRUB and VFIO kernel modules, binds detected GPU PCI devices, adds driver softdeps (NVIDIA/AMD), updates initramfs, extracts ACPI VFCT VBIOS for AMD GPUs, and provides verification and revert options.", + "install_methods": [ + { + "type": "default", + "script": "tools/pve/gpu-passthrough.sh", + "config_path": "/etc/modprobe.d/vfio.conf", + "resources": { + "cpu": null, + "ram": null, + "hdd": null, + "os": null, + "version": "PVE 8.x / 9.x" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Run this script directly in the Proxmox VE host shell as root.", + "type": "info" + }, + { + "text": "This tool configures vfio-pci to dedicate your GPU exclusively to Virtual Machines. Do not use this if you want to share your GPU with LXC containers (e.g., Plex or Ollama), as VFIO unbinds host drivers and disables /dev/dri and /dev/kfd.", + "type": "warning" + }, + { + "text": "A host reboot is required after enabling or reverting GPU passthrough for changes to take effect.", + "type": "info" + } + ] +}