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()
|
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:

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