298 lines
12 KiB
Bash
298 lines
12 KiB
Bash
|
|
#!/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
|