#!/usr/bin/env bash # INTERCEPT Setup Script (best-effort installs, hard-fail verification) # ---- Force bash even if launched with sh ---- if [ -z "${BASH_VERSION:-}" ]; then echo "[x] This script must be run with bash (not sh)." echo " Run: bash $0" exec bash "$0" "$@" fi set -Eeuo pipefail # Ensure admin paths are searchable (many tools live here) export PATH="/usr/local/sbin:/usr/sbin:/sbin:/opt/homebrew/sbin:/opt/homebrew/bin:$PATH" # ---------------------------- # Pretty output # ---------------------------- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' info() { echo -e "${BLUE}[*]${NC} $*"; } ok() { echo -e "${GREEN}[✓]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } fail() { echo -e "${RED}[x]${NC} $*"; } # ---------------------------- # Progress tracking # ---------------------------- CURRENT_STEP=0 TOTAL_STEPS=0 progress() { local msg="$1" ((CURRENT_STEP++)) || true local pct=$((CURRENT_STEP * 100 / TOTAL_STEPS)) local filled=$((pct / 5)) local empty=$((20 - filled)) local bar=$(printf '█%.0s' $(seq 1 $filled 2>/dev/null) || true) bar+=$(printf '░%.0s' $(seq 1 $empty 2>/dev/null) || true) echo -e "${BLUE}[${CURRENT_STEP}/${TOTAL_STEPS}]${NC} ${bar} ${pct}% - ${msg}" } on_error() { local line="$1" local cmd="${2:-unknown}" fail "Setup failed at line ${line}: ${cmd}" exit 1 } trap 'on_error $LINENO "$BASH_COMMAND"' ERR # ---------------------------- # Banner # ---------------------------- echo -e "${BLUE}" echo " ___ _ _ _____ _____ ____ ____ _____ ____ _____ " echo " |_ _| \\ | |_ _| ____| _ \\ / ___| ____| _ \\_ _|" echo " | || \\| | | | | _| | |_) | | | _| | |_) || | " echo " | || |\\ | | | | |___| _ <| |___| |___| __/ | | " echo " |___|_| \\_| |_| |_____|_| \\_\\\\____|_____|_| |_| " echo -e "${NC}" echo "INTERCEPT - Setup Script" echo "============================================" echo # ---------------------------- # Helpers # ---------------------------- NON_INTERACTIVE=false for arg in "$@"; do case "$arg" in --non-interactive) NON_INTERACTIVE=true ;; *) ;; esac done cmd_exists() { local c="$1" command -v "$c" >/dev/null 2>&1 && return 0 [[ -x "/usr/sbin/$c" || -x "/sbin/$c" || -x "/usr/local/sbin/$c" || -x "/opt/homebrew/sbin/$c" ]] && return 0 return 1 } ask_yes_no() { local prompt="$1" local default="${2:-n}" # default to no for safety local response if $NON_INTERACTIVE; then info "Non-interactive mode: defaulting to ${default} for prompt: ${prompt}" [[ "$default" == "y" ]] return fi if [[ ! -t 0 ]]; then warn "No TTY available for prompt: ${prompt}" [[ "$default" == "y" ]] return fi if [[ "$default" == "y" ]]; then read -r -p "$prompt [Y/n]: " response [[ -z "$response" || "$response" =~ ^[Yy] ]] else read -r -p "$prompt [y/N]: " response [[ "$response" =~ ^[Yy] ]] fi } have_any() { local c for c in "$@"; do cmd_exists "$c" && return 0 done return 1 } need_sudo() { if [[ "$(id -u)" -eq 0 ]]; then SUDO="" ok "Running as root" else if cmd_exists sudo; then SUDO="sudo" else fail "sudo is not installed and you're not root." echo "Either run as root or install sudo first." exit 1 fi fi } # Refresh sudo credential cache so long-running builds don't trigger # mid-compilation password prompts (which can fail due to TTY issues # inside subshells). Safe to call multiple times. refresh_sudo() { [[ -z "${SUDO:-}" ]] && return 0 sudo -v 2>/dev/null || true } detect_os() { if [[ "${OSTYPE:-}" == "darwin"* ]]; then OS="macos" elif [[ -f /etc/debian_version ]]; then OS="debian" else OS="unknown" fi info "Detected OS: ${OS}" [[ "$OS" != "unknown" ]] || { fail "Unsupported OS (macOS + Debian/Ubuntu only)."; exit 1; } } detect_dragonos() { IS_DRAGONOS=false # Check for DragonOS markers if [[ -f /etc/dragonos-release ]] || \ [[ -d /usr/share/dragonos ]] || \ grep -qi "dragonos" /etc/os-release 2>/dev/null; then IS_DRAGONOS=true warn "DragonOS detected! This distro has many tools pre-installed." warn "The script will prompt before making system changes." fi } # ---------------------------- # Required tool checks (with alternates) # ---------------------------- missing_required=() missing_recommended=() check_required() { local label="$1"; shift local desc="$1"; shift if have_any "$@"; then ok "${label} - ${desc}" else warn "${label} - ${desc} (missing, required)" missing_required+=("$label") fi } check_recommended() { local label="$1"; shift local desc="$1"; shift if have_any "$@"; then ok "${label} - ${desc}" else warn "${label} - ${desc} (missing, recommended)" missing_recommended+=("$label") fi } check_optional() { local label="$1"; shift local desc="$1"; shift if have_any "$@"; then ok "${label} - ${desc}" else warn "${label} - ${desc} (missing, optional)" fi } check_tools() { info "Checking required tools..." missing_required=() echo info "Core SDR:" check_required "rtl_fm" "RTL-SDR FM demodulator" rtl_fm check_required "rtl_test" "RTL-SDR device detection" rtl_test check_required "rtl_tcp" "RTL-SDR TCP server" rtl_tcp check_required "multimon-ng" "Pager decoder" multimon-ng check_required "rtl_433" "433MHz sensor decoder" rtl_433 rtl433 check_optional "rtlamr" "Utility meter decoder (requires Go)" rtlamr check_optional "hackrf_transfer" "HackRF SubGHz transceiver" hackrf_transfer check_optional "hackrf_sweep" "HackRF spectrum analyzer" hackrf_sweep check_required "dump1090" "ADS-B decoder" dump1090 check_required "acarsdec" "ACARS decoder" acarsdec check_optional "dumpvdl2" "VDL2 decoder" dumpvdl2 check_required "AIS-catcher" "AIS vessel decoder" AIS-catcher aiscatcher check_optional "satdump" "Weather satellite decoder (NOAA/Meteor)" satdump check_optional "auto_rx.py" "Radiosonde weather balloon decoder" auto_rx.py echo info "GPS:" check_required "gpsd" "GPS daemon" gpsd echo info "Audio:" check_required "ffmpeg" "Audio encoder/decoder" ffmpeg echo info "WiFi:" check_required "airmon-ng" "Monitor mode helper" airmon-ng check_required "airodump-ng" "WiFi scanner" airodump-ng check_required "aireplay-ng" "Injection/deauth" aireplay-ng check_required "hcxdumptool" "PMKID capture" hcxdumptool check_required "hcxpcapngtool" "PMKID/pcapng conversion" hcxpcapngtool echo info "Bluetooth:" check_required "bluetoothctl" "Bluetooth controller CLI" bluetoothctl check_required "hcitool" "Bluetooth scan utility" hcitool check_required "hciconfig" "Bluetooth adapter config" hciconfig echo info "SoapySDR:" check_required "SoapySDRUtil" "SoapySDR CLI utility" SoapySDRUtil echo } # ---------------------------- # Python venv + deps # ---------------------------- check_python_version() { if ! cmd_exists python3; then fail "python3 not found." [[ "$OS" == "macos" ]] && echo "Install with: brew install python" [[ "$OS" == "debian" ]] && echo "Install with: sudo apt-get install python3" exit 1 fi local ver ver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')" info "Python version: ${ver}" python3 - <<'PY' import sys raise SystemExit(0 if sys.version_info >= (3,9) else 1) PY ok "Python version OK (>= 3.9)" } install_python_deps() { progress "Setting up Python environment" check_python_version if [[ ! -f requirements.txt ]]; then warn "requirements.txt not found; skipping Python dependency install." return 0 fi # On Debian/Ubuntu, try apt packages first as they're more reliable if [[ "$OS" == "debian" ]]; then info "Installing Python packages via apt (more reliable on Debian/Ubuntu)..." $SUDO apt-get install -y python3-flask python3-requests python3-serial >/dev/null 2>&1 || true # skyfield may not be available in all distros, try apt first then pip if ! $SUDO apt-get install -y python3-skyfield >/dev/null 2>&1; then warn "python3-skyfield not in apt, will try pip later" fi ok "Installed available Python packages via apt" fi if [[ ! -d venv ]]; then python3 -m venv --system-site-packages venv ok "Created venv/ (with system site-packages)" else ok "Using existing venv/" fi # shellcheck disable=SC1091 source venv/bin/activate local PIP="venv/bin/python -m pip" local PY="venv/bin/python" $PIP install --upgrade pip setuptools wheel >/dev/null 2>&1 || true ok "Upgraded pip tooling" progress "Installing Python dependencies" # Install critical packages first to avoid all-or-nothing failures # (C extension packages like scipy/numpy can fail on newer Python versions # and cause pip to roll back pure-Python packages like flask) info "Installing core packages..." $PIP install --quiet "flask>=3.0.0" "flask-limiter>=2.5.4" "requests>=2.28.0" \ "Werkzeug>=3.1.5" "pyserial>=3.5" "flask-sock" "websocket-client>=1.6.0" 2>/dev/null || true # Verify critical packages $PY -c "import flask; import requests; from flask_limiter import Limiter" 2>/dev/null || { fail "Critical Python packages (flask, requests, flask-limiter) not installed" echo "Try: venv/bin/pip install flask requests flask-limiter" exit 1 } ok "Core Python packages installed" # Install optional packages individually (some may fail on newer Python) info "Installing optional packages..." for pkg in "numpy>=1.24.0" "scipy>=1.10.0" "Pillow>=9.0.0" "skyfield>=1.45" \ "bleak>=0.21.0" "psycopg2-binary>=2.9.9" "meshtastic>=2.0.0" \ "scapy>=2.4.5" "qrcode[pil]>=7.4" "cryptography>=41.0.0" \ "gunicorn>=21.2.0" "gevent>=23.9.0" "psutil>=5.9.0"; do pkg_name="${pkg%%>=*}" if ! $PIP install "$pkg" 2>/dev/null; then warn "${pkg_name} failed to install (optional - related features may be unavailable)" fi done ok "Optional packages processed" echo } # ---------------------------- # macOS install (Homebrew) # ---------------------------- ensure_brew() { cmd_exists brew && return 0 warn "Homebrew not found. Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" if [[ -x /opt/homebrew/bin/brew ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" elif [[ -x /usr/local/bin/brew ]]; then eval "$(/usr/local/bin/brew shellenv)" fi cmd_exists brew || { fail "Homebrew install failed. Install manually then re-run."; exit 1; } } brew_install() { local pkg="$1" if brew list --formula "$pkg" >/dev/null 2>&1; then ok "brew: ${pkg} already installed" return 0 fi info "brew: installing ${pkg}..." if brew install "$pkg" 2>&1; then ok "brew: installed ${pkg}" return 0 else return 1 fi } install_rtlamr_from_source() { info "Installing rtlamr from source (requires Go)..." # Check if Go is installed, install if needed if ! cmd_exists go; then if [[ "$OS" == "macos" ]]; then info "Installing Go via Homebrew..." brew_install go || { warn "Failed to install Go. Cannot install rtlamr."; return 1; } else info "Installing Go via apt..." $SUDO apt-get install -y golang >/dev/null 2>&1 || { warn "Failed to install Go. Cannot install rtlamr."; return 1; } fi fi # Set up Go environment export GOPATH="${GOPATH:-$HOME/go}" export PATH="$GOPATH/bin:$PATH" mkdir -p "$GOPATH/bin" info "Building rtlamr..." if go install github.com/bemasher/rtlamr@latest 2>/dev/null; then # Link to system path if [[ -f "$GOPATH/bin/rtlamr" ]]; then if [[ "$OS" == "macos" ]]; then if [[ -w /usr/local/bin ]]; then ln -sf "$GOPATH/bin/rtlamr" /usr/local/bin/rtlamr else $SUDO ln -sf "$GOPATH/bin/rtlamr" /usr/local/bin/rtlamr fi else $SUDO ln -sf "$GOPATH/bin/rtlamr" /usr/local/bin/rtlamr fi ok "rtlamr installed successfully" else warn "rtlamr binary not found after build" return 1 fi else warn "Failed to build rtlamr" return 1 fi } install_multimon_ng_from_source_macos() { info "multimon-ng not available via Homebrew. Building from source..." # Ensure build dependencies are installed brew_install cmake brew_install libsndfile ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning multimon-ng..." git clone --depth 1 https://github.com/EliasOenal/multimon-ng.git "$tmp_dir/multimon-ng" >/dev/null 2>&1 \ || { fail "Failed to clone multimon-ng"; exit 1; } cd "$tmp_dir/multimon-ng" info "Compiling multimon-ng..." mkdir -p build && cd build cmake .. >/dev/null 2>&1 || { fail "cmake failed for multimon-ng"; exit 1; } make >/dev/null 2>&1 || { fail "make failed for multimon-ng"; exit 1; } # Install to /usr/local/bin (no sudo needed on Homebrew systems typically) if [[ -w /usr/local/bin ]]; then install -m 0755 multimon-ng /usr/local/bin/multimon-ng else refresh_sudo $SUDO install -m 0755 multimon-ng /usr/local/bin/multimon-ng fi ok "multimon-ng installed successfully from source" ) } install_dump1090_from_source_macos() { info "dump1090 not available via Homebrew. Building from source..." brew_install cmake brew_install librtlsdr brew_install pkg-config ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning FlightAware dump1090..." git clone --depth 1 https://github.com/flightaware/dump1090.git "$tmp_dir/dump1090" >/dev/null 2>&1 \ || { warn "Failed to clone dump1090"; exit 1; } cd "$tmp_dir/dump1090" sed -i '' 's/-Werror//g' Makefile 2>/dev/null || true info "Compiling dump1090..." if make BLADERF=no RTLSDR=yes 2>&1 | tail -5; then if [[ -w /usr/local/bin ]]; then install -m 0755 dump1090 /usr/local/bin/dump1090 else refresh_sudo $SUDO install -m 0755 dump1090 /usr/local/bin/dump1090 fi ok "dump1090 installed successfully from source" else warn "Failed to build dump1090. ADS-B decoding will not be available." fi ) } install_acarsdec_from_source_macos() { info "acarsdec not available via Homebrew. Building from source..." brew_install cmake brew_install librtlsdr brew_install libsndfile brew_install pkg-config ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning acarsdec..." git clone --depth 1 https://github.com/TLeconte/acarsdec.git "$tmp_dir/acarsdec" >/dev/null 2>&1 \ || { warn "Failed to clone acarsdec"; exit 1; } cd "$tmp_dir/acarsdec" # Fix compiler flags for macOS Apple Silicon (ARM64) # -march=native can fail with Apple Clang on M-series chips # -Ofast is deprecated in modern Clang if [[ "$(uname -m)" == "arm64" ]]; then sed -i '' 's/-Ofast -march=native/-O3 -ffast-math/g' CMakeLists.txt info "Patched compiler flags for Apple Silicon (arm64)" fi # Fix pthread_tryjoin_np (Linux-only GNU extension) for macOS # Replace with pthread_join which provides equivalent behavior if grep -q 'pthread_tryjoin_np' rtl.c 2>/dev/null; then sed -i '' 's/pthread_tryjoin_np(\([^,]*\), NULL)/pthread_join(\1, NULL)/g' rtl.c info "Patched pthread_tryjoin_np for macOS compatibility" fi # Fix libacars linking on macOS (upstream issue #112) # Use LIBACARS_LINK_LIBRARIES (full path) instead of LIBACARS_LIBRARIES (name only) if grep -q 'LIBACARS_LIBRARIES' CMakeLists.txt 2>/dev/null; then sed -i '' 's/${LIBACARS_LIBRARIES}/${LIBACARS_LINK_LIBRARIES}/g' CMakeLists.txt info "Patched libacars linking for macOS" fi mkdir -p build && cd build # Set Homebrew paths for Apple Silicon (/opt/homebrew) or Intel (/usr/local) HOMEBREW_PREFIX="$(brew --prefix)" export PKG_CONFIG_PATH="${HOMEBREW_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH:-}" export CMAKE_PREFIX_PATH="${HOMEBREW_PREFIX}" info "Compiling acarsdec..." build_log="$tmp_dir/acarsdec-build.log" if cmake .. -Drtl=ON \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_C_FLAGS="-I${HOMEBREW_PREFIX}/include" \ -DCMAKE_EXE_LINKER_FLAGS="-L${HOMEBREW_PREFIX}/lib" \ >"$build_log" 2>&1 \ && make >>"$build_log" 2>&1; then if [[ -w /usr/local/bin ]]; then install -m 0755 acarsdec /usr/local/bin/acarsdec else refresh_sudo $SUDO install -m 0755 acarsdec /usr/local/bin/acarsdec fi ok "acarsdec installed successfully from source" else warn "Failed to build acarsdec. ACARS decoding will not be available." warn "Build log (last 30 lines):" tail -30 "$build_log" | while IFS= read -r line; do warn " $line"; done fi ) } install_dumpvdl2_from_source_macos() { info "Building dumpvdl2 from source (with libacars dependency)..." brew_install cmake brew_install librtlsdr brew_install pkg-config brew_install glib ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT HOMEBREW_PREFIX="$(brew --prefix)" export PKG_CONFIG_PATH="${HOMEBREW_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH:-}" export CMAKE_PREFIX_PATH="${HOMEBREW_PREFIX}" # Build libacars first info "Cloning libacars..." git clone --depth 1 https://github.com/szpajder/libacars.git "$tmp_dir/libacars" >/dev/null 2>&1 \ || { warn "Failed to clone libacars"; exit 1; } cd "$tmp_dir/libacars" mkdir -p build && cd build info "Compiling libacars..." build_log="$tmp_dir/libacars-build.log" if cmake .. \ -DCMAKE_C_FLAGS="-I${HOMEBREW_PREFIX}/include" \ -DCMAKE_EXE_LINKER_FLAGS="-L${HOMEBREW_PREFIX}/lib" \ >"$build_log" 2>&1 \ && make >>"$build_log" 2>&1; then if [[ -w /usr/local/lib ]]; then make install >>"$build_log" 2>&1 else refresh_sudo $SUDO make install >>"$build_log" 2>&1 fi ok "libacars installed" else warn "Failed to build libacars." tail -20 "$build_log" | while IFS= read -r line; do warn " $line"; done exit 1 fi # Build dumpvdl2 info "Cloning dumpvdl2..." git clone --depth 1 https://github.com/szpajder/dumpvdl2.git "$tmp_dir/dumpvdl2" >/dev/null 2>&1 \ || { warn "Failed to clone dumpvdl2"; exit 1; } cd "$tmp_dir/dumpvdl2" mkdir -p build && cd build info "Compiling dumpvdl2..." build_log="$tmp_dir/dumpvdl2-build.log" if cmake .. \ -DCMAKE_C_FLAGS="-I${HOMEBREW_PREFIX}/include" \ -DCMAKE_EXE_LINKER_FLAGS="-L${HOMEBREW_PREFIX}/lib" \ >"$build_log" 2>&1 \ && make >>"$build_log" 2>&1; then if [[ -w /usr/local/bin ]]; then install -m 0755 src/dumpvdl2 /usr/local/bin/dumpvdl2 else refresh_sudo $SUDO install -m 0755 src/dumpvdl2 /usr/local/bin/dumpvdl2 fi ok "dumpvdl2 installed successfully from source" else warn "Failed to build dumpvdl2. VDL2 decoding will not be available." warn "Build log (last 30 lines):" tail -30 "$build_log" | while IFS= read -r line; do warn " $line"; done fi ) } install_aiscatcher_from_source_macos() { info "AIS-catcher not available via Homebrew. Building from source..." brew_install cmake brew_install librtlsdr brew_install curl brew_install pkg-config ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning AIS-catcher..." git clone --depth 1 https://github.com/jvde-github/AIS-catcher.git "$tmp_dir/AIS-catcher" >/dev/null 2>&1 \ || { warn "Failed to clone AIS-catcher"; exit 1; } cd "$tmp_dir/AIS-catcher" mkdir -p build && cd build info "Compiling AIS-catcher..." if cmake .. >/dev/null 2>&1 && make >/dev/null 2>&1; then if [[ -w /usr/local/bin ]]; then install -m 0755 AIS-catcher /usr/local/bin/AIS-catcher else refresh_sudo $SUDO install -m 0755 AIS-catcher /usr/local/bin/AIS-catcher fi ok "AIS-catcher installed successfully from source" else warn "Failed to build AIS-catcher. AIS vessel tracking will not be available." fi ) } install_satdump_from_source_debian() { info "Building SatDump v1.2.2 from source (weather satellite decoder)..." # Core deps — hard-fail if missing apt_install build-essential git cmake pkg-config \ libpng-dev libtiff-dev libzstd-dev \ libsqlite3-dev libcurl4-openssl-dev zlib1g-dev libzmq3-dev libfftw3-dev # libvolk: package name differs between distros # Ubuntu / Debian Trixie+: libvolk-dev # Raspberry Pi OS Bookworm / Debian Bookworm: libvolk2-dev apt_try_install_any libvolk-dev libvolk2-dev \ || warn "libvolk not found — SatDump will build without VOLK acceleration" # Optional SDR hardware libs — soft-fail so missing hardware doesn't abort for pkg in libjemalloc-dev libnng-dev libsoapysdr-dev libhackrf-dev liblimesuite-dev; do $SUDO apt-get install -y --no-install-recommends "$pkg" >/dev/null 2>&1 \ || warn "${pkg} not available — skipping (SatDump can build without it)" done # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning SatDump v1.2.2..." git clone --depth 1 --branch 1.2.2 https://github.com/SatDump/SatDump.git "$tmp_dir/SatDump" >/dev/null 2>&1 \ || { warn "Failed to clone SatDump"; exit 1; } cd "$tmp_dir/SatDump" # Patch: fix deprecated std::allocator usage for newer compilers # GCC 13+ errors on deprecated allocator members in sol2. # Pragmas must go in lua_utils.cpp (the instantiation site), not sol.hpp (definition site). lua_utils="src-core/common/lua/lua_utils.cpp" if [ -f "$lua_utils" ]; then { echo '#pragma GCC diagnostic push' echo '#pragma GCC diagnostic ignored "-Wdeprecated"' echo '#pragma GCC diagnostic ignored "-Wdeprecated-declarations"' cat "$lua_utils" echo # ensure the file ends with a newline before the closing pragma echo '#pragma GCC diagnostic pop' } > "${lua_utils}.patched" && mv "${lua_utils}.patched" "$lua_utils" fi mkdir -p build && cd build info "Compiling SatDump (this is a large C++ project and may take 10-30 minutes)..." build_log="$tmp_dir/satdump-build.log" # Show periodic progress while building so the user knows it's not hung ( while true; do sleep 30 if [ -f "$build_log" ]; then local_lines=$(wc -l < "$build_log" 2>/dev/null || echo 0) printf " [*] Still compiling SatDump... (%s lines of build output so far)\n" "$local_lines" fi done ) & progress_pid=$! if cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_GUI=OFF -DCMAKE_INSTALL_LIBDIR=lib .. >"$build_log" 2>&1 \ && make -j "$(nproc)" >>"$build_log" 2>&1; then kill $progress_pid 2>/dev/null; wait $progress_pid 2>/dev/null $SUDO make install >/dev/null 2>&1 $SUDO ldconfig # Ensure plugins are in the expected path (handles multiarch differences) $SUDO mkdir -p /usr/local/lib/satdump/plugins if [ -z "$(ls /usr/local/lib/satdump/plugins/*.so 2>/dev/null)" ]; then for dir in /usr/local/lib/*/satdump/plugins /usr/lib/*/satdump/plugins /usr/lib/satdump/plugins; do if [ -d "$dir" ] && [ -n "$(ls "$dir"/*.so 2>/dev/null)" ]; then $SUDO ln -sf "$dir"/*.so /usr/local/lib/satdump/plugins/ break fi done fi ok "SatDump installed successfully." else kill $progress_pid 2>/dev/null; wait $progress_pid 2>/dev/null warn "Failed to build SatDump from source. Weather satellite decoding will not be available." warn "Build log (last 30 lines):" tail -30 "$build_log" | while IFS= read -r line; do warn " $line"; done fi ) } install_satdump_macos() { info "Installing SatDump v1.2.2 from pre-built release (weather satellite decoder)..." # Determine architecture local arch arch="$(uname -m)" local dmg_name if [ "$arch" = "arm64" ]; then dmg_name="SatDump-macOS-Silicon.dmg" else dmg_name="SatDump-macOS-Intel.dmg" fi local dmg_url="https://github.com/SatDump/SatDump/releases/download/1.2.2/${dmg_name}" local install_dir="/usr/local/lib/satdump" # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap 'hdiutil detach "$tmp_dir/mnt" -quiet 2>/dev/null || true; rm -rf "$tmp_dir"' EXIT info "Downloading ${dmg_name}..." if ! curl -sL -o "$tmp_dir/satdump.dmg" "$dmg_url"; then warn "Failed to download SatDump. Weather satellite decoding will not be available." exit 1 fi info "Installing SatDump..." # Mount the DMG hdiutil attach "$tmp_dir/satdump.dmg" -nobrowse -quiet -mountpoint "$tmp_dir/mnt" \ || { warn "Failed to mount SatDump DMG"; exit 1; } local app_dir="$tmp_dir/mnt/SatDump.app" if [ ! -d "$app_dir" ]; then warn "SatDump.app not found in DMG" exit 1 fi # Install: copy app contents to /usr/local/lib/satdump refresh_sudo $SUDO mkdir -p "$install_dir" $SUDO cp -R "$app_dir/Contents/MacOS/"* "$install_dir/" $SUDO cp -R "$app_dir/Contents/Resources/"* "$install_dir/" # Create wrapper script so satdump can find its resources via @executable_path $SUDO tee /usr/local/bin/satdump >/dev/null <<'WRAPPER' #!/bin/sh exec /usr/local/lib/satdump/satdump "$@" WRAPPER $SUDO chmod +x /usr/local/bin/satdump hdiutil detach "$tmp_dir/mnt" -quiet 2>/dev/null # Verify installation if /usr/local/lib/satdump/satdump 2>&1 | grep -q "Usage"; then ok "SatDump v1.2.2 installed successfully." else warn "SatDump installed but may not work correctly." fi ) } install_radiosonde_auto_rx() { info "Installing radiosonde_auto_rx (weather balloon decoder)..." local install_dir="/opt/radiosonde_auto_rx" local project_dir="$(pwd)" ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning radiosonde_auto_rx..." if ! git clone --depth 1 https://github.com/projecthorus/radiosonde_auto_rx.git "$tmp_dir/radiosonde_auto_rx"; then warn "Failed to clone radiosonde_auto_rx" exit 1 fi info "Installing Python dependencies..." cd "$tmp_dir/radiosonde_auto_rx/auto_rx" # Use project venv pip to avoid PEP 668 externally-managed-environment errors if [ -x "$project_dir/venv/bin/pip" ]; then "$project_dir/venv/bin/pip" install --quiet -r requirements.txt || { warn "Failed to install radiosonde_auto_rx Python dependencies" exit 1 } else pip3 install --quiet --break-system-packages -r requirements.txt 2>/dev/null \ || pip3 install --quiet -r requirements.txt || { warn "Failed to install radiosonde_auto_rx Python dependencies" exit 1 } fi info "Building radiosonde_auto_rx C decoders..." if ! bash build.sh; then warn "Failed to build radiosonde_auto_rx decoders" exit 1 fi info "Installing to ${install_dir}..." refresh_sudo $SUDO mkdir -p "$install_dir/auto_rx" $SUDO cp -r . "$install_dir/auto_rx/" $SUDO chmod +x "$install_dir/auto_rx/auto_rx.py" ok "radiosonde_auto_rx installed to ${install_dir}" ) } install_macos_packages() { need_sudo # Prime sudo credentials upfront so builds don't prompt mid-compilation if [[ -n "${SUDO:-}" ]]; then info "Some tools require sudo to install. You may be prompted for your password." sudo -v || { fail "sudo authentication failed"; exit 1; } fi TOTAL_STEPS=22 CURRENT_STEP=0 progress "Checking Homebrew" ensure_brew progress "Installing RTL-SDR libraries" brew_install librtlsdr progress "Installing multimon-ng" # multimon-ng is not in Homebrew core, so build from source if ! cmd_exists multimon-ng; then install_multimon_ng_from_source_macos else ok "multimon-ng already installed" fi progress "Installing direwolf (APRS decoder)" (brew_install direwolf) || warn "direwolf not available via Homebrew" progress "SSTV decoder" ok "SSTV uses built-in pure Python decoder (no external tools needed)" progress "Installing ffmpeg" brew_install ffmpeg progress "Installing rtl_433" brew_install rtl_433 progress "Installing HackRF tools" brew_install hackrf progress "Installing rtlamr (optional)" # rtlamr is optional - used for utility meter monitoring if ! cmd_exists rtlamr; then echo info "rtlamr is used for utility meter monitoring (electric/gas/water meters)." if ask_yes_no "Do you want to install rtlamr?"; then install_rtlamr_from_source else warn "Skipping rtlamr installation. You can install it later if needed." fi else ok "rtlamr already installed" fi progress "Installing dump1090" if ! cmd_exists dump1090; then (brew_install dump1090-mutability) || install_dump1090_from_source_macos || warn "dump1090 not available" else ok "dump1090 already installed" fi progress "Installing acarsdec" if ! cmd_exists acarsdec; then (brew_install acarsdec) || install_acarsdec_from_source_macos || warn "acarsdec not available" else ok "acarsdec already installed" fi progress "Installing dumpvdl2" if ! cmd_exists dumpvdl2; then install_dumpvdl2_from_source_macos || warn "dumpvdl2 not available. VDL2 decoding will not be available." else ok "dumpvdl2 already installed" fi progress "Installing AIS-catcher" if ! cmd_exists AIS-catcher && ! cmd_exists aiscatcher; then (brew_install aiscatcher) || install_aiscatcher_from_source_macos || warn "AIS-catcher not available" else ok "AIS-catcher already installed" fi progress "Installing SatDump (optional)" if ! cmd_exists satdump; then echo info "SatDump is used for weather satellite imagery (NOAA APT & Meteor LRPT)." if ask_yes_no "Do you want to install SatDump?"; then install_satdump_macos || warn "SatDump installation failed. Weather satellite decoding will not be available." else warn "Skipping SatDump installation. You can install it later if needed." fi else ok "SatDump already installed" fi progress "Installing radiosonde_auto_rx (optional)" if ! cmd_exists auto_rx.py && [ ! -f /opt/radiosonde_auto_rx/auto_rx/auto_rx.py ] \ || { [ -f /opt/radiosonde_auto_rx/auto_rx/auto_rx.py ] && [ ! -f /opt/radiosonde_auto_rx/auto_rx/dft_detect ]; }; then echo info "radiosonde_auto_rx is used for weather balloon (radiosonde) tracking." if ask_yes_no "Do you want to install radiosonde_auto_rx?"; then install_radiosonde_auto_rx || warn "radiosonde_auto_rx installation failed. Radiosonde tracking will not be available." else warn "Skipping radiosonde_auto_rx. You can install it later if needed." fi else ok "radiosonde_auto_rx already installed" fi progress "Installing aircrack-ng" brew_install aircrack-ng progress "Installing hcxtools" brew_install hcxtools progress "Installing SoapySDR" brew_install soapysdr progress "Installing gpsd" brew_install gpsd progress "Installing Ubertooth tools (optional)" if ! cmd_exists ubertooth-btle; then echo info "Ubertooth is used for advanced Bluetooth packet sniffing with Ubertooth One hardware." if ask_yes_no "Do you want to install Ubertooth tools?"; then brew_install ubertooth || warn "Ubertooth not available via Homebrew" else warn "Skipping Ubertooth installation. You can install it later if needed." fi else ok "Ubertooth already installed" fi warn "macOS note: hcitool/hciconfig are Linux (BlueZ) utilities and often unavailable on macOS." info "TSCM BLE scanning uses bleak library (installed via pip) for manufacturer data detection." echo } # ---------------------------- # Debian/Ubuntu install (APT) # ---------------------------- apt_install() { local pkgs="$*" local output local ret=0 output=$($SUDO apt-get install -y --no-install-recommends "$@" 2>&1) || ret=$? if [[ $ret -ne 0 ]]; then fail "Failed to install: $pkgs" echo "$output" | tail -10 fail "Try running: sudo apt-get update && sudo apt-get install -y $pkgs" return 1 fi } apt_try_install_any() { local p for p in "$@"; do if $SUDO apt-get install -y --no-install-recommends "$p" >/dev/null 2>&1; then ok "apt: installed ${p}" return 0 fi done return 1 } apt_install_if_missing() { local pkg="$1" if dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then ok "apt: ${pkg} already installed" return 0 fi apt_install "$pkg" } install_dump1090_from_source_debian() { info "dump1090 not available via APT. Building from source (required)..." apt_install build-essential git pkg-config \ librtlsdr-dev libusb-1.0-0-dev \ libncurses-dev tcl-dev python3-dev local JOBS JOBS="$(nproc 2>/dev/null || echo 1)" # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap '{ [[ -n "${progress_pid:-}" ]] && kill "$progress_pid" 2>/dev/null && wait "$progress_pid" 2>/dev/null || true; }; rm -rf "$tmp_dir"' EXIT info "Cloning FlightAware dump1090..." git clone --depth 1 https://github.com/flightaware/dump1090.git "$tmp_dir/dump1090" >/dev/null 2>&1 \ || { fail "Failed to clone FlightAware dump1090"; exit 1; } cd "$tmp_dir/dump1090" # Remove -Werror to prevent build failures on newer GCC versions sed -i 's/-Werror//g' Makefile 2>/dev/null || true info "Compiling FlightAware dump1090 (using ${JOBS} CPU cores)..." build_log="$tmp_dir/dump1090-build.log" (while true; do sleep 20; printf " [*] Still compiling dump1090...\n"; done) & progress_pid=$! if make -j "$JOBS" BLADERF=no RTLSDR=yes >"$build_log" 2>&1; then kill "$progress_pid" 2>/dev/null; wait "$progress_pid" 2>/dev/null || true; progress_pid= $SUDO install -m 0755 dump1090 /usr/local/bin/dump1090 ok "dump1090 installed successfully (FlightAware)." exit 0 fi kill "$progress_pid" 2>/dev/null; wait "$progress_pid" 2>/dev/null || true; progress_pid= warn "FlightAware build failed. Falling back to wiedehopf/readsb..." warn "Build log (last 20 lines):" tail -20 "$build_log" | while IFS= read -r line; do warn " $line"; done rm -rf "$tmp_dir/dump1090" git clone --depth 1 https://github.com/wiedehopf/readsb.git "$tmp_dir/dump1090" >/dev/null 2>&1 \ || { fail "Failed to clone wiedehopf/readsb"; exit 1; } cd "$tmp_dir/dump1090" info "Compiling readsb (using ${JOBS} CPU cores)..." build_log="$tmp_dir/readsb-build.log" (while true; do sleep 20; printf " [*] Still compiling readsb...\n"; done) & progress_pid=$! if ! make -j "$JOBS" RTLSDR=yes >"$build_log" 2>&1; then kill "$progress_pid" 2>/dev/null; wait "$progress_pid" 2>/dev/null || true; progress_pid= warn "Build log (last 20 lines):" tail -20 "$build_log" | while IFS= read -r line; do warn " $line"; done fail "Failed to build readsb from source (required)." exit 1 fi kill "$progress_pid" 2>/dev/null; wait "$progress_pid" 2>/dev/null || true; progress_pid= $SUDO install -m 0755 readsb /usr/local/bin/dump1090 ok "dump1090 installed successfully (via readsb)." ) } install_acarsdec_from_source_debian() { info "acarsdec not available via APT. Building from source..." apt_install build-essential git cmake \ librtlsdr-dev libusb-1.0-0-dev libsndfile1-dev # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning acarsdec..." git clone --depth 1 https://github.com/TLeconte/acarsdec.git "$tmp_dir/acarsdec" >/dev/null 2>&1 \ || { warn "Failed to clone acarsdec"; exit 1; } cd "$tmp_dir/acarsdec" mkdir -p build && cd build info "Compiling acarsdec..." if cmake .. -Drtl=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 >/dev/null 2>&1 && make >/dev/null 2>&1; then $SUDO install -m 0755 acarsdec /usr/local/bin/acarsdec ok "acarsdec installed successfully." else warn "Failed to build acarsdec from source. ACARS decoding will not be available." fi ) } install_dumpvdl2_from_source_debian() { info "Building dumpvdl2 from source (with libacars dependency)..." apt_install build-essential git cmake \ librtlsdr-dev libusb-1.0-0-dev libglib2.0-dev libxml2-dev ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT # Build libacars first info "Cloning libacars..." git clone --depth 1 https://github.com/szpajder/libacars.git "$tmp_dir/libacars" >/dev/null 2>&1 \ || { warn "Failed to clone libacars"; exit 1; } cd "$tmp_dir/libacars" mkdir -p build && cd build info "Compiling libacars..." if cmake .. >/dev/null 2>&1 && make >/dev/null 2>&1; then $SUDO make install >/dev/null 2>&1 $SUDO ldconfig ok "libacars installed" else warn "Failed to build libacars." exit 1 fi # Build dumpvdl2 info "Cloning dumpvdl2..." git clone --depth 1 https://github.com/szpajder/dumpvdl2.git "$tmp_dir/dumpvdl2" >/dev/null 2>&1 \ || { warn "Failed to clone dumpvdl2"; exit 1; } cd "$tmp_dir/dumpvdl2" mkdir -p build && cd build info "Compiling dumpvdl2..." if cmake .. >/dev/null 2>&1 && make >/dev/null 2>&1; then $SUDO install -m 0755 src/dumpvdl2 /usr/local/bin/dumpvdl2 ok "dumpvdl2 installed successfully." else warn "Failed to build dumpvdl2 from source. VDL2 decoding will not be available." fi ) } install_aiscatcher_from_source_debian() { info "AIS-catcher not available via APT. Building from source..." apt_install build-essential git cmake pkg-config \ librtlsdr-dev libusb-1.0-0-dev libcurl4-openssl-dev zlib1g-dev # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning AIS-catcher..." git clone --depth 1 https://github.com/jvde-github/AIS-catcher.git "$tmp_dir/AIS-catcher" >/dev/null 2>&1 \ || { warn "Failed to clone AIS-catcher"; exit 1; } cd "$tmp_dir/AIS-catcher" mkdir -p build && cd build info "Compiling AIS-catcher..." if cmake .. >/dev/null 2>&1 && make >/dev/null 2>&1; then $SUDO install -m 0755 AIS-catcher /usr/local/bin/AIS-catcher ok "AIS-catcher installed successfully." else warn "Failed to build AIS-catcher from source. AIS vessel tracking will not be available." fi ) } install_ubertooth_from_source_debian() { info "Building Ubertooth from source..." apt_install build-essential git cmake libusb-1.0-0-dev pkg-config libbluetooth-dev # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning Ubertooth..." git clone --depth 1 https://github.com/greatscottgadgets/ubertooth.git "$tmp_dir/ubertooth" >/dev/null 2>&1 \ || { warn "Failed to clone Ubertooth"; exit 1; } cd "$tmp_dir/ubertooth/host" mkdir -p build && cd build info "Compiling Ubertooth..." if cmake .. >/dev/null 2>&1 && make >/dev/null 2>&1; then $SUDO make install >/dev/null 2>&1 $SUDO ldconfig ok "Ubertooth installed successfully from source." else warn "Failed to build Ubertooth from source." fi ) } install_rtlsdr_blog_drivers_debian() { # The RTL-SDR Blog drivers provide better support for: # - RTL-SDR Blog V4 (R828D tuner) # - RTL-SDR Blog V3 with bias-t improvements # - Better overall compatibility with all RTL-SDR devices # These drivers are backward compatible with standard RTL-SDR devices. info "Installing RTL-SDR Blog drivers (improved V4 support)..." # Install build dependencies apt_install build-essential git cmake libusb-1.0-0-dev pkg-config # Run in subshell to isolate EXIT trap ( tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT info "Cloning RTL-SDR Blog driver fork..." git clone https://github.com/rtlsdrblog/rtl-sdr-blog.git "$tmp_dir/rtl-sdr-blog" >/dev/null 2>&1 \ || { warn "Failed to clone RTL-SDR Blog drivers"; exit 1; } cd "$tmp_dir/rtl-sdr-blog" mkdir -p build && cd build info "Compiling RTL-SDR Blog drivers..." if cmake .. -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON >/dev/null 2>&1 && make >/dev/null 2>&1; then $SUDO make install >/dev/null 2>&1 $SUDO ldconfig # Copy udev rules if they exist if [[ -f ../rtl-sdr.rules ]]; then $SUDO cp ../rtl-sdr.rules /etc/udev/rules.d/20-rtlsdr-blog.rules $SUDO udevadm control --reload-rules || true $SUDO udevadm trigger || true fi # Make the Blog drivers' library take priority over the apt-installed # librtlsdr. Removing apt packages is too destructive (dump1090-mutability # and other tools depend on librtlsdr0 and get swept out). Instead, # prepend /usr/local/lib to ldconfig's search path — files named 00-* # sort before the distro's aarch64-linux-gnu.conf — so ldconfig lists # /usr/local/lib/librtlsdr.so.0 first and the dynamic linker uses it. if [[ -d /etc/ld.so.conf.d ]]; then echo '/usr/local/lib' | $SUDO tee /etc/ld.so.conf.d/00-local-first.conf >/dev/null fi $SUDO ldconfig ok "RTL-SDR Blog drivers installed successfully." info "These drivers provide improved support for RTL-SDR Blog V4 and other devices." warn "Unplug and replug your RTL-SDR devices for the new drivers to take effect." else warn "Failed to build RTL-SDR Blog drivers. Using stock drivers." warn "If you have an RTL-SDR Blog V4, you may need to install drivers manually." warn "See: https://github.com/rtlsdrblog/rtl-sdr-blog" fi ) } setup_udev_rules_debian() { [[ -d /etc/udev/rules.d ]] || { warn "udev not found; skipping RTL-SDR udev rules."; return 0; } local rules_file="/etc/udev/rules.d/20-rtlsdr.rules" [[ -f "$rules_file" ]] && { ok "RTL-SDR udev rules already present: $rules_file"; return 0; } info "Installing RTL-SDR udev rules..." $SUDO tee "$rules_file" >/dev/null <<'EOF' SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2832", MODE="0666" EOF $SUDO udevadm control --reload-rules || true $SUDO udevadm trigger || true ok "udev rules installed. Unplug/replug your RTL-SDR if connected." echo } blacklist_kernel_drivers_debian() { local blacklist_file="/etc/modprobe.d/blacklist-rtlsdr.conf" if [[ -f "$blacklist_file" ]]; then ok "RTL-SDR kernel driver blacklist already present" else info "Blacklisting conflicting DVB kernel drivers..." $SUDO tee "$blacklist_file" >/dev/null <<'EOF' # Blacklist DVB-T drivers to allow rtl-sdr to access RTL2832U devices blacklist dvb_usb_rtl28xxu blacklist rtl2832 blacklist rtl2830 blacklist r820t EOF fi # Always unload modules if currently loaded — this must happen even on # re-runs where the blacklist file already exists, since the modules may # still be live from the current boot (e.g. blacklist wasn't in initramfs). local unloaded=false for mod in dvb_usb_rtl28xxu rtl2832 rtl2830 r820t; do if lsmod | grep -q "^$mod"; then $SUDO modprobe -r "$mod" 2>/dev/null || true unloaded=true fi done $unloaded && info "Unloaded conflicting DVB kernel modules from current session." # Bake the blacklist into the initramfs so it survives reboots on # Raspberry Pi OS / Debian (without this the modules can reload on boot). if cmd_exists update-initramfs; then info "Updating initramfs to persist driver blacklist across reboots..." $SUDO update-initramfs -u >/dev/null 2>&1 || true fi ok "Kernel drivers blacklisted. Unplug/replug your RTL-SDR if connected." echo } install_debian_packages() { need_sudo # Keep APT interactive when a TTY is available. if $NON_INTERACTIVE; then export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a elif [[ -t 0 ]]; then export DEBIAN_FRONTEND=readline export NEEDRESTART_MODE=a else export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a fi TOTAL_STEPS=28 CURRENT_STEP=0 progress "Updating APT package lists" $SUDO apt-get update -y >/dev/null progress "Installing RTL-SDR" if ! $IS_DRAGONOS; then # Handle package conflict between librtlsdr0 and librtlsdr2 # The newer librtlsdr0 (2.0.2) conflicts with older librtlsdr2 (2.0.1) if dpkg -l | grep -q "librtlsdr2"; then info "Detected librtlsdr2 conflict - upgrading to librtlsdr0..." # Remove packages that depend on librtlsdr2, then remove librtlsdr2 # These will be reinstalled with librtlsdr0 support $SUDO apt-get remove -y dump1090-mutability libgnuradio-osmosdr0.2.0t64 rtl-433 librtlsdr2 rtl-sdr 2>/dev/null || true $SUDO apt-get autoremove -y 2>/dev/null || true ok "Removed conflicting librtlsdr2 packages" fi # If rtl-sdr is in broken state, remove it completely first if dpkg -l | grep -q "^.[^i].*rtl-sdr" || ! dpkg -l rtl-sdr 2>/dev/null | grep -q "^ii"; then info "Removing broken rtl-sdr package..." $SUDO dpkg --remove --force-remove-reinstreq rtl-sdr 2>/dev/null || true $SUDO dpkg --purge --force-remove-reinstreq rtl-sdr 2>/dev/null || true fi # Force remove librtlsdr2 if it still exists if dpkg -l | grep -q "librtlsdr2"; then info "Force removing librtlsdr2..." $SUDO dpkg --remove --force-all librtlsdr2 2>/dev/null || true $SUDO dpkg --purge --force-all librtlsdr2 2>/dev/null || true fi # Clean up any partial installations $SUDO dpkg --configure -a 2>/dev/null || true $SUDO apt-get --fix-broken install -y 2>/dev/null || true fi apt_install_if_missing rtl-sdr progress "RTL-SDR Blog drivers (V4 support)" if $IS_DRAGONOS; then info "DragonOS: skipping RTL-SDR Blog driver install (pre-configured)." else echo info "RTL-SDR Blog drivers add V4 (R828D tuner) support and bias-tee improvements." info "They are backward-compatible with all RTL-SDR devices." if ask_yes_no "Install RTL-SDR Blog drivers? (recommended for V4 users, safe for all)" "y"; then install_rtlsdr_blog_drivers_debian else warn "Skipping RTL-SDR Blog drivers. V4 devices may not work correctly." fi fi progress "Installing multimon-ng" apt_install multimon-ng progress "Installing direwolf (APRS decoder)" apt_install direwolf || true progress "SSTV decoder" ok "SSTV uses built-in pure Python decoder (no external tools needed)" progress "Installing ffmpeg" apt_install ffmpeg progress "Installing rtl_433" apt_try_install_any rtl-433 rtl433 || warn "rtl-433 not available" progress "Installing HackRF tools" apt_install hackrf || warn "hackrf tools not available" progress "Installing rtlamr (optional)" # rtlamr is optional - used for utility meter monitoring if ! cmd_exists rtlamr; then echo info "rtlamr is used for utility meter monitoring (electric/gas/water meters)." if ask_yes_no "Do you want to install rtlamr?"; then install_rtlamr_from_source else warn "Skipping rtlamr installation. You can install it later if needed." fi else ok "rtlamr already installed" fi progress "Installing aircrack-ng" apt_install aircrack-ng || true progress "Installing hcxdumptool" apt_install hcxdumptool || true progress "Installing hcxtools" apt_install hcxtools || true progress "Installing Bluetooth tools" apt_install bluez bluetooth || true progress "Installing Ubertooth tools (optional)" if ! cmd_exists ubertooth-btle; then echo info "Ubertooth is used for advanced Bluetooth packet sniffing with Ubertooth One hardware." if ask_yes_no "Do you want to install Ubertooth tools?"; then apt_install libubertooth-dev ubertooth || install_ubertooth_from_source_debian else warn "Skipping Ubertooth installation. You can install it later if needed." fi else ok "Ubertooth already installed" fi progress "Installing SoapySDR" # Exclude xtrx-dkms - its kernel module fails to build on newer kernels (6.14+) # and causes apt to hang. Most users don't have XTRX hardware anyway. apt_install soapysdr-tools xtrx-dkms- || true progress "Installing gpsd" apt_install gpsd gpsd-clients || true progress "Installing Python packages" apt_install python3-venv python3-pip || true # Install Python packages via apt (more reliable than pip on modern Debian/Ubuntu) $SUDO apt-get install -y python3-flask python3-requests python3-serial >/dev/null 2>&1 || true $SUDO apt-get install -y python3-skyfield >/dev/null 2>&1 || true # bleak for BLE scanning with manufacturer data (TSCM mode) $SUDO apt-get install -y python3-bleak >/dev/null 2>&1 || true progress "Installing dump1090" # Remove any stale symlink left from a previous run where dump1090-mutability # was later uninstalled — cmd_exists finds the broken symlink and skips the # real install, leaving dump1090 seemingly present but non-functional. local dump1090_path dump1090_path="$(command -v dump1090 2>/dev/null || true)" if [[ -n "$dump1090_path" ]] && [[ ! -x "$dump1090_path" ]]; then info "Removing broken dump1090 symlink: $dump1090_path" $SUDO rm -f "$dump1090_path" fi if ! cmd_exists dump1090 && ! cmd_exists dump1090-mutability; then apt_try_install_any dump1090-fa dump1090-mutability dump1090 || true fi if ! cmd_exists dump1090; then if cmd_exists dump1090-mutability; then $SUDO ln -s "$(which dump1090-mutability)" /usr/local/sbin/dump1090 fi fi cmd_exists dump1090 || install_dump1090_from_source_debian progress "Installing acarsdec" if ! cmd_exists acarsdec; then apt_install acarsdec || true fi cmd_exists acarsdec || install_acarsdec_from_source_debian progress "Installing dumpvdl2" if ! cmd_exists dumpvdl2; then install_dumpvdl2_from_source_debian || warn "dumpvdl2 not available. VDL2 decoding will not be available." else ok "dumpvdl2 already installed" fi progress "Installing AIS-catcher" if ! cmd_exists AIS-catcher && ! cmd_exists aiscatcher; then install_aiscatcher_from_source_debian else ok "AIS-catcher already installed" fi progress "Installing SatDump (optional)" if ! cmd_exists satdump; then echo info "SatDump is used for weather satellite imagery (NOAA APT & Meteor LRPT)." if ask_yes_no "Do you want to install SatDump?"; then install_satdump_from_source_debian || warn "SatDump build failed. Weather satellite decoding will not be available." else warn "Skipping SatDump installation. You can install it later if needed." fi else ok "SatDump already installed" fi progress "Installing radiosonde_auto_rx (optional)" if ! cmd_exists auto_rx.py && [ ! -f /opt/radiosonde_auto_rx/auto_rx/auto_rx.py ] \ || { [ -f /opt/radiosonde_auto_rx/auto_rx/auto_rx.py ] && [ ! -f /opt/radiosonde_auto_rx/auto_rx/dft_detect ]; }; then echo info "radiosonde_auto_rx is used for weather balloon (radiosonde) tracking." if ask_yes_no "Do you want to install radiosonde_auto_rx?"; then install_radiosonde_auto_rx || warn "radiosonde_auto_rx installation failed. Radiosonde tracking will not be available." else warn "Skipping radiosonde_auto_rx. You can install it later if needed." fi else ok "radiosonde_auto_rx already installed" fi progress "Configuring udev rules" setup_udev_rules_debian progress "Kernel driver configuration" if $IS_DRAGONOS; then info "DragonOS already has RTL-SDR drivers configured correctly." elif [[ -f /etc/modprobe.d/blacklist-rtlsdr.conf ]]; then ok "DVB kernel drivers already blacklisted" else echo echo "The DVB-T kernel drivers conflict with RTL-SDR userspace access." echo "Blacklisting them allows rtl_sdr tools to access the device." if ask_yes_no "Blacklist conflicting kernel drivers?"; then blacklist_kernel_drivers_debian else warn "Skipped kernel driver blacklist. RTL-SDR may not work without manual config." fi fi } # ---------------------------- # Final summary / hard fail # ---------------------------- final_summary_and_hard_fail() { check_tools echo "============================================" echo echo "To start INTERCEPT:" echo " sudo ./start.sh" echo echo "Or for quick local dev:" echo " sudo -E venv/bin/python intercept.py" echo echo "Then open http://localhost:5050 in your browser" echo echo "============================================" if [[ "${#missing_required[@]}" -eq 0 ]]; then ok "All REQUIRED tools are installed." else fail "Missing REQUIRED tools:" for t in "${missing_required[@]}"; do echo " - $t"; done echo if [[ "$OS" == "macos" ]]; then warn "macOS note: bluetoothctl/hcitool/hciconfig are Linux (BlueZ) tools and unavailable on macOS." warn "Bluetooth functionality will be limited. Other features should work." else fail "Exiting because required tools are missing." exit 1 fi fi if [[ "${#missing_recommended[@]}" -gt 0 ]]; then echo warn "Missing RECOMMENDED tools (some features will not work):" for t in "${missing_recommended[@]}"; do echo " - $t"; done echo warn "Install these for full functionality" fi } # ---------------------------- # Pre-flight summary # ---------------------------- show_install_summary() { info "Installation Summary:" echo echo " OS: $OS" $IS_DRAGONOS && echo " DragonOS: Yes (safe mode enabled)" echo echo " This script will:" echo " - Install missing SDR tools (rtl-sdr, multimon-ng, etc.)" echo " - Install Python dependencies in a virtual environment" echo if ! $IS_DRAGONOS; then echo " You will be prompted before:" echo " - Installing RTL-SDR Blog drivers (replaces existing)" echo " - Blacklisting kernel DVB drivers" fi echo if $NON_INTERACTIVE; then info "Non-interactive mode: continuing without prompt." return fi if ! ask_yes_no "Continue with installation?" "y"; then info "Installation cancelled." exit 0 fi } # ---------------------------- # MAIN # ---------------------------- main() { detect_os detect_dragonos show_install_summary if [[ "$OS" == "macos" ]]; then install_macos_packages else install_debian_packages fi install_python_deps # Download leaflet-heat plugin (offline mode) if [ ! -f "static/vendor/leaflet-heat/leaflet-heat.js" ]; then info "Downloading leaflet-heat plugin..." mkdir -p static/vendor/leaflet-heat if curl -sL "https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js" \ -o static/vendor/leaflet-heat/leaflet-heat.js; then ok "leaflet-heat plugin downloaded" else warn "Failed to download leaflet-heat plugin. Heatmap will use CDN." fi fi final_summary_and_hard_fail } main "$@" # Clear traps before exiting to prevent spurious errors during cleanup trap - ERR EXIT exit 0