Files
intercept/setup.sh
Smittix 6cbe94cf20 fix: restore flask-limiter as mandatory dependency
Rate limiting on login is a security requirement, not optional.
Reverts the no-op fallback — if flask-limiter is missing, the app
will fail fast with a clear import error rather than silently
running without rate limiting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:35:46 +00:00

1692 lines
54 KiB
Bash
Executable File

#!/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