#!/usr/bin/env bash # ============================================================================ # install-driver.sh # ---------------------------------------------------------------------------- # Idempotent HOST installer for capture-card runtime drivers / SDKs. # # Runs ON the cluster node's HOST kernel. The node-agent invokes it inside a # one-shot PRIVILEGED ubuntu container that bind-mounts this repo plus the host # paths needed to affect the host kernel (/lib/modules, /usr/src, /boot, /dev, # and the host apt/dpkg via the mounted root). dkms / modprobe / ldconfig # therefore operate against the running host kernel. # # Reads the proprietary vendor file(s) from sdk// (in this repo). # NO binaries are committed — if the expected file is missing the script exits # non-zero with a clear message telling the operator what to drop in. # # Vendors (ALLOWLIST — nothing else is accepted): # blackmagic Desktop Video .deb (DKMS kernel module) # aja NTV2 driver source/SDK (built kernel module) # deltacast VideoMaster installer (kernel module) # ndi redistributable runtime libs (user-space only, no module) # # Exit codes: # 0 installed (or already present / up to date) # 2 bad usage / unknown vendor # 3 expected vendor file missing in sdk// # 4 missing kernel headers (cannot build DKMS / module) # 5 build / install / module-load failure # # `bash -n` must pass. set -euo pipefail with `|| true` guarding every probe. # ============================================================================ set -euo pipefail # --------------------------------------------------------------------------- # Resolve our own repo dir (deploy/ -> repo root), regardless of CWD. # --------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)" SDK_ROOT="$REPO_DIR/sdk" VENDOR="${1:-}" KVER="$(uname -r 2>/dev/null || echo unknown)" REBOOT_REQUIRED=0 log() { echo "[install-driver] $*"; } warn() { echo "[install-driver] WARN: $*" >&2; } die() { echo "[install-driver] ERROR: $*" >&2; exit "${2:-5}"; } usage() { echo "Usage: install-driver.sh " >&2 exit 2 } [ -n "$VENDOR" ] || usage case "$VENDOR" in blackmagic|aja|deltacast|ndi) : ;; *) echo "[install-driver] ERROR: unknown vendor '$VENDOR' (allowed: blackmagic aja deltacast ndi)" >&2; exit 2 ;; esac VENDOR_DIR="$SDK_ROOT/$VENDOR" log "vendor=$VENDOR kernel=$KVER repo=$REPO_DIR" log "reading vendor files from $VENDOR_DIR" [ -d "$VENDOR_DIR" ] || die "vendor dir $VENDOR_DIR does not exist (repo not mounted?)" 3 # Pick the newest file matching a glob; echo its path or empty. newest_match() { # shellcheck disable=SC2012 ls -1t $1 2>/dev/null | head -n1 || true } ensure_headers() { if [ -d "/lib/modules/$KVER/build" ] || dpkg -s "linux-headers-$KVER" >/dev/null 2>&1; then log "kernel headers for $KVER present" return 0 fi log "installing linux-headers-$KVER ..." apt-get update -y >/dev/null 2>&1 || true if ! apt-get install -y "linux-headers-$KVER" >/dev/null 2>&1; then # Fall back to the generic meta-package; still may not match a custom kernel. apt-get install -y linux-headers-generic >/dev/null 2>&1 || true fi if [ -d "/lib/modules/$KVER/build" ] || dpkg -s "linux-headers-$KVER" >/dev/null 2>&1; then log "kernel headers ready" return 0 fi die "kernel headers for $KVER unavailable — cannot build module. Install linux-headers-$KVER on the host." 4 } # =========================================================================== # blackmagic — Desktop Video .deb (DKMS) + modprobe + restart desktopvideo # =========================================================================== install_blackmagic() { # Detect existing state first (idempotent skip). if lsmod 2>/dev/null | grep -q '^blackmagic' && [ -e /dev/blackmagic ]; then log "blackmagic module loaded and /dev/blackmagic present — already installed" if command -v dkms >/dev/null 2>&1; then dkms status 2>/dev/null | grep -i blackmagic || true; fi return 0 fi local deb deb="$(newest_match "$VENDOR_DIR/desktopvideo_*_amd64.deb")" [ -n "$deb" ] && [ -f "$deb" ] || die \ "no desktopvideo_*_amd64.deb in $VENDOR_DIR — download Desktop Video for Ubuntu 22.04 and drop the .deb there (see sdk/blackmagic/README.md)." 3 log "using package: $(basename "$deb")" ensure_headers log "apt-get install (DKMS build) ..." apt-get update -y >/dev/null 2>&1 || true apt-get install -y "$deb" || die "apt-get install of $(basename "$deb") failed (DKMS build?)" 5 log "depmod + modprobe blackmagic ..." depmod -a "$KVER" 2>/dev/null || true if ! modprobe blackmagic 2>/dev/null; then warn "modprobe blackmagic failed — a reboot may be required for the DKMS module to bind" REBOOT_REQUIRED=1 fi # Restart the DesktopVideoHelper daemon if present. if command -v systemctl >/dev/null 2>&1; then systemctl restart desktopvideo 2>/dev/null \ || systemctl restart DesktopVideoHelper 2>/dev/null || true fi if lsmod 2>/dev/null | grep -q '^blackmagic' || [ -e /dev/blackmagic ]; then log "blackmagic installed (module loaded / device present)" else warn "blackmagic installed but module not yet loaded — reboot required" REBOOT_REQUIRED=1 fi } # =========================================================================== # aja — build ntv2 kernel module from source archive + modprobe # =========================================================================== install_aja() { if lsmod 2>/dev/null | grep -q 'ajantv2'; then log "ajantv2 module already loaded — already installed" return 0 fi local src src="$(newest_match "$VENDOR_DIR/ntv2sdk*.zip")" [ -n "$src" ] || src="$(newest_match "$VENDOR_DIR/libajantv2*.tar.gz")" [ -n "$src" ] && [ -f "$src" ] || die \ "no ntv2sdk*.zip / libajantv2*.tar.gz in $VENDOR_DIR — download the AJA NTV2 Linux SDK and drop it there (see sdk/aja/README.md)." 3 log "using source: $(basename "$src")" ensure_headers apt-get install -y build-essential unzip >/dev/null 2>&1 || true local work; work="$(mktemp -d)" log "extracting into $work ..." case "$src" in *.zip) unzip -q -o "$src" -d "$work" || die "failed to unzip $(basename "$src")" 5 ;; *.tar.gz) tar -xzf "$src" -C "$work" || die "failed to untar $(basename "$src")" 5 ;; esac # Find the linux driver dir (contains a Makefile producing ajantv2.ko). local drvdir drvdir="$(dirname "$(find "$work" -type d -path '*driver/linux' -print -quit 2>/dev/null || true)/." 2>/dev/null)" [ -d "$drvdir" ] || drvdir="$(dirname "$(find "$work" -name 'ajantv2.c' -print -quit 2>/dev/null || true)" 2>/dev/null)" [ -n "$drvdir" ] && [ -d "$drvdir" ] || die "could not locate AJA driver/linux source inside the archive" 5 log "building module in $drvdir ..." make -C "$drvdir" >/dev/null 2>&1 || die "AJA module build failed" 5 local ko ko="$(find "$drvdir" -name 'ajantv2.ko' -print -quit 2>/dev/null || true)" if [ -n "$ko" ]; then install -D -m 0644 "$ko" "/lib/modules/$KVER/extra/ajantv2.ko" 2>/dev/null || true depmod -a "$KVER" 2>/dev/null || true fi if ! modprobe ajantv2 2>/dev/null; then # Fall back to the SDK's own load script if shipped. local loader; loader="$(find "$work" -name 'load_ajantv2' -print -quit 2>/dev/null || true)" if [ -n "$loader" ]; then bash "$loader" 2>/dev/null || true; fi fi rm -rf "$work" 2>/dev/null || true if lsmod 2>/dev/null | grep -q 'ajantv2'; then log "ajantv2 installed and loaded" else warn "ajantv2 built but not loaded — a reboot may be required (old in-tree module wedged?)" REBOOT_REQUIRED=1 fi } # =========================================================================== # deltacast — VideoMaster installer + module load # =========================================================================== install_deltacast() { if lsmod 2>/dev/null | grep -q 'videomaster' || ls /dev/deltacast* >/dev/null 2>&1; then log "Deltacast module loaded / device present — already installed" return 0 fi local pkg pkg="$(newest_match "$VENDOR_DIR/VideoMaster*.run")" [ -n "$pkg" ] || pkg="$(newest_match "$VENDOR_DIR/VideoMaster*.tar.gz")" [ -n "$pkg" ] && [ -f "$pkg" ] || die \ "no VideoMaster*.run / VideoMaster*.tar.gz in $VENDOR_DIR — obtain the Deltacast VideoMaster Linux installer and drop it there (see sdk/deltacast/README.md)." 3 log "using installer: $(basename "$pkg")" ensure_headers apt-get install -y build-essential dkms >/dev/null 2>&1 || true local work; work="$(mktemp -d)" case "$pkg" in *.run) log "extracting self-extractor ..." chmod +x "$pkg" 2>/dev/null || true "$pkg" --noexec --target "$work" >/dev/null 2>&1 \ || cp "$pkg" "$work/installer.run" ;; *.tar.gz) tar -xzf "$pkg" -C "$work" || die "failed to untar $(basename "$pkg")" 5 ;; esac local installer installer="$(find "$work" -name 'install.sh' -print -quit 2>/dev/null || true)" [ -n "$installer" ] || installer="$(find "$work" -name 'installer.run' -print -quit 2>/dev/null || true)" [ -n "$installer" ] && [ -f "$installer" ] || die "Deltacast installer (install.sh) not found inside the package" 5 log "running vendor installer: $(basename "$installer") ..." chmod +x "$installer" 2>/dev/null || true ( cd "$(dirname "$installer")" && bash "$installer" ) || die "Deltacast VideoMaster installer failed" 5 depmod -a "$KVER" 2>/dev/null || true modprobe videomasterhd 2>/dev/null || modprobe videomaster 2>/dev/null || true rm -rf "$work" 2>/dev/null || true if lsmod 2>/dev/null | grep -q 'videomaster' || ls /dev/deltacast* >/dev/null 2>&1; then log "Deltacast VideoMaster installed" fi # First-time VideoMaster installs lay down udev rules + firmware that need a reboot. warn "Deltacast: a REBOOT is recommended after a first-time VideoMaster install (udev + firmware)" REBOOT_REQUIRED=1 } # =========================================================================== # ndi — copy redistributable runtime libs to /usr/local/lib + ldconfig # =========================================================================== install_ndi() { local target="/opt/ndi-lib" local found=0 # shellcheck disable=SC2231 for f in "$VENDOR_DIR"/libndi*.so*; do [ -e "$f" ] || continue found=1 break done [ "$found" = 1 ] || die \ "no libndi*.so* in $VENDOR_DIR — drop the NDI runtime redistributable libs there (see sdk/ndi/README.md)." 3 log "copying NDI runtime libs to $target ..." mkdir -p "$target" cp -av "$VENDOR_DIR"/libndi*.so* "$target"/ 2>/dev/null || die "failed copying NDI libs" 5 # Recreate the libndi.so dev symlink if only versioned libs were shipped. if [ ! -e "$target/libndi.so" ]; then local versioned versioned="$(newest_match "$target/libndi.so.*")" if [ -n "$versioned" ]; then ln -sf "$(basename "$versioned")" "$target/libndi.so" 2>/dev/null || true fi fi echo "$target" > /etc/ld.so.conf.d/ndi.conf ldconfig 2>/dev/null || true if ldconfig -p 2>/dev/null | grep -q 'libndi'; then log "NDI runtime registered with the dynamic linker" else die "NDI libs copied but ldconfig did not resolve libndi" 5 fi log "NDI: no kernel module and no reboot required." log "NDI: restart any process that already loaded an older libndi to pick up the new version." } # --------------------------------------------------------------------------- case "$VENDOR" in blackmagic) install_blackmagic ;; aja) install_aja ;; deltacast) install_deltacast ;; ndi) install_ndi ;; esac if [ "$REBOOT_REQUIRED" = 1 ]; then log "RESULT: $VENDOR install completed — REBOOT REQUIRED" echo "[install-driver] REBOOT_REQUIRED=1" else log "RESULT: $VENDOR install completed — no reboot required" echo "[install-driver] REBOOT_REQUIRED=0" fi exit 0