#!/bin/sh ############################################################## ## _ __ __ _ _ ## ## | || \/ | | |(_) ## ## ___ _ __ __| || \ / | ___ _ __ | | _ _ __ ## ## / __|| '_ \ / _` || |\/| | / _ \| '__|| || || '_ \ ## ## \__ \| |_) || (_| || | | || __/| | | || || | | | ## ## |___/| .__/ \__,_||_| |_| \___||_| |_||_||_| |_| ## ## | | ## ## |_| ## ## ## ## https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## ############## Shellcheck directives ############# # shellcheck disable=SC2009 # shellcheck disable=SC2012 # shellcheck disable=SC2016 # shellcheck disable=SC2018 # shellcheck disable=SC2019 # shellcheck disable=SC2028 # shellcheck disable=SC2039 # shellcheck disable=SC2059 # shellcheck disable=SC2086 # shellcheck disable=SC2155 # shellcheck disable=SC3045 ############################################################## ### Start of script variables ### readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.5" SCRIPT_BRANCH="master" SCRIPT_REPO="https://jackyaz.io/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" readonly SCRIPT_WEBPAGE_DIR="$(readlink /www/user)" readonly SCRIPT_WEB_DIR="$SCRIPT_WEBPAGE_DIR/$SCRIPT_NAME_LOWER" readonly SHARED_DIR="/jffs/addons/shared-jy" readonly SHARED_REPO="https://jackyaz.io/shared-jy/master" readonly SHARED_WEB_DIR="$SCRIPT_WEBPAGE_DIR/shared-jy" readonly HOME_DIR="/$(readlink "$HOME")" readonly OOKLA_DIR="$SCRIPT_DIR/ookla" readonly OOKLA_LICENSE_DIR="$SCRIPT_DIR/ooklalicense" readonly OOKLA_HOME_DIR="$HOME_DIR/.config/ookla" [ -z "$(nvram get odmpid)" ] && ROUTER_MODEL=$(nvram get productid) || ROUTER_MODEL=$(nvram get odmpid) [ -f /opt/bin/sqlite3 ] && SQLITE3_PATH=/opt/bin/sqlite3 || SQLITE3_PATH=/usr/sbin/sqlite3 if [ "$(uname -m)" = "aarch64" ]; then ARCH="aarch64" else /bin/grep -Eq 'Features\s*:.*\s+v?fp\s+' /proc/cpuinfo && ARCH="arm" || ARCH="armel" fi ### End of script variables ### ### Start of output format variables ### readonly CRIT="\\e[41m" readonly ERR="\\e[31m" readonly WARN="\\e[33m" readonly PASS="\\e[32m" readonly BOLD="\\e[1m" readonly SETTING="${BOLD}\\e[36m" readonly CLEARFORMAT="\\e[0m" ### End of output format variables ### ### Start of Speedtest Server Variables ### serverno="" servername="" ### End of Speedtest Server Variables ### # $1 = print to syslog, $2 = message to print, $3 = log level Print_Output(){ if [ "$1" = "true" ]; then logger -t "$SCRIPT_NAME" "$2" fi printf "${BOLD}${3}%s${CLEARFORMAT}\\n\\n" "$2" } Firmware_Version_Check(){ if nvram get rc_support | grep -qF "am_addons"; then return 0 else return 1 fi } ### Code for these functions inspired by https://github.com/Adamm00 - credit to @Adamm ### Check_Lock(){ if [ -f "/tmp/$SCRIPT_NAME.lock" ]; then ageoflock=$(($(date +%s) - $(date +%s -r /tmp/$SCRIPT_NAME.lock))) if [ "$ageoflock" -gt 600 ]; then Print_Output true "Stale lock file found (>600 seconds old) - purging lock" "$ERR" kill "$(sed -n '1p' /tmp/$SCRIPT_NAME.lock)" >/dev/null 2>&1 Clear_Lock echo "$$" > "/tmp/$SCRIPT_NAME.lock" return 0 else Print_Output true "Lock file found (age: $ageoflock seconds) - stopping to prevent duplicate runs" "$ERR" if [ -z "$1" ]; then exit 1 else if [ "$1" = "webui" ]; then echo 'var spdteststatus = "LOCKED";' > /tmp/detect_spdtest.js exit 1 fi return 1 fi fi else echo "$$" > "/tmp/$SCRIPT_NAME.lock" return 0 fi } Clear_Lock(){ rm -f "/tmp/$SCRIPT_NAME.lock" 2>/dev/null return 0 } Check_Swap(){ if [ "$(wc -l < /proc/swaps)" -ge 2 ]; then return 0; else return 1; fi } ############################################################################ Set_Version_Custom_Settings(){ SETTINGSFILE="/jffs/addons/custom_settings.txt" case "$1" in local) if [ -f "$SETTINGSFILE" ]; then if [ "$(grep -c "spdmerlin_version_local" $SETTINGSFILE)" -gt 0 ]; then if [ "$2" != "$(grep "spdmerlin_version_local" /jffs/addons/custom_settings.txt | cut -f2 -d' ')" ]; then sed -i "s/spdmerlin_version_local.*/spdmerlin_version_local $2/" "$SETTINGSFILE" fi else echo "spdmerlin_version_local $2" >> "$SETTINGSFILE" fi else echo "spdmerlin_version_local $2" >> "$SETTINGSFILE" fi ;; server) if [ -f "$SETTINGSFILE" ]; then if [ "$(grep -c "spdmerlin_version_server" $SETTINGSFILE)" -gt 0 ]; then if [ "$2" != "$(grep "spdmerlin_version_server" /jffs/addons/custom_settings.txt | cut -f2 -d' ')" ]; then sed -i "s/spdmerlin_version_server.*/spdmerlin_version_server $2/" "$SETTINGSFILE" fi else echo "spdmerlin_version_server $2" >> "$SETTINGSFILE" fi else echo "spdmerlin_version_server $2" >> "$SETTINGSFILE" fi ;; esac } Update_Check(){ echo 'var updatestatus = "InProgress";' > "$SCRIPT_WEB_DIR/detect_update.js" doupdate="false" localver=$(grep "SCRIPT_VERSION=" "/jffs/scripts/$SCRIPT_NAME_LOWER" | grep -m1 -oE 'v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})') /usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/404/$SCRIPT_NAME_LOWER.sh" | grep -qF "jackyaz" || { Print_Output true "404 error detected - stopping update" "$ERR"; return 1; } serverver=$(/usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/version/$SCRIPT_NAME_LOWER.sh" | grep "SCRIPT_VERSION=" | grep -m1 -oE 'v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})') if [ "$localver" != "$serverver" ]; then doupdate="version" Set_Version_Custom_Settings server "$serverver" echo 'var updatestatus = "'"$serverver"'";' > "$SCRIPT_WEB_DIR/detect_update.js" else localmd5="$(md5sum "/jffs/scripts/$SCRIPT_NAME_LOWER" | awk '{print $1}')" remotemd5="$(/usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/md5/$SCRIPT_NAME_LOWER.sh" | md5sum | awk '{print $1}')" if [ "$localmd5" != "$remotemd5" ]; then doupdate="md5" Set_Version_Custom_Settings server "$serverver-hotfix" echo 'var updatestatus = "'"$serverver-hotfix"'";' > "$SCRIPT_WEB_DIR/detect_update.js" fi fi if [ "$doupdate" = "false" ]; then echo 'var updatestatus = "None";' > "$SCRIPT_WEB_DIR/detect_update.js" fi echo "$doupdate,$localver,$serverver" } Update_Version(){ if [ -z "$1" ]; then updatecheckresult="$(Update_Check)" isupdate="$(echo "$updatecheckresult" | cut -f1 -d',')" localver="$(echo "$updatecheckresult" | cut -f2 -d',')" serverver="$(echo "$updatecheckresult" | cut -f3 -d',')" if [ "$isupdate" = "version" ]; then Print_Output true "New version of $SCRIPT_NAME available - $serverver" "$PASS" elif [ "$isupdate" = "md5" ]; then Print_Output true "MD5 hash of $SCRIPT_NAME does not match - hotfix available - $serverver" "$PASS" fi if [ "$isupdate" != "false" ]; then printf "\\n${BOLD}Do you want to continue with the update? (y/n)${CLEARFORMAT} " read -r confirm case "$confirm" in y|Y) printf "\\n" Update_File README.md Update_File LICENSE Update_File "$ARCH.tar.gz" Update_File spdstats_www.asp Update_File shared-jy.tar.gz Download_File "$SCRIPT_REPO/update/$SCRIPT_NAME_LOWER.sh" "/jffs/scripts/$SCRIPT_NAME_LOWER" && Print_Output true "$SCRIPT_NAME successfully updated" chmod 0755 "/jffs/scripts/$SCRIPT_NAME_LOWER" Set_Version_Custom_Settings local "$serverver" Set_Version_Custom_Settings server "$serverver" Clear_Lock PressEnter exec "$0" exit 0 ;; *) printf "\\n" Clear_Lock return 1 ;; esac else Print_Output true "No updates available - latest is $localver" "$WARN" Clear_Lock fi fi if [ "$1" = "force" ]; then serverver=$(/usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/version/$SCRIPT_NAME_LOWER.sh" | grep "SCRIPT_VERSION=" | grep -m1 -oE 'v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})') Print_Output true "Downloading latest version ($serverver) of $SCRIPT_NAME" "$PASS" Update_File README.md Update_File LICENSE Update_File "$ARCH.tar.gz" Update_File spdstats_www.asp Update_File shared-jy.tar.gz Download_File "$SCRIPT_REPO/update/$SCRIPT_NAME_LOWER.sh" "/jffs/scripts/$SCRIPT_NAME_LOWER" && Print_Output true "$SCRIPT_NAME successfully updated" chmod 0755 "/jffs/scripts/$SCRIPT_NAME_LOWER" Set_Version_Custom_Settings local "$serverver" Set_Version_Custom_Settings server "$serverver" Clear_Lock if [ -z "$2" ]; then PressEnter exec "$0" elif [ "$2" = "unattended" ]; then exec "$0" postupdate fi exit 0 fi } Update_File(){ if [ "$1" = "$ARCH.tar.gz" ]; then tmpfile="/tmp/$1" Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" tar -xzf "$tmpfile" -C "/tmp" rm -f "$tmpfile" localmd5="$(md5sum "$OOKLA_DIR/speedtest" | awk '{print $1}')" tmpmd5="$(md5sum /tmp/speedtest | awk '{print $1}')" if [ "$localmd5" != "$tmpmd5" ]; then rm -f "$OOKLA_DIR"/* Download_File "$SCRIPT_REPO/files/$1" "$OOKLA_DIR/$1" tar -xzf "$OOKLA_DIR/$1" -C "$OOKLA_DIR" rm -f "$OOKLA_DIR/$1" chmod 0755 "$OOKLA_DIR/speedtest" Print_Output true "New version of Speedtest CLI downloaded to $OOKLA_DIR/speedtest" "$PASS" fi rm -f "/tmp/speedtest*" elif [ "$1" = "spdstats_www.asp" ]; then tmpfile="/tmp/$1" Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" if ! diff -q "$tmpfile" "$SCRIPT_DIR/$1" >/dev/null 2>&1; then if [ -f "$SCRIPT_DIR/$1" ]; then Get_WebUI_Page "$SCRIPT_DIR/$1" sed -i "\\~$MyPage~d" /tmp/menuTree.js rm -f "$SCRIPT_WEBPAGE_DIR/$MyPage" 2>/dev/null fi Download_File "$SCRIPT_REPO/files/$1" "$SCRIPT_DIR/$1" Print_Output true "New version of $1 downloaded" "$PASS" Mount_WebUI fi rm -f "$tmpfile" elif [ "$1" = "shared-jy.tar.gz" ]; then if [ ! -f "$SHARED_DIR/$1.md5" ]; then Download_File "$SHARED_REPO/$1" "$SHARED_DIR/$1" Download_File "$SHARED_REPO/$1.md5" "$SHARED_DIR/$1.md5" tar -xzf "$SHARED_DIR/$1" -C "$SHARED_DIR" rm -f "$SHARED_DIR/$1" Print_Output true "New version of $1 downloaded" "$PASS" else localmd5="$(cat "$SHARED_DIR/$1.md5")" remotemd5="$(/usr/sbin/curl -fsL --retry 3 "$SHARED_REPO/$1.md5")" if [ "$localmd5" != "$remotemd5" ]; then Download_File "$SHARED_REPO/$1" "$SHARED_DIR/$1" Download_File "$SHARED_REPO/$1.md5" "$SHARED_DIR/$1.md5" tar -xzf "$SHARED_DIR/$1" -C "$SHARED_DIR" rm -f "$SHARED_DIR/$1" Print_Output true "New version of $1 downloaded" "$PASS" fi fi elif [ "$1" = "README.md" ] || [ "$1" = "LICENSE" ]; then tmpfile="/tmp/$1" Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" if ! diff -q "$tmpfile" "$SCRIPT_DIR/$1" >/dev/null 2>&1; then Download_File "$SCRIPT_REPO/files/$1" "$SCRIPT_DIR/$1" fi rm -f "$tmpfile" else return 1 fi } Validate_Bandwidth(){ if echo "$1" | /bin/grep -oq "^[0-9]*\.\?[0-9]*$"; then return 0 else return 1 fi } Validate_Number(){ if [ "$1" -eq "$1" ] 2>/dev/null; then return 0 else return 1 fi } Create_Dirs(){ if [ ! -d "$SCRIPT_DIR" ]; then mkdir -p "$SCRIPT_DIR" fi if [ ! -d "$SCRIPT_STORAGE_DIR" ]; then mkdir -p "$SCRIPT_STORAGE_DIR" fi if [ ! -d "$CSV_OUTPUT_DIR" ]; then mkdir -p "$CSV_OUTPUT_DIR" fi if [ ! -d "$OOKLA_DIR" ]; then mkdir -p "$OOKLA_DIR" fi if [ ! -d "$OOKLA_LICENSE_DIR" ]; then mkdir -p "$OOKLA_LICENSE_DIR" fi if [ ! -d "$OOKLA_HOME_DIR" ]; then mkdir -p "$OOKLA_HOME_DIR" fi if [ ! -d "$SHARED_DIR" ]; then mkdir -p "$SHARED_DIR" fi if [ ! -d "$SCRIPT_WEBPAGE_DIR" ]; then mkdir -p "$SCRIPT_WEBPAGE_DIR" fi if [ ! -d "$SCRIPT_WEB_DIR" ]; then mkdir -p "$SCRIPT_WEB_DIR" fi } Create_Symlinks(){ printf "WAN\\n" > "$SCRIPT_INTERFACES" for index in 1 2 3 4 5; do comment="" if [ ! -f "/sys/class/net/tun1$index/operstate" ] || [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ]; then comment=" #excluded - interface not up#" fi if [ "$index" -lt 5 ]; then printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" else printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" fi done if [ "$1" = "force" ]; then rm -f "$SCRIPT_INTERFACES_USER" fi if [ ! -f "$SCRIPT_INTERFACES_USER" ]; then touch "$SCRIPT_INTERFACES_USER" fi while IFS='' read -r line || [ -n "$line" ]; do if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ]; then printf "%s\\n" "$line" >> "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 until [ $COUNTER -gt "$interfacecount" ]; do Set_Interface_State "$COUNTER" COUNTER=$((COUNTER + 1)) done rm -rf "${SCRIPT_WEB_DIR:?}/"* 2>/dev/null ln -s /tmp/spd-stats.txt "$SCRIPT_WEB_DIR/spd-stats.htm" 2>/dev/null ln -s /tmp/spd-result.txt "$SCRIPT_WEB_DIR/spd-result.htm" 2>/dev/null ln -s /tmp/detect_spdtest.js "$SCRIPT_WEB_DIR/detect_spdtest.js" 2>/dev/null ln -s /tmp/spdmerlin-binary "$SCRIPT_WEB_DIR/spd-binary.htm" 2>/dev/null ln -s "$SCRIPT_STORAGE_DIR/.autobwoutfile" "$SCRIPT_WEB_DIR/autobwoutfile.htm" 2>/dev/null ln -s "$SCRIPT_CONF" "$SCRIPT_WEB_DIR/config.htm" 2>/dev/null ln -s "$SCRIPT_INTERFACES_USER" "$SCRIPT_WEB_DIR/interfaces_user.htm" 2>/dev/null ln -s "$SCRIPT_STORAGE_DIR/spdtitletext.js" "$SCRIPT_WEB_DIR/spdtitletext.js" 2>/dev/null FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5" for IFACE_NAME in $FULL_IFACELIST; do ln -s "$SCRIPT_STORAGE_DIR/lastx_${IFACE_NAME}.csv" "$SCRIPT_WEB_DIR/lastx_${IFACE_NAME}.htm" done ln -s "$CSV_OUTPUT_DIR" "$SCRIPT_WEB_DIR/csv" 2>/dev/null if [ ! -d "$SHARED_WEB_DIR" ]; then ln -s "$SHARED_DIR" "$SHARED_WEB_DIR" 2>/dev/null fi } Conf_FromSettings(){ SETTINGSFILE="/jffs/addons/custom_settings.txt" TMPFILE="/tmp/spdmerlin_settings.txt" if [ -f "$SETTINGSFILE" ]; then if [ "$(grep "spdmerlin_" $SETTINGSFILE | grep -v "version" -c)" -gt 0 ]; then Print_Output true "Updated settings from WebUI found, merging into $SCRIPT_CONF" "$PASS" cp -a "$SCRIPT_CONF" "$SCRIPT_CONF.bak" grep "spdmerlin_" "$SETTINGSFILE" | grep -v "version" > "$TMPFILE" sed -i "s/spdmerlin_//g;s/ /=/g" "$TMPFILE" while IFS='' read -r line || [ -n "$line" ]; do SETTINGNAME="$(echo "$line" | cut -f1 -d'=' | awk '{ print toupper($1) }')" SETTINGVALUE="$(echo "$line" | cut -f2- -d'=' | sed "s/=/ /g")" sed -i "s~$SETTINGNAME=.*~$SETTINGNAME=$SETTINGVALUE~" "$SCRIPT_CONF" done < "$TMPFILE" grep 'spdmerlin_version' "$SETTINGSFILE" > "$TMPFILE" sed -i "\\~spdmerlin_~d" "$SETTINGSFILE" mv "$SETTINGSFILE" "$SETTINGSFILE.bak" cat "$SETTINGSFILE.bak" "$TMPFILE" > "$SETTINGSFILE" rm -f "$TMPFILE" rm -f "$SETTINGSFILE.bak" ScriptStorageLocation "$(ScriptStorageLocation check)" Create_Symlinks if AutomaticMode check; then Auto_Cron delete 2>/dev/null Auto_Cron create 2>/dev/null else Auto_Cron delete 2>/dev/null fi if [ "$(AutoBWEnable check)" = "true" ]; then if [ "$(ExcludeFromQoS check)" = "false" ]; then Print_Output true "Enabling Exclude from QoS (required for AutoBW)" ExcludeFromQoS enable fi fi Generate_CSVs Print_Output true "Merge of updated settings from WebUI completed successfully" "$PASS" else Print_Output true "No updated settings from WebUI found, no merge into $SCRIPT_CONF necessary" "$PASS" fi fi } Interfaces_FromSettings(){ SETTINGSFILE="/jffs/addons/custom_settings.txt" if [ -f "$SETTINGSFILE" ]; then if grep -q "spdmerlin_ifaces_enabled" "$SETTINGSFILE"; then Print_Output true "Updated interfaces from WebUI found, merging into $SCRIPT_INTERFACES_USER" "$PASS" cp -a "$SCRIPT_INTERFACES_USER" "$SCRIPT_INTERFACES_USER.bak" SETTINGVALUE="$(grep "spdmerlin_ifaces_enabled" "$SETTINGSFILE" | cut -f2 -d' ')" sed -i "\\~spdmerlin_ifaces_enabled~d" "$SETTINGSFILE" printf "WAN #excluded#\\n" > "$SCRIPT_INTERFACES" for index in 1 2 3 4 5; do comment=" #excluded#" if [ ! -f "/sys/class/net/tun1$index/operstate" ] || [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ]; then comment=" #excluded - interface not up#" fi printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" done echo "" > "$SCRIPT_INTERFACES_USER" while IFS='' read -r line || [ -n "$line" ]; do if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ]; then printf "%s\\n" "$line" >> "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 until [ $COUNTER -gt "$interfacecount" ]; do Set_Interface_State "$COUNTER" COUNTER=$((COUNTER + 1)) done for iface in $(echo "$SETTINGVALUE" | sed "s/,/ /g"); do ifacelinenumber="$(grep -n "$iface" "$SCRIPT_INTERFACES_USER" | cut -f1 -d':')" interfaceline="$(sed "$ifacelinenumber!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" if echo "$interfaceline" | grep -q "#excluded" ; then IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then sed -i "${ifacelinenumber}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" else sed -i "${ifacelinenumber}s/ #excluded - interface not up#//" "$SCRIPT_INTERFACES_USER" sed -i "${ifacelinenumber}s/ #excluded#//" "$SCRIPT_INTERFACES_USER" fi else IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then sed -i "$ifacelinenumber"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" fi fi done awk 'NF' "$SCRIPT_INTERFACES_USER" > /tmp/spd-interfaces mv /tmp/spd-interfaces "$SCRIPT_INTERFACES_USER" Print_Output true "Merge of updated interfaces from WebUI completed successfully" "$PASS" else Print_Output true "No updated interfaces from WebUI found, no merge into $SCRIPT_INTERFACES_USER necessary" "$PASS" fi fi } Conf_Exists(){ if [ -f "$SCRIPT_CONF" ]; then dos2unix "$SCRIPT_CONF" chmod 0644 "$SCRIPT_CONF" sed -i -e 's/"//g' "$SCRIPT_CONF" if grep -q "SCHEDULESTART" "$SCRIPT_CONF"; then { echo "SCHDAYS=*"; echo "SCHHOURS=*"; echo "SCHMINS=12,42"; } >> "$SCRIPT_CONF" sed -i '/SCHEDULESTART/d;/SCHEDULEEND/d;/MINUTE/d;/TESTFREQUENCY/d' "$SCRIPT_CONF" if AutomaticMode check; then Auto_Cron delete 2>/dev/null Auto_Cron create 2>/dev/null else Auto_Cron delete 2>/dev/null fi fi if ! grep -q "AUTOBW_AVERAGE_CALC" "$SCRIPT_CONF"; then echo "AUTOBW_AVERAGE_CALC=10" >> "$SCRIPT_CONF" fi if grep -q "OUTPUTDATAMODE" "$SCRIPT_CONF"; then sed -i '/OUTPUTDATAMODE/d;' "$SCRIPT_CONF" fi if ! grep -q "DAYSTOKEEP" "$SCRIPT_CONF"; then echo "DAYSTOKEEP=30" >> "$SCRIPT_CONF" fi if ! grep -q "LASTXRESULTS" "$SCRIPT_CONF"; then echo "LASTXRESULTS=10" >> "$SCRIPT_CONF" fi if ! grep -q "SPEEDTESTBINARY" "$SCRIPT_CONF"; then if [ -f /usr/sbin/ookla ]; then echo "SPEEDTESTBINARY=builtin" >> "$SCRIPT_CONF" else echo "SPEEDTESTBINARY=external" >> "$SCRIPT_CONF" fi fi return 0 else { echo "PREFERREDSERVER_WAN=0|None configured"; echo "USEPREFERRED_WAN=false"; echo "AUTOMATED=true" ; echo "OUTPUTTIMEMODE=unix"; echo "STORAGELOCATION=jffs"; } >> "$SCRIPT_CONF" for index in 1 2 3 4 5; do { echo "PREFERREDSERVER_VPNC$index=0|None configured"; echo "USEPREFERRED_VPNC$index=false"; } >> "$SCRIPT_CONF" done { echo "AUTOBW_ENABLED=false"; echo "AUTOBW_SF_DOWN=95"; echo "AUTOBW_SF_UP=95"; echo "AUTOBW_ULIMIT_DOWN=0"; echo "AUTOBW_LLIMIT_DOWN=0"; echo "AUTOBW_ULIMIT_UP=0"; echo "AUTOBW_LLIMIT_UP=0"; echo "AUTOBW_THRESHOLD_UP=10"; echo "AUTOBW_THRESHOLD_DOWN=10"; echo "AUTOBW_AVERAGE_CALC=10"; echo "STORERESULTURL=true"; echo "EXCLUDEFROMQOS=true"; echo "SCHDAYS=*"; echo "SCHHOURS=*"; echo "SCHMINS=12,42"; echo "DAYSTOKEEP=30"; echo "LASTXRESULTS=10";} >> "$SCRIPT_CONF" if [ -f /usr/sbin/ookla ]; then echo "SPEEDTESTBINARY=builtin" >> "$SCRIPT_CONF" else echo "SPEEDTESTBINARY=external" >> "$SCRIPT_CONF" fi return 1 fi } Auto_ServiceEvent(){ case $1 in create) if [ -f /jffs/scripts/service-event ]; then STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/service-event) STARTUPLINECOUNTEX=$(grep -cx 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { /jffs/scripts/'"$SCRIPT_NAME_LOWER"' service_event "$@" & }; fi # '"$SCRIPT_NAME" /jffs/scripts/service-event) if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ]; }; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/service-event fi if [ "$STARTUPLINECOUNTEX" -eq 0 ]; then echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { /jffs/scripts/'"$SCRIPT_NAME_LOWER"' service_event "$@" & }; fi # '"$SCRIPT_NAME" >> /jffs/scripts/service-event fi else echo "#!/bin/sh" > /jffs/scripts/service-event echo "" >> /jffs/scripts/service-event echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { /jffs/scripts/'"$SCRIPT_NAME_LOWER"' service_event "$@" & }; fi # '"$SCRIPT_NAME" >> /jffs/scripts/service-event chmod 0755 /jffs/scripts/service-event fi ;; delete) if [ -f /jffs/scripts/service-event ]; then STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/service-event) if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/service-event fi fi ;; esac } Auto_Startup(){ case $1 in create) if [ -f /jffs/scripts/services-start ]; then STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/services-start) if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/services-start fi fi if [ -f /jffs/scripts/post-mount ]; then STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/post-mount) STARTUPLINECOUNTEX=$(grep -cx "/jffs/scripts/$SCRIPT_NAME_LOWER startup"' "$@" & # '"$SCRIPT_NAME" /jffs/scripts/post-mount) if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ]; }; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/post-mount fi if [ "$STARTUPLINECOUNTEX" -eq 0 ]; then echo "/jffs/scripts/$SCRIPT_NAME_LOWER startup"' "$@" & # '"$SCRIPT_NAME" >> /jffs/scripts/post-mount fi else echo "#!/bin/sh" > /jffs/scripts/post-mount echo "" >> /jffs/scripts/post-mount echo "/jffs/scripts/$SCRIPT_NAME_LOWER startup"' "$@" & # '"$SCRIPT_NAME" >> /jffs/scripts/post-mount chmod 0755 /jffs/scripts/post-mount fi ;; delete) if [ -f /jffs/scripts/services-start ]; then STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/services-start) if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/services-start fi fi if [ -f /jffs/scripts/post-mount ]; then STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/post-mount) if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/post-mount fi fi ;; esac } Auto_Cron(){ case $1 in create) STARTUPLINECOUNT=$(cru l | grep -c "$SCRIPT_NAME") if [ "$STARTUPLINECOUNT" -eq 0 ]; then CRU_DAYNUMBERS="$(grep "SCHDAYS" "$SCRIPT_CONF" | cut -f2 -d"=" | sed 's/Sun/0/;s/Mon/1/;s/Tues/2/;s/Wed/3/;s/Thurs/4/;s/Fri/5/;s/Sat/6/;')" CRU_HOURS="$(grep "SCHHOURS" "$SCRIPT_CONF" | cut -f2 -d"=")" CRU_MINUTES="$(grep "SCHMINS" "$SCRIPT_CONF" | cut -f2 -d"=")" cru a "$SCRIPT_NAME" "$CRU_MINUTES $CRU_HOURS * * $CRU_DAYNUMBERS /jffs/scripts/$SCRIPT_NAME_LOWER generate" fi ;; delete) STARTUPLINECOUNT=$(cru l | grep -c "$SCRIPT_NAME") if [ "$STARTUPLINECOUNT" -gt 0 ]; then cru d "$SCRIPT_NAME" fi ;; esac } Get_Interface_From_Name(){ IFACE="" case "$1" in WAN) if [ "$(nvram get sw_mode)" -ne 1 ]; then IFACE="br0" elif [ "$(nvram get wan0_proto)" = "pppoe" ] || [ "$(nvram get wan0_proto)" = "pptp" ] || [ "$(nvram get wan0_proto)" = "l2tp" ]; then IFACE="ppp0" else IFACE="$(nvram get wan0_ifname)" fi ;; VPNC1) IFACE="tun11" ;; VPNC2) IFACE="tun12" ;; VPNC3) IFACE="tun13" ;; VPNC4) IFACE="tun14" ;; VPNC5) IFACE="tun15" ;; esac echo "$IFACE" } Set_Interface_State(){ interfaceline="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" if echo "$interfaceline" | grep -q "VPN" ; then if echo "$interfaceline" | grep -q "#excluded" ; then IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then sed -i "$1"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" else sed -i "$1"'s/ #excluded - interface not up#/ #excluded#/' "$SCRIPT_INTERFACES_USER" fi fi fi } Generate_Interface_List(){ ScriptHeader goback="false" printf "Retrieving list of interfaces...\\n\\n" interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 until [ $COUNTER -gt "$interfacecount" ]; do Set_Interface_State "$COUNTER" interfaceline="$(sed "$COUNTER!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" printf "%s) %s\\n" "$COUNTER" "$interfaceline" COUNTER=$((COUNTER + 1)) done printf "\\ne) Go back\\n" while true; do printf "\\n${BOLD}Please select a chart to toggle inclusion in %s (1-%s):${CLEARFORMAT} " "$SCRIPT_NAME" "$interfacecount" read -r interface if [ "$interface" = "e" ]; then goback="true" break elif ! Validate_Number "$interface"; then printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$interfacecount" else if [ "$interface" -lt 1 ] || [ "$interface" -gt "$interfacecount" ]; then printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$interfacecount" else interfaceline="$(sed "$interface!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" if echo "$interfaceline" | grep -q "#excluded" ; then IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then sed -i "$interface"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" else sed -i "$interface"'s/ #excluded - interface not up#//' "$SCRIPT_INTERFACES_USER" sed -i "$interface"'s/ #excluded#//' "$SCRIPT_INTERFACES_USER" fi else IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then sed -i "$interface"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" else sed -i "$interface"'s/$/ #excluded#/' "$SCRIPT_INTERFACES_USER" fi fi sed -i 's/ *$//' "$SCRIPT_INTERFACES_USER" printf "\\n" break fi fi done if [ "$goback" != "true" ]; then Generate_Interface_List fi } Download_File(){ /usr/sbin/curl -fsL --retry 3 "$1" -o "$2" } Get_WebUI_Page(){ MyPage="none" for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do page="/www/user/user$i.asp" if [ -f "$page" ] && [ "$(md5sum < "$1")" = "$(md5sum < "$page")" ]; then MyPage="user$i.asp" return elif [ "$MyPage" = "none" ] && [ ! -f "$page" ]; then MyPage="user$i.asp" fi done } ### function based on @dave14305's FlexQoS webconfigpage function ### Get_WebUI_URL(){ urlpage="" urlproto="" urldomain="" urlport="" urlpage="$(sed -nE "/$SCRIPT_NAME/ s/.*url\: \"(user[0-9]+\.asp)\".*/\1/p" /tmp/menuTree.js)" if [ "$(nvram get http_enable)" -eq 1 ]; then urlproto="https" else urlproto="http" fi if [ -n "$(nvram get lan_domain)" ]; then urldomain="$(nvram get lan_hostname).$(nvram get lan_domain)" else urldomain="$(nvram get lan_ipaddr)" fi if [ "$(nvram get ${urlproto}_lanport)" -eq 80 ] || [ "$(nvram get ${urlproto}_lanport)" -eq 443 ]; then urlport="" else urlport=":$(nvram get ${urlproto}_lanport)" fi if echo "$urlpage" | grep -qE "user[0-9]+\.asp"; then echo "${urlproto}://${urldomain}${urlport}/${urlpage}" | tr "A-Z" "a-z" else echo "WebUI page not found" fi } ### ### ### locking mechanism code credit to Martineau (@MartineauUK) ### Mount_WebUI(){ Print_Output true "Mounting WebUI tab for $SCRIPT_NAME" "$PASS" LOCKFILE=/tmp/addonwebui.lock FD=386 eval exec "$FD>$LOCKFILE" flock -x "$FD" Get_WebUI_Page "$SCRIPT_DIR/spdstats_www.asp" if [ "$MyPage" = "none" ]; then Print_Output true "Unable to mount $SCRIPT_NAME WebUI page, exiting" "$CRIT" flock -u "$FD" return 1 fi cp -f "$SCRIPT_DIR/spdstats_www.asp" "$SCRIPT_WEBPAGE_DIR/$MyPage" echo "$SCRIPT_NAME" > "$SCRIPT_WEBPAGE_DIR/$(echo $MyPage | cut -f1 -d'.').title" if [ "$(uname -o)" = "ASUSWRT-Merlin" ]; then if [ ! -f /tmp/index_style.css ]; then cp -f /www/index_style.css /tmp/ fi if ! grep -q '.menu_Addons' /tmp/index_style.css ; then echo ".menu_Addons { background: url(ext/shared-jy/addons.png); }" >> /tmp/index_style.css fi umount /www/index_style.css 2>/dev/null mount -o bind /tmp/index_style.css /www/index_style.css if [ ! -f /tmp/menuTree.js ]; then cp -f /www/require/modules/menuTree.js /tmp/ fi sed -i "\\~$MyPage~d" /tmp/menuTree.js if ! grep -q 'menuName: "Addons"' /tmp/menuTree.js ; then lineinsbefore="$(( $(grep -n "exclude:" /tmp/menuTree.js | cut -f1 -d':') - 1))" sed -i "$lineinsbefore"'i,\n{\nmenuName: "Addons",\nindex: "menu_Addons",\ntab: [\n{url: "javascript:var helpwindow=window.open('"'"'/ext/shared-jy/redirect.htm'"'"')", tabName: "Help & Support"},\n{url: "NULL", tabName: "__INHERIT__"}\n]\n}' /tmp/menuTree.js fi sed -i "/url: \"javascript:var helpwindow=window.open('\/ext\/shared-jy\/redirect.htm'/i {url: \"$MyPage\", tabName: \"$SCRIPT_NAME\"}," /tmp/menuTree.js umount /www/require/modules/menuTree.js 2>/dev/null mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js fi flock -u "$FD" Print_Output true "Mounted $SCRIPT_NAME WebUI page as $MyPage" "$PASS" } AutomaticMode(){ case "$1" in enable) sed -i 's/^AUTOMATED=.*$/AUTOMATED=true/' "$SCRIPT_CONF" Auto_Cron create 2>/dev/null ;; disable) sed -i 's/^AUTOMATED=.*$/AUTOMATED=false/' "$SCRIPT_CONF" Auto_Cron delete 2>/dev/null ;; check) AUTOMATED=$(grep "AUTOMATED" "$SCRIPT_CONF" | cut -f2 -d"=") if [ "$AUTOMATED" = "true" ]; then return 0; else return 1; fi ;; esac } TestSchedule(){ case "$1" in update) sed -i 's/^SCHDAYS=.*$/SCHDAYS='"$(echo "$2" | sed 's/0/Sun/;s/1/Mon/;s/2/Tues/;s/3/Wed/;s/4/Thurs/;s/5/Fri/;s/6/Sat/;')"'/' "$SCRIPT_CONF" sed -i 's~^SCHHOURS=.*$~SCHHOURS='"$3"'~' "$SCRIPT_CONF" sed -i 's~^SCHMINS=.*$~SCHMINS='"$4"'~' "$SCRIPT_CONF" Auto_Cron delete 2>/dev/null Auto_Cron create 2>/dev/null ;; check) SCHDAYS=$(grep "SCHDAYS" "$SCRIPT_CONF" | cut -f2 -d"=") SCHHOURS=$(grep "SCHHOURS" "$SCRIPT_CONF" | cut -f2 -d"=") SCHMINS=$(grep "SCHMINS" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$SCHDAYS|$SCHHOURS|$SCHMINS" ;; esac } ScriptStorageLocation(){ case "$1" in usb) sed -i 's/^STORAGELOCATION=.*$/STORAGELOCATION=usb/' "$SCRIPT_CONF" mkdir -p "/opt/share/$SCRIPT_NAME_LOWER.d/" mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/csv" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces_user" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/config" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/config.bak" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdtitletext.js" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdstats.db" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null SCRIPT_CONF="/opt/share/$SCRIPT_NAME_LOWER.d/config" ScriptStorageLocation load ;; jffs) sed -i 's/^STORAGELOCATION=.*$/STORAGELOCATION=jffs/' "$SCRIPT_CONF" mkdir -p "/jffs/addons/$SCRIPT_NAME_LOWER.d/" mv "/opt/share/$SCRIPT_NAME_LOWER.d/csv" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces_user" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/opt/share/$SCRIPT_NAME_LOWER.d/config" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/opt/share/$SCRIPT_NAME_LOWER.d/config.bak" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/opt/share/$SCRIPT_NAME_LOWER.d/spdtitletext.js" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null mv "/opt/share/$SCRIPT_NAME_LOWER.d/spdstats.db" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null SCRIPT_CONF="/jffs/addons/$SCRIPT_NAME_LOWER.d/config" ScriptStorageLocation load ;; check) STORAGELOCATION=$(grep "STORAGELOCATION" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$STORAGELOCATION" ;; load) STORAGELOCATION=$(grep "STORAGELOCATION" "$SCRIPT_CONF" | cut -f2 -d"=") if [ "$STORAGELOCATION" = "usb" ]; then SCRIPT_STORAGE_DIR="/opt/share/$SCRIPT_NAME_LOWER.d" elif [ "$STORAGELOCATION" = "jffs" ]; then SCRIPT_STORAGE_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" fi SCRIPT_INTERFACES="$SCRIPT_STORAGE_DIR/.interfaces" SCRIPT_INTERFACES_USER="$SCRIPT_STORAGE_DIR/.interfaces_user" CSV_OUTPUT_DIR="$SCRIPT_STORAGE_DIR/csv" ;; esac } OutputTimeMode(){ case "$1" in unix) sed -i 's/^OUTPUTTIMEMODE=.*$/OUTPUTTIMEMODE=unix/' "$SCRIPT_CONF" Generate_CSVs ;; non-unix) sed -i 's/^OUTPUTTIMEMODE=.*$/OUTPUTTIMEMODE=non-unix/' "$SCRIPT_CONF" Generate_CSVs ;; check) OUTPUTTIMEMODE=$(grep "OUTPUTTIMEMODE" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$OUTPUTTIMEMODE" ;; esac } SpeedtestBinary(){ case "$1" in builtin) sed -i 's/^SPEEDTESTBINARY=.*$/SPEEDTESTBINARY=builtin/' "$SCRIPT_CONF" ;; external) sed -i 's/^SPEEDTESTBINARY=.*$/SPEEDTESTBINARY=external/' "$SCRIPT_CONF" ;; check) SPEEDTESTBINARY=$(grep "SPEEDTESTBINARY" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$SPEEDTESTBINARY" ;; esac } DaysToKeep(){ case "$1" in update) daystokeep=30 exitmenu="" ScriptHeader while true; do printf "\\n${BOLD}Please enter the desired number of days\\nto keep data for (30-365 days):${CLEARFORMAT} " read -r daystokeep_choice if [ "$daystokeep_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$daystokeep_choice"; then printf "\\n${ERR}Please enter a valid number (30-365)${CLEARFORMAT}\\n" elif [ "$daystokeep_choice" -lt 30 ] || [ "$daystokeep_choice" -gt 365 ]; then printf "\\n${ERR}Please enter a number between 30 and 365${CLEARFORMAT}\\n" else daystokeep="$daystokeep_choice" printf "\\n" break fi done if [ "$exitmenu" != "exit" ]; then sed -i 's/^DAYSTOKEEP=.*$/DAYSTOKEEP='"$daystokeep"'/' "$SCRIPT_CONF" return 0 else printf "\\n" return 1 fi ;; check) DAYSTOKEEP=$(grep "DAYSTOKEEP" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$DAYSTOKEEP" ;; esac } LastXResults(){ case "$1" in update) lastxresults=10 exitmenu="" ScriptHeader while true; do printf "\\n${BOLD}Please enter the desired number of results\\nto display in the WebUI (1-100):${CLEARFORMAT} " read -r lastx_choice if [ "$lastx_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$lastx_choice"; then printf "\\n${ERR}Please enter a valid number (1-100)${CLEARFORMAT}\\n" elif [ "$lastx_choice" -lt 1 ] || [ "$lastx_choice" -gt 100 ]; then printf "\\n${ERR}Please enter a number between 1 and 100${CLEARFORMAT}\\n" else lastxresults="$lastx_choice" printf "\\n" break fi done if [ "$exitmenu" != "exit" ]; then sed -i 's/^LASTXRESULTS=.*$/LASTXRESULTS='"$lastxresults"'/' "$SCRIPT_CONF" IFACELIST="" while IFS='' read -r line || [ -n "$line" ]; do IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" if [ "$IFACELIST" != "" ]; then for IFACE_NAME in $IFACELIST; do Generate_LastXResults "$IFACE_NAME" done fi return 0 else printf "\\n" return 1 fi ;; check) LASTXRESULTS=$(grep "LASTXRESULTS" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$LASTXRESULTS" ;; esac } StoreResultURL(){ case "$1" in enable) sed -i 's/^STORERESULTURL=.*$/STORERESULTURL=true/' "$SCRIPT_CONF" ;; disable) sed -i 's/^STORERESULTURL=.*$/STORERESULTURL=false/' "$SCRIPT_CONF" ;; check) STORERESULTURL=$(grep "STORERESULTURL" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$STORERESULTURL" ;; esac } ExcludeFromQoS(){ case "$1" in enable) sed -i 's/^EXCLUDEFROMQOS=.*$/EXCLUDEFROMQOS=true/' "$SCRIPT_CONF" ;; disable) sed -i 's/^EXCLUDEFROMQOS=.*$/EXCLUDEFROMQOS=false/' "$SCRIPT_CONF" ;; check) EXCLUDEFROMQOS=$(grep "EXCLUDEFROMQOS" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$EXCLUDEFROMQOS" ;; esac } AutoBWEnable(){ case "$1" in enable) sed -i 's/^AUTOBW_ENABLED=.*$/AUTOBW_ENABLED=true/' "$SCRIPT_CONF" ;; disable) sed -i 's/^AUTOBW_ENABLED=.*$/AUTOBW_ENABLED=false/' "$SCRIPT_CONF" ;; check) AUTOBW_ENABLED=$(grep "AUTOBW_ENABLED" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$AUTOBW_ENABLED" ;; esac } AutoBWConf(){ case "$1" in update) sed -i 's/^AUTOBW_'"$2"'_'"$3"'=.*$/AUTOBW_'"$2"'_'"$3"'='"$4"'/' "$SCRIPT_CONF" ;; check) grep "AUTOBW_${2}_$3" "$SCRIPT_CONF" | cut -f2 -d"=" ;; esac } #$1 fieldname $2 tablename $3 frequency (hours) $4 length (days) $5 outputfile $6 outputfrequency $7 interfacename $8 sqlfile $9 timestamp WriteSql_ToFile(){ timenow="$9" maxcount="$(echo "$3" "$4" | awk '{printf ((24*$2)/$1)}')" if ! echo "$5" | grep -q "day"; then { echo ".mode csv" echo ".headers off" echo ".output ${5}_${6}_${7}.tmp" echo "SELECT '$1' Metric,Min(strftime('%s',datetime(strftime('%Y-%m-%d %H:00:00',datetime([Timestamp],'unixepoch'))))) Time,IFNULL(printf('%f',Avg($1)),'NaN') Value FROM $2 WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-$maxcount hour'))) GROUP BY strftime('%m',datetime([Timestamp],'unixepoch')),strftime('%d',datetime([Timestamp],'unixepoch')),strftime('%H',datetime([Timestamp],'unixepoch')) ORDER BY [Timestamp] DESC;" } > "$8" else { echo ".mode csv" echo ".headers off" echo ".output ${5}_${6}_${7}.tmp" echo "SELECT '$1' Metric,Max(strftime('%s',datetime([Timestamp],'unixepoch','localtime','start of day','utc'))) Time,IFNULL(printf('%f',Avg($1)),'NaN') Value FROM $2 WHERE ([Timestamp] > strftime('%s',datetime($timenow,'unixepoch','localtime','start of day','utc','+1 day','-$maxcount day'))) GROUP BY strftime('%m',datetime([Timestamp],'unixepoch','localtime')),strftime('%d',datetime([Timestamp],'unixepoch','localtime')) ORDER BY [Timestamp] DESC;" } > "$8" fi } WriteStats_ToJS(){ echo "function $3(){" >> "$2" html='document.getElementById("'"$4"'").innerHTML="' while IFS='' read -r line || [ -n "$line" ]; do html="${html}${line}\\r\\n" done < "$1" html="$html"'"' printf "%s\\r\\n}\\r\\n" "$html" >> "$2" } GenerateServerList(){ if [ ! -f /opt/bin/jq ]; then opkg update opkg install jq fi promptforservername="$2" printf "Generating list of closest servers for %s...\\n\\n" "$1" CONFIG_STRING="" LICENSE_STRING="--accept-license --accept-gdpr" SPEEDTEST_BINARY="" if [ "$(SpeedtestBinary check)" = "builtin" ]; then SPEEDTEST_BINARY=/usr/sbin/ookla elif [ "$(SpeedtestBinary check)" = "external" ]; then SPEEDTEST_BINARY="$OOKLA_DIR/speedtest" fi if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then CONFIG_STRING="-c http://www.speedtest.net/api/embed/vz0azjarf5enop8a/config" LICENSE_STRING="" fi serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$1")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null if [ -z "$serverlist" ]; then Print_Output true "Error retrieving server list for for $1" "$CRIT" serverno="exit" return 1 fi servercount="$(echo "$serverlist" | jq '.servers | length')" COUNTER=1 until [ $COUNTER -gt "$servercount" ]; do serverdetails="$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')|$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" if [ "$COUNTER" -lt 10 ]; then printf "%s) %s\\n" "$COUNTER" "$serverdetails" elif [ "$COUNTER" -ge 10 ]; then printf "%s) %s\\n" "$COUNTER" "$serverdetails" fi COUNTER=$((COUNTER + 1)) done printf "\\ne) Go back\\n" while true; do printf "\\n${BOLD}Please select a server from the list above (1-%s):${CLEARFORMAT}\\n" "$servercount" printf "\\n${BOLD}Or press c to enter a known server ID${CLEARFORMAT}\\n" printf "${BOLD}Enter answer:${CLEARFORMAT} " read -r server if [ "$server" = "e" ]; then serverno="exit" break elif [ "$server" = "c" ]; then while true; do printf "\\n${BOLD}Please enter server ID (WARNING: this is not validated) or e to go back${CLEARFORMAT} " read -r customserver if [ "$customserver" = "e" ]; then break elif ! Validate_Number "$customserver"; then printf "\\n\\e[31mPlease enter a valid number${CLEARFORMAT}\\n" else serverno="$customserver" if [ "$promptforservername" != "no" ]; then while true; do printf "\\n${BOLD}Would you like to enter a name for this server? (default: Custom) (y/n)?${CLEARFORMAT} " read -r servername_select if [ "$servername_select" = "n" ] || [ "$servername_select" = "N" ]; then servername="Custom" break elif [ "$servername_select" = "y" ] || [ "$servername_select" = "Y" ]; then printf "\\n${BOLD}Please enter the name for this server:${CLEARFORMAT} " read -r servername printf "\\n${BOLD}%s${CLEARFORMAT}\\n" "$servername" printf "\\n${BOLD}Is that correct (y/n)?${CLEARFORMAT} " read -r servername_confirm if [ "$servername_confirm" = "y" ] || [ "$servername_confirm" = "Y" ]; then break else printf "\\n\\e[31mPlease enter y or n${CLEARFORMAT}\\n" fi else printf "\\n\\e[31mPlease enter y or n${CLEARFORMAT}\\n" fi done else servername="Custom" fi printf "\\n" return 0 fi done elif ! Validate_Number "$server"; then printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$servercount" else if [ "$server" -lt 1 ] || [ "$server" -gt "$servercount" ]; then printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$servercount" else serverno="$(echo "$serverlist" | jq -r --argjson index "$((server-1))" '.servers[$index] | .id')" servername="$(echo "$serverlist" | jq -r --argjson index "$((server-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" printf "\\n" break fi fi done } GenerateServerList_WebUI(){ serverlistfile="$2" rm -f "/tmp/$serverlistfile.txt" rm -f "$SCRIPT_WEB_DIR/$serverlistfile.htm" SPEEDTEST_BINARY="" if [ "$(SpeedtestBinary check)" = "builtin" ]; then SPEEDTEST_BINARY=/usr/sbin/ookla elif [ "$(SpeedtestBinary check)" = "external" ]; then SPEEDTEST_BINARY="$OOKLA_DIR/speedtest" fi CONFIG_STRING="" LICENSE_STRING="--accept-license --accept-gdpr" if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then CONFIG_STRING="-c http://www.speedtest.net/api/embed/vz0azjarf5enop8a/config" LICENSE_STRING="" fi spdifacename="$1" if [ ! -f /opt/bin/jq ]; then opkg update opkg install jq fi if [ "$spdifacename" = "ALL" ]; then while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" for IFACE_NAME in $IFACELIST; do serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$IFACE_NAME")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null servercount="$(echo "$serverlist" | jq '.servers | length')" COUNTER=1 until [ $COUNTER -gt "$servercount" ]; do printf "%s|%s\\n" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" COUNTER=$((COUNTER + 1)) done printf "-----\\n" >> "/tmp/$serverlistfile.tmp" done else serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$spdifacename")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null servercount="$(echo "$serverlist" | jq '.servers | length')" COUNTER=1 until [ $COUNTER -gt "$servercount" ]; do printf "%s|%s\\n" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" COUNTER=$((COUNTER + 1)) done fi sleep 1 mv "/tmp/$serverlistfile.tmp" "/tmp/$serverlistfile.txt" ln -s "/tmp/$serverlistfile.txt" "$SCRIPT_WEB_DIR/$serverlistfile.htm" 2>/dev/null } PreferredServer(){ case "$1" in update) GenerateServerList "$2" if [ "$serverno" != "exit" ]; then sed -i 's/^PREFERREDSERVER_'"$2"'=.*$/PREFERREDSERVER_'"$2"'='"$serverno|$servername"'/' "$SCRIPT_CONF" else return 1 fi ;; enable) sed -i 's/^USEPREFERRED_'"$2"'=.*$/USEPREFERRED_'"$2"'=true/' "$SCRIPT_CONF" ;; disable) sed -i 's/^USEPREFERRED_'"$2"'=.*$/USEPREFERRED_'"$2"'=false/' "$SCRIPT_CONF" ;; check) USEPREFERRED=$(grep "USEPREFERRED_$2" "$SCRIPT_CONF" | cut -f2 -d"=") if [ "$USEPREFERRED" = "true" ]; then return 0; else return 1; fi ;; list) PREFERREDSERVER=$(grep "PREFERREDSERVER_$2" "$SCRIPT_CONF" | cut -f2 -d"=") echo "$PREFERREDSERVER" ;; esac } Run_Speedtest(){ if [ ! -f /opt/bin/xargs ]; then Print_Output true "Installing findutils from Entware" opkg update opkg install findutils fi if [ -n "$PPID" ]; then ps | grep -v grep | grep -v $$ | grep -v "$PPID" | grep -i "$SCRIPT_NAME_LOWER" | grep generate | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 else ps | grep -v grep | grep -v $$ | grep -i "$SCRIPT_NAME_LOWER" | grep generate | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 fi Create_Dirs Conf_Exists if [ "$(SpeedtestBinary check)" = "builtin" ]; then printf "/usr/sbin/ookla" > /tmp/spdmerlin-binary elif [ "$(SpeedtestBinary check)" = "external" ]; then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi Auto_Startup create 2>/dev/null if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create ScriptStorageLocation load Create_Symlinks mode="$1" specificiface="$2" speedtestserverno="" speedtestservername="" CONFIG_STRING="" LICENSE_STRING="--accept-license --accept-gdpr" PROC_NAME="speedtest" SPEEDTEST_BINARY="" if [ "$(SpeedtestBinary check)" = "builtin" ]; then SPEEDTEST_BINARY=/usr/sbin/ookla elif [ "$(SpeedtestBinary check)" = "external" ]; then SPEEDTEST_BINARY="$OOKLA_DIR/speedtest" fi if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then CONFIG_STRING="-c http://www.speedtest.net/api/embed/vz0azjarf5enop8a/config" LICENSE_STRING="" PROC_NAME="ookla" fi echo 'var spdteststatus = "InProgress";' > /tmp/detect_spdtest.js tmpfile=/tmp/spd-stats.txt resultfile=/tmp/spd-result.txt rm -f "$resultfile" rm -f "$tmpfile" if [ -n "$(pidof "$PROC_NAME")" ]; then killall -q "$PROC_NAME" fi if Check_Swap ; then IFACELIST="" if [ -z "$specificiface" ]; then while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "#")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" elif [ "$specificiface" = "All" ]; then while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" else IFACELIST="$specificiface" fi if [ "$IFACELIST" != "" ]; then stoppedqos="false" if [ "$(ExcludeFromQoS check)" = "true" ]; then if [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -eq 1 ]; then for ACTION in -D -A ; do for proto in tcp udp; do iptables "$ACTION" OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables "$ACTION" OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle "$ACTION" OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle "$ACTION" POSTROUTING -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle "$ACTION" OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle "$ACTION" POSTROUTING -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null done stoppedqos="true" done elif [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -ne 1 ] && [ -f /tmp/qos ]; then /tmp/qos stop >/dev/null 2>&1 stoppedqos="true" elif [ "$(nvram get qos_enable)" -eq 0 ] && [ -f /jffs/addons/cake-qos/cake-qos ]; then /jffs/addons/cake-qos/cake-qos stop >/dev/null 2>&1 stoppedqos="true" fi fi applyautobw="false" if [ "$mode" = "schedule" ] && [ "$(AutoBWEnable check)" = "true" ]; then applyautobw="true" fi for IFACE_NAME in $IFACELIST; do IFACE="$(Get_Interface_From_Name "$IFACE_NAME")" IFACE_LOWER="$(echo "$IFACE" | tr "A-Z" "a-z")" if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then Print_Output true "$IFACE not up, please check. Skipping speedtest for $IFACE_NAME" "$WARN" continue else if [ "$mode" = "webui_user" ]; then mode="user" elif [ "$mode" = "webui_auto" ]; then mode="auto" elif [ "$mode" = "webui_onetime" ]; then mode="user" fi if [ "$mode" = "schedule" ]; then if PreferredServer check "$IFACE_NAME"; then speedtestserverno="$(PreferredServer list "$IFACE_NAME" | cut -f1 -d"|")" speedtestservername="$(PreferredServer list "$IFACE_NAME" | cut -f2 -d"|")" else mode="auto" fi elif [ "$mode" = "onetime" ]; then GenerateServerList "$IFACE_NAME" no if [ "$serverno" != "exit" ]; then speedtestserverno="$serverno" speedtestservername="$servername" else Clear_Lock return 1 fi elif [ "$mode" = "user" ]; then speedtestserverno="$(PreferredServer list "$IFACE_NAME" | cut -f1 -d"|")" speedtestservername="$(PreferredServer list "$IFACE_NAME" | cut -f2 -d"|")" fi echo 'var spdteststatus = "InProgress_'"$IFACE_NAME"'";' > /tmp/detect_spdtest.js printf "" > "$tmpfile" if [ "$mode" = "auto" ]; then Print_Output true "Starting speedtest using auto-selected server for $IFACE_NAME interface" "$PASS" "$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$IFACE" --format="human-readable" --unit="Mbps" -p $LICENSE_STRING | tee "$tmpfile" & sleep 2 speedtestcount=0 while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedtestcount" -lt 120 ]; do speedtestcount="$((speedtestcount + 1))" sleep 1 done if [ "$speedtestcount" -ge 120 ]; then Print_Output true "Speedtest for $IFACE_NAME hung (> 2 mins), killing process" "$CRIT" killall -q "$PROC_NAME" continue fi else if [ "$speedtestserverno" -ne 0 ]; then Print_Output true "Starting speedtest using $speedtestservername for $IFACE_NAME interface" "$PASS" "$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$IFACE" --server-id="$speedtestserverno" --format="human-readable" --unit="Mbps" -p $LICENSE_STRING | tee "$tmpfile" & sleep 2 speedtestcount=0 while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedtestcount" -lt 120 ]; do speedtestcount="$((speedtestcount + 1))" sleep 1 done if [ "$speedtestcount" -ge 120 ]; then Print_Output true "Speedtest for $IFACE_NAME hung (> 2 mins), killing process" "$CRIT" killall -q "$PROC_NAME" continue fi else Print_Output true "Starting speedtest using using auto-selected server for $IFACE_NAME interface" "$PASS" "$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$IFACE" --format="human-readable" --unit="Mbps" -p $LICENSE_STRING | tee "$tmpfile" & sleep 2 speedtestcount=0 while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedtestcount" -lt 120 ]; do speedtestcount="$((speedtestcount + 1))" sleep 1 done if [ "$speedtestcount" -ge 120 ]; then Print_Output true "Speedtest for $IFACE_NAME hung (> 2 mins), killing process" "$CRIT" killall -q "$PROC_NAME" continue fi fi fi if [ ! -f "$tmpfile" ] || [ -z "$(cat "$tmpfile")" ] || [ "$(grep -c FAILED $tmpfile)" -gt 0 ]; then Print_Output true "Error running speedtest for $IFACE_NAME" "$CRIT" continue fi ScriptStorageLocation load TZ=$(cat /etc/TZ) export TZ timenow=$(date +"%s") timenowfriendly=$(date +"%c") ## New if-then-else block added to with ookla output when buffer bloat has been added to the human readable output BUFFBLOAT=$(grep "Idle Latency:" "$tmpfile") if [ -n "$BUFFBLOAT" ]; then # Parse human readable output when buffer bloat data is included. download="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" upload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" latency="$(grep "Idle Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}')" jitter="$(grep "Idle Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}' | tr -d 'ms,')" pktloss="$(grep "Packet Loss:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}' | tr -d '%')" resulturl="$(grep "Result URL:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}')" datadownload="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" dataupload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" datadownloadunit="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" datauploadunit="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" servername="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f1 -d'(' | cut -f2 -d':' | awk '{$1=$1;print}')" serverid="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f2 -d'(' | awk '{print $2}' | tr -d ')')" else download="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" upload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" latency="$(grep "Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" jitter="$(grep "Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $4}' | tr -d '(')" pktloss="$(grep "Packet Loss:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}' | tr -d '%')" resulturl="$(grep "Result URL:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}')" datadownload="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" dataupload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" datadownloadunit="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" datauploadunit="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" servername="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f1 -d'(' | cut -f2 -d':' | awk '{$1=$1;print}')" serverid="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f2 -d'(' | awk '{print $3}' | tr -d ')')" fi ! Validate_Bandwidth "$download" && download=0; ! Validate_Bandwidth "$upload" && upload=0; ! Validate_Bandwidth "$latency" && latency="null"; ! Validate_Bandwidth "$jitter" && jitter="null"; ! Validate_Bandwidth "$pktloss" && pktloss="null"; ! Validate_Bandwidth "$datadownload" && datadownload=0; ! Validate_Bandwidth "$dataupload" && dataupload=0; if [ "$datadownloadunit" = "GB" ]; then datadownload="$(echo "$datadownload" | awk '{printf ($1*1024)}')" fi if [ "$datauploadunit" = "GB" ]; then dataupload="$(echo "$dataupload" | awk '{printf ($1*1024)}')" fi if [ "$(SpeedtestBinary check)" = "builtin" ]; then curllatency="$latency" if [ "$curllatency" = "null" ]; then curllatency=0 fi curlresult=$(/usr/sbin/curl -fsL --retry 3 -d "recommendedserverid=$serverid" \ -d "ping=$(echo "$curllatency" | awk '{printf("%.0f\n", $1);}')" \ -d "screenresolution=" \ -d "promo=" \ -d "download=$(echo "$download" | awk '{printf("%.0f\n", $1*1000);}')" \ -d "screendpi=" \ -d "upload=$(echo "$upload" | awk '{printf("%.0f\n", $1*1000);}')" \ -d "testmethod=http" \ -d "hash=$(printf "$(echo "$curllatency" | awk '{printf("%.0f\n", $1);}')-$(echo "$upload" | awk '{printf("%.0f\n", $1*1000);}')-$(echo "$download" | awk '{printf("%.0f\n", $1*1000);}')-297aae72" | md5sum | cut -f1 -d' ')" \ -d "touchscreen=none" \ -d "startmode=pingselect" \ -d "accuracy=1" \ -d "bytesreceived=$(echo "$datadownload" | awk '{printf("%.0f\n", $1*1024);}')" \ -d "bytessent=$(echo "$dataupload" | awk '{printf("%.0f\n", $1*1024);}')" \ -d "serverid=$serverid" \ -H "Referer: http://c.speedtest.net/flash/speedtest.swf" https://www.speedtest.net/api/api.php) resulturl="https://www.speedtest.net/result/$(echo "$curlresult" | cut -f2 -d'&' | cut -f2 -d'=')" printf " Result URL: %s\\n" "$resulturl" | tee -a "$tmpfile" fi echo "CREATE TABLE IF NOT EXISTS [spdstats_$IFACE_NAME] ([StatID] INTEGER PRIMARY KEY NOT NULL,[Timestamp] NUMERIC NOT NULL,[Download] REAL NOT NULL,[Upload] REAL NOT NULL,[Latency] REAL,[Jitter] REAL,[PktLoss] REAL,[ResultURL] TEXT,[DataDownload] REAL NOT NULL,[DataUpload] REAL NOT NULL,[ServerID] TEXT,[ServerName] TEXT);" > /tmp/spd-stats.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql STORERESULTURL="$(StoreResultURL check)" if [ "$STORERESULTURL" = "true" ]; then echo "INSERT INTO spdstats_$IFACE_NAME ([Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[ResultURL],[DataDownload],[DataUpload],[ServerID],[ServerName]) values($timenow,$download,$upload,$latency,$jitter,$pktloss,'$resulturl',$datadownload,$dataupload,$serverid,'$servername');" > /tmp/spd-stats.sql elif [ "$STORERESULTURL" = "false" ]; then echo "INSERT INTO spdstats_$IFACE_NAME ([Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[ResultURL],[DataDownload],[DataUpload],[ServerID],[ServerName]) values($timenow,$download,$upload,$latency,$jitter,$pktloss,'',$datadownload,$dataupload,$serverid,'$servername');" > /tmp/spd-stats.sql fi "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql { echo "DELETE FROM [spdstats_$IFACE_NAME] WHERE [Timestamp] < strftime('%s',datetime($timenow,'unixepoch','-$(DaysToKeep check) day'));" echo "PRAGMA analysis_limit=0;" echo "PRAGMA cache_size=-20000;" echo "ANALYZE spdstats_$IFACE_NAME;" } > /tmp/spd-stats.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql >/dev/null 2>&1 rm -f /tmp/spd-stats.sql spdtestresult="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};'| awk '{$1=$1};1') - $(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};'| awk '{$1=$1};1')" spdtestresult2="$(grep "Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{$1=$1};1') - $(grep "Packet Loss:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{$1=$1};1')" printf "\\n" Print_Output true "Speedtest results - $spdtestresult" "$PASS" Print_Output true "Connection quality - $spdtestresult2" "$PASS" { printf "Speedtest result for %s\\n" "$IFACE_NAME" printf "\\nBandwidth - %s\\n" "$spdtestresult" printf "Quality - %s\\n\\n" "$spdtestresult2" grep "Result URL" "$tmpfile" | awk '{$1=$1};1' printf "\\n\\n\\n" } >> "$resultfile" #extStats extStats="/jffs/addons/extstats.d/mod_spdstats.sh" if [ -f "$extStats" ]; then sh "$extStats" ext "$download" "$upload" fi fi done if [ "$stoppedqos" = "true" ]; then if [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -eq 1 ]; then for proto in tcp udp; do iptables -D OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -D OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle -D OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle -D POSTROUTING -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle -D OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle -D POSTROUTING -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null done elif [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -ne 1 ] && [ -f /tmp/qos ]; then /tmp/qos start >/dev/null 2>&1 elif [ "$(nvram get qos_enable)" -eq 0 ] && [ -f /jffs/addons/cake-qos/cake-qos ]; then /jffs/addons/cake-qos/cake-qos start >/dev/null 2>&1 fi fi echo 'var spdteststatus = "GenerateCSV";' > /tmp/detect_spdtest.js Print_Output true "Retrieving data for WebUI charts" "$PASS" Generate_CSVs echo "Stats last updated: $timenowfriendly" > /tmp/spdstatstitle.txt rm -f "$SCRIPT_STORAGE_DIR/spdtitletext.js" WriteStats_ToJS /tmp/spdstatstitle.txt "$SCRIPT_STORAGE_DIR/spdtitletext.js" SetSPDStatsTitle statstitle if [ "$applyautobw" = "true" ]; then Menu_AutoBW_Update fi rm -f "$tmpfile" rm -f /tmp/spdstatstitle.txt echo 'var spdteststatus = "Done";' > /tmp/detect_spdtest.js Clear_Lock else echo 'var spdteststatus = "Error";' > /tmp/detect_spdtest.js Print_Output true "No interfaces enabled, exiting" "$CRIT" Clear_Lock return 1 fi Clear_Lock else echo 'var spdteststatus = "NoSwap";' > /tmp/detect_spdtest.js Print_Output true "Swap file not active, exiting" "$CRIT" Clear_Lock return 1 fi } Run_Speedtest_WebUI(){ spdteststring="$(echo "$1" | sed "s/${SCRIPT_NAME_LOWER}spdtest_//;s/%/ /g")"; spdtestmode="webui_$(echo "$spdteststring" | cut -f1 -d'_')"; spdifacename="$(echo "$spdteststring" | cut -f2 -d'_')"; cp -a "$SCRIPT_CONF" "$SCRIPT_CONF.bak" if [ "$spdtestmode" = "webui_onetime" ]; then spdtestserverlist="$(echo "$spdteststring" | cut -f3 -d'_')"; if [ "$spdifacename" = "All" ]; then while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" COUNT=1 for IFACE_NAME in $IFACELIST; do spdtestserver="$(grep -m1 "$(echo "$spdtestserverlist" | cut -f"$COUNT" -d'+')" /tmp/spdmerlin_manual_serverlist.txt)" sed -i 's/^PREFERREDSERVER_'"$IFACE_NAME"'=.*$/PREFERREDSERVER_'"$IFACE_NAME"'='"$spdtestserver"'/' "$SCRIPT_CONF" COUNT=$((COUNT+1)) done else spdtestserver="$(grep -m1 "$spdtestserverlist" /tmp/spdmerlin_manual_serverlist.txt)" sed -i 's/^PREFERREDSERVER_'"$spdifacename"'=.*$/PREFERREDSERVER_'"$spdifacename"'='"$spdtestserver"'/' "$SCRIPT_CONF" fi fi sleep 1 Run_Speedtest "$spdtestmode" "$spdifacename" cp -a "$SCRIPT_CONF.bak" "$SCRIPT_CONF" } Process_Upgrade(){ if [ ! -f "$OOKLA_DIR/speedtest" ]; then Download_File "$SCRIPT_REPO/files/$ARCH.tar.gz" "$OOKLA_DIR/$ARCH.tar.gz" tar -xzf "$OOKLA_DIR/$ARCH.tar.gz" -C "$OOKLA_DIR" rm -f "$OOKLA_DIR/$ARCH.tar.gz" chmod 0755 "$OOKLA_DIR/speedtest" fi rm -f "$SCRIPT_STORAGE_DIR/spdjs.js" rm -f "$SCRIPT_STORAGE_DIR/.tableupgraded"* if [ ! -f "$SCRIPT_STORAGE_DIR/spdtitletext.js" ]; then { echo 'function SetSPDStatsTitle(){'; echo 'document.getElementById("statstitle").innerHTML="Stats last updated: Not yet updated\r\n";'; echo "}"; } > "$SCRIPT_STORAGE_DIR/spdtitletext.js" fi if [ ! -f "$SCRIPT_DIR/README.md" ]; then Update_File README.md fi if [ ! -f "$SCRIPT_DIR/LICENSE" ]; then Update_File LICENSE fi if [ "$(AutoBWEnable check)" = "true" ]; then if [ "$(ExcludeFromQoS check)" = "false" ]; then Print_Output false "Enabling Exclude from QoS (required for AutoBW)" ExcludeFromQoS enable fi fi FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5" for IFACE_NAME in $FULL_IFACELIST; do echo "CREATE TABLE IF NOT EXISTS [spdstats_$IFACE_NAME] ([StatID] INTEGER PRIMARY KEY NOT NULL,[Timestamp] NUMERIC NOT NULL,[Download] REAL NOT NULL,[Upload] REAL NOT NULL,[Latency] REAL,[Jitter] REAL,[PktLoss] REAL,[ResultURL] TEXT,[DataDownload] REAL NOT NULL,[DataUpload] REAL NOT NULL,[ServerID] TEXT,[ServerName] TEXT);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql done if [ ! -f "$SCRIPT_STORAGE_DIR/.databaseupgraded" ]; then renice 15 $$ Print_Output true "Upgrading database..." "$PASS" FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5" for IFACE_NAME in $FULL_IFACELIST; do echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_download ON spdstats_${IFACE_NAME} (Timestamp,Download);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_upload ON spdstats_${IFACE_NAME} (Timestamp,Upload);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_latency ON spdstats_${IFACE_NAME} (Timestamp,Latency);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_jitter ON spdstats_${IFACE_NAME} (Timestamp,Jitter);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_pktloss ON spdstats_${IFACE_NAME} (Timestamp,PktLoss);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_resulturl ON spdstats_${IFACE_NAME} (Timestamp,ResultURL collate nocase);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_datadownload ON spdstats_${IFACE_NAME} (Timestamp,DataDownload);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_datadownload ON spdstats_${IFACE_NAME} (Timestamp,DataUpload);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "ALTER TABLE spdstats_${IFACE_NAME} ADD COLUMN [ServerID] TEXT" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql >/dev/null 2>&1 echo "ALTER TABLE spdstats_${IFACE_NAME} ADD COLUMN [ServerName] TEXT" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql >/dev/null 2>&1 echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_serverid ON spdstats_${IFACE_NAME} (Timestamp,ServerID);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_servername ON spdstats_${IFACE_NAME} (Timestamp,ServerName collate nocase);" > /tmp/spdstats-upgrade.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql Generate_LastXResults "$IFACE_NAME" done touch "$SCRIPT_STORAGE_DIR/.databaseupgraded" Generate_CSVs Print_Output true "Database ready, continuing..." "$PASS" renice 0 $$ fi rm -f /tmp/spdstats-upgrade.sql } #$1 iface name Generate_LastXResults(){ FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5" for IFACE_NAME in $FULL_IFACELIST; do rm -f "$SCRIPT_STORAGE_DIR/lastx_${IFACE_NAME}.htm" done { echo ".mode csv" echo ".output /tmp/spd-lastx.csv" } > /tmp/spd-lastx.sql echo "SELECT [Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[DataDownload],[DataUpload],[ResultURL],[ServerID],[ServerName] FROM spdstats_$1 ORDER BY [Timestamp] DESC LIMIT $(LastXResults check);" >> /tmp/spd-lastx.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-lastx.sql rm -f /tmp/spd-lastx.sql sed -i 's/,,/,null,/g;s/"//g;' /tmp/spd-lastx.csv mv /tmp/spd-lastx.csv "$SCRIPT_STORAGE_DIR/lastx_$1.csv" } Generate_CSVs(){ Process_Upgrade renice 15 $$ OUTPUTTIMEMODE="$(OutputTimeMode check)" STORERESULTURL="$(StoreResultURL check)" IFACELIST="" while IFS='' read -r line || [ -n "$line" ]; do IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" if [ "$IFACELIST" != "" ]; then for IFACE_NAME in $IFACELIST; do IFACE="$(Get_Interface_From_Name "$IFACE_NAME")" TZ=$(cat /etc/TZ) export TZ timenow=$(date +"%s") timenowfriendly=$(date +"%c") metriclist="Download Upload Latency Jitter PktLoss" # DataDownload DataUpload" for metric in $metriclist; do { echo ".mode csv" echo ".headers off" echo ".output $CSV_OUTPUT_DIR/${metric}_raw_daily_$IFACE_NAME.tmp" echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-1 day'))) ORDER BY [Timestamp] DESC;" } > /tmp/spd-stats.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql { echo ".mode csv" echo ".headers off" echo ".output $CSV_OUTPUT_DIR/${metric}_raw_weekly_$IFACE_NAME.tmp" echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-7 day'))) ORDER BY [Timestamp] DESC;" } > /tmp/spd-stats.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql { echo ".mode csv" echo ".headers off" echo ".output $CSV_OUTPUT_DIR/${metric}_raw_monthly_$IFACE_NAME.tmp" echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-30 day'))) ORDER BY [Timestamp] DESC;" } > /tmp/spd-stats.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 1 "$CSV_OUTPUT_DIR/${metric}_hour" daily "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 7 "$CSV_OUTPUT_DIR/${metric}_hour" weekly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 30 "$CSV_OUTPUT_DIR/${metric}_hour" monthly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 1 "$CSV_OUTPUT_DIR/${metric}_day" daily "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 7 "$CSV_OUTPUT_DIR/${metric}_day" weekly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 30 "$CSV_OUTPUT_DIR/${metric}_day" monthly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql done periodfilelist="raw hour day" for periodfile in $periodfilelist; do cat "$CSV_OUTPUT_DIR/Download_${periodfile}_daily_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Upload_${periodfile}_daily_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Combined_${periodfile}_daily_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Download_${periodfile}_weekly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Upload_${periodfile}_weekly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Combined_${periodfile}_weekly_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Download_${periodfile}_monthly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Upload_${periodfile}_monthly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Combined_${periodfile}_monthly_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Latency_${periodfile}_daily_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Jitter_${periodfile}_daily_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/PktLoss_${periodfile}_daily_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Quality_${periodfile}_daily_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Latency_${periodfile}_weekly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Jitter_${periodfile}_weekly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/PktLoss_${periodfile}_weekly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Quality_${periodfile}_weekly_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Latency_${periodfile}_monthly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Jitter_${periodfile}_monthly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/PktLoss_${periodfile}_monthly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Quality_${periodfile}_monthly_${IFACE_NAME}.htm" 2> /dev/null #cat "$CSV_OUTPUT_DIR/DataDownload_${periodfile}_daily_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/DataUpload_${periodfile}_daily_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/DataUsage_${periodfile}_daily_${IFACE_NAME}.htm" 2> /dev/null #cat "$CSV_OUTPUT_DIR/DataDownload_${periodfile}_weekly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/DataUpload_${periodfile}_weekly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/DataUsage_${periodfile}_weekly_${IFACE_NAME}.htm" 2> /dev/null #cat "$CSV_OUTPUT_DIR/DataDownload_${periodfile}_monthly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/DataUpload_${periodfile}_monthly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/DataUsage_${periodfile}_monthly_${IFACE_NAME}.htm" 2> /dev/null done csvlist="Combined Quality" # DataUsage" for csvfile in $csvlist; do rm -f "$CSV_OUTPUT_DIR/${csvfile}daily_${IFACE_NAME}.htm" rm -f "$CSV_OUTPUT_DIR/${csvfile}weekly_${IFACE_NAME}.htm" rm -f "$CSV_OUTPUT_DIR/${csvfile}monthly_${IFACE_NAME}.htm" for periodfile in $periodfilelist; do sed -i '1i Metric,Time,Value' "$CSV_OUTPUT_DIR/${csvfile}_${periodfile}_daily_${IFACE_NAME}.htm" sed -i '1i Metric,Time,Value' "$CSV_OUTPUT_DIR/${csvfile}_${periodfile}_weekly_${IFACE_NAME}.htm" sed -i '1i Metric,Time,Value' "$CSV_OUTPUT_DIR/${csvfile}_${periodfile}_monthly_${IFACE_NAME}.htm" done done INCLUDEURL="" if [ "$STORERESULTURL" = "true" ]; then INCLUDEURL=",[ResultURL]" fi { echo ".mode csv" echo ".headers on" echo ".output $CSV_OUTPUT_DIR/CompleteResults_${IFACE_NAME}.htm" echo "SELECT [Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss]$INCLUDEURL,[DataDownload],[DataUpload],[ServerID],[ServerName] FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-$(DaysToKeep check) day'))) ORDER BY [Timestamp] DESC;" } > /tmp/spd-complete.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-complete.sql rm -f /tmp/spd-complete.sql rm -f "$CSV_OUTPUT_DIR/Download"* rm -f "$CSV_OUTPUT_DIR/Upload"* rm -f "$CSV_OUTPUT_DIR/Latency"* rm -f "$CSV_OUTPUT_DIR/Jitter"* rm -f "$CSV_OUTPUT_DIR/PktLoss"* rm -f "$CSV_OUTPUT_DIR/DataDownload"* rm -f "$CSV_OUTPUT_DIR/DataUpload"* Generate_LastXResults "$IFACE_NAME" rm -f /tmp/spd-stats.sql done dos2unix "$CSV_OUTPUT_DIR/"*.htm tmpoutputdir="/tmp/${SCRIPT_NAME_LOWER}results" mkdir -p "$tmpoutputdir" mv "$CSV_OUTPUT_DIR/CompleteResults"*.htm "$tmpoutputdir/." if [ "$OUTPUTTIMEMODE" = "unix" ]; then find "$tmpoutputdir/" -name '*.htm' -exec sh -c 'i="$1"; mv -- "$i" "${i%.htm}.csv"' _ {} \; elif [ "$OUTPUTTIMEMODE" = "non-unix" ]; then for i in "$tmpoutputdir/"*".htm"; do awk -F"," 'NR==1 {OFS=","; print} NR>1 {OFS=","; $1=strftime("%Y-%m-%d %H:%M:%S", $1); print }' "$i" > "$i.out" done find "$tmpoutputdir/" -name '*.htm.out' -exec sh -c 'i="$1"; mv -- "$i" "${i%.htm.out}.csv"' _ {} \; rm -f "$tmpoutputdir/"*.htm fi if [ ! -f /opt/bin/7za ]; then opkg update opkg install p7zip fi /opt/bin/7za a -y -bsp0 -bso0 -tzip "/tmp/${SCRIPT_NAME_LOWER}data.zip" "$tmpoutputdir/*" mv "/tmp/${SCRIPT_NAME_LOWER}data.zip" "$CSV_OUTPUT_DIR" rm -rf "$tmpoutputdir" fi renice 0 $$ } Reset_DB(){ SIZEAVAIL="$(df -P -k "$SCRIPT_STORAGE_DIR" | awk '{print $4}' | tail -n 1)" SIZEDB="$(ls -l "$SCRIPT_STORAGE_DIR/spdstats.db" | awk '{print $5}')" if [ "$SIZEDB" -gt "$((SIZEAVAIL*1024))" ]; then Print_Output true "Database size exceeds available space. $(ls -lh "$SCRIPT_STORAGE_DIR/spdstats.db" | awk '{print $5}')B is required to create backup." "$ERR" return 1 else Print_Output true "Sufficient free space to back up database, proceeding..." "$PASS" if ! cp -a "$SCRIPT_STORAGE_DIR/spdstats.db" "$SCRIPT_STORAGE_DIR/spdstats.db.bak"; then Print_Output true "Database backup failed, please check storage device" "$WARN" fi tablelist="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5" for dbtable in $tablelist; do echo "DELETE FROM [spdstats_$dbtable];" > /tmp/spd-stats.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql rm -f /tmp/spd-stats.sql done Print_Output true "Database reset complete" "$WARN" fi } Shortcut_Script(){ case $1 in create) if [ -d "/opt/bin" ] && [ ! -f "/opt/bin/$SCRIPT_NAME_LOWER" ] && [ -f "/jffs/scripts/$SCRIPT_NAME_LOWER" ]; then ln -s "/jffs/scripts/$SCRIPT_NAME_LOWER" /opt/bin chmod 0755 "/opt/bin/$SCRIPT_NAME_LOWER" fi ;; delete) if [ -f "/opt/bin/$SCRIPT_NAME_LOWER" ]; then rm -f "/opt/bin/$SCRIPT_NAME_LOWER" fi ;; esac } PressEnter(){ while true; do printf "Press enter to continue..." read -r key case "$key" in *) break ;; esac done return 0 } ScriptHeader(){ clear printf "\\n" printf "${BOLD}####################################################################${CLEARFORMAT}\\n" printf "${BOLD}## _ __ __ _ _ ##${CLEARFORMAT}\\n" printf "${BOLD}## | || \/ | | |(_) ##${CLEARFORMAT}\\n" printf "${BOLD}## ___ _ __ __| || \ / | ___ _ __ | | _ _ __ ##${CLEARFORMAT}\\n" printf "${BOLD}## / __|| '_ \ / _ || |\/| | / _ \| '__|| || || '_ \ ##${CLEARFORMAT}\\n" printf "${BOLD}## \__ \| |_) || (_| || | | || __/| | | || || | | | ##${CLEARFORMAT}\\n" printf "${BOLD}## |___/| .__/ \__,_||_| |_| \___||_| |_||_||_| |_| ##${CLEARFORMAT}\\n" printf "${BOLD}## | | ##${CLEARFORMAT}\\n" printf "${BOLD}## |_| ##${CLEARFORMAT}\\n" printf "${BOLD}## ##${CLEARFORMAT}\\n" printf "${BOLD}## %s on %-11s ##${CLEARFORMAT}\\n" "$SCRIPT_VERSION" "$ROUTER_MODEL" printf "${BOLD}## ##${CLEARFORMAT}\\n" printf "${BOLD}## https://github.com/jackyaz/spdMerlin ##${CLEARFORMAT}\\n" printf "${BOLD}## ##${CLEARFORMAT}\\n" printf "${BOLD}####################################################################${CLEARFORMAT}\\n" printf "\\n" } MainMenu(){ AUTOMATIC_ENABLED="" EXCLUDEFROMQOS_MENU="" if AutomaticMode check; then AUTOMATIC_ENABLED="${PASS}Enabled"; else AUTOMATIC_ENABLED="${ERR}Disabled"; fi TEST_SCHEDULE="$(TestSchedule check)" if [ "$(echo "$TEST_SCHEDULE" | cut -f2 -d'|' | grep -c "/")" -gt 0 ] && [ "$(echo "$TEST_SCHEDULE" | cut -f3 -d'|')" -eq 0 ]; then TEST_SCHEDULE_MENU="Every $(echo "$TEST_SCHEDULE" | cut -f2 -d'|' | cut -f2 -d'/') hours" elif [ "$(echo "$TEST_SCHEDULE" | cut -f3 -d'|' | grep -c "/")" -gt 0 ] && [ "$(echo "$TEST_SCHEDULE" | cut -f2 -d'|')" = "*" ]; then TEST_SCHEDULE_MENU="Every $(echo "$TEST_SCHEDULE" | cut -f3 -d'|' | cut -f2 -d'/') minutes" else TEST_SCHEDULE_MENU="Hours: $(echo "$TEST_SCHEDULE" | cut -f2 -d'|') - Minutes: $(echo "$TEST_SCHEDULE" | cut -f3 -d'|')" fi if [ "$(echo "$TEST_SCHEDULE" | cut -f1 -d'|')" = "*" ]; then TEST_SCHEDULE_MENU2="Days of week: All" else TEST_SCHEDULE_MENU2="Days of week: $(echo "$TEST_SCHEDULE" | cut -f1 -d'|')" fi STORERESULTURL_MENU="" if [ "$(StoreResultURL check)" = "true" ]; then STORERESULTURL_MENU="Enabled" else STORERESULTURL_MENU="Disabled" fi printf "WebUI for %s is available at:\\n${SETTING}%s${CLEARFORMAT}\\n\\n" "$SCRIPT_NAME" "$(Get_WebUI_URL)" if [ "$(ExcludeFromQoS check)" = "true" ]; then EXCLUDEFROMQOS_MENU="excluded from"; else EXCLUDEFROMQOS_MENU="included in"; fi printf "1. Run a speedtest now\\n\\n" printf "2. Choose a preferred server for an interface\\n\\n" printf "3. Toggle automatic speedtests\\n Currently: ${BOLD}${AUTOMATIC_ENABLED}%s${CLEARFORMAT}\\n\\n" printf "4. Configure schedule for automatic speedtests\\n ${SETTING}%s\\n %s${CLEARFORMAT}\\n\\n" "$TEST_SCHEDULE_MENU" "$TEST_SCHEDULE_MENU2" printf "5. Toggle time output mode\\n Currently ${SETTING}%s${CLEARFORMAT} time values will be used for CSV exports\\n\\n" "$(OutputTimeMode check)" printf "6. Toggle storage of speedtest result URLs\\n Currently: ${SETTING}%s${CLEARFORMAT}\\n\\n" "$STORERESULTURL_MENU" printf "7. Set number of speedtest results to show in WebUI\\n Currently: ${SETTING}%s results will be shown${CLEARFORMAT}\\n\\n" "$(LastXResults check)" printf "8. Set number of days data to keep in database\\n Currently: ${SETTING}%s days data will be kept${CLEARFORMAT}\\n\\n" "$(DaysToKeep check)" printf "9. Toggle between built-in Ookla speedtest and speedtest-cli\\n Currently: ${SETTING}%s${CLEARFORMAT} will be used for speedtests${CLEARFORMAT}\\n\\n" "$(SpeedtestBinary check)" printf "c. Customise list of interfaces for automatic speedtests\\n" printf "r. Reset list of interfaces for automatic speedtests to default\\n\\n" printf "s. Toggle storage location for stats and config\\n Current location is ${SETTING}%s${CLEARFORMAT} \\n\\n" "$(ScriptStorageLocation check)" printf "q. Toggle exclusion of %s speedtests from QoS\\n Currently %s speedtests are ${SETTING}%s${CLEARFORMAT} QoS\\n\\n" "$SCRIPT_NAME" "$SCRIPT_NAME" "$EXCLUDEFROMQOS_MENU" printf "a. AutoBW\\n\\n" printf "u. Check for updates\\n" printf "uf. Update %s with latest version (force update)\\n\\n" "$SCRIPT_NAME" printf "rt. Reset %s database / delete all data\\n\\n" "$SCRIPT_NAME" printf "e. Exit %s\\n\\n" "$SCRIPT_NAME" printf "z. Uninstall %s\\n" "$SCRIPT_NAME" printf "\\n" printf "${BOLD}####################################################################${CLEARFORMAT}\\n" printf "\\n" while true; do printf "Choose an option: " read -r menu case "$menu" in 1) printf "\\n" Menu_RunSpeedtest PressEnter break ;; 2) printf "\\n" Menu_ConfigurePreferred PressEnter break ;; 3) printf "\\n" if AutomaticMode check; then AutomaticMode disable else AutomaticMode enable fi break ;; 4) printf "\\n" Menu_EditSchedule PressEnter break ;; 5) printf "\\n" if [ "$(OutputTimeMode check)" = "unix" ]; then OutputTimeMode non-unix elif [ "$(OutputTimeMode check)" = "non-unix" ]; then OutputTimeMode unix fi break ;; 6) printf "\\n" if [ "$(StoreResultURL check)" = "true" ]; then StoreResultURL disable elif [ "$(StoreResultURL check)" = "false" ]; then StoreResultURL enable fi break ;; 7) printf "\\n" LastXResults update PressEnter break ;; 8) printf "\\n" DaysToKeep update PressEnter break ;; 9) printf "\\n" if [ "$(SpeedtestBinary check)" = "builtin" ]; then SpeedtestBinary external elif [ "$(SpeedtestBinary check)" = "external" ]; then SpeedtestBinary builtin fi break ;; c) Generate_Interface_List printf "\\n" Create_Symlinks printf "\\n" PressEnter break ;; r) Create_Symlinks force printf "\\n" PressEnter break ;; s) printf "\\n" if [ "$(ScriptStorageLocation check)" = "jffs" ]; then ScriptStorageLocation usb Create_Symlinks elif [ "$(ScriptStorageLocation check)" = "usb" ]; then ScriptStorageLocation jffs Create_Symlinks fi break ;; q) printf "\\n" if [ "$(ExcludeFromQoS check)" = "true" ]; then if [ "$(AutoBWEnable check)" = "true" ]; then Print_Output false "Cannot disable Exclude from QoS when AutoBW is enabled" "$WARN" PressEnter elif [ "$(AutoBWEnable check)" = "false" ]; then ExcludeFromQoS disable fi elif [ "$(ExcludeFromQoS check)" = "false" ]; then ExcludeFromQoS enable fi break ;; a) printf "\\n" Menu_AutoBW break ;; u) printf "\\n" if Check_Lock menu; then Update_Version Clear_Lock fi PressEnter break ;; uf) printf "\\n" if Check_Lock menu; then Update_Version force Clear_Lock fi PressEnter break ;; rt) printf "\\n" if Check_Lock menu; then Menu_ResetDB Clear_Lock fi PressEnter break ;; e) ScriptHeader printf "\\n${BOLD}Thanks for using %s!${CLEARFORMAT}\\n\\n\\n" "$SCRIPT_NAME" exit 0 ;; z) while true; do printf "\\n${BOLD}Are you sure you want to uninstall %s? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" read -r confirm case "$confirm" in y|Y) Menu_Uninstall exit 0 ;; *) break ;; esac done ;; *) printf "\\nPlease choose a valid option\\n\\n" ;; esac done ScriptHeader MainMenu } Check_Requirements(){ CHECKSFAILED="false" if [ "$(nvram get jffs2_scripts)" -ne 1 ]; then nvram set jffs2_scripts=1 nvram commit Print_Output true "Custom JFFS Scripts enabled" "$WARN" fi if ! Check_Swap; then Print_Output false "No Swap file detected!" "$ERR" CHECKSFAILED="true" fi if [ ! -f /opt/bin/opkg ]; then Print_Output false "Entware not detected!" "$ERR" CHECKSFAILED="true" fi if ! Firmware_Version_Check; then Print_Output false "Unsupported firmware version detected" "$ERR" Print_Output false "$SCRIPT_NAME requires Merlin 384.15/384.13_4 or Fork 43E5 (or later)" "$ERR" CHECKSFAILED="true" fi if [ "$CHECKSFAILED" = "false" ]; then Print_Output false "Installing required packages from Entware" "$PASS" opkg update opkg install sqlite3-cli opkg install jq opkg install p7zip opkg install findutils return 0 else return 1 fi } Menu_Install(){ ScriptHeader Print_Output true "Welcome to $SCRIPT_NAME $SCRIPT_VERSION, a script by JackYaz" sleep 1 Print_Output true "By installing $SCRIPT_NAME you are agreeing to Ookla's license: $SCRIPT_REPO/files/speedtest-cli-license" "$WARN" printf "\\n${BOLD}Do you wish to continue? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" read -r confirm case "$confirm" in y|Y) : ;; *) Print_Output true "You did not agree to Ookla's license, removing $SCRIPT_NAME" "$CRIT" Clear_Lock rm -f "/jffs/scripts/$SCRIPT_NAME_LOWER" 2>/dev/null exit 1 ;; esac Print_Output true "Checking your router meets the requirements for $SCRIPT_NAME" if ! Check_Requirements; then Print_Output true "Requirements for $SCRIPT_NAME not met, please see above for the reason(s)" "$CRIT" PressEnter Clear_Lock rm -f "/jffs/scripts/$SCRIPT_NAME_LOWER" 2>/dev/null exit 1 fi Create_Dirs Conf_Exists if [ "$(SpeedtestBinary check)" = "builtin" ]; then printf "/usr/sbin/ookla" > /tmp/spdmerlin-binary elif [ "$(SpeedtestBinary check)" = "external" ]; then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi Set_Version_Custom_Settings local "$SCRIPT_VERSION" Set_Version_Custom_Settings server "$SCRIPT_VERSION" ScriptStorageLocation load Create_Symlinks Download_File "$SCRIPT_REPO/files/$ARCH.tar.gz" "$OOKLA_DIR/$ARCH.tar.gz" tar -xzf "$OOKLA_DIR/$ARCH.tar.gz" -C "$OOKLA_DIR" rm -f "$OOKLA_DIR/$ARCH.tar.gz" chmod 0755 "$OOKLA_DIR/speedtest" Update_File README.md Update_File spdstats_www.asp Update_File shared-jy.tar.gz Auto_Startup create 2>/dev/null Auto_Cron create 2>/dev/null Auto_ServiceEvent create 2>/dev/null Shortcut_Script create Process_Upgrade Run_Speedtest auto WAN Clear_Lock Download_File "$SCRIPT_REPO/install-success/LICENSE" "$SCRIPT_DIR/LICENSE" ScriptHeader MainMenu } Menu_Startup(){ if [ -z "$1" ]; then Print_Output true "Missing argument for startup, not starting $SCRIPT_NAME" "$WARN" exit 1 elif [ "$1" != "force" ]; then if [ ! -f "$1/entware/bin/opkg" ]; then Print_Output true "$1 does not contain Entware, not starting $SCRIPT_NAME" "$WARN" exit 1 else Print_Output true "$1 contains Entware, starting $SCRIPT_NAME" "$WARN" fi fi NTP_Ready Check_Lock if [ "$1" != "force" ]; then sleep 8 fi Create_Dirs Conf_Exists if [ "$(SpeedtestBinary check)" = "builtin" ]; then printf "/usr/sbin/ookla" > /tmp/spdmerlin-binary elif [ "$(SpeedtestBinary check)" = "external" ]; then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load Create_Symlinks Auto_Startup create 2>/dev/null if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create Mount_WebUI Clear_Lock } Menu_RunSpeedtest(){ exitmenu="" validselection="" useiface="" usepreferred="" ScriptHeader while true; do printf "Choose an interface to speedtest:\\n\\n" printf "1. All\\n" COUNTER="2" while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then printf "%s. %s\\n" "$COUNTER" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" COUNTER=$((COUNTER+1)) fi done < "$SCRIPT_INTERFACES_USER" printf "\\nChoose an option: " read -r iface_choice if [ "$iface_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$iface_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$((COUNTER-1))" validselection="false" else if [ "$iface_choice" -lt 1 ] || [ "$iface_choice" -gt "$((COUNTER-1))" ]; then printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$((COUNTER-1))" validselection="false" else if [ "$iface_choice" -gt "1" ]; then useiface="$(grep -v "interface not up" "$SCRIPT_INTERFACES_USER" | sed -n $((iface_choice-1))p | cut -f1 -d"#" | sed 's/ *$//')" else useiface="All" fi validselection="true" fi fi printf "\\n" if [ "$exitmenu" != "exit" ] && [ "$validselection" != "false" ]; then while true; do printf "What mode would you like to use?\\n\\n" printf "1. Auto-select\\n" printf "2. Preferred server\\n" printf "3. Choose a server\\n" printf "\\nChoose an option: " read -r usepref_choice if [ "$usepref_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$usepref_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$COUNTER" validselection="false" else if [ "$usepref_choice" -lt 0 ] || [ "$usepref_choice" -gt "3" ]; then printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$COUNTER" validselection="false" else case "$usepref_choice" in 1) usepreferred="auto" ;; 2) usepreferred="user" ;; 3) usepreferred="onetime" ;; esac validselection="true" printf "\\n" break fi fi done fi if [ "$exitmenu" != "exit" ] && [ "$validselection" != "false" ]; then if Check_Lock menu; then Run_Speedtest "$usepreferred" "$useiface" Clear_Lock fi elif [ "$exitmenu" = "exit" ]; then break fi printf "\\n" PressEnter ScriptHeader done if [ "$exitmenu" != "exit" ]; then return 0 else printf "\\n" return 1 fi } Menu_ConfigurePreferred(){ exitmenu="" prefiface="" ScriptHeader while true; do printf "Choose an interface to configure server preference for:\\n\\n" printf "1. All (on/off only)\\n\\n" COUNTER="2" while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then pref_enabled="" if PreferredServer check "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')"; then pref_enabled="On"; else pref_enabled="Off"; fi printf "%s. %s\\n" "$COUNTER" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" printf " Preferred: %s - Server: %s\\n\\n" "$pref_enabled" "$(PreferredServer list "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" | cut -f2 -d"|")" COUNTER=$((COUNTER+1)) fi done < "$SCRIPT_INTERFACES_USER" while true; do printf "\\nChoose an option: " read -r iface_choice if [ "$iface_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$iface_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$((COUNTER-1))" else if [ "$iface_choice" -lt 1 ] || [ "$iface_choice" -gt "$((COUNTER-1))" ]; then printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$((COUNTER-1))" else if [ "$iface_choice" -gt "1" ]; then prefiface="$(grep -v "interface not up" "$SCRIPT_INTERFACES_USER" | sed -n $((iface_choice-1))p | cut -f1 -d"#" | sed 's/ *$//')" break else prefiface="All" break fi fi fi done printf "\\n" if [ "$exitmenu" != "exit" ]; then if [ "$prefiface" = "All" ]; then while true; do printf "What would you like to do?\\n\\n" printf "1. Turn on preferred servers\\n" printf "2. Turn off preferred servers\\n" printf "\\nChoose an option: " read -r usepref_choice if [ "$usepref_choice" = "e" ]; then break elif ! Validate_Number "$usepref_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n" else if [ "$usepref_choice" -lt 1 ] || [ "$usepref_choice" -gt 2 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else prefenabledisable="" if [ "$usepref_choice" -eq 1 ]; then prefenabledisable="enable" else prefenabledisable="disable" fi while IFS='' read -r line || [ -n "$line" ]; do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then PreferredServer "$prefenabledisable" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" printf "\\n" break fi fi done else while true; do pref_enabled="" if PreferredServer check "$prefiface"; then pref_enabled="On"; else pref_enabled="Off"; fi printf "What would you like to do?\\n\\n" printf "1. Toggle preferred server on/off - currently: %s\\n" "$pref_enabled" printf "2. Set preferred server - currently: %s\\n" "$(PreferredServer list "$prefiface" | cut -f2 -d"|")" printf "\\nChoose an option: " read -r ifpref_choice if [ "$ifpref_choice" = "e" ]; then break elif ! Validate_Number "$ifpref_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n" else if [ "$ifpref_choice" -lt 1 ] || [ "$ifpref_choice" -gt 2 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n" else if [ "$ifpref_choice" -eq 1 ]; then printf "\\n" if PreferredServer check "$prefiface"; then PreferredServer disable "$prefiface" else PreferredServer enable "$prefiface" fi break elif [ "$ifpref_choice" -eq 2 ]; then printf "\\n" PreferredServer "update" "$prefiface" break fi fi fi done fi fi if [ "$exitmenu" = "exit" ]; then break fi printf "\\n" PressEnter ScriptHeader done if [ "$exitmenu" != "exit" ]; then return 0 else printf "\\n" return 1 fi } Menu_EditSchedule(){ exitmenu="" formattype="" crudays="" crudaysvalidated="" cruhours="" crumins="" while true; do printf "\\n${BOLD}Please choose which day(s) to run speedtest (0-6 - 0 = Sunday, * for every day, or comma separated days):${CLEARFORMAT} " read -r day_choice if [ "$day_choice" = "e" ]; then exitmenu="exit" break elif [ "$day_choice" = "*" ]; then crudays="$day_choice" printf "\\n" break elif [ -z "$day_choice" ]; then printf "\\n\\e[31mPlease enter a valid number (0-6) or comma separated values${CLEARFORMAT}\\n" else crudaystmp="$(echo "$day_choice" | sed "s/,/ /g")" crudaysvalidated="true" for i in $crudaystmp; do if echo "$i" | grep -q "-"; then if [ "$i" = "-" ]; then printf "\\n\\e[31mPlease enter a valid number (0-6)${CLEARFORMAT}\\n" crudaysvalidated="false" break fi crudaystmp2="$(echo "$i" | sed "s/-/ /")" for i2 in $crudaystmp2; do if ! Validate_Number "$i2"; then printf "\\n\\e[31mPlease enter a valid number (0-6)${CLEARFORMAT}\\n" crudaysvalidated="false" break elif [ "$i2" -lt 0 ] || [ "$i2" -gt 6 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 6${CLEARFORMAT}\\n" crudaysvalidated="false" break fi done elif ! Validate_Number "$i"; then printf "\\n\\e[31mPlease enter a valid number (0-6) or comma separated values${CLEARFORMAT}\\n" crudaysvalidated="false" break else if [ "$i" -lt 0 ] || [ "$i" -gt 6 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 6 or comma separated values${CLEARFORMAT}\\n" crudaysvalidated="false" break fi fi done if [ "$crudaysvalidated" = "true" ]; then crudays="$day_choice" printf "\\n" break fi fi done if [ "$exitmenu" != "exit" ]; then while true; do printf "\\n${BOLD}Please choose the format to specify the hour/minute(s) to run speedtest:${CLEARFORMAT}\\n" printf " 1. Every X hours/minutes\\n" printf " 2. Custom\\n\\n" printf "Choose an option: " read -r formatmenu case "$formatmenu" in 1) formattype="everyx" printf "\\n" break ;; 2) formattype="custom" printf "\\n" break ;; e) exitmenu="exit" break ;; *) printf "\\n\\e[31mPlease enter a valid choice (1-2)${CLEARFORMAT}\\n" ;; esac done fi if [ "$exitmenu" != "exit" ]; then if [ "$formattype" = "everyx" ]; then while true; do printf "\\n${BOLD}Please choose whether to specify every X hours or every X minutes to run speedtest:${CLEARFORMAT}\\n" printf " 1. Hours\\n" printf " 2. Minutes\\n\\n" printf "Choose an option: " read -r formatmenu case "$formatmenu" in 1) formattype="hours" printf "\\n" break ;; 2) formattype="mins" printf "\\n" break ;; e) exitmenu="exit" break ;; *) printf "\\n\\e[31mPlease enter a valid choice (1-2)${CLEARFORMAT}\\n" ;; esac done fi fi if [ "$exitmenu" != "exit" ]; then if [ "$formattype" = "hours" ]; then while true; do printf "\\n${BOLD}Please choose how often to run speedtest (every X hours, where X is 1-24):${CLEARFORMAT} " read -r hour_choice if [ "$hour_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$hour_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-24)${CLEARFORMAT}\\n" elif [ "$hour_choice" -lt 1 ] || [ "$hour_choice" -gt 24 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 24${CLEARFORMAT}\\n" elif [ "$hour_choice" -eq 24 ]; then cruhours=0 crumins=0 printf "\\n" break else cruhours="*/$hour_choice" crumins=0 printf "\\n" break fi done elif [ "$formattype" = "mins" ]; then while true; do printf "\\n${BOLD}Please choose how often to run speedtest (every X minutes, where X is 1-30):${CLEARFORMAT} " read -r min_choice if [ "$min_choice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$min_choice"; then printf "\\n\\e[31mPlease enter a valid number (1-30)${CLEARFORMAT}\\n" elif [ "$min_choice" -lt 1 ] || [ "$min_choice" -gt 30 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 30${CLEARFORMAT}\\n" else crumins="*/$min_choice" cruhours="*" printf "\\n" break fi done fi fi if [ "$exitmenu" != "exit" ]; then if [ "$formattype" = "custom" ]; then while true; do printf "\\n${BOLD}Please choose which hour(s) to run speedtest (0-23, * for every hour, or comma separated hours):${CLEARFORMAT} " read -r hour_choice if [ "$hour_choice" = "e" ]; then exitmenu="exit" break elif [ "$hour_choice" = "*" ]; then cruhours="$hour_choice" printf "\\n" break else cruhourstmp="$(echo "$hour_choice" | sed "s/,/ /g")" cruhoursvalidated="true" for i in $cruhourstmp; do if echo "$i" | grep -q "-"; then if [ "$i" = "-" ]; then printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi cruhourstmp2="$(echo "$i" | sed "s/-/ /")" for i2 in $cruhourstmp2; do if ! Validate_Number "$i2"; then printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" cruhoursvalidated="false" break elif [ "$i2" -lt 0 ] || [ "$i2" -gt 23 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 23${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi done elif echo "$i" | grep -q "/"; then cruhourstmp3="$(echo "$i" | sed "s/\*\///")" if ! Validate_Number "$cruhourstmp3"; then printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" cruhoursvalidated="false" break elif [ "$cruhourstmp3" -lt 0 ] || [ "$cruhourstmp3" -gt 23 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 23${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi elif ! Validate_Number "$i"; then printf "\\n\\e[31mPlease enter a valid number (0-23) or comma separated values${CLEARFORMAT}\\n" cruhoursvalidated="false" break elif [ "$i" -lt 0 ] || [ "$i" -gt 23 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 23 or comma separated values${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi done if [ "$cruhoursvalidated" = "true" ]; then if echo "$hour_choice" | grep -q "-"; then cruhours1="$(echo "$hour_choice" | cut -f1 -d'-')" cruhours2="$(echo "$hour_choice" | cut -f2 -d'-')" if [ "$cruhours1" -lt "$cruhours2" ]; then cruhours="$hour_choice" elif [ "$cruhours2" -lt "$cruhours1" ]; then cruhours="$cruhours1-23,0-$cruhours2" fi else cruhours="$hour_choice" fi printf "\\n" break fi fi done fi fi if [ "$exitmenu" != "exit" ]; then if [ "$formattype" = "custom" ]; then while true; do printf "\\n${BOLD}Please choose which minutes(s) to run speedtest (0-59, * for every minute, or comma separated minutes):${CLEARFORMAT} " read -r min_choice if [ "$min_choice" = "e" ]; then exitmenu="exit" break elif [ "$min_choice" = "*" ]; then crumins="$min_choice" printf "\\n" break else cruminstmp="$(echo "$min_choice" | sed "s/,/ /g")" cruminsvalidated="true" for i in $cruminstmp; do if echo "$i" | grep -q "-"; then if [ "$i" = "-" ]; then printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" cruminsvalidated="false" break fi cruminstmp2="$(echo "$i" | sed "s/-/ /")" for i2 in $cruminstmp2; do if ! Validate_Number "$i2"; then printf "\\n\\e[31mPlease enter a valid number (0-59)${CLEARFORMAT}\\n" cruminsvalidated="false" break elif [ "$i2" -lt 0 ] || [ "$i2" -gt 59 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 59${CLEARFORMAT}\\n" cruminsvalidated="false" break fi done elif echo "$i" | grep -q "/"; then cruminstmp3="$(echo "$i" | sed "s/\*\///")" if ! Validate_Number "$cruminstmp3"; then printf "\\n\\e[31mPlease enter a valid number (0-30)${CLEARFORMAT}\\n" cruminsvalidated="false" break elif [ "$cruminstmp3" -lt 0 ] || [ "$cruminstmp3" -gt 30 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 30${CLEARFORMAT}\\n" cruminsvalidated="false" break fi elif ! Validate_Number "$i"; then printf "\\n\\e[31mPlease enter a valid number (0-59) or comma separated values${CLEARFORMAT}\\n" cruminsvalidated="false" break elif [ "$i" -lt 0 ] || [ "$i" -gt 59 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 59 or comma separated values${CLEARFORMAT}\\n" cruminsvalidated="false" break fi done if [ "$cruminsvalidated" = "true" ]; then if echo "$min_choice" | grep -q "-"; then crumins1="$(echo "$min_choice" | cut -f1 -d'-')" crumins2="$(echo "$min_choice" | cut -f2 -d'-')" if [ "$crumins1" -lt "$crumins2" ]; then crumins="$min_choice" elif [ "$crumins2" -lt "$crumins1" ]; then crumins="$crumins1-59,0-$crumins2" fi else crumins="$min_choice" fi printf "\\n" break fi fi done fi fi if [ "$exitmenu" != "exit" ]; then TestSchedule update "$crudays" "$cruhours" "$crumins" return 0 else return 1 fi } Menu_ResetDB(){ printf "${BOLD}\\e[33mWARNING: This will reset the %s database by deleting all database records.\\n" "$SCRIPT_NAME" printf "A backup of the database will be created if you change your mind.${CLEARFORMAT}\\n" printf "\\n${BOLD}Do you want to continue? (y/n)${CLEARFORMAT} " read -r confirm case "$confirm" in y|Y) printf "\\n" Reset_DB ;; *) printf "\\n${BOLD}\\e[33mDatabase reset cancelled${CLEARFORMAT}\\n\\n" ;; esac } Menu_AutoBW(){ while true; do ScriptHeader AUTOBW_MENU="" if [ "$(AutoBWEnable check)" = "true" ]; then AUTOBW_MENU="Enabled" elif [ "$(AutoBWEnable check)" = "false" ]; then AUTOBW_MENU="Disabled" fi printf "1. Update QoS bandwidth values now\\n\\n" printf "2. Configure number of speedtests used to calculate average bandwidth\\n Currently bandwidth is calculated using the avergae of the last ${SETTING}%s${CLEARFORMAT} speedtest(s)\\n\\n" "$(AutoBWConf check AVERAGE CALC)" printf "3. Configure scale factor\\n Download: ${SETTING}%s%%${CLEARFORMAT} - Upload: ${SETTING}%s%%${CLEARFORMAT}\\n\\n" "$(AutoBWConf check SF DOWN)" "$(AutoBWConf check SF UP)" printf "4. Configure bandwidth limits\\n Upper Limit Download: ${SETTING}%s Mbps${CLEARFORMAT} - Upload: ${SETTING}%s Mbps${CLEARFORMAT}\\n Lower Limit Download: ${SETTING}%s Mbps${CLEARFORMAT} - Upload: ${SETTING}%s Mbps${CLEARFORMAT}\\n\\n" "$(AutoBWConf check ULIMIT DOWN)" "$(AutoBWConf check ULIMIT UP)" "$(AutoBWConf check LLIMIT DOWN)" "$(AutoBWConf check LLIMIT UP)" printf "5. Configure threshold for updating QoS bandwidth values\\n Download: ${SETTING}%s%%${CLEARFORMAT} - Upload: ${SETTING}%s%%${CLEARFORMAT}\\n\\n" "$(AutoBWConf check THRESHOLD DOWN)" "$(AutoBWConf check THRESHOLD UP)" printf "6. Toggle AutoBW on/off\\n Currently: ${SETTING}%s${CLEARFORMAT}\\n\\n" "$AUTOBW_MENU" printf "e. Go back\\n\\n" printf "${BOLD}####################################################################${CLEARFORMAT}\\n" printf "\\n" printf "Choose an option: " read -r autobwmenu case "$autobwmenu" in 1) printf "\\n" Menu_AutoBW_Update PressEnter ;; 2) while true; do ScriptHeader exitmenu="" avgnum="" while true; do printf "\\n" printf "Enter number of speedtests to use to calculate avg bandwidth (1-30): " read -r avgnumvalue if [ "$avgnumvalue" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$avgnumvalue"; then printf "\\n\\e[31mPlease enter a valid number (1-30)${CLEARFORMAT}\\n" else if [ "$avgnumvalue" -lt 1 ] || [ "$avgnumvalue" -gt 30 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 30${CLEARFORMAT}\\n" else avgnum="$avgnumvalue" break fi fi done if [ "$exitmenu" != "exit" ]; then AutoBWConf update AVERAGE CALC "$avgnum" break fi if [ "$exitmenu" = "exit" ]; then break fi done printf "\\n" PressEnter ;; 3) while true; do ScriptHeader exitmenu="" updown="" sfvalue="" printf "\\n" printf "Select a scale factor to set\\n" printf "1. Download\\n" printf "2. Upload\\n\\n" while true; do printf "Choose an option: " read -r autobwsfchoice if [ "$autobwsfchoice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwsfchoice"; then printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n\\n" else if [ "$autobwsfchoice" -lt 1 ] || [ "$autobwsfchoice" -gt 2 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else if [ "$autobwsfchoice" -eq 1 ]; then updown="DOWN" break elif [ "$autobwsfchoice" -eq 2 ]; then updown="UP" break fi fi fi done if [ "$exitmenu" != "exit" ]; then while true; do printf "\\n" printf "Enter percentage to scale bandwidth by (1-100): " read -r autobwsfvalue if [ "$autobwsfvalue" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwsfvalue"; then printf "\\n\\e[31mPlease enter a valid number (1-100)${CLEARFORMAT}\\n" else if [ "$autobwsfvalue" -lt 1 ] || [ "$autobwsfvalue" -gt 100 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 100${CLEARFORMAT}\\n" else sfvalue="$autobwsfvalue" break fi fi done fi if [ "$exitmenu" != "exit" ]; then AutoBWConf update SF "$updown" "$sfvalue" break fi if [ "$exitmenu" = "exit" ]; then break fi done printf "\\n" PressEnter ;; 4) while true; do ScriptHeader exitmenu="" updown="" limithighlow="" limitvalue="" printf "\\n" printf "Select a bandwidth to set limit for\\n" printf "1. Download\\n" printf "2. Upload\\n\\n" while true; do printf "Choose an option: " read -r autobwchoice if [ "$autobwchoice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwchoice"; then printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n\\n" else if [ "$autobwchoice" -lt 1 ] || [ "$autobwchoice" -gt 2 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else if [ "$autobwchoice" -eq 1 ]; then updown="DOWN" break elif [ "$autobwchoice" -eq 2 ]; then updown="UP" break fi fi fi done if [ "$exitmenu" != "exit" ]; then while true; do printf "\\n" printf "Select a limit to set\\n" printf "1. Upper\\n" printf "2. Lower\\n\\n" printf "Choose an option: " read -r autobwlimit if [ "$autobwlimit" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwlimit"; then printf "\\n\\e[31mPlease enter a valid number (1-100)${CLEARFORMAT}\\n" else if [ "$autobwlimit" -lt 1 ] || [ "$autobwlimit" -gt 100 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 100${CLEARFORMAT}\\n" else if [ "$autobwlimit" -eq 1 ]; then limithighlow="ULIMIT" elif [ "$autobwlimit" -eq 2 ]; then limithighlow="LLIMIT" fi fi fi if [ "$exitmenu" != "exit" ]; then while true; do printf "\\n" printf "Enter value to set limit to (0 = unlimited for upper): " read -r autobwlimvalue if [ "$autobwlimvalue" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwlimvalue"; then printf "\\n\\e[31mPlease enter a valid number (1-100)${CLEARFORMAT}\\n" else limitvalue="$autobwlimvalue" break fi done if [ "$exitmenu" != "exit" ]; then AutoBWConf update "$limithighlow" "$updown" "$limitvalue" exitmenu="exit" break fi fi done if [ "$exitmenu" = "exit" ]; then break fi fi done printf "\\n" PressEnter ;; 5) while true; do ScriptHeader exitmenu="" updown="" thvalue="" printf "\\n" printf "Select a threshold to set\\n" printf "1. Download\\n" printf "2. Upload\\n\\n" while true; do printf "Choose an option: " read -r autobwthchoice if [ "$autobwthchoice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwthchoice"; then printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n\\n" else if [ "$autobwthchoice" -lt 1 ] || [ "$autobwthchoice" -gt 2 ]; then printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else if [ "$autobwthchoice" -eq 1 ]; then updown="DOWN" break elif [ "$autobwthchoice" -eq 2 ]; then updown="UP" break fi fi fi done if [ "$exitmenu" != "exit" ]; then while true; do printf "\\n" printf "Enter percentage to use for result threshold: " read -r autobwthvalue if [ "$autobwthvalue" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwthvalue"; then printf "\\n\\e[31mPlease enter a valid number (0-100)${CLEARFORMAT}\\n" else if [ "$autobwthvalue" -lt 0 ] || [ "$autobwthvalue" -gt 100 ]; then printf "\\n\\e[31mPlease enter a number between 0 and 100${CLEARFORMAT}\\n" else thvalue="$autobwthvalue" break fi fi done fi if [ "$exitmenu" != "exit" ]; then AutoBWConf update THRESHOLD "$updown" "$thvalue" break fi if [ "$exitmenu" = "exit" ]; then break fi done printf "\\n" PressEnter ;; 6) printf "\\n" if [ "$(AutoBWEnable check)" = "true" ]; then AutoBWEnable disable elif [ "$(AutoBWEnable check)" = "false" ]; then AutoBWEnable enable if [ "$(ExcludeFromQoS check)" = "false" ]; then Print_Output false "Enabling Exclude from QoS (required for AutoBW)" ExcludeFromQoS enable PressEnter fi fi ;; e) break ;; esac done } Menu_AutoBW_Update(){ if [ "$(nvram get qos_enable)" -eq 0 ]; then Print_Output true "QoS is not enabled, please enable this in the Asus WebUI." "$ERR" return 1 fi dsf="$(AutoBWConf check SF DOWN | awk '{printf ($1/100)}')" usf="$(AutoBWConf check SF UP | awk '{printf ($1/100)}')" dlimitlow="$(($(AutoBWConf check LLIMIT DOWN)*1024))" dlimithigh="$(($(AutoBWConf check ULIMIT DOWN)*1024))" ulimitlow="$(($(AutoBWConf check LLIMIT UP)*1024))" ulimithigh="$(($(AutoBWConf check ULIMIT UP)*1024))" avgcalc="$(AutoBWConf check AVERAGE CALC)" metriclist="Download Upload" for metric in $metriclist; do { { echo ".mode list" echo ".headers off" echo ".output /tmp/spdbw$metric" echo "SELECT avg($metric) FROM (SELECT $metric FROM spdstats_WAN ORDER BY [Timestamp] DESC LIMIT $avgcalc);" } > /tmp/spd-autobw.sql "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-autobw.sql rm -f /tmp/spd-autobw.sql } done autobwoutfile="$SCRIPT_STORAGE_DIR/.autobwoutfile" TZ=$(cat /etc/TZ) export TZ printf "AutoBW report - %s\\n\\n" "$(date +"%c")" > "$autobwoutfile" dspdkbps="$(echo "$(awk '{printf (1024*$1)}' /tmp/spdbwDownload)" "$dsf" | awk '{printf int($1*$2)}')" uspdkbps="$(echo "$(awk '{printf (1024*$1)}' /tmp/spdbwUpload)" "$usf" | awk '{printf int($1*$2)}')" rm -f /tmp/spdbwDownload rm -f /tmp/spdbwUpload if [ "$dspdkbps" -lt "$dlimitlow" ]; then Print_Output true "Download speed ($dspdkbps Kbps) < lower limit ($dlimitlow Kbps)" "$WARN" | tee -a "$autobwoutfile" dspdkbps="$dlimitlow" elif [ "$dspdkbps" -gt "$dlimithigh" ] && [ "$dlimithigh" -gt 0 ]; then Print_Output true "Download speed ($dspdkbps Kbps) > upper limit ($dlimithigh Kbps)" "$WARN" | tee -a "$autobwoutfile" dspdkbps="$dlimithigh" fi if [ "$uspdkbps" -lt "$ulimitlow" ]; then Print_Output true "Upload speed ($uspdkbps Kbps) < lower limit ($ulimitlow Kbps)" "$WARN" | tee -a "$autobwoutfile" uspdkbps="$ulimitlow" elif [ "$uspdkbps" -gt "$ulimithigh" ] && [ "$ulimithigh" -gt 0 ]; then Print_Output true "Upload speed ($uspdkbps Kbps) > upper limit ($ulimithigh Kbps)" "$WARN" | tee -a "$autobwoutfile" uspdkbps="$ulimithigh" fi old_uspdkbps="$(nvram get qos_obw)" old_dspdkbps="$(nvram get qos_ibw)" bw_changed="false" dbw_threshold="$(AutoBWConf check THRESHOLD DOWN | awk '{printf ($1/100)}')" if [ "$dspdkbps" -gt "$(echo "$old_dspdkbps" "$dbw_threshold" | awk '{printf int($1+$1*$2)}')" ] || [ "$dspdkbps" -lt "$(echo "$old_dspdkbps" "$dbw_threshold" | awk '{printf int($1-$1*$2)}')" ]; then bw_changed="true" nvram set qos_ibw="$(echo $dspdkbps | cut -d'.' -f1)" Print_Output true "Setting QoS Download Speed to $dspdkbps Kbps (was $old_dspdkbps Kbps)" "$PASS" | tee -a "$autobwoutfile" else Print_Output true "Calculated Download speed ($dspdkbps Kbps) does not exceed $(AutoBWConf check THRESHOLD DOWN)% threshold of existing value ($old_dspdkbps Kbps)" "$WARN" | tee -a "$autobwoutfile" fi ubw_threshold="$(AutoBWConf check THRESHOLD UP | awk '{printf ($1/100)}')" if [ "$uspdkbps" -gt "$(echo "$old_uspdkbps" "$ubw_threshold" | awk '{printf int($1+$1*$2)}')" ] || [ "$uspdkbps" -lt "$(echo "$old_uspdkbps" "$ubw_threshold" | awk '{printf int($1-$1*$2)}')" ]; then bw_changed="true" nvram set qos_obw="$(echo $uspdkbps | cut -d'.' -f1)" Print_Output true "Setting QoS Upload Speed to $uspdkbps Kbps (was $old_uspdkbps Kbps)" "$PASS" | tee -a "$autobwoutfile" else Print_Output true "Calculated Upload speed ($uspdkbps Kbps) does not exceed $(AutoBWConf check THRESHOLD UP)% threshold of existing value ($old_uspdkbps Kbps)" "$WARN" | tee -a "$autobwoutfile" fi if [ "$bw_changed" = "true" ]; then nvram commit service "restart_qos;restart_firewall" >/dev/null 2>&1 printf "AutoBW made changes to QoS bandwidth, QoS will be restarted" >> "$autobwoutfile" else printf "No changes made to QoS by AutoBW" >> "$autobwoutfile" fi sed -i 's/[^a-zA-Z0-9():%<>-]/ /g;s/ 1m//g;s/ 33m//g;s/ 0m//g' "$autobwoutfile" Clear_Lock } Menu_Uninstall(){ if [ -n "$PPID" ]; then ps | grep -v grep | grep -v $$ | grep -v "$PPID" | grep -i "$SCRIPT_NAME_LOWER" | grep generate | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 else ps | grep -v grep | grep -v $$ | grep -i "$SCRIPT_NAME_LOWER" | grep generate | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 fi PROC_NAME="speedtest" if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then PROC_NAME="ookla" fi if [ -n "$(pidof "$PROC_NAME")" ]; then killall -q "$PROC_NAME" fi Print_Output true "Removing $SCRIPT_NAME..." "$PASS" Auto_Startup delete 2>/dev/null Auto_Cron delete 2>/dev/null Auto_ServiceEvent delete 2>/dev/null Get_WebUI_Page "$SCRIPT_DIR/spdstats_www.asp" if [ -n "$MyPage" ] && [ "$MyPage" != "none" ] && [ -f "/tmp/menuTree.js" ]; then sed -i "\\~$MyPage~d" /tmp/menuTree.js umount /www/require/modules/menuTree.js mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js rm -f "$SCRIPT_WEBPAGE_DIR/$MyPage" rm -f "$SCRIPT_WEBPAGE_DIR/$(echo $MyPage | cut -f1 -d'.').title" fi rm -f "$SCRIPT_DIR/spdstats_www.asp" 2>/dev/null printf "\\n${BOLD}Do you want to delete %s stats and config? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" read -r confirm case "$confirm" in y|Y) rm -rf "$SCRIPT_DIR" 2>/dev/null rm -rf "$SCRIPT_STORAGE_DIR" 2>/dev/null ;; *) : ;; esac Shortcut_Script delete rm -rf "$SCRIPT_WEB_DIR" 2>/dev/null rm -rf "$OOKLA_DIR" 2>/dev/null rm -rf "$OOKLA_LICENSE_DIR" 2>/dev/null rm -f "/jffs/scripts/$SCRIPT_NAME_LOWER" 2>/dev/null Clear_Lock Print_Output true "Uninstall completed" "$PASS" } NTP_Ready(){ if [ "$(nvram get ntp_ready)" -eq 0 ]; then ntpwaitcount=0 Check_Lock while [ "$(nvram get ntp_ready)" -eq 0 ] && [ "$ntpwaitcount" -lt 600 ]; do ntpwaitcount="$((ntpwaitcount + 30))" Print_Output true "Waiting for NTP to sync..." "$WARN" sleep 30 done if [ "$ntpwaitcount" -ge 600 ]; then Print_Output true "NTP failed to sync after 10 minutes. Please resolve!" "$CRIT" Clear_Lock exit 1 else Print_Output true "NTP synced, $SCRIPT_NAME will now continue" "$PASS" Clear_Lock fi fi } ### function based on @Adamm00's Skynet USB wait function ### Entware_Ready(){ if [ ! -f "/opt/bin/opkg" ]; then Check_Lock sleepcount=1 while [ ! -f /opt/bin/opkg ] && [ "$sleepcount" -le 10 ]; do Print_Output true "Entware not found, sleeping for 10s (attempt $sleepcount of 10)" "$ERR" sleepcount="$((sleepcount + 1))" sleep 10 done if [ ! -f /opt/bin/opkg ]; then Print_Output true "Entware not found and is required for $SCRIPT_NAME to run, please resolve" "$CRIT" Clear_Lock exit 1 else Print_Output true "Entware found, $SCRIPT_NAME will now continue" "$PASS" Clear_Lock fi fi } ### ### ### function based on @dave14305's FlexQoS about function ### Show_About(){ cat < /tmp/spdmerlin-binary elif [ "$(SpeedtestBinary check)" = "external" ]; then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load Create_Symlinks Process_Upgrade Auto_Startup create 2>/dev/null if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create ScriptHeader MainMenu exit 0 fi case "$1" in install) Check_Lock Menu_Install exit 0 ;; startup) Menu_Startup "$2" exit 0 ;; generate) NTP_Ready Entware_Ready Check_Lock Run_Speedtest schedule Clear_Lock exit 0 ;; service_event) if [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}spdtest"; then rm -f /tmp/detect_spdtest.js rm -f /tmp/spd-result.txt rm -f /tmp/spd-stats.txt Check_Lock webui sleep 3 Run_Speedtest_WebUI "$3" Clear_Lock exit 0 elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlistmanual"; then spdifacename="$(echo "$3" | sed "s/${SCRIPT_NAME_LOWER}serverlistmanual_//" | cut -f1 -d'_' | tr "a-z" "A-Z")"; GenerateServerList_WebUI "$spdifacename" "spdmerlin_manual_serverlist" elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlist"; then spdifacename="$(echo "$3" | sed "s/${SCRIPT_NAME_LOWER}serverlist_//" | cut -f1 -d'_' | tr "a-z" "A-Z")"; GenerateServerList_WebUI "$spdifacename" "spdmerlin_serverlist_$spdifacename" elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}config" ]; then Interfaces_FromSettings Conf_FromSettings exit 0 elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}checkupdate" ]; then Update_Check exit 0 elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}doupdate" ]; then Update_Version force unattended exit 0 fi exit 0 ;; outputcsv) NTP_Ready Entware_Ready Check_Lock Generate_CSVs Clear_Lock exit 0 ;; enable) Entware_Ready AutomaticMode enable exit 0 ;; disable) Entware_Ready AutomaticMode disable exit 0 ;; update) Update_Version exit 0 ;; forceupdate) Update_Version force exit 0 ;; postupdate) Create_Dirs Conf_Exists if [ "$(SpeedtestBinary check)" = "builtin" ]; then printf "/usr/sbin/ookla" > /tmp/spdmerlin-binary elif [ "$(SpeedtestBinary check)" = "external" ]; then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load Create_Symlinks Process_Upgrade Auto_Startup create 2>/dev/null if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create Set_Version_Custom_Settings local "$SCRIPT_VERSION" Set_Version_Custom_Settings server "$SCRIPT_VERSION" ;; checkupdate) Update_Check exit 0 ;; uninstall) Menu_Uninstall exit 0 ;; develop) SCRIPT_BRANCH="develop" SCRIPT_REPO="https://jackyaz.io/$SCRIPT_NAME/$SCRIPT_BRANCH" Update_Version force exit 0 ;; stable) SCRIPT_BRANCH="master" SCRIPT_REPO="https://jackyaz.io/$SCRIPT_NAME/$SCRIPT_BRANCH" Update_Version force exit 0 ;; about) ScriptHeader Show_About exit 0 ;; help) ScriptHeader Show_Help exit 0 ;; *) ScriptHeader Print_Output false "Command not recognised." "$ERR" Print_Output false "For a list of available commands run: $SCRIPT_NAME_LOWER help" exit 1 ;; esac