diff --git a/MerlinAU.asp b/MerlinAU.asp index 84e972e..fed30fd 100644 --- a/MerlinAU.asp +++ b/MerlinAU.asp @@ -68,6 +68,12 @@ var isEMailConfigEnabledInAMTM = false; var scriptAutoUpdateCronSchedHR = 'TBD'; var fwAutoUpdateCheckCronSchedHR = 'TBD'; var isScriptUpdateAvailable = 'TBD'; +var fwUpdateEstimatedRunDate = 'TBD'; +var MinimumScriptFWRequired = 'TBD'; + +let pendingScriptUpdateGateCheck = false; +let scriptUpdateGateTries = 0; +const scriptUpdateGateMaxTries = 12; const validationErrorMsg = 'Validation failed. Please correct invalid value and try again.'; @@ -170,7 +176,7 @@ function FormatNumericSetting (formInput) const numberRegExp = '^[0-9]+$'; const daysOfWeekNumbr = ['0', '1', '2', '3', '4', '5', '6']; const daysOfWeekNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; -const daysOfWeekRexpN = '([S|s]un|[M|m]on|[T|t]ue|[W|w]ed|[T|t]hu|[F|f]ri|[S|s]at)'; +const daysOfWeekRexpN = '([Ss]un|[Mm]on|[Tt]ue|[Ww]ed|[Tt]hu|[Ff]ri|[Ss]at)'; const daysOfWeekRexp1 = `${daysOfWeekRexpN}|[0-6]`; const daysOfWeekRexp2 = `${daysOfWeekRexpN}[-]${daysOfWeekRexpN}|[0-6][-][0-6]`; const daysOfWeekRexp3 = `${daysOfWeekRexpN}([,]${daysOfWeekRexpN})+|[0-6]([,][0-6])+`; @@ -248,6 +254,84 @@ const fwScheduleTime = } }; +function ExtractFWVersion(verStr) +{ + if (!verStr) { return ''; } + let match = String(verStr).trim().match(/\d+(?:\.\d+)+/); + return match ? match[0] : String(verStr).trim(); +} + +// Prefer hidden #firmver (same nvram source, no formatting risk), else fallback to #fwVersionInstalled +function GetInstalledFWVersionFromUI() +{ + let firmverInput = document.getElementById('firmver'); + if (firmverInput && firmverInput.value) + { return ExtractFWVersion(firmverInput.value); } + + let fwCell = document.getElementById('fwVersionInstalled'); + if (fwCell) + { return ExtractFWVersion(fwCell.textContent); } + + return ''; +} + +function RunScriptUpdateFirmwareGateCheck() +{ + if (!pendingScriptUpdateGateCheck) { return; } + + scriptUpdateGateTries++; + + $.ajax({ + url: '/ext/MerlinAU/checkHelper.js?_=' + new Date().getTime(), + dataType: 'script', + timeout: 5000, + + success: function() + { + let requiredStr = ExtractFWVersion(MinimumScriptFWRequired); + let installedStr = GetInstalledFWVersionFromUI(); + + // If required version isn't ready yet, retry a few times + if (!requiredStr || requiredStr === 'TBD') + { + if (scriptUpdateGateTries < scriptUpdateGateMaxTries) + { setTimeout(RunScriptUpdateFirmwareGateCheck, 1000); } + else + { pendingScriptUpdateGateCheck = false; isFormSubmitting = false; } + return; + } + + let installedNum = FWVersionStrToNum(installedStr); + let requiredNum = FWVersionStrToNum(requiredStr); + + pendingScriptUpdateGateCheck = false; + isFormSubmitting = false; + + if (installedNum === 0 || requiredNum === 0) { return; } + + if (installedNum < requiredNum) + { + alert( + "**SCRIPT UPDATE BLOCKED**\n\n" + + "MerlinAU cannot update because your installed firmware is " + + "below the minimum required firmware for this script update.\n\n" + + "Installed firmware: " + installedStr + "\n" + + "Minimum required: " + requiredStr + "\n\n" + + "Please update your router firmware, then try again." + ); + } + }, + + error: function() + { + if (scriptUpdateGateTries < scriptUpdateGateMaxTries) + { setTimeout(RunScriptUpdateFirmwareGateCheck, 1000); } + else + { pendingScriptUpdateGateCheck = false; isFormSubmitting = false; } + } + }); +} + /**-------------------------------------**/ /** Added by Martinski W. [2025-Jan-24] **/ /**-------------------------------------**/ @@ -910,6 +994,10 @@ function ShowLatestChangelog(e) box.scrollTop -= 40; ev.preventDefault(); break; + case 'Escape': + $('#changelogModal').hide(); + ev.preventDefault(); + break; default: break; } @@ -997,7 +1085,7 @@ function ValidateDirectoryPath (formField, dirType) function GetExternalCheckResults() { $.ajax({ - url: '/ext/MerlinAU/checkHelper.js', + url: '/ext/MerlinAU/checkHelper.js?_=' + new Date().getTime(), dataType: 'script', timeout: 5000, error: function(xhr){ @@ -1537,7 +1625,10 @@ function GetLoginPswdCheckStatus() document.getElementById('LoginPswdStatusText').textContent = pswdStatusText; showhide('LoginPswdStatusText',true); - loginPswdHint = loginPswdHint.replace (/PswdSTATUS/, pswdStatusHint1); + // Rebuild the base hint fresh EACH time (so PswdSTATUS always exists) // + let loginUserStr = document.getElementById('http_username')?.value.trim() || 'admin'; + let baseHint = loginPswdStatHintMsg.replace(/LoginUSER/, loginUserStr); + loginPswdHint = baseHint.replace(/PswdSTATUS/, pswdStatusHint1); pswdField = document.getElementById('routerPassword'); if (passwordFailed || pswdVerified || pswdUnverified) @@ -1688,7 +1779,7 @@ function InitializeFields() if (script_AutoUpdate_Check) { script_AutoUpdate_Check.checked = (custom_settings.Allow_Script_Auto_Update === 'ENABLED'); - UpdateForceScriptCheckboxState(script_AutoUpdate_Check?.checked); + UpdateForceScriptCheckboxState(script_AutoUpdate_Check && script_AutoUpdate_Check.checked); } if (betaToReleaseUpdatesEnabled) @@ -2132,7 +2223,14 @@ function initial() hiddenFrame.onload = function() { console.log("Hidden frame loaded with server response."); + + if (pendingScriptUpdateGateCheck) + { + // Wait a moment to allow the backend logic to finish writing checkHelper.js + setTimeout(RunScriptUpdateFirmwareGateCheck, 1000); + } }; + initializeCollapsibleSections(); } } @@ -2142,6 +2240,9 @@ function initial() /**----------------------------------------**/ function SaveCombinedConfig() { + // Reset containers per-save so stale values never leak forward // + advanced_settings = {}; + // Clear the hidden field before saving // document.getElementById('amng_custom').value = ''; @@ -2386,6 +2487,10 @@ function UpdateMerlinAUScript() ? 'start_MerlinAUscrptupdate_force' : 'start_MerlinAUscrptupdate'; + pendingScriptUpdateGateCheck = true; + scriptUpdateGateTries = 0; + isFormSubmitting = true; + document.form.action_script.value = actionScriptValue; document.form.action_wait.value = 10; showLoading(); @@ -2612,7 +2717,6 @@ function initializeCollapsibleSections() " /> " /> " /> -" /> @@ -2636,7 +2740,7 @@ function initializeCollapsibleSections()
MerlinAU
This is the MerlinAU add-on integrated into the router WebUI -[ +[ Wiki ] @@ -2761,7 +2865,7 @@ function initializeCollapsibleSections()
-
-
+
@@ -2838,7 +2942,7 @@ function initializeCollapsibleSections() onblur="ValidatePasswordString(this,'onBLUR')" onkeyup="ValidatePasswordString(this,'onKEYUP')"/>
+ style="position: relative; display: inline-block; margin-left: 5px; vertical-align: middle; width:24px; height:24px; background:url('/images/icon-visible@2x.png') no-repeat center; background-size: contain; cursor: pointer;">

@@ -3086,7 +3190,7 @@ function initializeCollapsibleSections() -
+
MerlinAU
diff --git a/MerlinAU.sh b/MerlinAU.sh index 9b8e0ec..922128d 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -9,11 +9,11 @@ set -u ## Set version for each Production Release ## -readonly SCRIPT_VERSION=1.5.8 -readonly SCRIPT_VERSTAG="26010210" +readonly SCRIPT_VERSION=1.5.9 +readonly SCRIPT_VERSTAG="26012310" 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,17 @@ readonly fwInstalledInnerVers="$(nvram get innerver)" readonly fwInstalledBranchVer="${fwInstalledBaseVers}.$(echo "$fwInstalledBuildVers" | awk -F'.' '{print $1}')" ##------------------------------------------## -## Modified by ExtremeFiretop [2025-Dec-30] ## +## Modified by ExtremeFiretop [2025-Jan-23] ## ##------------------------------------------## # For minimum supported firmware version check # +# TO NOTE: Due to Gnuton being behind RMerlin # +# Only lock out firmware versions that are 5 # +# builds behind the current production # MinFirmwareVerCheckFailed=false +NewMinSupportedFirmwareVers="TBD" readonly MinSupportedFW_3004_386_Ver="3004.386.13.2" -readonly MinSupportedFW_3004_388_Ver="3004.388.10.0" -readonly MinSupportedFW_3006_102_Ver="3006.102.4.0" +readonly MinSupportedFW_3004_388_Ver="3004.388.9.2" +readonly MinSupportedFW_3006_102_Ver="3004.388.8.4" ##----------------------------------------## ## Modified by Martinski W. [2025-Apr-09] ## @@ -2805,17 +2809,127 @@ _DownloadScriptFiles_() return "$retCode" } +##---------------------------------------## +## Added by ExtremeFiretop [2025-Jan-23] ## +##---------------------------------------## +_GetRemoteMinSupportedFirmwareVers_() +{ + # Echoes the minimum supported FW version (string) for the + # currently-installed FW branch (e.g. 3004.388), based on the + # *REMOTE* script content. Returns 0 on success. + local tmpScript srceScriptUrl current_version branchVer branchKey + local varName minVers defaultVar + + tmpScript="/tmp/${SCRIPT_NAME}.sh.minfw.tmp" + srceScriptUrl="${SCRIPT_URL_REPO}/${SCRIPT_NAME}.sh" + + current_version="$(_GetCurrentFWInstalledLongVersion_)" + if [ -n "$fwInstalledBranchVer" ] + then branchVer="$fwInstalledBranchVer" + else branchVer="$(echo "$current_version" | awk -F '.' '{print $1"."$2}')" + fi + branchKey="$(echo "$branchVer" | tr '.' '_')" + varName="MinSupportedFW_${branchKey}_Ver" + + curl -LSs --retry 4 --retry-delay 5 --retry-connrefused \ + "$srceScriptUrl" -o "$tmpScript" || return 1 + [ -s "$tmpScript" ] || { rm -f "$tmpScript"; return 1; } + + minVers="$(grep -m1 "^readonly ${varName}=" "$tmpScript" | \ + sed -n 's/^readonly [^=]*="\([^"]*\)".*/\1/p')" + + if [ -z "$minVers" ] + then + defaultVar="$(grep -m1 \ + '^[[:space:]]*\*\)[[:space:]]*MinSupportedFirmwareVers="\$MinSupportedFW_' \ + "$tmpScript" | \ + sed -n 's/.*\$\([A-Za-z0-9_]*\)".*/\1/p')" + + if [ -n "$defaultVar" ] + then + minVers="$(grep -m1 "^readonly ${defaultVar}=" "$tmpScript" | \ + sed -n 's/^readonly [^=]*="\([^"]*\)".*/\1/p')" + fi + fi + + rm -f "$tmpScript" + + [ -n "$minVers" ] || return 1 + printf '%s' "$minVers" + return 0 +} + +##---------------------------------------## +## Added by ExtremeFiretop [2025-Jan-23] ## +##---------------------------------------## +_CheckNewScriptMinFWBeforeUpdate_() +{ + # Returns 0 if OK (or unknown), 1 if the NEW script requires newer FW. + # Sets global NewMinSupportedFirmwareVers when known. + local current_version numOfFields numCurrentVers numNewMinVers + local newMinVers + + # If already known, reuse it (do not re-download / re-parse remote script) + if [ -n "$NewMinSupportedFirmwareVers" ] && [ "$NewMinSupportedFirmwareVers" != "TBD" ] + then + newMinVers="$NewMinSupportedFirmwareVers" + else + newMinVers="$(_GetRemoteMinSupportedFirmwareVers_)" || return 0 + [ -n "$newMinVers" ] || return 0 + + # Cache for future calls + NewMinSupportedFirmwareVers="$newMinVers" + fi + + current_version="$(_GetCurrentFWInstalledLongVersion_)" + numOfFields="$(echo "$current_version" | awk -F '.' '{print NF}')" + + numCurrentVers="$(_FWVersionStrToNum_ "$current_version" "$numOfFields")" + numNewMinVers="$(_FWVersionStrToNum_ "$newMinVers" "$numOfFields")" + + [ "$numCurrentVers" -lt "$numNewMinVers" ] && return 1 + return 0 +} + ##-------------------------------------------## -## Modified by ExtremeFiretop [2025-July-18] ## +## Modified by ExtremeFiretop [2025-Jan-23] ## ##-------------------------------------------## _SCRIPT_UPDATE_() { + ScriptUpdateDeclined=false if [ $# -gt 0 ] && [ "$1" = "force" ] then printf "\n${CYANct}Force downloading latest script version...${NOct}\n" _CheckForNewScriptUpdates_ -quietcheck printf "${CYANct}Downloading latest version [$DLRepoVersion] of ${SCRIPT_NAME}${NOct}\n" + current_version="$(_GetCurrentFWInstalledLongVersion_)" + if ! _CheckNewScriptMinFWBeforeUpdate_ + then + _WriteVarDefToHelperJSFile_ "MinimumScriptFWRequired" "$NewMinSupportedFirmwareVers" + printf "\n${CRITct}*WARNING*:${NOct} MerlinAU v${DLRepoVersion} " + printf "requires newer router firmware.\n" + printf "\nCurrent F/W version found: ${REDct}%s${NOct}" \ + "$current_version" + printf "\nMinimum version required: ${GRNct}%s${NOct}\n" \ + "$NewMinSupportedFirmwareVers" + printf "\n${BOLDct}Recommendation:${NOct} Update router firmware first.\n" + if "$isInteractive" + then + printf "\n${BOLDct}Continue downloading anyway?${NOct}" + if ! _WaitForYESorNO_ + then + ScriptUpdateDeclined=true + printf "\n${GRNct}Update cancelled.${NOct}\n" + return 1 + fi + else + # Non-interactive (auto-update): do NOT brick the script. + _SendEMailNotification_ FAILED_SCRIPT_UPDATE_MIN_FW_STATUS + return 1 + fi + fi + if _DownloadScriptFiles_ update then printf "${CYANct}$SCRIPT_NAME files were successfully updated.${NOct}\n\n" @@ -2843,6 +2957,12 @@ _SCRIPT_UPDATE_() ! _CheckForNewScriptUpdates_ && return 1 + if "$ScriptUpdateDeclined" + then + ScriptUpdateDeclined=false + return 0 + fi + clear _ShowLogo_ @@ -2853,6 +2973,19 @@ _SCRIPT_UPDATE_() if "$mountWebGUI_OK" then _SetVersionSharedSettings_ server "$DLRepoVersion" ; fi + current_version="$(_GetCurrentFWInstalledLongVersion_)" + if ! _CheckNewScriptMinFWBeforeUpdate_ + then + _WriteVarDefToHelperJSFile_ "MinimumScriptFWRequired" "$NewMinSupportedFirmwareVers" + printf "\n${CRITct}*WARNING*:${NOct} Updating to MerlinAU v${DLRepoVersion} " + printf "requires newer router firmware.\n" + printf "\nCurrent F/W version found: ${REDct}%s${NOct}" \ + "$current_version" + printf "\nMinimum version required: ${GRNct}%s${NOct}\n" \ + "$NewMinSupportedFirmwareVers" + printf "\n${BOLDct}Recommendation:${NOct} Update router firmware first.\n" + fi + if [ "$SCRIPT_VERSION" = "$DLRepoVersion" ] && \ { [ -z "$DLRepoBuildNum" ] || [ "$DLRepoBuildNum" = "$ScriptBuildNum" ]; } then @@ -2962,6 +3095,12 @@ _CheckForNewScriptUpdates_() [ "$DLRepoVersionNum" -eq "$ScriptVersionNum" ] } then + if ! _CheckNewScriptMinFWBeforeUpdate_ + then + _WriteVarDefToHelperJSFile_ "MinimumScriptFWRequired" "$NewMinSupportedFirmwareVers" + else + _WriteVarDefToHelperJSFile_ "MinimumScriptFWRequired" "TBD" + fi scriptUpdateNotify="New script update available. ${REDct}v${SCRIPT_VERSION}${NOct} --> ${GRNct}v${DLRepoVersion}${NOct}" _WriteVarDefToHelperJSFile_ "isScriptUpdateAvailable" "$DLRepoVersion" @@ -2975,6 +3114,7 @@ ${REDct}v${SCRIPT_VERSION}${NOct} --> ${GRNct}v${DLRepoVersion}${NOct}" fi else scriptUpdateNotify=0 + _WriteVarDefToHelperJSFile_ "MinimumScriptFWRequired" "TBD" _WriteVarDefToHelperJSFile_ "isScriptUpdateAvailable" "TBD" fi return 0 @@ -3001,9 +3141,9 @@ _GetLatestFWUpdateVersionFromRouter_() echo "$newVersionStr" ; return "$retCode" } -##----------------------------------------## -## Modified by Martinski W. [2025-Nov-10] ## -##----------------------------------------## +##------------------------------------------## +## Modified by ExtremeFiretop [2025-Jan-23] ## +##------------------------------------------## _CreateEMailContent_() { if [ $# -eq 0 ] || [ -z "$1" ] ; then return 1 ; fi @@ -3102,6 +3242,26 @@ _CreateEMailContent_() printf "\nThe currently installed script version is: ${NEW_SCRIPT_VERSION}\n" } > "$tempEMailBodyMsg" ;; + FAILED_SCRIPT_UPDATE_MIN_FW_STATUS) + # best-effort: use already-known minimum requirement if available, + # otherwise attempt to compute it again from remote script + minFwRequired="$NewMinSupportedFirmwareVers" + if [ -z "$minFwRequired" ] + then + minFwRequired="$(_GetRemoteMinSupportedFirmwareVers_ 2>/dev/null)" + fi + [ -z "$minFwRequired" ] && minFwRequired="TBD" + + emailBodyTitle="MerlinAU Script Update Blocked (Unsupported Firmware)" + { + echo "MerlinAU did NOT install the new Script Update version ${DLRepoVersion} on your ${MODEL_ID} router." + echo "Reason: Your router firmware is below the minimum supported firmware required by this MerlinAU update." + printf "\nCurrent router firmware version:\n${fwInstalledVersion}\n" + printf "\nMinimum firmware required for MerlinAU v${DLRepoVersion}:\n${minFwRequired}\n" + printf "\nThe installed script version remains: ${SCRIPT_VERSION}\n" + printf "\nRecommendation: Update your router firmware first, then retry the MerlinAU update.\n" + } > "$tempEMailBodyMsg" + ;; FAILED_SCRIPT_UPDATE_STATUS) emailBodyTitle="MerlinAU Script Update Failed" { diff --git a/README.md b/README.md index a2f11ee..ffa1058 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MerlinAU - AsusWRT-Merlin Firmware Auto Updater -## v1.5.8 -## 2026-Jan-02 +## v1.5.9 +## 2026-Jan-23 ## WebUI: ![image](https://github.com/user-attachments/assets/9c1dff99-9c13-491b-a7fa-aff924d5f02e) diff --git a/version.txt b/version.txt index 1cc9c18..2b26b8d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.8 +1.5.9