#!/usr/bin/env bash set -Eeuo pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" COMPOSE_FILE="${PROJECT_ROOT}/deploy/docker/docker-compose.yml" SYSTEMD_TARGET_DIR="/etc/systemd/system" RUN_USER="${SUDO_USER:-${USER}}" RUN_GROUP="$(id -gn "${RUN_USER}")" SDR_UNITS=( dronedetector-sdr-433.service dronedetector-sdr-750.service dronedetector-sdr-868.service dronedetector-sdr-3300.service dronedetector-sdr-4500.service dronedetector-sdr-5200.service dronedetector-sdr-5800.service dronedetector-sdr-915.service dronedetector-sdr-1200.service dronedetector-sdr-2400.service ) log() { printf '[install_all] %s\n' "$*" } die() { printf '[install_all] ERROR: %s\n' "$*" >&2 exit 1 } print_failure_logs() { log "Collecting diagnostics..." systemctl --no-pager --full status dronedetector-compose.service || true for unit in "${SDR_UNITS[@]}"; do systemctl --no-pager --full status "$unit" || true done if command -v docker >/dev/null 2>&1; then docker compose -f "$COMPOSE_FILE" ps || true docker compose -f "$COMPOSE_FILE" logs --tail=150 dronedetector-server-to-master || true docker compose -f "$COMPOSE_FILE" logs --tail=150 dronedetector-nn-server || true fi journalctl -u dronedetector-compose.service -n 150 --no-pager || true for unit in "${SDR_UNITS[@]}"; do journalctl -u "$unit" -n 120 --no-pager || true done } trap 'rc=$?; if [[ $rc -ne 0 ]]; then print_failure_logs; fi' EXIT require_root() { if [[ "${EUID}" -ne 0 ]]; then log "Switching to root via sudo..." exec sudo -E bash "$0" "$@" fi } preflight() { log "Preflight checks" [[ -f "${PROJECT_ROOT}/.env" ]] || die "Missing ${PROJECT_ROOT}/.env" [[ -f "${COMPOSE_FILE}" ]] || die "Missing ${COMPOSE_FILE}" if ! command -v apt-get >/dev/null 2>&1; then die "This installer currently supports Debian/Ubuntu only (apt-get required)." fi local free_mb free_mb="$(df -Pm "${PROJECT_ROOT}" | awk 'NR==2 {print $4}')" if [[ -z "$free_mb" || "$free_mb" -lt 10240 ]]; then die "At least 10 GB free disk space is required." fi if ! command -v nvidia-smi >/dev/null 2>&1; then die "nvidia-smi is required. GPU/NVIDIA driver is not available on host." fi log "Preflight OK" } install_host_non_python_deps() { log "Installing host non-python dependencies" apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ ca-certificates \ curl \ gnupg \ lsb-release \ jq \ git \ python3 \ python3-pip \ python3-venv \ build-essential \ pkg-config \ libusb-1.0-0 \ libusb-1.0-0-dev \ hackrf \ gnuradio \ gr-osmosdr } setup_sdr_python_env() { log "Setting up SDR python environment" local venv_path="${PROJECT_ROOT}/.venv-sdr" if [[ ! -d "$venv_path" ]]; then python3 -m venv --system-site-packages "$venv_path" fi "$venv_path/bin/pip" install --upgrade pip "$venv_path/bin/pip" install -r "${PROJECT_ROOT}/deploy/requirements/sdr_host.txt" chown -R "${RUN_USER}:${RUN_GROUP}" "$venv_path" } install_docker_if_needed() { if command -v docker >/dev/null 2>&1; then log "Docker already installed" return fi log "Installing Docker Engine" . /etc/os-release local distro_id="${ID}" if [[ "$distro_id" != "ubuntu" && "$distro_id" != "debian" ]]; then die "Unsupported distro for Docker auto-install: ${distro_id}" fi install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/${distro_id}/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${distro_id} \ ${VERSION_CODENAME} stable" | tee /etc/apt/sources.list.d/docker.list >/dev/null apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin systemctl enable --now docker } install_nvidia_container_toolkit() { log "Installing/Configuring NVIDIA Container Toolkit" if ! dpkg -s nvidia-container-toolkit >/dev/null 2>&1; then curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \ gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ tee /etc/apt/sources.list.d/nvidia-container-toolkit.list apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nvidia-container-toolkit fi nvidia-ctk runtime configure --runtime=docker systemctl restart docker } build_and_run_compose() { log "Building and starting Docker services" docker compose -f "$COMPOSE_FILE" up -d --build } install_systemd_units() { log "Installing systemd units" install -m 0755 "${PROJECT_ROOT}/deploy/systemd/precheck-sdr.sh" /usr/local/bin/dronedetector-precheck-sdr.sh local src dst for src in "${PROJECT_ROOT}"/deploy/systemd/*.service; do dst="${SYSTEMD_TARGET_DIR}/$(basename "$src")" sed \ -e "s|__PROJECT_ROOT__|${PROJECT_ROOT}|g" \ -e "s|__RUN_USER__|${RUN_USER}|g" \ -e "s|__RUN_GROUP__|${RUN_GROUP}|g" \ "$src" > "$dst" done systemctl daemon-reload systemctl enable dronedetector-compose.service systemctl restart dronedetector-compose.service for unit in "${SDR_UNITS[@]}"; do systemctl enable "$unit" systemctl restart "$unit" done } wait_for_systemd_active() { local unit="$1" local timeout_seconds="${2:-60}" local i for ((i=0; i