#!/bin/bash # # Script to install packages for TTN on MultiTech Linux Conduit # # Written by Jac Kersing # # Parts of the script based on tzselect by Paul Eggert. # STATUSFILE=/var/config/.installer VERSION=3.0.27-r1 FILENAME=mp-packet-forwarder_${VERSION}_arm926ejste.ipk URL=https://raw.github.com/kersing/multitech-installer/master/${FILENAME} grep package $STATUSFILE > /dev/null 2> /dev/null if [ $? -eq 0 ] ; then if [ ! -x /opt/lora/mp_pkt_fwd -a ! -x /opt/lora/poly_pkt_fwd ] ; then # statusfile not reset, but gateway has been reflashed, clear file rm $STATUSFILE # and remove software to force re-install opkg remove poly-packet-forwarder > /dev/null 2>&1 opkg remove mp-packet-forwarder > /dev/null 2>&1 fi fi if [ ! -f $STATUSFILE ] ; then touch $STATUSFILE fi # now set the time zone # Output one argument as-is to standard output. # Safer than 'echo', which can mishandle '\' or leading '-'. say() { printf '%s\n' "$1" } # Ask the user to select from the function's arguments, # and assign the selected argument to the variable 'select_result'. # Exit on EOF or I/O error. Use the shell's 'select' builtin if available, # falling back on a less-nice but portable substitute otherwise. if case $BASH_VERSION in ?*) : ;; '') # '; exit' should be redundant, but Dash doesn't properly fail without it. (eval 'set --; select x; do break; done; exit') /dev/null esac then # Do this inside 'eval', as otherwise the shell might exit when parsing it # even though it is never executed. eval ' doselect() { select select_result do case $select_result in "") echo >&2 "Please enter a number in range." ;; ?*) break esac done || exit } # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout. case $BASH_VERSION in [01].*) case `echo 1 | (select x in x; do break; done) 2>/dev/null` in ?*) PS3= esac esac ' else doselect() { # Field width of the prompt numbers. select_width=`expr $# : '.*'` select_i= while : do case $select_i in '') select_i=0 for select_word do select_i=`expr $select_i + 1` printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word" done ;; *[!0-9]*) echo >&2 'Please enter a number in range.' ;; *) if test 1 -le $select_i && test $select_i -le $#; then shift `expr $select_i - 1` select_result=$1 break fi echo >&2 'Please enter a number in range.' esac # Prompt and read input. printf >&2 %s "${PS3-#? }" read select_i || exit done } fi # check for AEP model and ask user to skip network/timezone setup # /var/config/db.json is only present on AEP models, use it to detect AEP grep network $STATUSFILE > /dev/null 2> /dev/null if [ $? -ne 0 -a -f /var/config/db.json ] ; then # Securing the device should be done using the web interface echo "secure" >> $STATUSFILE echo "AEP Model detected, have time zone and network been setup?" doselect Yes No if [ "$select_result" = "No" ] ; then echo "Please configure \"network interfaces\" and \"time\" using the web interface and restart." echo "DO NOT configure \"LoRa Network Server\" in the web interface!" exit fi echo "timezone" >> $STATUSFILE echo "network" >> $STATUSFILE fi grep secure $STATUSFILE > /dev/null 2> /dev/null if [ $? -ne 0 ] ; then # Start by securing the device echo "Securing access to the device, enter the same password twice and" echo "make sure to save this password as the device requires factory" echo "reset when the password is lost!!" passwd root echo "secure" >> $STATUSFILE fi grep timezone $STATUSFILE > /dev/null 2> /dev/null if [ $? -ne 0 ] ; then # -------------------------------- from tzselect code: -------------------- # Interact with the user via stderr and stdin. # Contributed by Paul Eggert. This file is in the public domain. # Specify default values for environment variables if they are unset. AWK=awk TZDIR=/usr/share/zoneinfo coord= location_limit=10 zonetabtype=zone # Make sure the tables are readable. TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE do <"$f" || { say >&2 "$0: time zone files are not set up correctly" exit 1 } done # If the current locale does not support UTF-8, convert data to current # locale's format if possible, as the shell aligns columns better that way. # Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI. ! $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) != 1 }' && { tmp=`(mktemp -d) 2>/dev/null` || { tmp=${TMPDIR-/tmp}/tzselect.$$ && (umask 77 && mkdir -- "$tmp") };} && trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM && (iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \ 2>/dev/null && TZ_COUNTRY_TABLE=$tmp/iso3166.tab && iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab && TZ_ZONE_TABLE=$tmp/$zonetabtype.tab newline=' ' IFS=$newline # Awk script to read a time zone table and output the same table, # with each column preceded by its distance from 'here'. output_distances=' BEGIN { FS = "\t" while (getline &2 'Please identify a location' \ 'so that time zone rules can be set correctly.' continent= country= region= case $coord in ?*) continent=coord;; '') # Ask the user for continent or ocean. echo >&2 'Please select a continent, ocean, "coord", or "TZ".' quoted_continents=` $AWK ' BEGIN { FS = "\t" } /^[^#]/ { entry = substr($3, 1, index($3, "/") - 1) if (entry == "America") entry = entry "s" if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) entry = entry " Ocean" printf "'\''%s'\''\n", entry } ' <"$TZ_ZONE_TABLE" | sort -u | tr '\n' ' ' echo '' ` eval ' doselect '"$quoted_continents"' \ "coord - I want to use geographical coordinates." \ "TZ - I want to specify the time zone using the Posix TZ format." continent=$select_result case $continent in Americas) continent=America;; *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''` esac ' esac case $continent in TZ) # Ask the user for a Posix TZ string. Check that it conforms. while echo >&2 'Please enter the desired value' \ 'of the TZ environment variable.' echo >&2 'For example, GST-10 is a zone named GST' \ 'that is 10 hours ahead (east) of UTC.' read TZ $AWK -v TZ="$TZ" 'BEGIN { tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})" time = "(2[0-4]|[0-1]?[0-9])" \ "(:[0-5][0-9](:[0-5][0-9])?)?" offset = "[-+]?" time mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]" jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \ "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])" datetime = ",(" mdate "|" jdate ")(/" time ")?" tzpattern = "^(:.*|" tzname offset "(" tzname \ "(" offset ")?(" datetime datetime ")?)?)$" if (TZ ~ tzpattern) exit 1 exit 0 }' do say >&2 "'$TZ' is not a conforming Posix time zone string." done TZ_for_date=$TZ;; *) case $continent in coord) case $coord in '') echo >&2 'Please enter coordinates' \ 'in ISO 6709 notation.' echo >&2 'For example, +4042-07403 stands for' echo >&2 '40 degrees 42 minutes north,' \ '74 degrees 3 minutes west.' read coord;; esac distance_table=`$AWK \ -v coord="$coord" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ "$output_distances" <"$TZ_ZONE_TABLE" | sort -n | sed "${location_limit}q" ` regions=`say "$distance_table" | $AWK ' BEGIN { FS = "\t" } { print $NF } '` echo >&2 'Please select one of the following' \ 'time zone regions,' echo >&2 'listed roughly in increasing order' \ "of distance from $coord". doselect $regions region=$select_result TZ=`say "$distance_table" | $AWK -v region="$region" ' BEGIN { FS="\t" } $NF == region { print $4 } '` ;; *) # Get list of names of countries in the continent or ocean. countries=`$AWK \ -v continent="$continent" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { FS = "\t" } /^#/ { next } $3 ~ ("^" continent "/") { ncc = split($1, cc, /,/) for (i = 1; i <= ncc; i++) if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i] } END { while (getline &2 'Please select a country' \ 'whose clocks agree with yours.' doselect $countries country=$select_result;; *) country=$countries esac # Get list of names of time zone rule regions in the country. regions=`$AWK \ -v country="$country" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { FS = "\t" cc = country while (getline &2 'Please select one of the following' \ 'time zone regions.' doselect $regions region=$select_result;; *) region=$regions esac # Determine TZ from country and region. TZ=`$AWK \ -v country="$country" \ -v region="$region" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { FS = "\t" cc = country while (getline &2 "$0: time zone files are not set up correctly" exit 1 } esac # Output TZ info and ask the user to confirm. echo >&2 "" echo >&2 "The following information has been given:" echo >&2 "" case $country%$region%$coord in ?*%?*%) say >&2 " $country$newline $region";; ?*%%) say >&2 " $country";; %?*%?*) say >&2 " coord $coord$newline $region";; %%?*) say >&2 " coord $coord";; *) say >&2 " TZ='$TZ'" esac say >&2 "" say >&2 "Therefore TZ='$TZ' will be used." say >&2 "Is the above information OK?" doselect Yes No ok=$select_result case $ok in Yes) break esac do coord= done # -------------------------------- end tzselect -------------------- # link choosen timezone ln -sf /usr/share/zoneinfo/$TZ /etc/localtime echo "timezone" >> $STATUSFILE fi # On to the network information grep network $STATUSFILE > /dev/null 2> /dev/null if [ $? -ne 0 ] ; then echo "" echo "NETWORK SETUP" echo "" echo "Do you want to use DHCP" doselect Yes No ok=$select_result case $ok in Yes) if [ ! -f /var/config/network/interfaces.org ] ; then mv /var/config/network/interfaces /etc/network/interfaces.org fi cat << _EOF_ > /var/config/network/interfaces # /etc/network/interfaces -- configuration file for ifup(8), ifdown(8) # The loopback interface auto lo iface lo inet loopback # Wired interface auto eth0 iface eth0 inet dhcp #iface eth0 inet static #address 192.168.2.1 #netmask 255.255.255.0 #gateway 192.168.2.254 # Bridge interface with eth0 (comment out eth0 lines above to use with bridge) # iface eth0 inet manual # # auto br0 # iface br0 inet static # bridge_ports eth0 # address 192.168.2.1 # netmask 255.255.255.0 # Wifi client # NOTE: udev rules will bring up wlan0 automatically if a wifi device is detected # and the wlan0 interface is defined, therefore an "auto wlan0" line is not needed. # If "auto wlan0" is also specified, startup conflicts may result. #iface wlan0 inet dhcp #wpa-conf /var/config/wpa_supplicant.conf #wpa-driver nl80211 _EOF_ ;; No) got_it=No while [ $got_it != Yes ] ; do echo "Please provide network parameters" echo -n "IP address: " read ip echo -n "netmask: " read mask echo -n "gateway: " read gw echo -n "DNS IP (use 8.8.8.8 for Google DNS): " read dns echo echo "Supplied information:" echo "IP : $ip" echo "Netmask: $mask" echo "Gateway: $gw" echo "DNS IP : $dns" doselect Yes No got_it=$select_result done cat << _EOF_ > /var/config/network/interfaces # /etc/network/interfaces -- configuration file for ifup(8), ifdown(8) # The loopback interface auto lo iface lo inet loopback # Wired interface auto eth0 iface eth0 inet static address $ip netmask $mask gateway $gw post-up echo 'nameserver $dns' >/etc/resolv.conf # Bridge interface with eth0 (comment out eth0 lines above to use with bridge) # iface eth0 inet manual # # auto br0 # iface br0 inet static # bridge_ports eth0 # address 192.168.2.1 # netmask 255.255.255.0 # Wifi client # NOTE: udev rules will bring up wlan0 automatically if a wifi device is detected # and the wlan0 interface is defined, therefore an "auto wlan0" line is not needed. # If "auto wlan0" is also specified, startup conflicts may result. #iface wlan0 inet dhcp #wpa-conf /var/config/wpa_supplicant.conf #wpa-driver nl80211 _EOF_ esac echo "network" >> $STATUSFILE echo "Network configuration written" echo "" echo "The gateway will now shutdown. Remove power once the status led" echo "stopped blinking, connect the gateway to the new network and reapply" echo "power." echo "" echo "Press enter to continue" read n sync;sync;sync shutdown -h now sleep 600 fi # Network should be configured allowing access to remote servers at this point # wget https://www.github.com/ --no-check-certificate -O /dev/null -o /dev/null if [ $? -ne 0 ] ; then echo "Error in network settings, cannot access www.github.com" echo "Check network settings and rerun this script to correct the setup" grep -v network $STATUSFILE > $STATUSFILE.tmp mv $STATUSFILE.tmp $STATUSFILE exit 1 fi # Set date and time using ntpdate grep date $STATUSFILE > /dev/null 2> /dev/null if [ $? -ne 0 ] ; then ntpdate 0.europe.pool.ntp.org hwclock -u -w echo "date" >> $STATUSFILE fi if [ ! -d /var/config/lora ] ; then mkdir /var/config/lora fi skip=0 gwname="" gwkey="" cluster="" grep '"serv_type": "ttn"' /var/config/lora/local_conf.json > /dev/null 2> /dev/null if [ $? -eq 0 ] ; then echo "Gateway seems to be configured for TTN already, update configuration?" doselect Yes No if [ "$select_result" == "No" ] ; then skip=1 else gwname=$(grep -oE '"serv_gw_id": "[^\\"]*"' /var/config/lora/local_conf.json | sed -e 's/.*": "//' -e 's/"//g' | head -1) gwkey=$(grep -oE '"serv_gw_key": "[^\\"]*"' /var/config/lora/local_conf.json | sed -e 's/.*": "//' -e 's/"//g' | head -1) while :; do echo "Please provide the (short) name of the cluster to use" echo -n "TTN Cluster (eu1/nam1/au1): " read cluster echo "Cluster: $cluster.cloud.thethings.network" echo "Is this correct?" doselect Yes No if [ "$select_result" == "Yes" ] ; then break fi done fi fi if [ $skip -eq 0 ] ; then # Get data from TTN console echo "Provide the gateway registration data from the TTN console" echo "The required key is for an API key with" echo " 'link as Gateway to a Gateway Server for traffic exchange, i.e. write uplink and read downlink' " echo "rights granted" while :; do if [ X"$gwkey" == X"" ] ; then echo "Please enter gateway informtion:" echo -n "Gateway ID: " read gwname echo -n "Gateway Key: " read gwkey echo -n "TTN Cluster (eu1/nam1/au1): " read cluster fi echo echo "Gateway ID: $gwname" echo "Gateway Key: $gwkey" echo "Cluster: $cluster.cloud.thethings.network" echo "Are these values correct?" doselect Yes No if [ "$select_result" == "Yes" ] ; then wget --header="Authorization: Bearer $gwkey" https://${cluster}.cloud.thethings.network/api/v3/gcs/gateways/$gwname/semtechudp/global_conf.json -O /tmp/gwinfo -o /tmp/getres --no-check-certificate grep "server_address" /tmp/gwinfo > /dev/null 2> /dev/null if [ $? -eq 0 ] ; then router=$(grep -oE '"server_address":[ ]*"[^,]*,' /tmp/gwinfo | tail -1 | sed -e 's/.*":[ ]*"//' -e 's/"//g' -e 's/,//') # check for valid router information if [ X"$router" == X"" ] ; then echo "" echo "ERROR:" echo "Can not get gateway configuration from TTN Console" echo "Please check the gateway id and key" echo "" continue fi break else echo "Could not get configuration information." tail -2 /tmp/getres | head -1 fi else gwname="" gwkey="" fi done # Create lora configuration directory and initial files got_it="No" while [ "$got_it" != "Yes" ] ; do echo "SETUP LORA GATEWAY CONFIGURATION" echo -n "E-mail address of gateway operator: " read email echo "" echo "Is the information correct?" doselect Yes No got_it=$select_result done gwid=$(mts-io-sysfs show lora/eui 2> /dev/null | sed 's/://g') if [ X"$gwid" == X"" ] ; then echo "FATAL ERROR: could not obtain gateway id, Lora card not found" exit 1 fi cat << _EOF_ > /var/config/lora/local_conf.json { /* Settings defined in global_conf will be overwritten by those in local_conf */ "gateway_conf": { /* gateway_ID is based on unique hardware ID, do not edit */ "gateway_ID": "$gwid", /* Email of gateway operator, max 40 chars*/ "contact_email": "$email", /* Public description of this device, max 64 chars */ "description": "$descr", "gps": false, "fake_gps": false, "servers": [ { "serv_type": "ttn", "server_address": "$router:1881", "serv_gw_id": "$gwname", "serv_gw_key": "$gwkey", "serv_enabled": true } ] } } _EOF_ fi # Disable the MultiTech lora server processes grep disable-mtech $STATUSFILE > /dev/null 2> /dev/null if [ $? -ne 0 ] ; then echo "Disable MultiTech packet forwarder" /etc/init.d/lora-network-server stop update-rc.d -f lora-network-server remove > /dev/null 2> /dev/null if [ -f /etc/init.d/lora-packet-forwarder ] ; then /etc/init.d/lora-packet-forwarder stop update-rc.d -f lora-packet-forwarder remove > /dev/null 2> /dev/null fi cat << _EOF_ > /etc/default/lora-network-server # set to "yes" or "no" to control starting on boot ENABLED="no" _EOF_ if [ -f /etc/default/lora-packet-forwarder ] ; then cat << _EOF_ > /etc/default/lora-packet-forwarder # set to "yes" or "no" to control starting on boot ENABLED="no" _EOF_ fi echo "disable-mtech" >> $STATUSFILE fi if [ -f /etc/init.d/ttn-pkt-forwarder ] ; then echo "Stopping existing forwarder" /etc/init.d/ttn-pkt-forwarder stop fi # Check for previous forwarder opkg list-installed poly-packet-forwarder | grep poly-packet-forwarder > /dev/null 2> /dev/null if [ $? -eq 0 ] ; then echo "Removing obsolete Poly Packet Forwarder" opkg remove poly-packet-forwarder fi fnd=$(opkg list-installed mp-packet-forwarder) version=$(echo $fnd | cut -d' ' -f 3) if [ X"$version" != X"$VERSION" ] ; then wget $URL -O /tmp/$FILENAME -o /dev/null --no-check-certificate if [ X"$version" == X"" ] ; then echo "Installing TTN Multi Protocol Packet Forwarder" else echo "Upgrading TTN Multi Protocol Packet Forwarder" fi opkg install /tmp/$FILENAME fi # Get global config echo "Get up-to-date TTN configuration for packet forwarder" cp /tmp/gwinfo /var/config/lora/global_conf.json # Everything is in place, start forwarder /etc/init.d/ttn-pkt-forwarder start echo "The installation is now complete." echo "Check the gateway output using:" echo "tail -f /var/log/lora-pkt-fwd.log" echo "(It might take some minutes for the first output to appear!)" echo "Check the gateways last seen status in the TTN console after" echo "a few minutes to verify the setup is working correctly"