Emergency Hotline: Call 1-844-363-1423 (United We Dream Hotline)
ICE Encounter

Implementation Roadmap: Edge Node Deployment

This guide provides step-by-step instructions for building a flight tracking and alert system, from hardware procurement through rapid response network integration.


System Overview

Architecture Summary

[Edge Node]           [Central Server]         [Alert Recipients]
Raspberry Pi     →    Cloud VPS           →    Signal Groups
RTL-SDR              PostgreSQL                Telegram Channels
tar1090              Python Workers            Rapid Response
                     Kafka/Redis

Component Requirements

Component Minimum Recommended
Edge nodes 1 3+ (for MLAT)
Central server 1 vCPU, 1GB RAM 2 vCPU, 4GB RAM
Database SQLite PostgreSQL
Message broker Redis Apache Kafka

Phase 1: Hardware Procurement

Bill of Materials (Edge Node)

Component Specification Est. Cost
Compute Raspberry Pi 4 (2GB+) $45-55
SDR receiver FlightAware Pro Stick Plus $35-45
Antenna 1090 MHz collinear $40-60
Coax cable LMR-400, 25ft $30-40
Power supply 5V 3A USB-C $10-15
MicroSD 32GB+ Class 10 $10-15
Case Weatherproof enclosure $15-25
Cooling Heatsinks/fan $10-15

Total: $195-270 per edge node

Component Selection Notes

Component Recommendation
Receiver Pro Stick Plus has integrated 1090 MHz filter
Antenna Higher = better; minimize coax length
Compute Pi 4 for reliability; Zero 2 W for low power
Storage Use quality MicroSD; enable log rotation

Phase 2: Base Station Software

Operating System Setup

# Flash Raspberry Pi OS Lite to MicroSD
# Boot and connect via SSH

# Update system
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install -y git rtl-sdr librtlsdr-dev \
    build-essential pkg-config libncurses5-dev \
    lighttpd nginx python3 python3-pip

Install readsb (ADS-B Decoder)

# Clone and build readsb
git clone https://github.com/wiedehopf/readsb.git
cd readsb
make -j4
sudo make install

# Create service user
sudo useradd -r -s /bin/false readsb

# Create systemd service
sudo tee /etc/systemd/system/readsb.service << 'EOF'
[Unit]
Description=readsb ADS-B receiver
After=network.target

[Service]
Type=simple
User=readsb
ExecStart=/usr/local/bin/readsb \
    --device-type rtlsdr \
    --gain -10 \
    --ppm 0 \
    --net \
    --net-ro-size 500 \
    --net-ro-interval 1 \
    --net-buffer 5 \
    --net-connector localhost,30004,beast_reduce_out \
    --lat YOUR_LAT \
    --lon YOUR_LON

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable readsb
sudo systemctl start readsb

Install tar1090 (Web Interface)

# Clone tar1090
cd /opt
sudo git clone https://github.com/wiedehopf/tar1090.git

# Run installation script
cd tar1090
sudo bash install.sh

