dragonflight/deploy/install-driver.sh

298 lines
12 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# ============================================================================
# install-driver.sh <vendor>
# ----------------------------------------------------------------------------
# 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/<vendor>/ (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/<vendor>/
# 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 <blackmagic|aja|deltacast|ndi>" >&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