From ee06d2ab9f162debaed99df1d8f57c7d45ba32ad Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Wed, 16 Sep 2026 01:40:56 -0400 Subject: [PATCH 1/3] Prevent mesh firmware checks from interrupting node updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a new bug identified and introduced by commit: https://github.com/ExtremeFiretop/MerlinAutoUpdate-Router/commit/bae364025176113bcedff624fc665087954a7f8a And PR: https://github.com/ExtremeFiretop/MerlinAutoUpdate-Router/pull/539 This bug appears to be a concurrent firmware management collision, essentially a race while flashing a node while checking it for updates from the primary at the same time. I accidently ran into this, the seems to be that the node had prepared for the update, authenticated to its own WebUI, and was just about to hand the beta1 image to /upgrade.cgi When at that exact time, I started an upgrade from the primary, which logged into the node to check for updates, triggered start_webs_update, and resulted in putting the node’s ASUS firmware update system into a competing update/check state and ultimately MerlinAU rebooted the node without flashing any firmware. This may also be related to a report from JimbobJay here: https://www.snbforums.com/threads/merlinau-v1-6-8-the-ultimate-firmware-auto-updater.96306/post-999444 --- MerlinAU.sh | 103 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 17 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 1d3eaabb..ffdc1ce3 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -19,11 +19,11 @@ set -u ## Set version for each Production Release ## -readonly SCRIPT_VERSION=1.6.8 -readonly SCRIPT_VERSTAG="26090718" +readonly SCRIPT_VERSION=1.6.9 +readonly SCRIPT_VERSTAG="26091601" readonly SCRIPT_NAME="MerlinAU" ## Set to "master" for Production Releases ## -SCRIPT_BRANCH="master" +SCRIPT_BRANCH="dev" ##----------------------------------------## ## Modified by Martinski W. [2024-Jul-03] ## @@ -4335,7 +4335,7 @@ _ReEnableAsusTrendMicroProcesses_() } ##------------------------------------------## -## Modified by ExtremeFiretop [2024-Jan-26] ## +## Modified by ExtremeFiretop [2025-Sep-16] ## ##------------------------------------------## _DoCleanUp_() { @@ -4353,6 +4353,10 @@ _DoCleanUp_() [ $# -gt 1 ] && [ "$2" -eq 1 ] && keepZIPfile=true [ $# -gt 2 ] && [ "$3" -eq 1 ] && keepWfile=true + # Clear the volatile F/W-update guard used by AiMesh primaries. # + # This value is intentionally never committed to NVRAM. # + nvram unset merlinau_fw_update 2>/dev/null + # Stop the LEDs blinking # _Reset_LEDs_ 1 @@ -5429,9 +5433,9 @@ _DoMeshNodeLogin_() return "$?" } -##----------------------------------------## -## Modified by Martinski W. [2026-Jan-01] ## -##----------------------------------------## +##------------------------------------------## +## Modified by ExtremeFiretop [2026-Sep-16] ## +##------------------------------------------## # Trigger the node "Check for updates" (no waiting here) _MeshNodeTriggerFWCheck_() { @@ -5443,6 +5447,7 @@ _MeshNodeTriggerFWCheck_() local safeID="$(_MeshSafeID_ "$nodeIPv4addr")" local nodeURL="$(_GetNodeURL_ "$nodeIPv4addr")" local cookieFile="/tmp/${runID}.${safeID}.cookie" + local nodeBusy nodeBusyRC # Check for Login Credentials # credsENC="$(Get_Custom_Setting credentials_base64)" @@ -5463,6 +5468,30 @@ _MeshNodeTriggerFWCheck_() return 1 fi + # Check if the AiMesh node is already performing a MerlinAU F/W update + # before triggering the built-in firmware update check. + nodeBusy="$(curl -s -k "${nodeURL}/appGet.cgi?hook=nvram_get(merlinau_fw_update)" \ + -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' \ + -H 'Accept: application/json,text/plain,*/*' \ + -H 'Accept-Language: en-US,en;q=0.5' \ + -H 'Connection: keep-alive' \ + -H "Referer: ${nodeURL}/index.asp" \ + --cookie "$cookieFile" \ + --max-time 2 2>/dev/null)" + nodeBusyRC="$?" + + if [ "$nodeBusyRC" -eq 0 ] && echo "$nodeBusy" | grep -Eq '"merlinau_fw_update"[[:space:]]*:[[:space:]]*"1"' + then + Say "AiMesh Node [$nodeIPv4addr] entered an active MerlinAU F/W update before start_webs_update. Skipping firmware check." + + # Best-effort logout of this primary-router session, then remove its cookie. + curl -s -k "${nodeURL}/Logout.asp" \ + --cookie "$cookieFile" \ + --max-time 2 >/dev/null 2>&1 + rm -f "$cookieFile" + return 0 + fi + # Trigger firmware check (mimic WebUI "Check" button) # curl -s -k "${nodeURL}/start_apply.htm" \ --referer "${nodeURL}/Advanced_FirmwareUpgrade_Content.asp" \ @@ -9755,11 +9784,15 @@ _Unmount_Eject_USB_Drives_() "$ejectUSB_OK" && return 0 || return 1 } -##----------------------------------------## -## Modified by Martinski W. [2026-Jan-01] ## -##----------------------------------------## +##------------------------------------------## +## Modified by ExtremeFiretop [2026-Sep-16] ## +##------------------------------------------## _RunFirmwareUpdateNow_() { + local fwUploadResponseFile="/tmp/upload_response.txt" + local fwUploadDiagFile="${SETTINGS_DIR}/last_fw_upload_response.txt" + local curlRC=0 uploadHTTPcode="" + # Double-check the directory exists before using it # [ ! -d "$FW_LOG_DIR" ] && mkdir -p -m 755 "$FW_LOG_DIR" @@ -10219,6 +10252,15 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or fi fi + #------------------------------------------------------------------------# + # A volatile guard before restarting/logging into the WebGUI. + # Primary routers running MerlinAU can query this nvram value with appGet.cgi + # and avoid triggering start_webs_update on this router mid-flash. + # Do not commit this value since a reboot should clear it automatically. + #------------------------------------------------------------------------# + nvram set merlinau_fw_update=1 + rm -f "$fwUploadResponseFile" "$fwUploadDiagFile" + #------------------------------------------------------------# # Restart the WebGUI to make sure nobody else is logged in # so that the F/W Update can start without interruptions. @@ -10311,7 +10353,8 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or Say "Flashing ${GRNct}${firmware_file}${NOct}...\n${REDct}Please wait for reboot in about 4 minutes or less.${NOct}" echo - # *WARNING*: NO MORE logging at this point & beyond # + # Avoid persistent logging from this point during the normal flash path. # + # Failure diagnostics are written only if the router does not reboot. # sync ; sleep 2 ; echo 3 > /proc/sys/vm/drop_caches ; sleep 3 ##-------------------------------------## @@ -10327,7 +10370,7 @@ 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 "${routerURL}/upgrade.cgi" \ + nohup curl -sS -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' \ @@ -10340,7 +10383,9 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or -F 'preferred_lang=EN' \ -F "firmver=${dottedVersion}" \ -F "file=@${firmware_file}" \ - --cookie "$cookieFile" > /tmp/upload_response.txt 2>&1 & + --cookie "$cookieFile" \ + --write-out '\nMERLINAU_HTTP_CODE:%{http_code}\n' \ + > "$fwUploadResponseFile" 2>&1 & curlPID=$! #----------------------------------------------------------# @@ -10356,16 +10401,40 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or sleep 180 if [ "$curlPID" -gt 0 ] then - kill -EXIT $curlPID 2>/dev/null || return - kill -TERM $curlPID 2>/dev/null + kill -EXIT "$curlPID" 2>/dev/null || return + kill -TERM "$curlPID" 2>/dev/null fi ) & - wait $curlPID ; curlPID=0 + + # Preserve Curl's actual result instead of discarding it. # + wait "$curlPID" + curlRC=$? + curlPID=0 + uploadHTTPcode="$(sed -n 's/^MERLINAU_HTTP_CODE://p' "$fwUploadResponseFile" 2>/dev/null | tail -n 1)" + #----------------------------------------------------------# # Let's wait for 3 minutes here. If the router does not - # reboot by itself after the process returns, do it now. + # reboot by itself after the process returns, + # preserve any diagnostics then reboot. + # A successful flash reboots before this step. #----------------------------------------------------------# sleep 180 + + { + echo "MerlinAU v$SCRIPT_VERSION firmware upload diagnostics" + echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S %Z')" + echo "Router: $MODEL_ID" + echo "Firmware image: $firmware_file" + echo "Curl exit code: $curlRC" + echo "HTTP status: ${uploadHTTPcode:-UNKNOWN}" + echo "------------------------------------------------------------" + [ -s "$fwUploadResponseFile" ] && cat "$fwUploadResponseFile" + } > "$fwUploadDiagFile" 2>/dev/null + chmod 600 "$fwUploadDiagFile" 2>/dev/null + + _MsgToSysLog_ "F/W upload did not cause the router to reboot within 180 seconds. Curl exit code [$curlRC], HTTP status [${uploadHTTPcode:-UNKNOWN}]." + _MsgToSysLog_ "F/W upload diagnostics saved to [$fwUploadDiagFile]." + _ReleaseLock_ /sbin/service reboot else From 6212bf4a7f3fbefb6a58ae9709ba21833638c529 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Wed, 16 Sep 2026 02:24:14 -0400 Subject: [PATCH 2/3] Stop logging out early, allow _GetNodeInfo_ to logout Stop logging out early, allow _GetNodeInfo_ to logout --- MerlinAU.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index ffdc1ce3..22ba8786 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -5483,12 +5483,6 @@ _MeshNodeTriggerFWCheck_() if [ "$nodeBusyRC" -eq 0 ] && echo "$nodeBusy" | grep -Eq '"merlinau_fw_update"[[:space:]]*:[[:space:]]*"1"' then Say "AiMesh Node [$nodeIPv4addr] entered an active MerlinAU F/W update before start_webs_update. Skipping firmware check." - - # Best-effort logout of this primary-router session, then remove its cookie. - curl -s -k "${nodeURL}/Logout.asp" \ - --cookie "$cookieFile" \ - --max-time 2 >/dev/null 2>&1 - rm -f "$cookieFile" return 0 fi From c2c2e463fd68ba6857525a4686b0eb783b097960 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Wed, 16 Sep 2026 02:56:36 -0400 Subject: [PATCH 3/3] Tightening up the Flash Order Tightening up the Flash Order Adjusting the flash order so we delete cron jobs before unloading Entware. (Incase a cron tries to fire for an entware script that is unloaded) This may also be related to a report from JimbobJay here: https://www.snbforums.com/threads/merlinau-v1-6-8-the-ultimate-firmware-auto-updater.96306/post-999444 Also adjusted the WebUI restart to be RIGHT before we login to the router to start the flash. The vulnerable window was the gap between the WebUI restart, unloading the USB, and flashing. Also adjusted the order so we say flashing right before we actually restart the WebUI and flash --- MerlinAU.sh | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 22ba8786..044e7986 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -10255,14 +10255,6 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or nvram set merlinau_fw_update=1 rm -f "$fwUploadResponseFile" "$fwUploadDiagFile" - #------------------------------------------------------------# - # Restart the WebGUI to make sure nobody else is logged in - # so that the F/W Update can start without interruptions. - #------------------------------------------------------------# - "$isInteractive" && printf "\nRestarting web server... Please wait.\n" - /sbin/service restart_httpd >/dev/null 2>&1 & - sleep 4 - # Send last email notification before F/W flash # _SendEMailNotification_ START_FW_UPDATE_STATUS @@ -10333,19 +10325,16 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or # Remove SIGHUP to allow script to continue # trap '' HUP - # Stop Entware services WITHOUT exceptions BEFORE the F/W flash # - _EntwareServicesHandler_ stop -noskip - ##-------------------------------------## ## Added by Martinski W. [2024-Sep-15] ## ##-------------------------------------## # Remove cron jobs from 3rd-party Add-Ons # _RemoveCronJobsFromAddOns_ + # Stop Entware services WITHOUT exceptions BEFORE the F/W flash # + _EntwareServicesHandler_ stop -noskip + _Do_PostReboot_FWUpdate_Setup_ - echo - Say "Flashing ${GRNct}${firmware_file}${NOct}...\n${REDct}Please wait for reboot in about 4 minutes or less.${NOct}" - echo # Avoid persistent logging from this point during the normal flash path. # # Failure diagnostics are written only if the router does not reboot. # @@ -10358,6 +10347,18 @@ Please manually update to version ${GRNct}${MinSupportedFirmwareVers}${NOct} or #------------------------------------------------------------------# _Unmount_Eject_USB_Drives_ + echo + Say "Flashing ${GRNct}${firmware_file}${NOct}...\n${REDct}Please wait for reboot in about 4 minutes or less.${NOct}" + echo + + #------------------------------------------------------------# + # Restart the WebGUI to make sure nobody else is logged in + # so that the F/W Update can start without interruptions. + #------------------------------------------------------------# + "$isInteractive" && printf "\nRestarting web server... Please wait.\n" + /sbin/service restart_httpd >/dev/null 2>&1 & + sleep 3 + #----------------------------------------------------------------------------------# # **IMPORTANT NOTE**: # Due to the nature of 'nohup' and the specific behavior of this 'Curl' request,