# Configure Nginx
sudo tee /etc/nginx/sites-available/tar1090 << 'EOF'
server {
    listen 80;
    root /run/tar1090;
    index index.html;

    location /data/ {
        alias /run/tar1090/;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/tar1090 /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Verify Installation

# Check services
sudo systemctl status readsb
sudo systemctl status tar1090

# Test local JSON endpoint
curl http://localhost/tar1090/data/aircraft.json | jq '.aircraft | length'

# Check receiver stats
cat /run/readsb/stats.json | jq '.total.messages'

Phase 3: Watchlist Integration

Create Watchlist Database

# watchlist.py
import json
import sqlite3

def init_database():
    """Initialize watchlist database"""
    conn = sqlite3.connect('watchlist.db')
    cursor = conn.cursor()

    cursor.execute('''
        CREATE TABLE IF NOT EXISTS aircraft (
            hex TEXT PRIMARY KEY,
            n_number TEXT,
            operator TEXT,
            aircraft_type TEXT,
            confidence TEXT,
            last_verified DATE,
            notes TEXT
        )
    ''')

    conn.commit()
    return conn

def load_watchlist(filename):
    """Load watchlist from JSON file"""
    with open(filename, 'r') as f:
        data = json.load(f)

    conn = init_database()
    cursor = conn.cursor()

    for aircraft in data.get('watchlist', []):
        cursor.execute('''
            INSERT OR REPLACE INTO aircraft
            VALUES (?, ?, ?, ?, ?, ?, ?)
        ''', (
            aircraft['hex'].upper(),
            aircraft.get('n_number'),
            aircraft.get('operator'),
            aircraft.get('aircraft_type'),
            aircraft.get('confidence'),
            aircraft.get('last_verified'),
            aircraft.get('notes')
        ))

    conn.commit()
    print(f"Loaded {len(data.get('watchlist', []))} aircraft")

Sample Watchlist JSON

{
  "watchlist": [
    {
      "hex": "A12345",
      "n_number": "N801XT",
      "operator": "GlobalX Airlines",
      "aircraft_type": "A320-200",
      "confidence": "high",
      "last_verified": "2026-03-15",
      "notes": "Primary international removal aircraft"
    }
  ]
}

Phase 4: Geofencing Configuration

Define Geofences

# geofences.py
GEOFENCES = [
    {
        "name": "Alexandria Staging (AEX)",
        "lat": 31.3274,
        "lon": -92.5499,
        "radius_miles": 30,
        "type": "staging_hub",
        "priority": "high"
    },
    {
        "name": "Mesa Gateway (IWA)",
        "lat": 33.3078,
        "lon": -111.6556,
        "radius_miles": 30,
        "type": "staging_hub",
        "priority": "high"
    },
    {
        "name": "Brownsville (BRO)",
        "lat": 25.9069,
        "lon": -97.4258,
        "radius_miles": 30,
        "type": "staging_hub",
        "priority": "high"
    },
    {
        "name": "San Antonio (SAT)",
        "lat": 29.5337,
        "lon": -98.4698,
        "radius_miles": 30,
        "type": "staging_hub",
        "priority": "medium"
    },
    {
        "name": "Miami (MIA)",
        "lat": 25.7959,
        "lon": -80.2870,
        "radius_miles": 30,
        "type": "staging_hub",
        "priority": "medium"
    }
]

Haversine Implementation

from math import radians, sin, cos, sqrt, atan2

def haversine(lat1, lon1, lat2, lon2):
    """Calculate great-circle distance in miles"""
    R = 3959  # Earth radius in miles

    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
    dlat = lat2 - lat1
    dlon = lon2 - lon1

    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * atan2(sqrt(a), sqrt(1-a))

    return R * c

def check_geofences(lat, lon):
    """Check if position is within any geofence"""
    for fence in GEOFENCES:
        distance = haversine(lat, lon, fence['lat'], fence['lon'])
        if distance <= fence['radius_miles']:
            return {
                'match': True,
                'geofence': fence['name'],
                'distance': round(distance, 1),
                'priority': fence['priority']
            }
    return {'match': False}

Phase 5: Alert Service

Main Monitoring Script

#!/usr/bin/env python3
# monitor.py

import requests
import sqlite3
import time
import json
from datetime import datetime
from geofences import GEOFENCES, haversine, check_geofences

# Configuration
LOCAL_URL = "http://localhost/tar1090/data/aircraft.json"
POLL_INTERVAL = 5
DB_PATH = "watchlist.db"

# State tracking
aircraft_history = {}
alerts_sent = {}
ALERT_COOLDOWN = 300  # 5 minutes

def load_watchlist():
    """Load hex codes from database"""
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    cursor.execute("SELECT hex FROM aircraft")
    return set(row[0] for row in cursor.fetchall())

def poll_aircraft():
    """Poll local receiver for aircraft"""
    try:
        response = requests.get(LOCAL_URL, timeout=10)
        return response.json().get('aircraft', [])
    except Exception as e:
        print(f"Poll error: {e}")
        return []

def should_alert(hex_code, aircraft, geofence_result):
    """Determine if alert should be sent"""

    # Check cooldown
    if hex_code in alerts_sent:
        elapsed = (datetime.utcnow() - alerts_sent[hex_code]).seconds
        if elapsed < ALERT_COOLDOWN:
            return False

    # Must be in geofence
    if not geofence_result['match']:
        return False

    # Check altitude
    altitude = aircraft.get('alt_baro', 40000)
    if altitude > 10000:
        return False

    # Check descent
    baro_rate = aircraft.get('baro_rate', 0)
    if baro_rate >= 0:
        return False

    return True

def send_alert(hex_code, aircraft, geofence_result):
    """Send alert via configured channels"""

    callsign = aircraft.get('flight', 'N/A').strip()
    altitude = aircraft.get('alt_baro', 'N/A')
    tracking_url = f"https://globe.adsbexchange.com/?icao={hex_code.lower()}"

    message = f"""
⚠️ FLIGHT ALERT

Aircraft: {hex_code}
Call Sign: {callsign}
Altitude: {altitude} ft
Location: {geofence_result['geofence']}
Distance: {geofence_result['distance']} mi

Track: {tracking_url}
"""

    # Send via your configured channel (Signal, Telegram, etc.)
    print(message)

    # Update cooldown
    alerts_sent[hex_code] = datetime.utcnow()

def main():
    """Main monitoring loop"""
    watchlist = load_watchlist()
    print(f"Loaded {len(watchlist)} aircraft to watchlist")

    while True:
        aircraft_list = poll_aircraft()

        for aircraft in aircraft_list:
            hex_code = aircraft.get('hex', '').upper()

            # Check watchlist
            if hex_code not in watchlist:
                continue

            # Get position
            lat = aircraft.get('lat')
            lon = aircraft.get('lon')
            if lat is None or lon is None:
                continue

            # Check geofences
            geofence_result = check_geofences(lat, lon)

            # Determine if alert needed
            if should_alert(hex_code, aircraft, geofence_result):
                send_alert(hex_code, aircraft, geofence_result)

            # Update history
            if hex_code not in aircraft_history:
                aircraft_history[hex_code] = []
            aircraft_history[hex_code].append({
                'timestamp': datetime.utcnow(),
                'lat': lat,
                'lon': lon,
                'alt_baro': aircraft.get('alt_baro'),
                'baro_rate': aircraft.get('baro_rate')
            })

            # Limit history size
            aircraft_history[hex_code] = aircraft_history[hex_code][-20:]

        time.sleep(POLL_INTERVAL)

if __name__ == "__main__":
    main()

Systemd Service

sudo tee /etc/systemd/system/flight-monitor.service << 'EOF'
[Unit]
Description=Flight Tracking Monitor
After=network.target readsb.service

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/flight-tracking
ExecStart=/usr/bin/python3 /home/pi/flight-tracking/monitor.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable flight-monitor
sudo systemctl start flight-monitor

Phase 6: Signal Integration

Install signal-cli

# Download signal-cli
wget https://github.com/AsamK/signal-cli/releases/download/v0.12.0/signal-cli-0.12.0.tar.gz
tar xf signal-cli-0.12.0.tar.gz
sudo mv signal-cli-0.12.0 /opt/signal-cli

# Link binary
sudo ln -s /opt/signal-cli/bin/signal-cli /usr/local/bin/signal-cli

# Register phone number
signal-cli -u +1XXXXXXXXXX register
signal-cli -u +1XXXXXXXXXX verify CODE

Signal Alert Function

import subprocess

SIGNAL_CLI = "/usr/local/bin/signal-cli"
SENDER = "+1XXXXXXXXXX"
GROUP_ID = "base64_group_id_here"

def send_signal_alert(message):
    """Send alert to Signal group"""
    try:
        cmd = [
            SIGNAL_CLI,
            "-u", SENDER,
            "send",
            "-g", GROUP_ID,
            "-m", message
        ]
        subprocess.run(cmd, check=True, timeout=30)
        return True
    except Exception as e:
        print(f"Signal error: {e}")
        return False

Phase 7: Central Server Deployment

Server Requirements

Specification Minimum Recommended
Provider Any VPS DigitalOcean, Linode
CPU 1 vCPU 2 vCPU
RAM 1 GB 4 GB
Storage 20 GB SSD 50 GB SSD
Network 1 TB transfer Unmetered

Database Setup

# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# Create database
sudo -u postgres psql << EOF
CREATE USER flighttrack WITH PASSWORD 'secure_password';
CREATE DATABASE flighttrack OWNER flighttrack;
\c flighttrack
CREATE EXTENSION IF NOT EXISTS postgis;
EOF

Central Aggregation

# aggregator.py
from flask import Flask, request, jsonify
import psycopg2
from datetime import datetime

app = Flask(__name__)

DB_CONFIG = {
    'host': 'localhost',
    'database': 'flighttrack',
    'user': 'flighttrack',
    'password': 'secure_password'
}

@app.route('/api/position', methods=['POST'])
def receive_position():
    """Receive position data from edge nodes"""
    data = request.json

    conn = psycopg2.connect(**DB_CONFIG)
    cursor = conn.cursor()

    cursor.execute('''
        INSERT INTO positions (timestamp, hex, lat, lon, altitude, source)
        VALUES (%s, %s, %s, %s, %s, %s)
    ''', (
        datetime.utcnow(),
        data['hex'],
        data['lat'],
        data['lon'],
        data.get('alt_baro'),
        data.get('source', 'unknown')
    ))

    conn.commit()
    conn.close()

    return jsonify({'status': 'ok'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Phase 8: Rapid Response Integration

Webhook Integration

def notify_rapid_response(alert_data):
    """Send alert to rapid response network webhook"""

    WEBHOOK_URL = "https://dispatch.rapidresponse.org/webhook"

    payload = {
        "type": "flight_alert",
        "timestamp": datetime.utcnow().isoformat(),
        "aircraft": alert_data['hex'],
        "callsign": alert_data['callsign'],
        "location": alert_data['geofence'],
        "tracking_url": alert_data['tracking_url'],
        "action_needed": "Monitor for ground arrival"
    }

    try:
        response = requests.post(
            WEBHOOK_URL,
            json=payload,
            headers={"Content-Type": "application/json"},
            timeout=10
        )
        return response.status_code == 200
    except Exception as e:
        print(f"Webhook error: {e}")
        return False

Alert Protocol Documentation

Provide rapid response networks with:

Document Content
Alert format Message structure and fields
Response protocol Actions upon receiving alert
Verification steps How to confirm landing
Escalation path Who to contact
False positive handling When alerts are incorrect

Maintenance and Monitoring

Health Check Script

#!/bin/bash
# health_check.sh

# Check readsb
if ! systemctl is-active --quiet readsb; then
    echo "ALERT: readsb is not running"
    systemctl restart readsb
fi

# Check tar1090
if ! systemctl is-active --quiet tar1090; then
    echo "ALERT: tar1090 is not running"
    systemctl restart tar1090
fi

# Check message rate
MESSAGES=$(curl -s http://localhost/tar1090/data/stats.json | jq '.total.messages')
if [ "$MESSAGES" -lt 1000 ]; then
    echo "WARNING: Low message count: $MESSAGES"
fi

# Check disk space
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 80 ]; then
    echo "WARNING: Disk usage at $DISK_USAGE%"
fi

Cron Jobs

# /etc/cron.d/flight-tracking

# Health check every 5 minutes
*/5 * * * * pi /home/pi/flight-tracking/health_check.sh

# Rotate logs daily
0 0 * * * pi /usr/sbin/logrotate /home/pi/flight-tracking/logrotate.conf

# Update watchlist weekly
0 3 * * 0 pi /home/pi/flight-tracking/update_watchlist.sh

Related Resources


Last updated: March 25, 2026

Legal Disclaimer

This website does not provide legal advice. The information provided on this site is for general informational and educational purposes only. It does not create an attorney-client relationship.

Information on this website may not be current or accurate. Immigration law is complex and varies by jurisdiction and individual circumstances. Always consult with a qualified immigration attorney for advice specific to your situation.

Neither ICE Encounter, its developers, partners, nor any contributors shall be liable for any actions taken or not taken based on information from this site. Use of this site is subject to our Terms of Use and Privacy Policy.