diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index 0cd286d9..fdfd46e7 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 with: fetch-depth: 0 ref: 'main' # Ensure we're tagging the main branch after the merge @@ -85,7 +85,7 @@ jobs: git push origin ${{ steps.nextver.outputs.tag }} - name: Create Release with Automated Release Notes - uses: softprops/action-gh-release@v2.4.2 + uses: softprops/action-gh-release@v2.5.0 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ steps.nextver.outputs.tag }} diff --git a/MerlinAU.sh b/MerlinAU.sh index 420f09ad..8ecbf110 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -4,16 +4,16 @@ # # Original Creation Date: 2023-Oct-01 by @ExtremeFiretop. # Official Co-Author: @Martinski W. - Date: 2023-Nov-01 -# Last Modified: 2025-Nov-16 +# Last Modified: 2026-Jan-02 ################################################################### set -u ## Set version for each Production Release ## -readonly SCRIPT_VERSION=1.5.7 -readonly SCRIPT_VERSTAG="25111620" +readonly SCRIPT_VERSION=1.5.8 +readonly SCRIPT_VERSTAG="26010210" readonly SCRIPT_NAME="MerlinAU" ## Set to "master" for Production Releases ## -SCRIPT_BRANCH="master" +SCRIPT_BRANCH="dev" ##----------------------------------------## ## Modified by Martinski W. [2024-Jul-03] ## @@ -198,13 +198,13 @@ readonly fwInstalledInnerVers="$(nvram get innerver)" readonly fwInstalledBranchVer="${fwInstalledBaseVers}.$(echo "$fwInstalledBuildVers" | awk -F'.' '{print $1}')" ##------------------------------------------## -## Modified by ExtremeFiretop [2025-Apr-09] ## +## Modified by ExtremeFiretop [2025-Dec-30] ## ##------------------------------------------## # For minimum supported firmware version check # MinFirmwareVerCheckFailed=false readonly MinSupportedFW_3004_386_Ver="3004.386.13.2" -readonly MinSupportedFW_3004_388_Ver="3004.388.8.4" -readonly MinSupportedFW_3006_102_Ver="3004.388.8.2" +readonly MinSupportedFW_3004_388_Ver="3004.388.10.0" +readonly MinSupportedFW_3006_102_Ver="3006.102.4.0" ##----------------------------------------## ## Modified by Martinski W. [2025-Apr-09] ## @@ -927,6 +927,7 @@ else ## Set 20 minutes AFTER for APs and AiMesh Nodes ## readonly FW_Update_CRON_DefaultSchedule="20 0 * * *" fi +readonly meshUpdate_WaitSecs=8 ## Recommended 15 minutes BEFORE the F/W Update ## readonly ScriptAU_CRON_DefaultSchedule="45 23 * * *" @@ -2087,23 +2088,23 @@ isEMailConfigEnabledInAMTM=false # Define the CRON job command to execute # readonly SCRIPT_UP_CRON_JOB_RUN="sh $ScriptFilePath checkupdates" readonly SCRIPT_UP_CRON_JOB_TAG="${ScriptFNameTag}_ScriptUpdate" -readonly DAILY_SCRIPT_UPDATE_CHECK_JOB="sh $ScriptFilePath scriptAUCronJob & $hookScriptTagStr" -readonly DAILY_SCRIPT_UPDATE_CHECK_HOOK="[ -f $ScriptFilePath ] && $DAILY_SCRIPT_UPDATE_CHECK_JOB" +readonly DAILY_SCRIPT_UPDATE_CHECK_JOB="$ScriptFilePath scriptAUCronJob &" +readonly DAILY_SCRIPT_UPDATE_CHECK_HOOK="[ -x $ScriptFilePath ] && $DAILY_SCRIPT_UPDATE_CHECK_JOB $hookScriptTagStr" # Define the CRON job command to execute # readonly CRON_JOB_RUN="sh $ScriptFilePath run_now" readonly CRON_JOB_TAG_OLD="$ScriptFNameTag" readonly CRON_JOB_TAG="${ScriptFNameTag}_FWUpdate" -readonly CRON_SCRIPT_JOB="sh $ScriptFilePath addCronJob & $hookScriptTagStr" -readonly CRON_SCRIPT_HOOK="[ -f $ScriptFilePath ] && $CRON_SCRIPT_JOB" +readonly CRON_SCRIPT_JOB="$ScriptFilePath addCronJob &" +readonly CRON_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $CRON_SCRIPT_JOB $hookScriptTagStr" # Define post-reboot run job command to execute # -readonly POST_REBOOT_SCRIPT_JOB="sh $ScriptFilePath postRebootRun & $hookScriptTagStr" -readonly POST_REBOOT_SCRIPT_HOOK="[ -f $ScriptFilePath ] && $POST_REBOOT_SCRIPT_JOB" +readonly POST_REBOOT_SCRIPT_JOB="$ScriptFilePath postRebootRun &" +readonly POST_REBOOT_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $POST_REBOOT_SCRIPT_JOB $hookScriptTagStr" # Define post-update email notification job command to execute # -readonly POST_UPDATE_EMAIL_SCRIPT_JOB="sh $ScriptFilePath postUpdateEmail & $hookScriptTagStr" -readonly POST_UPDATE_EMAIL_SCRIPT_HOOK="[ -f $ScriptFilePath ] && $POST_UPDATE_EMAIL_SCRIPT_JOB" +readonly POST_UPDATE_EMAIL_SCRIPT_JOB="$ScriptFilePath postUpdateEmail &" +readonly POST_UPDATE_EMAIL_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $POST_UPDATE_EMAIL_SCRIPT_JOB $hookScriptTagStr" if [ -d "$FW_LOG_DIR" ] then @@ -3433,7 +3434,7 @@ _DelPostUpdateEmailNotifyScriptHook_() if grep -qE "$POST_UPDATE_EMAIL_SCRIPT_JOB" "$hookScriptFile" then - sed -i -e '/\/'"$ScriptFileName"' postUpdateEmail & '"$hookScriptTagStr"'/d' "$hookScriptFile" + sed -i -e '/\/'"$ScriptFileName"' postUpdateEmail & /d' "$hookScriptFile" if [ $? -eq 0 ] then Say "Post-update email notification hook was deleted successfully from '$hookScriptFile' script." @@ -3486,7 +3487,7 @@ _DelPostRebootRunScriptHook_() if grep -qE "$POST_REBOOT_SCRIPT_JOB" "$hookScriptFile" then - sed -i -e '/\/'"$ScriptFileName"' postRebootRun & '"$hookScriptTagStr"'/d' "$hookScriptFile" + sed -i -e '/\/'"$ScriptFileName"' postRebootRun & /d' "$hookScriptFile" if [ $? -eq 0 ] then Say "Post-reboot run hook was deleted successfully from '$hookScriptFile' script." @@ -4046,53 +4047,75 @@ _CheckForMinimumModelSupport_() "$routerModelCheckFailed" && return 1 || return 0 } +##-------------------------------------## +## Added by Martinski W. [2026-Jan-01] ## +##-------------------------------------## +_DoMainRouterLogin_() +{ + if [ $# -lt 3 ] || [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] + then + echo ; return 1 + fi + local routerURL="$1" + local credsENC="$2" + local cookieFile="$3" + local curlCode curlResponse + + curlResponse="$(curl -k "${routerURL}/login.cgi" \ + --referer "${routerURL}/Main_Login.asp" \ + --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ + -H 'Accept-Language: en-US,en;q=0.5' \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -H "Origin: ${routerURL}/" \ + -H 'Connection: keep-alive' \ + --data-raw "group_id=&action_mode=&action_script=&action_wait=5¤t_page=Main_Login.asp&next_page=index.asp&login_authorization=$credsENC" \ + --cookie-jar "$cookieFile")" + curlCode="$?" + + echo "$curlResponse" + return "$curlCode" +} + ##----------------------------------------## -## Modified by Martinski W. [2025-Mar-07] ## +## Modified by Martinski W. [2026-Jan-01] ## ##----------------------------------------## _TestLoginCredentials_() { - local credsENC curlResponse routerURLstr + local credsENC routerURL cookieFile curlResponse retCode if [ $# -gt 0 ] && [ -n "$1" ] then credsENC="$1" else credsENC="$(Get_Custom_Setting credentials_base64)" fi - # Define routerURLstr # - routerURLstr="$(_GetRouterURL_)" + routerURL="$(_GetRouterURL_)" + cookieFile="/tmp/MerlinAU_LoginCookie.txt" printf "\nRestarting web server... Please wait.\n" - /sbin/service restart_httpd >/dev/null 2>&1 & - sleep 5 - - curlResponse="$(curl -k "${routerURLstr}/login.cgi" \ - --referer "${routerURLstr}/Main_Login.asp" \ - --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ - -H 'Accept-Language: en-US,en;q=0.5' \ - -H 'Content-Type: application/x-www-form-urlencoded' \ - -H "Origin: ${routerURLstr}/" \ - -H 'Connection: keep-alive' \ - --data-raw "group_id=&action_mode=&action_script=&action_wait=5¤t_page=Main_Login.asp&next_page=index.asp&login_authorization=${credsENC}" \ - --cookie-jar /tmp/cookie.txt)" + /sbin/service restart_httpd >/dev/null 2>&1 + sleep 4 - # Determine login success or failure. This is a basic check # - if echo "$curlResponse" | grep -Eq 'url=index\.asp|url=GameDashboard\.asp' + if curlResponse="$(_DoMainRouterLogin_ "$routerURL" "$credsENC" "$cookieFile")" && \ + echo "$curlResponse" | grep -Eq 'url=index\.asp|url=GameDashboard\.asp' then _UpdateLoginPswdCheckHelper_ SUCCESS printf "\n${GRNct}Router Login test passed.${NOct}" printf "\nRestarting web server... Please wait.\n" /sbin/service restart_httpd >/dev/null 2>&1 & sleep 2 - return 0 + retCode=0 else _UpdateLoginPswdCheckHelper_ FAILURE printf "\n${REDct}**ERROR**${NOct}: Router Login test failed.\n" printf "\n${routerLoginFailureMsg}\n\n" if _WaitForYESorNO_ "Would you like to try again?" - then return 1 # Indicates failure but with intent to retry # - else return 0 # User opted not to retry so just return # + then retCode=1 # Indicates failure but with intent to retry # + else retCode=0 # User chose not to retry so just return # fi fi + + rm -f "$cookieFile" + return "$retCode" } ##----------------------------------------## @@ -4619,13 +4642,14 @@ _GetLoginCredentials_() then _UpdateLoginPswdCheckHelper_ NewPSWD savedMsgStr="${GRNct}New credentials saved.${NOct}" + oldPWSDstring="$thePWSDstring" else _UpdateLoginPswdCheckHelper_ OldPSWD savedMsgStr="${GRNct}Credentials remain unchanged.${NOct}" fi printf "\n${savedMsgStr}\n" printf "Encoded Credentials:\n" - printf "${GRNct}$loginCredsENC${NOct}\n" + printf "${GRNct}${loginCredsENC}${NOct}\n" if _WaitForYESorNO_ "\nWould you like to test the current login credentials?" then @@ -4719,13 +4743,20 @@ _Populate_Node_Settings_() Update_Custom_Settings "${nodeKeyPrefix}New_Notification_Vers" "$update_vers" } +##---------------------------------------## +## Added by ExtremeFiretop [2025-Dec-30] ## +##---------------------------------------## +# Make an ID safe for filenames # +_MeshSafeID_() +{ printf "%s" "$1" | tr '.:' '__' ; } + ##---------------------------------------## ## Added by ExtremeFiretop [2024-Mar-26] ## ##---------------------------------------## _GetNodeURL_() { - local NodeIP_Address="$1" - local urlProto urlDomain urlPort + local nodeIPv4addr="$1" + local urlProto urlDomain urlPort if [ "$(nvram get http_enable)" = "1" ]; then urlProto="https" @@ -4740,38 +4771,128 @@ _GetNodeURL_() urlPort=":$urlPort" fi - echo "${urlProto}://${NodeIP_Address}${urlPort}" + echo "${urlProto}://${nodeIPv4addr}${urlPort}" } -##------------------------------------------## -## Modified by ExtremeFiretop [2025-Jun-08] ## -##------------------------------------------## +##-------------------------------------## +## Added by Martinski W. [2026-Jan-01] ## +##-------------------------------------## +_DoMeshNodeLogin_() +{ + if [ $# -lt 3 ] || [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] + then + return 1 + fi + local nodeURL="$1" + local credsENC="$2" + local cookieFile="$3" + + curl -s -k "${nodeURL}/login.cgi" \ + --referer "${nodeURL}/Main_Login.asp" \ + --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ + -H 'Accept-Language: en-US,en;q=0.5' \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -H "Origin: ${nodeURL}" \ + -H 'Connection: keep-alive' \ + --data-raw "group_id=&action_mode=&action_script=&action_wait=5¤t_page=Main_Login.asp&next_page=index.asp&login_authorization=$credsENC" \ + --cookie-jar "$cookieFile" \ + --max-time 3 >/dev/null 2>&1 + + return "$?" +} + +##----------------------------------------## +## Modified by Martinski W. [2026-Jan-01] ## +##----------------------------------------## +# Trigger the node "Check for updates" (no waiting here) +_MeshNodeTriggerFWCheck_() +{ + if [ $# -lt 2 ] || [ -z "$1" ] || [ -z "$2" ] + then echo "**ERROR** **NO_PARAMS**" ; return 1 + fi + + local nodeIPv4addr="$1" runID="$2" + local safeID="$(_MeshSafeID_ "$nodeIPv4addr")" + local nodeURL="$(_GetNodeURL_ "$nodeIPv4addr")" + local cookieFile="/tmp/${runID}.${safeID}.cookie" + + # Check for Login Credentials # + credsENC="$(Get_Custom_Setting credentials_base64)" + if [ -z "$credsENC" ] || [ "$credsENC" = "TBD" ] + then + _UpdateLoginPswdCheckHelper_ InitPWD + Say "${REDct}**ERROR**${NOct}: No login credentials have been saved. Use the Main Menu to save login credentials." + "$inMenuMode" && _WaitForEnterKey_ "$mainMenuReturnPromptStr" + return 1 + fi + + if _DoMeshNodeLogin_ "$nodeURL" "$credsENC" "$cookieFile" + then + Say "${GRNct}Successful Login for AiMesh Node [$nodeIPv4addr].${NOct}" + else + rm -f "$cookieFile" + Say "${REDct}Failed Login for AiMesh Node [$nodeIPv4addr].${NOct}" + return 1 + fi + + # Trigger firmware check (mimic WebUI "Check" button) # + curl -s -k "${nodeURL}/start_apply.htm" \ + --referer "${nodeURL}/Advanced_FirmwareUpgrade_Content.asp" \ + --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ + -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \ + -H 'Accept-Language: en-US,en;q=0.5' \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -H "Origin: ${nodeURL}" \ + -H 'Connection: keep-alive' \ + --data "current_page=Advanced_FirmwareUpgrade_Content.asp" \ + --data "next_page=Advanced_FirmwareUpgrade_Content.asp" \ + --data "flag=liveUpdate" \ + --data "action_mode=apply" \ + --data "action_script=start_webs_update" \ + --data "action_wait=webs_update_trigger" \ + --cookie "$cookieFile" \ + --max-time 3 >/dev/null 2>&1 + + return "$?" +} + +##----------------------------------------## +## Modified by Martinski W. [2026-Jan-01] ## +##----------------------------------------## _GetNodeInfo_() { - local NodeIP_Address="$1" - local NodeURLstr="$(_GetNodeURL_ "$NodeIP_Address")" + if [ $# -lt 2 ] || [ -z "$1" ] || [ -z "$2" ] + then echo "**ERROR** **NO_PARAMS**" ; return 1 + fi + + local curlCode htmlContent + local nodeIPv4addr="$1" runID="$2" + local safeID="$(_MeshSafeID_ "$nodeIPv4addr")" + local nodeURL="$(_GetNodeURL_ "$nodeIPv4addr")" + local cookieFile="/tmp/${runID}.${safeID}.cookie" + local varsFile="/tmp/${runID}.${safeID}.vars" + + # Shell-safe single-quote wrapper for writing vars files # + _sh_quote_() + { # prints a single-quoted literal that can be safely "sourced" # + printf "'%s'" "$(printf "%s" "$1" | sed "s/'/'\\\\''/g")" + } - ## Default values for specific variables + # Default values for specific variables # node_productid="Unreachable" Node_combinedVer="Unreachable" - node_asus_device_list="" - node_cfg_device_list="" node_firmver="Unreachable" node_buildno="Unreachable" node_extendno="Unreachable" node_webs_state_flag="" node_webs_state_info="" - node_odmpid="Unreachable" - node_wps_modelnum="Unreachable" - node_model="Unreachable" - node_build_name="Unreachable" node_lan_hostname="Unreachable" node_label_mac="Unreachable" NodeGNUtonFW=false - ## Check for Login Credentials ## - credsBase64="$(Get_Custom_Setting credentials_base64)" - if [ -z "$credsBase64" ] || [ "$credsBase64" = "TBD" ] + # Check for Login Credentials # + credsENC="$(Get_Custom_Setting credentials_base64)" + if [ -z "$credsENC" ] || [ "$credsENC" = "TBD" ] then _UpdateLoginPswdCheckHelper_ InitPWD Say "${REDct}**ERROR**${NOct}: No login credentials have been saved. Use the Main Menu to save login credentials." @@ -4779,55 +4900,44 @@ _GetNodeInfo_() return 1 fi - # Perform login request # - curl -s -k "${NodeURLstr}/login.cgi" \ - --referer "${NodeURLstr}/Main_Login.asp" \ - --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ - -H 'Accept-Language: en-US,en;q=0.5' \ - -H 'Content-Type: application/x-www-form-urlencoded' \ - -H "Origin: ${NodeURLstr}" \ - -H 'Connection: keep-alive' \ - --data-raw "group_id=&action_mode=&action_script=&action_wait=5¤t_page=Main_Login.asp&next_page=index.asp&login_authorization=$credsBase64" \ - --cookie-jar '/tmp/nodecookies.txt' \ - --max-time 2 > /tmp/login_response.txt 2>&1 - - if [ $? -ne 0 ] + # If already created a cookie, reuse it (skip login), else perform login request # + if [ ! -s "$cookieFile" ] && \ + ! _DoMeshNodeLogin_ "$nodeURL" "$credsENC" "$cookieFile" then - printf "\n${REDct}Login failed for AiMesh Node [$NodeIP_Address].${NOct}\n" + rm -f "$cookieFile" + Say "${REDct}Failed Login for AiMesh Node [$nodeIPv4addr].${NOct}" return 1 fi - # Retrieve the HTML content # - htmlContent="$(curl -s -k "${NodeURLstr}/appGet.cgi?hook=nvram_get(productid)%3bnvram_get(asus_device_list)%3bnvram_get(cfg_device_list)%3bnvram_get(firmver)%3bnvram_get(buildno)%3bnvram_get(extendno)%3bnvram_get(webs_state_flag)%3bnvram_get(odmpid)%3bnvram_get(wps_modelnum)%3bnvram_get(model)%3bnvram_get(build_name)%3bnvram_get(lan_hostname)%3bnvram_get(webs_state_info)%3bnvram_get(label_mac)" \ + # Retrieve Info # + htmlContent="$(curl -s -k "${nodeURL}/appGet.cgi?hook=nvram_get(productid)%3bnvram_get(firmver)%3bnvram_get(buildno)%3bnvram_get(extendno)%3bnvram_get(webs_state_flag)%3bnvram_get(lan_hostname)%3bnvram_get(webs_state_info)%3bnvram_get(label_mac)" \ -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' \ -H 'Accept-Language: en-US,en;q=0.5' \ -H 'Accept-Encoding: gzip, deflate' \ -H 'Connection: keep-alive' \ - -H "Referer: ${NodeURLstr}/index.asp" \ + -H "Referer: ${nodeURL}/index.asp" \ -H 'Upgrade-Insecure-Requests: 0' \ - --cookie '/tmp/nodecookies.txt' \ - --max-time 2 2>&1)" + --cookie "$cookieFile" \ + --max-time 2 2>/dev/null)" + curlCode="$?" - if [ $? -ne 0 ] || [ -z "$htmlContent" ] + if [ "$curlCode" -ne 0 ] || [ -z "$htmlContent" ] then - printf "\n${REDct}Failed to get information for AiMesh Node [$NodeIP_Address].${NOct}\n" + # Logout best-effort # + curl -s -k "${nodeURL}/Logout.asp" --cookie "$cookieFile" --max-time 2 >/dev/null 2>&1 + printf "\n${REDct}Failed to get information for AiMesh Node [$nodeIPv4addr].${NOct}\n" + rm -f "$cookieFile" return 1 fi - # Extract values using regular expressions # + # Extract values # node_productid="$(echo "$htmlContent" | grep -o '"productid":"[^"]*' | sed 's/"productid":"//')" - node_asus_device_list="$(echo "$htmlContent" | grep -o '"asus_device_list":"[^"]*' | sed 's/"asus_device_list":"//')" - node_cfg_device_list="$(echo "$htmlContent" | grep -o '"cfg_device_list":"[^"]*' | sed 's/"cfg_device_list":"//')" node_firmver="$(echo "$htmlContent" | grep -o '"firmver":"[^"]*' | sed 's/"firmver":"//' | tr -d '.')" node_buildno="$(echo "$htmlContent" | grep -o '"buildno":"[^"]*' | sed 's/"buildno":"//')" node_extendno="$(echo "$htmlContent" | grep -o '"extendno":"[^"]*' | sed 's/"extendno":"//')" node_webs_state_flag="$(echo "$htmlContent" | grep -o '"webs_state_flag":"[^"]*' | sed 's/"webs_state_flag":"//')" node_webs_state_info="$(echo "$htmlContent" | grep -o '"webs_state_info":"[^"]*' | sed 's/"webs_state_info":"//')" - node_odmpid="$(echo "$htmlContent" | grep -o '"odmpid":"[^"]*' | sed 's/"odmpid":"//')" - node_wps_modelnum="$(echo "$htmlContent" | grep -o '"wps_modelnum":"[^"]*' | sed 's/"wps_modelnum":"//')" - node_model="$(echo "$htmlContent" | grep -o '"model":"[^"]*' | sed 's/"model":"//')" - node_build_name="$(echo "$htmlContent" | grep -o '"build_name":"[^"]*' | sed 's/"build_name":"//')" node_lan_hostname="$(echo "$htmlContent" | grep -o '"lan_hostname":"[^"]*' | sed 's/"lan_hostname":"//')" node_label_mac="$(echo "$htmlContent" | grep -o '"label_mac":"[^"]*' | sed 's/"label_mac":"//')" @@ -4837,50 +4947,65 @@ _GetNodeInfo_() # Combine extracted information into one string # Node_combinedVer="${node_firmver}.${node_buildno}.$node_extendno" - # Perform logout request # - curl -s -k "${NodeURLstr}/Logout.asp" \ + # Logout request # + curl -s -k "${nodeURL}/Logout.asp" \ -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' \ -H 'Accept-Language: en-US,en;q=0.5' \ -H 'Accept-Encoding: gzip, deflate' \ -H 'Connection: keep-alive' \ - -H "Referer: ${NodeURLstr}/Main_Login.asp" \ + -H "Referer: ${nodeURL}/Main_Login.asp" \ -H 'Upgrade-Insecure-Requests: 0' \ - --cookie '/tmp/nodecookies.txt' \ - --max-time 2 > /tmp/logout_response.txt 2>&1 + --cookie "$cookieFile" \ + --max-time 2 >/dev/null 2>&1 + curlCode="$?" - if [ $? -ne 0 ]; then - return 1 - fi + # Write a vars file the parent shell can source safely # + { + printf "node_productid=%s\n" "$(_sh_quote_ "$node_productid")" + printf "node_firmver=%s\n" "$(_sh_quote_ "$node_firmver")" + printf "node_buildno=%s\n" "$(_sh_quote_ "$node_buildno")" + printf "node_extendno=%s\n" "$(_sh_quote_ "$node_extendno")" + printf "node_webs_state_flag=%s\n" "$(_sh_quote_ "$node_webs_state_flag")" + printf "node_webs_state_info=%s\n" "$(_sh_quote_ "$node_webs_state_info")" + printf "node_lan_hostname=%s\n" "$(_sh_quote_ "$node_lan_hostname")" + printf "node_label_mac=%s\n" "$(_sh_quote_ "$node_label_mac")" + printf "Node_combinedVer=%s\n" "$(_sh_quote_ "$Node_combinedVer")" + printf "NodeGNUtonFW=%s\n" "$NodeGNUtonFW" + } > "$varsFile" + + rm -f "$cookieFile" + return "$curlCode" } -##----------------------------------------## -## Modified by Martinski W. [2024-Apr-06] ## -##----------------------------------------## +##------------------------------------------## +## Modified by ExtremeFiretop [2025-Dec-30] ## +##------------------------------------------## _GetLatestFWUpdateVersionFromNode_() { - local retCode=0 webState newVersionStr + local retCode=0 webState newVersionStr - if [ -z "${node_webs_state_flag:+xSETx}" ] - then webState="" - else webState="$node_webs_state_flag" - fi - if [ -z "$webState" ] || [ "$webState" -eq 0 ] - then retCode=1 ; fi + webState="${node_webs_state_flag:-}" - if [ -z "${node_webs_state_info:+xSETx}" ] - then - newVersionStr="" - else - newVersionStr="$(echo "$node_webs_state_info" | sed 's/_/./g')" - if [ $# -eq 0 ] || [ -z "$1" ] - then - newVersionStr="$(echo "$newVersionStr" | awk -F '-' '{print $1}')" - fi - fi + # Treat missing/non-numeric/zero as "no update info" + case "$webState" in + ""|*[!0-9]*|0) retCode=1 ;; + esac - [ -z "$newVersionStr" ] && retCode=1 - echo "$newVersionStr" ; return "$retCode" + if [ -n "${node_webs_state_info:-}" ] + then + newVersionStr="$(printf "%s" "$node_webs_state_info" | sed 's/_/./g')" + if [ -z "$1" ] + then + newVersionStr="$(printf "%s" "$newVersionStr" | awk -F '-' '{print $1}')" + fi + else + newVersionStr="" + fi + + [ -z "$newVersionStr" ] && retCode=1 + echo "$newVersionStr" + return "$retCode" } ##----------------------------------------## @@ -8460,7 +8585,7 @@ _SelectOfflineUpdateFile_() return 1 fi - # Confirm the selection + # Confirm selection # if _WaitForYESorNO_ "\nDo you want to continue with the selected file?" then printf "\n---------------------------------------------------\n" @@ -8857,7 +8982,7 @@ _Unmount_Eject_USB_Drives_() } ##----------------------------------------## -## Modified by Martinski W. [2025-Nov-09] ## +## Modified by Martinski W. [2026-Jan-01] ## ##----------------------------------------## _RunFirmwareUpdateNow_() { @@ -8932,10 +9057,10 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or if ! node_online_status="$(_NodeActiveStatus_)" then node_online_status="" - else _ProcessMeshNodes_ 0 + else _ProcessMeshNodes_ false fi - local retCode credsBase64="" + local retCode credsENC="" local currentVersionNum="" releaseVersionNum="" local current_version="" @@ -9042,8 +9167,8 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or dottedVersion="$(echo "$fwUpdateBaseNum" | sed 's/./&./g' | sed 's/.$//')" ## Check for Login Credentials ## - credsBase64="$(Get_Custom_Setting credentials_base64)" - if [ -z "$credsBase64" ] || [ "$credsBase64" = "TBD" ] + credsENC="$(Get_Custom_Setting credentials_base64)" + if [ -z "$credsENC" ] || [ "$credsENC" = "TBD" ] then _UpdateLoginPswdCheckHelper_ InitPWD Say "${REDct}**ERROR**${NOct}: No login credentials have been saved. Use the Main Menu to save login credentials." @@ -9091,7 +9216,6 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or Say "Please fix the BACKUPMON configuration, or consider uninstalling it to proceed flash.\n" Say "Resolving the BACKUPMON configuration is HIGHLY recommended for safety of the upgrade.\n" "$inMenuMode" && _WaitForEnterKey_ "$mainMenuReturnPromptStr" - _Reset_LEDs_ return 1 fi @@ -9304,8 +9428,9 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or Say "Required RAM: ${requiredRAM_kb} KB - RAM Free: ${freeRAM_kb} KB - RAM Available: ${availableRAM_kb} KB" check_memory_and_prompt_reboot "$requiredRAM_kb" "$availableRAM_kb" - routerURLstr="$(_GetRouterURL_)" - Say "Router Web URL is: ${routerURLstr}" + routerURL="$(_GetRouterURL_)" + Say "Router Web URL is: ${routerURL}" + cookieFile="/tmp/FW_UpgradeCookie.txt" if "$isInteractive" then @@ -9326,7 +9451,7 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or #------------------------------------------------------------# "$isInteractive" && printf "\nRestarting web server... Please wait.\n" /sbin/service restart_httpd >/dev/null 2>&1 & - sleep 5 + sleep 4 # Send last email notification before F/W flash # _SendEMailNotification_ START_FW_UPDATE_STATUS @@ -9361,20 +9486,11 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or requiredDIVER_version="$(_ScriptVersionStrToNum_ "5.2.0")" fi - ##------------------------------------------## - ## Modified by ExtremeFiretop [2024-Sep-07] ## - ##------------------------------------------## - curl_response="$(curl -k "${routerURLstr}/login.cgi" \ - --referer "${routerURLstr}/Main_Login.asp" \ - --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ - -H 'Accept-Language: en-US,en;q=0.5' \ - -H 'Content-Type: application/x-www-form-urlencoded' \ - -H "Origin: ${routerURLstr}/" \ - -H 'Connection: keep-alive' \ - --data-raw "group_id=&action_mode=&action_script=&action_wait=5¤t_page=Main_Login.asp&next_page=index.asp&login_authorization=${credsBase64}" \ - --cookie-jar /tmp/cookie.txt)" - - if echo "$curl_response" | grep -Eq 'url=index\.asp|url=GameDashboard\.asp' + ##----------------------------------------## + ## Modified by Martinski W. [2026-Jan-01] ## + ##----------------------------------------## + if curlResponse="$(_DoMainRouterLogin_ "$routerURL" "$credsENC" "$cookieFile")" && \ + echo "$curlResponse" | grep -Eq 'url=index\.asp|url=GameDashboard\.asp' then _UpdateLoginPswdCheckHelper_ SUCCESS @@ -9437,11 +9553,11 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or # the following 'curl' command MUST always be the last step in this block. # Do NOT insert any commands after it! (unless you understand the implications). #----------------------------------------------------------------------------------# - nohup curl -k "${routerURLstr}/upgrade.cgi" \ - --referer "${routerURLstr}/Advanced_FirmwareUpgrade_Content.asp" \ + nohup curl -k "${routerURL}/upgrade.cgi" \ + --referer "${routerURL}/Advanced_FirmwareUpgrade_Content.asp" \ --user-agent 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ -H 'Accept-Language: en-US,en;q=0.5' \ - -H "Origin: ${routerURLstr}/" \ + -H "Origin: ${routerURL}/" \ -F 'current_page=Advanced_FirmwareUpgrade_Content.asp' \ -F 'next_page=' \ -F 'action_mode=' \ @@ -9450,7 +9566,7 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or -F 'preferred_lang=EN' \ -F "firmver=${dottedVersion}" \ -F "file=@${firmware_file}" \ - --cookie /tmp/cookie.txt > /tmp/upload_response.txt 2>&1 & + --cookie "$cookieFile" > /tmp/upload_response.txt 2>&1 & curlPID=$! #----------------------------------------------------------# @@ -9486,6 +9602,7 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or printf "\n${routerLoginFailureMsg}\n\n" _WaitForEnterKey_ fi + rm -f "$cookieFile" _SendEMailNotification_ FAILED_FW_UPDATE_STATUS _DoCleanUp_ 1 "$keepZIPfile" "$keepWfile" _EntwareServicesHandler_ start @@ -9617,7 +9734,7 @@ _DelFWAutoUpdateHook_() if grep -qE "$CRON_SCRIPT_JOB" "$hookScriptFile" then - sed -i -e '/\/'"$ScriptFileName"' addCronJob & '"$hookScriptTagStr"'/d' "$hookScriptFile" + sed -i -e '/\/'"$ScriptFileName"' addCronJob & /d' "$hookScriptFile" if [ $? -eq 0 ] then Say "F/W Update cron job hook was deleted successfully from '$hookScriptFile' script." @@ -9701,7 +9818,7 @@ _DelScriptAutoUpdateHook_() if grep -qE "$DAILY_SCRIPT_UPDATE_CHECK_JOB" "$hookScriptFile" then - sed -i -e '/\/'"$ScriptFileName"' scriptAUCronJob & '"$hookScriptTagStr"'/d' "$hookScriptFile" + sed -i -e '/\/'"$ScriptFileName"' scriptAUCronJob & /d' "$hookScriptFile" if [ $? -eq 0 ] then Say "ScriptAU cron job hook was deleted successfully from '$hookScriptFile' script." @@ -10087,14 +10204,17 @@ _ValidatePrivateIPv4Address_() fi } -##------------------------------------------## -## Modified by ExtremeFiretop [2024-Apr-30] ## -##------------------------------------------## +##----------------------------------------## +## Modified by Martinski W. [2026-Jan-01] ## +##----------------------------------------## _ProcessMeshNodes_() { - includeExtraLogic="$1" # Use '1' to include extra logic, '0' to exclude if [ $# -eq 0 ] || [ -z "$1" ] - then echo "**ERROR** **NO_PARAMS**" ; return 1 ; fi + then echo "**ERROR** **NO_PARAMS**" ; return 1 + fi + # 'true' to include extra logic, 'false' to exclude # + local includeExtraLogic="$1" + local runID if "$aiMeshNodes_OK" then @@ -10104,11 +10224,65 @@ _ProcessMeshNodes_() if [ -n "$node_list" ] then - # Iterate over the list of nodes and print information for each node + # Unique run ID for temp files # + runID="mesh_$$.$(date +%s)" + + # ---- Trigger F/W check on all nodes in parallel ---- # + for nodeIPv4addr in $node_list + do + _ValidatePrivateIPv4Address_ "$nodeIPv4addr" || continue + _MeshNodeTriggerFWCheck_ "$nodeIPv4addr" "$runID" >/dev/null 2>&1 & + done + wait + + # ---- Single wait ---- # + local waitSeconds="${meshUpdate_WaitSecs:-8}" + if "$includeExtraLogic" + then + local waitMsg="Please wait while we query the node(s) for status..." + echo + local idx=0 + while [ "$idx" -lt "$waitSeconds" ] + do + printf "\r%s (%ds)" "$waitMsg" "$((waitSeconds - idx))" + sleep 1 + idx="$((idx + 1))" + done + printf "\r%s Done. " "$waitMsg" + else + sleep "$waitSeconds" + fi + + # ---- Fetch info from all nodes in parallel ---- # + for nodeIPv4addr in $node_list + do + _ValidatePrivateIPv4Address_ "$nodeIPv4addr" || continue + _GetNodeInfo_ "$nodeIPv4addr" "$runID" >/dev/null 2>&1 & + done + wait + + # ---- Read each node's vars ---- # for nodeIPv4addr in $node_list do - ! _ValidatePrivateIPv4Address_ "$nodeIPv4addr" && continue - _GetNodeInfo_ "$nodeIPv4addr" + _ValidatePrivateIPv4Address_ "$nodeIPv4addr" || continue + + local safeID="$(_MeshSafeID_ "$nodeIPv4addr")" + local varsFile="/tmp/${runID}.${safeID}.vars" + + # Load per-node globals (node_*, Node_combinedVer, NodeGNUtonFW) # + if [ -s "$varsFile" ] + then + . "$varsFile" + else + # keep defaults consistent # + node_productid="Unreachable" + Node_combinedVer="Unreachable" + node_extendno="Unreachable" + node_webs_state_flag="" + node_webs_state_info="" + NodeGNUtonFW=false + fi + if ! Node_FW_NewUpdateVersion="$(_GetLatestFWUpdateVersionFromNode_ 1)" then Node_FW_NewUpdateVersion="NONE FOUND" @@ -10116,17 +10290,22 @@ _ProcessMeshNodes_() _CheckNodeFWUpdateNotification_ "$Node_combinedVer" "$Node_FW_NewUpdateVersion" fi - # Apply extra logic if flag is '1' - if [ "$includeExtraLogic" -eq 1 ]; then + if "$includeExtraLogic" + then _PrintNodeInfo "$nodeIPv4addr" "$node_online_status" "$Node_FW_NewUpdateVersion" "$uid" uid="$((uid + 1))" fi done - if [ -s "$tempNodeEMailList" ]; then + + if [ -s "$tempNodeEMailList" ] + then _SendEMailNotification_ AGGREGATED_UPDATE_NOTIFICATION fi + + rm -f "/tmp/${runID}."*.vars 2>/dev/null else - if [ "$includeExtraLogic" -eq 1 ]; then + if "$includeExtraLogic" + then printf "\n${padStr}${padStr}${padStr}${REDct}No AiMesh Node(s)${NOct}" fi fi @@ -10971,7 +11150,8 @@ _ShowNodesMenu_() printf "${SEPstr}\n" if ! node_online_status="$(_NodeActiveStatus_)" - then node_online_status="" ; fi + then node_online_status="" + fi # Count the number of IP addresses local numIPs="$(echo "$node_list" | wc -w)" @@ -10979,9 +11159,8 @@ _ShowNodesMenu_() # Print the result printf "\n${padStr}${padStr}${padStr}${GRNct} AiMesh Node(s): ${numIPs}${NOct}" - _ProcessMeshNodes_ 1 - - echo "" + _ProcessMeshNodes_ true + echo printf "\n ${GRNct}e${NOct}. Return to Main Menu\n" printf "${SEPstr}" @@ -11405,7 +11584,7 @@ then _ReleaseLock_ cliFileLock fi ;; - processNodes) _ProcessMeshNodes_ 0 + processNodes) _ProcessMeshNodes_ false ;; addCronJob) _AddFWAutoUpdateCronJob_ ;; diff --git a/README.md b/README.md index 5236d0e9..a2f11ee1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MerlinAU - AsusWRT-Merlin Firmware Auto Updater -## v1.5.7 -## 2025-Nov-25 +## v1.5.8 +## 2026-Jan-02 ## WebUI: ![image](https://github.com/user-attachments/assets/9c1dff99-9c13-491b-a7fa-aff924d5f02e) diff --git a/version.txt b/version.txt index f01291b8..1cc9c180 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.7 +1.5.8