ProxmoxVED/misc/passthrough.func
CanbiZ 333ac1edcd
Some checks failed
Bump build.func Revision / bump-revision (push) Has been cancelled
Automate VAAPI and NVIDIA setup inside LXC containers
Added direct installation of VAAPI and NVIDIA drivers inside LXC containers during setup, removing reliance on custom in-container scripts. The process now ensures required packages are installed and user permissions are set, improving automation and compatibility for supported distributions.
2025-09-22 13:59:34 +02:00

392 lines
12 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# passthrough.func — host-side passthrough logic (VAAPI & NVIDIA) for LXC
# This file ONLY touches host config (/etc/pve/lxc/<CTID>.conf) and whiptail.
# Inside-CT package setup lives in tools.func (hwaccel_setup_in_ct / nvidia_setup_in_ct).
# --------------------------- Common helpers -----------------------------------
_whiptail_dims() {
local n="$1" L="$2"
local maxW=$((L + 8))
((maxW < 70)) && maxW=70
((maxW > 100)) && maxW=100
local H=$((10 + n * 2))
((H > 22)) && H=22
echo "$H $maxW"
}
# ------------------------------ USB -------------------------------------------
usb_handle_passthrough() {
local CTID="$1" CTTYPE="$2"
local LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
[[ "$CTTYPE" != "0" ]] && return 0 # USB passthrough only for privileged CTs
cat <<EOF >>"$LXC_CONFIG"
# USB passthrough
lxc.cgroup2.devices.allow: a
lxc.cap.drop:
lxc.cgroup2.devices.allow: c 188:* rwm
lxc.cgroup2.devices.allow: c 189:* rwm
lxc.mount.entry: /dev/serial/by-id dev/serial/by-id none bind,optional,create=dir
lxc.mount.entry: /dev/ttyUSB0 dev/ttyUSB0 none bind,optional,create=file
lxc.mount.entry: /dev/ttyUSB1 dev/ttyUSB1 none bind,optional,create=file
lxc.mount.entry: /dev/ttyACM0 dev/ttyACM0 none bind,optional,create=file
lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create=file
EOF
}
# ------------------------------ VAAPI -----------------------------------------
_vaapi_gid() {
local g="$1" gid
gid="$(getent group "$g" | cut -d: -f3)"
if [[ -z "$gid" ]]; then
case "$g" in
video) echo 44 ;;
render) echo 104 ;;
*) echo 44 ;;
esac
else
echo "$gid"
fi
}
_vaapi_pairs() {
local seen=() by real id idx card pci pci_info name
shopt -s nullglob
for by in /dev/dri/by-path/*-render /dev/dri/renderD*; do
[[ -e "$by" ]] || continue
real="$(readlink -f "$by" || true)"
[[ -e "$real" ]] || continue
id="$(basename "$real")"
[[ " ${seen[*]} " == *" $id "* ]] && continue
seen+=("$id")
idx="${id#renderD}"
if [[ "$idx" =~ ^[0-9]+$ ]]; then
idx=$((idx - 128))
else
idx=0
fi
card="/dev/dri/card${idx}"
[[ -e "$card" ]] || card=""
if [[ "$by" == *"/by-path/"* ]]; then
pci="$(basename "$by" | sed -E 's/^pci-([0-9a-fA-F:.]+)-render/\1/')"
pci_info="$(lspci -nn 2>/dev/null | grep -i "${pci#0000:}" || true)"
name="${pci_info#*: }"
[[ -z "$name" ]] && name="GPU ${pci}"
else
name="DRM $(basename "$real")"
fi
label="$(basename "$real")"
[[ -n "$card" ]] && label+=" + $(basename "$card")"
label+=" ${name}"
printf "%s:%s\t%s\n" "$real" "$card" "$label"
done
shopt -u nullglob
}
vaapi_select_and_apply() {
local CTID="$1" CTTYPE="$2"
local LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
local pairs=() items=() maxlen=0 n h w
mapfile -t pairs < <(_vaapi_pairs)
if ((${#pairs[@]} == 0)); then
msg_warn "No VAAPI devices detected skipping."
return 0
fi
for p in "${pairs[@]}"; do
local devs="${p%%$'\t'*}"
local label="${p#*$'\t'}"
items+=("$devs" "$label" "OFF")
((${#label} > maxlen)) && maxlen=${#label}
done
n=$((${#items[@]} / 3))
read -r h w < <(_whiptail_dims "$n" "$maxlen")
if [[ "$CTTYPE" == "0" ]]; then
whiptail --title "VAAPI passthrough" --msgbox "\
VAAPI passthrough will be enabled.
• Privileged CT: full DRM access
• You may need to install drivers inside the CT (intel-media-driver, vainfo)." 12 "$w"
else
whiptail --title "VAAPI passthrough (unprivileged)" --msgbox "\
Unprivileged CT: VAAPI may be limited.
If it fails, consider a privileged CT. You may still need drivers inside the CT." 12 "$w"
fi
local SELECTED
SELECTED="$(
whiptail --title "VAAPI Device Selection" \
--checklist "Select GPU / VAAPI device(s) to passthrough:" "$h" "$w" "$((n > 6 ? 6 : n))" \
"${items[@]}" 3>&1 1>&2 2>&3
)" || {
msg_warn "VAAPI selection cancelled."
return 0
}
[[ -z "$SELECTED" ]] && {
msg_warn "No devices selected skipping."
return 0
}
local DID_MOUNT_DRI=0 idx=0
for dev in $SELECTED; do
dev="${dev%\"}"
dev="${dev#\"}"
IFS=":" read -r path card <<<"$dev"
for d in "$path" "$card"; do
[[ -n "$d" && -e "$d" ]] || continue
if [[ "$CTTYPE" == "0" ]]; then
if [[ "$DID_MOUNT_DRI" -eq 0 && -d /dev/dri ]]; then
echo "lxc.mount.entry: /dev/dri /dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG"
DID_MOUNT_DRI=1
fi
if mm=$(stat -c '%t:%T' "$d" 2>/dev/null | awk -F: '{printf "%d:%d","0x"$1,"0x"$2}'); then
echo "lxc.cgroup2.devices.allow: c $mm rwm" >>"$LXC_CONFIG"
echo "lxc.mount.entry: $d $d none bind,optional,create=file" >>"$LXC_CONFIG"
else
msg_warn "Could not stat $d skipping."
fi
else
local gid
if [[ "$d" =~ renderD ]]; then gid="$(_vaapi_gid render)"; else gid="$(_vaapi_gid video)"; fi
echo "dev${idx}: $d,gid=${gid}" >>"$LXC_CONFIG"
idx=$((idx + 1))
fi
done
done
# fallback for card0/card1 flip
if [[ "$CTTYPE" == "0" ]]; then
cat <<'EOF' >>"$LXC_CONFIG"
# VAAPI fallback: bind /dev/dri and allow 226:* to survive node flips
lxc.mount.entry: /dev/dri /dev/dri none bind,optional,create=dir
lxc.cgroup2.devices.allow: c 226:* rwm
EOF
fi
}
vaapi_handle_passthrough() {
local CTID="$1" CTTYPE="$2" APP="$3"
local VAAPI_APPS=(immich Channels Emby ErsatzTV Frigate Jellyfin Plex Scrypted Tdarr Unmanic Ollama FileFlows "Open WebUI" Tunarr Debian)
local is_vaapi_app=false
for a in "${VAAPI_APPS[@]}"; do [[ "$APP" == "$a" ]] && is_vaapi_app=true && break; done
if [[ "$CTTYPE" == "0" || "$is_vaapi_app" == "true" ]]; then
vaapi_select_and_apply "$CTID" "$CTTYPE"
# ensure CT is running
if ! pct status "$CTID" | grep -q running; then
pct start "$CTID"
for i in {1..10}; do
pct status "$CTID" | grep -q running && break
sleep 1
done
fi
# run apt install directly inside CT
pct exec "$CTID" -- bash -lc '
. /etc/os-release
case "$VERSION_CODENAME" in
trixie|noble)
apt-get update
apt-get install -y intel-media-va-driver-non-free ocl-icd-libopencl1 \
mesa-opencl-icd mesa-va-drivers libvpl2 vainfo intel-gpu-tools
;;
*)
apt-get update
apt-get install -y va-driver-all ocl-icd-libopencl1 \
mesa-opencl-icd mesa-va-drivers vainfo intel-gpu-tools
;;
esac
if [[ "'"$CTTYPE"'" == "0" ]]; then
adduser "$(id -un)" video || true
adduser "$(id -un)" render || true
fi
'
fi
}
# ----------------------------- NVIDIA -----------------------------------------
nvidia_passthrough_to_lxc() {
local CTID="$1" CTTYPE="${2:-0}"
local LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
local found=0 dev mm
for dev in /dev/nvidia0 /dev/nvidia1 /dev/nvidiactl /dev/nvidia-uvm /dev/nvidia-uvm-tools /dev/nvidia-modeset; do
[[ -e "$dev" ]] || continue
found=1
if mm="$(stat -c '%t:%T' "$dev" 2>/dev/null | awk -F: '{printf "%d:%d","0x"$1,"0x"$2}')"; then
echo "lxc.cgroup2.devices.allow: c $mm rwm" >>"$LXC_CONFIG"
echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG"
fi
done
if [[ "$found" -eq 0 ]]; then
msg_warn "No /dev/nvidia* devices found on host; skipping NVIDIA passthrough."
return 0
fi
# optional: expose /dev/dri for apps probing VAAPI; harmless with NVIDIA
if [[ -d /dev/dri && "$CTTYPE" == "0" ]]; then
echo "lxc.mount.entry: /dev/dri /dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG"
fi
msg_ok "NVIDIA devices mapped to CT ${CTID}"
}
nvidia_handle_passthrough() {
local CTID="$1" CTTYPE="$2" APP="$3"
compgen -G "/dev/nvidia*" >/dev/null || return 0
if whiptail --title "NVIDIA passthrough" \
--yesno "NVIDIA GPU detected. Map /dev/nvidia* into CT $CTID and install drivers inside?" 12 70; then
nvidia_passthrough_to_lxc "$CTID" "$CTTYPE"
if ! pct status "$CTID" | grep -q running; then
pct start "$CTID"
for i in {1..10}; do
pct status "$CTID" | grep -q running && break
sleep 1
done
fi
pct exec "$CTID" -- bash -lc '
. /etc/os-release
apt-get update
apt-get install -y nvidia-driver nvidia-utils libnvidia-encode1 libcuda1
if [[ "'"$CTTYPE"'" == "0" ]]; then
adduser "$(id -un)" video || true
fi
'
fi
}
# ------------------------------------------------------------------------------
# Hardware acceleration setup inside container (Intel/AMD via VAAPI)
# Debian 12 (bookworm), Debian 13 (trixie), Ubuntu 24.04 (noble)
# Usage: hwaccel_setup_in_ct <CTTYPE 0|1> [--nonfree-intel]
# ------------------------------------------------------------------------------
hwaccel_setup_in_ct() {
local CTTYPE="$1" NONFREE=0
[[ "$2" == "--nonfree-intel" ]] && NONFREE=1
local ID VERSION_CODENAME
if [[ -r /etc/os-release ]]; then . /etc/os-release; fi
ID="${ID:-debian}"
VERSION_CODENAME="${VERSION_CODENAME:-bookworm}"
msg_info "Setting up VAAPI userland for ${ID^} ($VERSION_CODENAME)"
case "$ID" in
debian | ubuntu)
if ((NONFREE)) && [[ "$VERSION_CODENAME" =~ (trixie|noble) ]]; then
# Debian 13 / Ubuntu 24.04 — enable non-free Intel media (Debian deb822)
if [[ "$VERSION_CODENAME" == "trixie" ]]; then
cat >/etc/apt/sources.list.d/non-free.sources <<'SRC'
Types: deb deb-src
URIs: http://deb.debian.org/debian
Suites: trixie
Components: non-free non-free-firmware
Types: deb deb-src
URIs: http://deb.debian.org/debian-security
Suites: trixie-security
Components: non-free non-free-firmware
Types: deb deb-src
URIs: http://deb.debian.org/debian
Suites: trixie-updates
Components: non-free non-free-firmware
SRC
fi
$STD apt-get update
$STD apt-get install -y \
intel-media-va-driver-non-free \
ocl-icd-libopencl1 \
mesa-opencl-icd \
mesa-va-drivers \
libvpl2 \
vainfo \
intel-gpu-tools
else
$STD apt-get update
$STD apt-get install -y \
va-driver-all \
ocl-icd-libopencl1 \
mesa-opencl-icd \
mesa-va-drivers \
vainfo \
intel-gpu-tools
fi
;;
*)
msg_warn "Unsupported distro ($ID $VERSION_CODENAME) skipping VAAPI setup."
return 0
;;
esac
if [[ "$CTTYPE" == "0" ]]; then
$STD adduser "$(id -un)" video || true
$STD adduser "$(id -un)" render || true
fi
msg_ok "VAAPI userland ready"
}
# ------------------------------------------------------------------------------
# NVIDIA userland inside container
# Debian 12/13, Ubuntu 24.04
# Usage: nvidia_setup_in_ct <CTTYPE 0|1>
# ------------------------------------------------------------------------------
nvidia_setup_in_ct() {
local CTTYPE="$1"
local ID VERSION_CODENAME
if [[ -r /etc/os-release ]]; then . /etc/os-release; fi
ID="${ID:-debian}"
VERSION_CODENAME="${VERSION_CODENAME:-bookworm}"
msg_info "Installing NVIDIA userland on ${ID^} ($VERSION_CODENAME)"
case "$ID" in
debian | ubuntu)
$STD apt-get update
$STD apt-get install -y \
nvidia-driver \
nvidia-utils \
libnvidia-encode1 \
libcuda1 || {
msg_error "Failed to install NVIDIA packages"
return 1
}
;;
*)
msg_warn "Unsupported distro ($ID $VERSION_CODENAME) skipping NVIDIA setup."
return 0
;;
esac
if [[ "$CTTYPE" == "0" ]]; then
$STD adduser "$(id -un)" video || true
fi
msg_ok "NVIDIA userland ready"
}