747 字
4 分钟
TrueNAS Build Nvidia vGPU Driver extensions (systemd-sysext)
2025-06-27

Running TrueNAS on Proxmox VE is the great way to get great VM and storage management at same time, especially for homelab users. Because TrueNAS’s built-in VM management is difficult to use, offers limited VM options, and doesn’t allow for system setting modifications. While it uses Incus as its VM backend after 25.04, the TrueNAS WebUI and middleware still need a lot of work.

And vGPU has become a prevalent method for enabling VMs on Proxmox VE to utilize a GPU card for video encoding and decoding. However, TrueNAS, when run as a VM guest, defaults to using standard Nvidia drivers, not the specialized Grid drivers (NVIDIA’s commercial drivers). Consequently, while some Tesla cards might correctly load nvidia-smi, they often can’t actually use their encoding and decoding units with these default drivers.

The good news is that starting with TrueNAS SCALE 24.10+, TrueNAS now uses systemd-sysext to load Nvidia drivers that are pre-packaged during compilation. This extension can load when needed, Which also means you can overwrite and install any custom driver without using install-dev-tools, avoiding system modifications. Additionally, recovery is quick after TrueNAS updates.


How It Works#

The core principle behind the packaged extension source code is quite straightforward:

  1. It compares the contents of self.chroot and self.chroot_base directories.
  2. It extracts any new or modified files.
  3. Only files under usr/ (excluding usr/src/) are kept; others are deleted.
  4. It cleans up self.chroot by removing files and empty directories that don’t belong to the extension.
  5. An extension-release.d file is added to identify the extension.
  6. Finally, self.chroot is packaged into a SquashFS file and saved to dst_path.

Here’s the Python code:

def build_extension(self, name, dst_path):
changed_files = [
os.path.relpath(filename, self.chroot)
for filename in map(
lambda filename: os.path.join(os.getcwd(), filename),
run(
["rsync", "-avn", "--out-format=%f", f"{self.chroot}/", f"{self.chroot_base}/"],
log=False,
).stdout.split("\n")
)
if os.path.abspath(filename).startswith(os.path.abspath(self.chroot))
]
sysext_files = [f for f in changed_files if f.startswith("usr/") and not (f.startswith("usr/src/"))]
for root, dirs, files in os.walk(self.chroot, topdown=False):
for f in files:
path = os.path.relpath(os.path.abspath(os.path.join(root, f)), self.chroot)
if path not in sysext_files:
os.unlink(os.path.join(root, f))
for d in dirs:
try:
os.rmdir(os.path.join(root, d))
except NotADirectoryError:
os.unlink(os.path.join(root, d)) # It's a symlink
except OSError as e:
if e.errno == errno.ENOTEMPTY:
pass
else:
raise
os.makedirs(f"{self.chroot}/usr/lib/extension-release.d", exist_ok=True)
with open(f"{self.chroot}/usr/lib/extension-release.d/extension-release.{name}", "w") as f:
f.write("ID=_any\n")
run(["mksquashfs", self.chroot, dst_path, "-comp", "xz"])

Specifically for the Nvidia driver, this is the relevant part:

def download_nvidia_driver(self):
prefix = "https://us.download.nvidia.com/XFree86/Linux-x86_64"
version = get_manifest()["extensions"]["nvidia"]["current"]
filename = f"NVIDIA-Linux-x86_64-{version}-no-compat32.run"
result = f"{self.chroot}/{filename}"
self.run(["wget", "-c", "-O", f"/{filename}", f"{prefix}/{version}/{filename}"])
os.chmod(result, 0o755)
return result
def install_nvidia_driver(self, kernel_version):
driver = self.download_nvidia_driver()
self.run([f"/{os.path.basename(driver)}", "--skip-module-load", "--silent", f"--kernel-name={kernel_version}",
"--allow-installation-with-running-driver", "--no-rebuild-initramfs"])
os.unlink(driver)

So, modifying this is actually quite simple:

  • Replace the download link.
  • Adjust the execution options (Grid drivers have different options).

PS: When choosing drivers, pay attention to kernel compatibility. For instance, with the current 25.04 version (kernel version 6.12.15), if you’re using a 16.x driver (the last usable driver for Tesla P4), your grid driver version needs to be greater than 16.9.

Here’s how the modified code would look:

def download_nvidia_driver(self):
# i don't where i can find the grid driver
prefix = "<put your grid download url without filename in here>"
#useless
#version = get_manifest()["extensions"]["nvidia"]["current"]
filename = f"<filename of your grid driver>"
result = f"{self.chroot}/{filename}"
self.run(["wget", "-c", "-O", f"/{filename}", f"{prefix}/{filename}"])
os.chmod(result, 0o755)
return result
def install_nvidia_driver(self, kernel_version):
driver = self.download_nvidia_driver()
# fix option
self.run([f"/{os.path.basename(driver)}", "--silent", f"--kernel-name={kernel_version}"])
os.unlink(driver)

Building the Extension#

In here, I’ll use 25.04.0 as an example.

The official documentation already provides some hints, but here are some key things to note:

Install Dependencies#

(Nothing need to notice)

Terminal window
sudo apt install build-essential debootstrap git python3-pip python3-venv squashfs-tools unzip libjson-perl rsync libarchive-tools

Select the Tags Branch#

Use the tag branch instead of release/xxxxx

Terminal window
# tag name is TS-<version>
git clone -b TS-25.04.0 https://github.com/truenas/scale-build.git

Configure Environment Variables#

The code reads environment variables to determine the compiled “version” and “Train”. So, if you don’t specify them, you’ll only be able to compile the Master train.

Terminal window
export TRUENAS_TRAIN="TrueNAS-SCALE-Fangtooth"
export TRUENAS_VERSION="25.04.0"

Start the Build#

Terminal window
make checkout
make packages
make update

Once the build is complete, you’ll need to mount the compiled rootfs.squashfs and extract its contents.

Terminal window
mkdir -p ./tmpfile/rootfs
mount ./tmp/update/rootfs.squashfs ./tmpfile/rootfs

At this point, your nvidia.raw extension should be available:

Terminal window
ls -al ./tmpfile/rootfs/usr/share/truenas/sysext-extensions/nvidia.raw

Overwriting the Existing Driver#

You’ll need to replace the nvidia.raw file on your running TrueNAS system at /usr/share/truenas/sysext-extensions/nvidia.raw with the one you just compiled.

First, you need to make the /usr dataset writable:

Terminal window
zfs set readonly=off boot-pool/ROOT/<system version>/usr
# For 25.04.0, it would be:
zfs set readonly=off boot-pool/ROOT/25.04.0/usr

PS: If you’ve previously enabled the Nvidia driver via the WebUI, you might need to run systemd-sysext unmerge first. This is because systemd-sysext will set /usr as read-only.

Overwrite it!

cp /the-path-you-upload/nvidia.raw /usr/share/truenas/sysext-extensions/nvidia.raw

After you’ve copied the file, simply run:

Terminal window
systemd-sysext merge
# Don't forget to restart docker service
systemctl restart docker

And, It should work.

nvidia-smi
TrueNAS Build Nvidia vGPU Driver extensions (systemd-sysext)
https://www.homelabproject.cc/posts/truenas/truenas-build-nvidia-vgpu-driver-extensions-systemd-sysext/
作者
Channing He
发布于
2025-06-27
许可协议
CC BY-NC-SA 4.0