mirror of
https://github.com/sdr-enthusiasts/docker-adsb-ultrafeeder.git
synced 2024-12-26 14:22:00 +00:00
361 lines
11 KiB
Bash
Executable file
361 lines
11 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
#
|
|
# Upload output data from decoder to remote server
|
|
#
|
|
REMOTE_URL="https://adsbexchange.com/api/receive/"
|
|
|
|
REMOTE_HOST=$( echo $REMOTE_URL | awk -F'/' '{print $3}' )
|
|
|
|
# run with lowest priority
|
|
renice 20 $$ || true
|
|
|
|
# Set this to '0' if you don't want this script to ever try to self-cache DNS.
|
|
# Default is on, but script will automatically not cache if resolver is localhost, or if curl version is too old.
|
|
DNS_CACHE=1
|
|
# Cache time, default 10min
|
|
DNS_TTL=600
|
|
# Set this to 1 if you want to force using the cache always even if there is a local resolver.
|
|
DNS_IGNORE_LOCAL=1
|
|
|
|
# List all paths, IN PREFERRED ORDER, separated by a SPACE
|
|
# By default, only use the json from the feed client
|
|
JSON_PATHS=("/run/readsb")
|
|
|
|
######################################################################################################################
|
|
# If you know what you're doing, and you want to override the search path, you can do it easily in
|
|
# /etc/default/adsbexchange-stats, by setting the JSON_PATHS variable to something else (or even multiple).
|
|
# For example, the old stats used this:
|
|
# JSON_PATHS=("/run/adsbexchange-feed" "/run/readsb" "/run/dump1090-fa" "/run/dump1090-mutability" "/run/dump1090" )
|
|
# You can enable this old path by setting "USE_OLD_PATH=1", preferrably in /etc/default/adsbexchange-stats
|
|
######################################################################################################################
|
|
|
|
|
|
# source local overrides (commonly the JSON_PATH, or DNS cache settings)
|
|
if [ -r /etc/default/adsbexchange-stats ]; then
|
|
. /etc/default/adsbexchange-stats
|
|
|
|
# If 'USE_OLD_PATH' is set, override the entire list
|
|
if [ "x$USE_OLD_PATH" != "x" ] && [ $USE_OLD_PATH -eq 1 ]; then
|
|
echo "Note: 'USE_OLD_PATH' is set."
|
|
JSON_PATHS=("/run/readsb" "/run/adsbexchange-feed")
|
|
fi
|
|
fi
|
|
|
|
# Small bit of sanity...
|
|
if [ "${#JSON_PATHS[@]}" -le 0 ]; then
|
|
echo "FATAL - You broke something. JSON_PATHS variable has no locations listed. Please fix."
|
|
exit 5
|
|
fi
|
|
|
|
JSON_DIR=""
|
|
|
|
TEMP_DIR="/run/adsbexchange-stats/"
|
|
TMPFILE="${TEMP_DIR}/tmp.json"
|
|
NEWFILE="${TEMP_DIR}/new.json"
|
|
|
|
# Sanity to make sure we can write to our scratch dir in /run
|
|
T=$(touch $TMPFILE 2>&1)
|
|
RV=$?
|
|
if [ $RV -ne 0 ]; then
|
|
echo "ERROR: Unable to write to $TMPFILE, aborting! ($T)"
|
|
exit 99
|
|
fi
|
|
|
|
|
|
# load bash sleep builtin if available
|
|
[[ -f /usr/lib/bash/sleep ]] && enable -f /usr/lib/bash/sleep sleep || true
|
|
|
|
# Do this a few times, in case we're still booting up (wait a bit between checks)
|
|
CHECK_LOOP=0
|
|
while [ "x$JSON_DIR" = "x" ]; do
|
|
# Check the paths IN ORDER, preferring the first one we find
|
|
for i in ${!JSON_PATHS[@]}; do
|
|
CHECK=${JSON_PATHS[$i]}
|
|
|
|
if [ -d $CHECK ]; then
|
|
JSON_DIR=$CHECK
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Couldn't find any of them...
|
|
if [ "x$JSON_DIR" = "x" ]; then
|
|
CHECK_LOOP=$(( CHECK_LOOP + 1 ))
|
|
|
|
if [ $CHECK_LOOP -gt 4 ]; then
|
|
# Bad news. Complain and exit.
|
|
echo "ERROR: Tried multiple times, could not find any of the directories - ABORTING!"
|
|
exit 10
|
|
fi
|
|
echo "No valid data source directory found, do you have the adsbexchange feed scripts installed? Tried each of: [${JSON_PATHS[@]}]"
|
|
sleep 20
|
|
fi
|
|
done
|
|
|
|
UUID=$ADSBX_UUID
|
|
|
|
if ! [[ $UUID =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then
|
|
# Data in UUID file is invalid
|
|
echo "FATAL: Data in UUID file was invalid, exiting!"
|
|
exit 1
|
|
fi
|
|
|
|
#####################
|
|
# DNS cache setup #
|
|
#####################
|
|
|
|
declare -A DNS_LOOKUP
|
|
declare -A DNS_EXPIRE
|
|
|
|
# Let's FIRST make sure our version of curl will support what we need (--resolve arg)
|
|
CURL_VER=$( curl -V | head -1 | awk '{print $2}' )
|
|
if [ "x$CURL_VER" = "x" ]; then
|
|
echo "FATAL - curl is malfunctioning, can't get version info."
|
|
exit 11
|
|
fi
|
|
|
|
# This routine assumes you do no santiy-checking.
|
|
#
|
|
# Checks for the host in $DNS_LOOKUP{}, and if the corresponding $DNS_EXPIRE{} is less than NOW, return success.
|
|
# Otherwise, try looking it up. Save value if lookup succeeded.
|
|
#
|
|
# Returns:
|
|
# On Success: returns 0, and host will be in DNS_LOOKUP assoc array.
|
|
# On Fail: Various return codes:
|
|
# - 10 = No Hostname Provided
|
|
# - 20 = Hostname Format Invalid
|
|
# - 30 = Lookup Failed even after $DNS_MAX_LOOPS tries
|
|
DNS_WAIT=5
|
|
DNS_MAX_LOOPS=2
|
|
|
|
dns_lookup () {
|
|
local HOST=$1
|
|
|
|
local NOW=$( date +%s )
|
|
|
|
# You need to pass in a hostname :)
|
|
if [ "x$HOST" = "x" ]; then
|
|
echo "ERROR: dns_lookup called without a hostname" >&2
|
|
return 10
|
|
fi
|
|
|
|
# (is it even a syntactically-valid hostname?)
|
|
if ! [[ $HOST =~ ^[a-zA-Z0-9\.-]+$ ]]; then
|
|
echo "ERROR: Invalid hostname passed into dns_lookup [$HOST]" >&2
|
|
return 20
|
|
fi
|
|
|
|
# If the host is cached, and the TTL hasn't expired, return the cached data.
|
|
if [ ${DNS_LOOKUP[$HOST]} ]; then
|
|
if [ ${DNS_EXPIRE[$HOST]} -ge $NOW ]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Try this several times
|
|
local LOOP=$DNS_MAX_LOOPS
|
|
|
|
while [ $LOOP -ge 1 ]; do
|
|
# Ok, let's look this hostname up! Use the first IP returned.
|
|
# - XXX : WARNING: This assumed the output format of 'host -v' doesn't change drastically! XXX -
|
|
# - Because this uses the "Trying" line, it should work for non-FQDN lookups, too -
|
|
|
|
sleep $DNS_WAIT &
|
|
HOST_IP=$( host -v -W $DNS_WAIT -t a "$HOST" | perl -ne 'if (/^Trying "(.*)"/){$h=$1; next;} if (/^$h\.\s+(\d+)\s+IN\s+A\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {$i=$2; last}; END {printf("%s", $i);}' )
|
|
RV=$?
|
|
# If this is empty, something failed. Sleep some and try again...
|
|
if [ $RV -ne 0 ] || [ "x$HOST_IP" == "x" ]; then
|
|
if ping -c1 "$HOST" &>/dev/null && ! host -v -W $DNS_WAIT -t a "$HOST" &>/dev/null; then
|
|
echo "host not working but ping is, disabling DNS caching!"
|
|
DNS_CACHE=0
|
|
return 1
|
|
fi
|
|
echo "Failure resolving [$HOST], waiting and trying again..." >&2
|
|
LOOP=$(( LOOP - 1 ))
|
|
wait
|
|
continue
|
|
fi
|
|
# If we get here, we successfully resolved it
|
|
break;
|
|
done
|
|
|
|
# If LOOP is zero, Something Bad happened.
|
|
if [ $LOOP -le 0 ]; then
|
|
echo "FATAL: unable to resolve $HOST even after $DNS_MAX_LOOPS tries. Giving up." >&2
|
|
return 30
|
|
fi
|
|
|
|
# Resolved ok!
|
|
NOW=$( date +%s )
|
|
DNS_LOOKUP["$HOST"]=$HOST_IP
|
|
DNS_EXPIRE["$HOST"]=$(( NOW + DNS_TTL ))
|
|
return 0
|
|
}
|
|
|
|
# First, see if we have a localhost resolver...
|
|
# - Only look at the first 'nameserver' entry in resolv.conf
|
|
# - This will assume any 127.x.x.x resolver entry is "local"
|
|
LOCAL_RESOLVER=$( grep nameserver /etc/resolv.conf | head -1 | egrep -c '[[:space:]]127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' )
|
|
if [ $LOCAL_RESOLVER -ne 0 ]; then
|
|
if [ $DNS_IGNORE_LOCAL -eq 1 ]; then
|
|
echo "Found local resolver in resolv.conf, but DNS_IGNORE_LOCAL is on, so ignoring" >&2
|
|
else
|
|
echo "Found local resolver in resolv.conf, disabling DNS Cache" >&2
|
|
DNS_CACHE=0
|
|
fi
|
|
fi
|
|
|
|
if ! command -v host &>/dev/null; then
|
|
echo "host command not available, disabling DNS Cache" >&2
|
|
DNS_CACHE=0
|
|
fi
|
|
|
|
|
|
# If we have a local resolver, just use the URL. If not, look up the host and use that IP (replace the URL appropriately)
|
|
# -- DNS Setup done
|
|
|
|
|
|
echo "Using UUID [${UUID}] for stats uploads"
|
|
echo "Using JSON directory [${JSON_DIR}] for source data"
|
|
|
|
|
|
if [ $DNS_CACHE -ne 0 ]; then
|
|
echo "Using script's DNS cache ($DNS_TTL seconds)"
|
|
else
|
|
echo "NOT using script's DNS cache"
|
|
fi
|
|
|
|
JSON_FILE="${JSON_DIR}/aircraft.json"
|
|
|
|
STAT_COUNT=0
|
|
# Grab the current timestamp of the file. Try in a loop a few times, in case
|
|
while [ $STAT_COUNT -lt 5 ]; do
|
|
JSON_STAT=$(stat --printf="%Y" $JSON_FILE 2> /dev/null)
|
|
RV=$?
|
|
|
|
if [ $RV -eq 0 ]; then
|
|
break
|
|
fi
|
|
STAT_COUNT=$(( STAT_COUNT + 1 ))
|
|
sleep 15
|
|
done
|
|
|
|
# Bad juju if we still don't have a stat...
|
|
if [ "x$JSON_STAT" = "x" ]; then
|
|
echo "ERROR: Can't seem to stat $JSON_FILE at startup, bailing out..."
|
|
exit 15
|
|
fi
|
|
|
|
# Complain if this file seems really old
|
|
NOW=$(date +%s)
|
|
DIFF=$(( NOW - JSON_STAT ))
|
|
if [ $DIFF -gt 60 ]; then
|
|
echo "WARNING: $JSON_FILE seems old, are you sure we're using the right path?"
|
|
fi
|
|
|
|
# How long to wait before uploads, minimum (in seconds)
|
|
WAIT_TIME=5
|
|
|
|
# random sleep on startup ... reduce load spikes
|
|
sleep "$(( RANDOM % WAIT_TIME )).$(( RANDOM % 100))"
|
|
|
|
# How long curl will wait to send data (10 sec default)
|
|
MAX_CURL_TIME=10
|
|
|
|
# How much time (sec) has to pass since last JSON update before we say something
|
|
# Initial value is "AGE_COMPLAIN", and then it complains every "AGE_INTERVAL" after that
|
|
# Deftauls are:
|
|
# AGE_COMPLAIN = 30 sec
|
|
# AGE_INTERVAL = 30 min (1800 sec)
|
|
AGE_COMPLAIN=30
|
|
AGE_INTERVAL=$(( 30 * 60 ))
|
|
OLD_AGE=$AGE_COMPLAIN
|
|
while true; do
|
|
wait
|
|
# make this loop from now to the next start last exactly $WAIT_TIME secons
|
|
# sleep in the background then wait for it at the end of the loop
|
|
sleep $WAIT_TIME &
|
|
|
|
NOW=$(date +%s)
|
|
|
|
# Grab new stat. If it fails, wait longer (otherwise assign to the main var)
|
|
NEW_STAT=$(stat --printf="%Y" $JSON_FILE 2> /dev/null)
|
|
RV=$?
|
|
if [ $RV -ne 0 ]; then
|
|
sleep 10
|
|
else
|
|
JSON_STAT=$NEW_STAT
|
|
fi
|
|
DIFF=$(( NOW - JSON_STAT ))
|
|
if [ $DIFF -gt $OLD_AGE ]; then
|
|
echo "WARNING: JSON file $JSON_FILE has not been updated in $DIFF seconds. Did your decoder die?"
|
|
OLD_AGE=$(( OLD_AGE + AGE_INTERVAL ))
|
|
else
|
|
# Reset this here, in case it comes back ;)
|
|
OLD_AGE=$AGE_COMPLAIN
|
|
fi
|
|
|
|
# Move the JSON somewhere before operating on it...
|
|
|
|
rm -f $TMPFILE $NEWFILE
|
|
CP=$(cp $JSON_FILE $TMPFILE 2>&1)
|
|
RV=$?
|
|
if [ $RV -ne 0 ]; then
|
|
# cp failed (file changed during copy, usually), wait a few and loop again
|
|
sleep 2
|
|
continue
|
|
fi
|
|
|
|
if STATUS=$(vcgencmd get_throttled 2>/dev/null | tr -d '"'); then
|
|
STATUS="${STATUS#*=}"
|
|
else
|
|
STATUS=""
|
|
fi
|
|
|
|
if ! jq -c \
|
|
--arg STATUS "$STATUS" \
|
|
--arg UUID "$UUID" \
|
|
' .
|
|
| ."uuid"=$UUID
|
|
| ."v"=$STATUS
|
|
| ."rssi"=(if (.aircraft | length <= 0) then 0 else ([.aircraft[].rssi] | select(. >=0) | add / length | floor) end)
|
|
| ."rssi-min"=(if (.aircraft | length <= 0) then 0 else ([.aircraft[].rssi] | select(. >=0) | min | floor) end)
|
|
| ."rssi-max"=(if (.aircraft | length <= 0) then 0 else ([.aircraft[].rssi] | select(. >=0) | max | floor) end)
|
|
' < $TMPFILE > $NEWFILE
|
|
then
|
|
# this shouldn't happen, don't spam the syslog with the error quite as much
|
|
sleep 15
|
|
# we don't have a json output, let's try again from the start
|
|
continue
|
|
fi
|
|
|
|
|
|
CURL_EXTRA=""
|
|
# If DNS_CACHE is set, use the builtin cache (and correspondingly the additional curl arg
|
|
if [ $DNS_CACHE -ne 0 ]; then
|
|
dns_lookup $REMOTE_HOST
|
|
RV=$?
|
|
if [ $RV -ne 0 ]; then
|
|
# Some sort of error... We'll fall back to normal curl usage, but sleep a little.
|
|
echo "DNS Error for ${REMOTE_HOST}, fallback ..."
|
|
else
|
|
REMOTE_IP=${DNS_LOOKUP[$REMOTE_HOST]}
|
|
CURL_EXTRA="--resolve ${REMOTE_HOST}:443:$REMOTE_IP"
|
|
fi
|
|
fi
|
|
|
|
sleep 0.314
|
|
gzip -c <$NEWFILE >$TEMP_DIR/upload.gz
|
|
sleep 0.314
|
|
|
|
# Push up the data. 'curl' will wait no more than $MAX_CURL_TIME seconds for upload to complete
|
|
curl -m $MAX_CURL_TIME $CURL_EXTRA -sS -X POST -H "adsbx-uuid: ${UUID}" -H "Content_Encoding: gzip" --data-binary @- $REMOTE_URL 2>&1 <$TEMP_DIR/upload.gz
|
|
RV=$?
|
|
|
|
if [ $RV -ne 0 ]; then
|
|
echo "WARNING: curl process returned non-zero ($RV): [$CURL]; Sleeping a little extra."
|
|
sleep $(( 5 + RANDOM % 15 ))
|
|
fi
|
|
done
|
|
|