Last active
February 13, 2026 13:35
-
-
Save adujardin/9154a619cbf5a5508e5fad336839702b to your computer and use it in GitHub Desktop.
Screenless jetson with hw acceleration (including EGL, Argus, CUDA and remote nomachine screen)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ============================================================================= | |
| # Jetson Headless Virtual Display Setup (GPU-Accelerated) | |
| # ============================================================================= | |
| # | |
| # Creates a virtual 1920x1080@60Hz display on Jetson AGX Orin without a | |
| # physical monitor. EGL, CUDA, and NVIDIA Argus all work. A real monitor | |
| # can still be hot-plugged at any time. Works with NoMachine/VNC for remote | |
| # desktop access. | |
| # | |
| # Platform: Jetson AGX Orin | |
| # - JetPack 5.x (L4T R35.x, Ubuntu 20.04) — Xorg "nvidia" DDX driver | |
| # - JetPack 6.x (L4T R36.x, Ubuntu 22.04) — Xorg "nvidia" DDX driver | |
| # | |
| # HOW IT WORKS: | |
| # ------------- | |
| # Uses the nvidia DDX Xorg driver with ConnectedMonitor + CustomEDID options | |
| # to fake a connected 1920x1080 display on the DP port. This provides: | |
| # - Full NVIDIA EGL support (nvbufsurface, Argus camera can create EGLImages) | |
| # - CUDA-EGL interop (cuGraphicsEGLRegisterImage works) | |
| # - Hardware-accelerated X11/GLX rendering via NVIDIA GPU | |
| # - Proper DRI2 for remote desktop protocols (NoMachine, VNC, etc.) | |
| # | |
| # JP6-SPECIFIC NOTES: | |
| # ------------------- | |
| # nvidia-drm.modeset=1 is NOT compatible with nvidia DDX driver — it causes | |
| # "Failed to acquire modesetting permission". We remove it from boot params. | |
| # The nvidia DDX handles modesetting directly without nvidia-drm KMS. | |
| # | |
| # EGL/CUDA/Argus work fine WITHOUT nvidia-drm.modeset=1 when using nvidia DDX, | |
| # because applications access NVIDIA EGL/CUDA directly via the ICD, not through | |
| # the kernel DRM modesetting path. | |
| # | |
| # REAL MONITOR: | |
| # ------------- | |
| # If you plug in a real DP monitor, it appears automatically in xrandr | |
| # and is usable alongside (or instead of) the virtual framebuffer. | |
| # | |
| # USAGE: | |
| # chmod +x setup-virtual-display-fixed.sh | |
| # sudo ./setup-virtual-display-fixed.sh | |
| # sudo reboot | |
| # | |
| # VERIFY: | |
| # DISPLAY=:0 xrandr # 1920x1080 screen | |
| # DISPLAY=:0 eglinfo 2>&1 | head -15 # EGL vendor: NVIDIA | |
| # python3 -c "import ctypes; c=ctypes.CDLL('libcuda.so.1'); print(c.cuInit(0))" # 0 | |
| # # Connect via NoMachine — should show full GNOME desktop | |
| # | |
| # TO REVERT: | |
| # sudo cp /etc/X11/xorg.conf.bak.original /etc/X11/xorg.conf # or rm it | |
| # sudo rm -f /etc/X11/edid-1080p.bin | |
| # sudo rm -f /etc/udev/rules.d/99-jetson-drm-seat.rules | |
| # sudo systemctl enable seatd 2>/dev/null # if you need seatd back | |
| # # For JP6: add back nvidia-drm.modeset=1 to /boot/extlinux/extlinux.conf if needed | |
| # sudo reboot | |
| # | |
| # ============================================================================= | |
| set -euo pipefail | |
| if [[ $EUID -ne 0 ]]; then | |
| echo "ERROR: Must run as root (sudo)." | |
| exit 1 | |
| fi | |
| # --- Detect JetPack / L4T version --- | |
| detect_jetpack_version() { | |
| local l4t_major="" | |
| if [[ -f /etc/nv_tegra_release ]]; then | |
| l4t_major=$(sed -n 's/^# R\([0-9]*\).*/\1/p' /etc/nv_tegra_release) | |
| fi | |
| if [[ -z "$l4t_major" ]] && command -v dpkg-query &>/dev/null; then | |
| l4t_major=$(dpkg-query -W -f='${Version}' nvidia-l4t-core 2>/dev/null \ | |
| | sed -n 's/^\([0-9]*\)\..*/\1/p') || true | |
| fi | |
| if [[ -n "$l4t_major" && "$l4t_major" -ge 36 ]]; then | |
| echo 6 | |
| elif [[ -n "$l4t_major" && "$l4t_major" -ge 35 ]]; then | |
| echo 5 | |
| else | |
| echo 0 | |
| fi | |
| } | |
| # --- Detect first available DP connector --- | |
| detect_dp_connector_drm() { | |
| # DRM naming (1-indexed): DP-1, DP-2, etc. | |
| for card_dp in /sys/class/drm/card*-DP-*; do | |
| if [[ -d "$card_dp" ]]; then | |
| basename "$card_dp" | sed 's/card[0-9]*-//' | |
| return | |
| fi | |
| done | |
| echo "DP-1" | |
| } | |
| detect_dp_connector_xorg() { | |
| # nvidia DDX uses 0-indexed: DP-0, DP-1, etc. | |
| local drm_idx | |
| drm_idx=$(detect_dp_connector_drm | sed 's/.*DP-//') | |
| echo "DP-$((drm_idx - 1))" | |
| } | |
| JP_VERSION=$(detect_jetpack_version) | |
| DP_DRM=$(detect_dp_connector_drm) | |
| DP_XORG=$(detect_dp_connector_xorg) | |
| echo "=== Jetson Virtual Display Setup ===" | |
| if [[ "$JP_VERSION" -eq 6 ]]; then | |
| echo "Detected: JetPack 6 (L4T R36.x)" | |
| elif [[ "$JP_VERSION" -eq 5 ]]; then | |
| echo "Detected: JetPack 5 (L4T R35.x)" | |
| else | |
| echo "WARNING: Could not detect JetPack version, defaulting to JP5 path" | |
| JP_VERSION=5 | |
| fi | |
| # ========================================================================= | |
| # COMMON PATH (JP5 + JP6): nvidia DDX driver with ConnectedMonitor + CustomEDID | |
| # ========================================================================= | |
| echo "Using nvidia DDX driver with connector ${DP_XORG}" | |
| STEP=0 | |
| TOTAL_JP5=3 | |
| TOTAL_JP6=5 | |
| # ------------------------------------------------------------------ | |
| # Step 1: Backup xorg.conf | |
| # ------------------------------------------------------------------ | |
| STEP=$((STEP+1)) | |
| if [[ -f /etc/X11/xorg.conf && ! -f /etc/X11/xorg.conf.bak.original ]]; then | |
| cp /etc/X11/xorg.conf /etc/X11/xorg.conf.bak.original | |
| echo "[${STEP}] Backed up xorg.conf" | |
| else | |
| echo "[${STEP}] Backup: skipped (already exists or no file)" | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Step 2: EDID binary | |
| # ------------------------------------------------------------------ | |
| STEP=$((STEP+1)) | |
| base64 -d > /etc/X11/edid-1080p.bin << 'EDID_EOF' | |
| AP///////wBZ5QEAAQAAAAEiAQOAAAB4Cu6Ro1RMmSYPUFQhCAABAQEBAQEBAQEBAQEBAQEBAjqA | |
| GHE4LUBYLEUADyghAAAeAAAA/ABWaXJ0dWFsIDEwODBwAAAA/QAySx5REQAKICAgICAgAAAA/wAw | |
| MDAwMDAwMDAwMDAxAJc= | |
| EDID_EOF | |
| chmod 644 /etc/X11/edid-1080p.bin | |
| echo "[${STEP}] Installed EDID binary" | |
| # ------------------------------------------------------------------ | |
| # Step 3: xorg.conf (nvidia DDX) | |
| # ------------------------------------------------------------------ | |
| STEP=$((STEP+1)) | |
| cat > /etc/X11/xorg.conf << XORG_EOF | |
| # Jetson JP${JP_VERSION} - nvidia DDX driver, virtual display | |
| Section "DRI" | |
| Mode 0666 | |
| EndSection | |
| Section "Module" | |
| Disable "dri" | |
| SubSection "extmod" | |
| Option "omit xfree86-dga" | |
| EndSubSection | |
| EndSection | |
| Section "Device" | |
| Identifier "Tegra0" | |
| Driver "nvidia" | |
| Option "AllowEmptyInitialConfiguration" "true" | |
| Option "ConnectedMonitor" "${DP_XORG}" | |
| Option "CustomEDID" "${DP_XORG}:/etc/X11/edid-1080p.bin" | |
| Option "HardDPMS" "false" | |
| EndSection | |
| Section "Monitor" | |
| Identifier "Virtual-1080p" | |
| HorizSync 30-81 | |
| VertRefresh 50-75 | |
| ModeLine "1920x1080_60" 148.50 1920 2008 2052 2200 1080 1084 1089 1125 +HSync +VSync | |
| Option "PreferredMode" "1920x1080_60" | |
| EndSection | |
| Section "Screen" | |
| Identifier "Default Screen" | |
| Device "Tegra0" | |
| Monitor "Virtual-1080p" | |
| DefaultDepth 24 | |
| SubSection "Display" | |
| Depth 24 | |
| Modes "1920x1080_60" | |
| EndSubSection | |
| EndSection | |
| XORG_EOF | |
| echo "[${STEP}] Wrote xorg.conf (nvidia DDX)" | |
| # ------------------------------------------------------------------ | |
| # JP6-SPECIFIC: Additional steps | |
| # ------------------------------------------------------------------ | |
| if [[ "$JP_VERSION" -eq 6 ]]; then | |
| # ------------------------------------------------------------------ | |
| # Step 4: Remove nvidia-drm.modeset=1 from boot params | |
| # ------------------------------------------------------------------ | |
| STEP=$((STEP+1)) | |
| EXTLINUX_CONF="/boot/extlinux/extlinux.conf" | |
| if [[ -f "$EXTLINUX_CONF" ]]; then | |
| if grep -q "nvidia-drm.modeset=1" "$EXTLINUX_CONF"; then | |
| cp "$EXTLINUX_CONF" "${EXTLINUX_CONF}.bak.$(date +%Y%m%d%H%M%S)" | |
| sed -i 's| nvidia-drm\.modeset=1||g' "$EXTLINUX_CONF" | |
| echo "[${STEP}] Removed nvidia-drm.modeset=1 from extlinux.conf" | |
| else | |
| echo "[${STEP}] nvidia-drm.modeset=1 not present (OK)" | |
| fi | |
| else | |
| echo "[${STEP}] WARNING: ${EXTLINUX_CONF} not found" | |
| fi | |
| # ------------------------------------------------------------------ | |
| # Step 5: Disable seatd if present (optional, helps avoid conflicts) | |
| # ------------------------------------------------------------------ | |
| STEP=$((STEP+1)) | |
| if systemctl is-enabled seatd &>/dev/null 2>&1; then | |
| systemctl stop seatd 2>/dev/null || true | |
| systemctl disable seatd 2>/dev/null || true | |
| echo "[${STEP}] Disabled seatd (avoids DRM master conflicts)" | |
| else | |
| echo "[${STEP}] seatd not present or already disabled" | |
| fi | |
| fi | |
| echo "" | |
| echo "=== Done! ===" | |
| echo "" | |
| echo "A reboot is REQUIRED:" | |
| echo " sudo reboot" | |
| echo "" | |
| echo "After reboot, verify:" | |
| echo " DISPLAY=:0 xrandr # 1920x1080 screen" | |
| echo " DISPLAY=:0 eglinfo 2>&1 | head -15 # EGL vendor: NVIDIA" | |
| echo " python3 -c 'import ctypes; c=ctypes.CDLL(\"libcuda.so.1\"); print(c.cuInit(0))' # 0" | |
| echo "" | |
| if [[ "$JP_VERSION" -eq 6 ]]; then | |
| echo "JP6 NOTES:" | |
| echo " - nvidia DDX driver active (NOT modesetting)" | |
| echo " - nvidia-drm.modeset=1 removed from boot (incompatible with nvidia DDX)" | |
| echo " - EGL/CUDA/Argus work via nvidia DDX's own rendering path" | |
| echo " - NoMachine/VNC will show full hardware-accelerated desktop" | |
| echo "" | |
| fi | |
| echo "Connect via NoMachine or VNC to access the desktop remotely." | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment