Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 91 additions & 95 deletions MerlinAU.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
#
# Original Creation Date: 2023-Oct-01 by @ExtremeFiretop.
# Official Co-Author: @Martinski W. - Date: 2023-Nov-01
# Last Modified: 2026-May-21
# Last Modified: 2026-Jun-11
###################################################################
set -u

## Set version for each Production Release ##
readonly SCRIPT_VERSION=1.6.4
readonly SCRIPT_VERSTAG="26061019"
readonly SCRIPT_VERSTAG="26061118"
readonly SCRIPT_NAME="MerlinAU"
## Set to "master" for Production Releases ##
SCRIPT_BRANCH="master"
SCRIPT_BRANCH="dev"

##----------------------------------------##
## Modified by Martinski W. [2024-Jul-03] ##
Expand Down Expand Up @@ -434,7 +434,7 @@ _ReleaseLock_()
fi
[ -s "$LockFilePath" ] && return 0
fi
rm -f "$LockFilePath"
printf '' > "$LockFilePath"
}

## Defaults ##
Expand All @@ -445,7 +445,7 @@ LockFileMaxAgeSecs=600 #10-minutes#
if [ $# -eq 0 ] || [ -z "$1" ]
then
#Interactive Mode#
LockMaxTimeoutSecs=3
LockMaxTimeoutSecs=5
LockFileMaxAgeSecs=1200
else
case "$1" in
Expand Down Expand Up @@ -478,7 +478,7 @@ _AcquireLock_()
_CreateLockFile_()
{ echo "$$|$lockTypeReq" > "$LockFilePath" ; }

if [ ! -f "$LockFilePath" ]
if [ ! -s "$LockFilePath" ]
then _CreateLockFile_ ; return 0
fi

Expand Down Expand Up @@ -539,19 +539,40 @@ _AcquireLock_()
return "$retCode"
}

##-------------------------------------##
## Added for initialization operations ##
##-------------------------------------##
initMutexFLock_FD=577
initMutexFLock_FN="/tmp/var/${ScriptFNameTag}_Initialization.FLock"

#--------------------------------------------------------------#
# This is a mutually exclusive, blocking FLOCK mechanism used
# to protect initialization routines when concurrent MerlinAU
# executions are running (e.g. called from services-start).
#--------------------------------------------------------------#
_AcquireInitMutexFLock_()
{
eval exec "${initMutexFLock_FD}>$initMutexFLock_FN"
if flock -x "$initMutexFLock_FD" 2>/dev/null
then return 0
fi
eval exec "${initMutexFLock_FD}>&-"
return 1
}

_ReleaseInitMutexFLock_()
{
flock -u "$initMutexFLock_FD" 2>/dev/null
eval exec "${initMutexFLock_FD}>&-"
}

##-------------------------------------##
## Added by Martinski W. [2026-Mar-18] ##
##-------------------------------------##
fwupMutexFLock_FD=576
fwupMutexFLock_FN="/tmp/var/${ScriptFNameTag}_FW_Update.FLock"
fwupMutexFLock_OK=false #DO NOT have FLock#

##-------------------------------------##
## Added for initialization operations ##
##-------------------------------------##
initMutexFLock_FD=577
initMutexFLock_FN="/tmp/var/${ScriptFNameTag}_Initialization.FLock"

_ReleaseMutexFLock_()
{
if [ $# -gt 0 ] && \
Expand Down Expand Up @@ -779,7 +800,8 @@ Toggle_LEDs_PID=""

# To enable/disable the built-in "F/W Update Check" #
FW_UpdateCheckState="$(nvram get firmware_check_enable)"
FW_UpdateCheckScript="/usr/sbin/webs_update.sh"
readonly FW_WebsUpdateFile="webs_update.sh"
readonly FW_UpdateCheckScript="/usr/sbin/$FW_WebsUpdateFile"

##-------------------------------------##
## Added by Martinski W. [2023-Nov-24] ##
Expand Down Expand Up @@ -2229,20 +2251,19 @@ readonly POST_UPDATE_EMAIL_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $POST_UPDATE_E
_CleanUpOldLogFiles_()
{
[ ! -d "$FW_LOG_DIR" ] && return 1
local numLogFiles topFile savedTopFile
local numLogFiles topLogFile savedTopLogFile=""

numLogFiles="$(ls -1lt "$FW_LOG_DIR"/*.log 2>/dev/null | wc -l)"
# Leave one log file (if any available) #
[ "$numLogFiles" -lt 2 ] && return 0

# Save the most recent log file #
topFile="$(ls -1t "$FW_LOG_DIR"/*.log 2>/dev/null | head -n1)"
topLogFile="$(ls -1t "$FW_LOG_DIR"/*.log 2>/dev/null | head -n1)"

if [ -n "$topFile" ]
if [ -n "$topLogFile" ] && [ -s "$topLogFile" ]
then
savedTopFile="${topFile}.SAVED.$$.TEMP.LOG"

if ! mv -f "$topFile" "$savedTopFile"
savedTopLogFile="${topLogFile}.SAVED.$$.TEMP.LOG"
if ! mv -f "$topLogFile" "$savedTopLogFile" 2>/dev/null
then
return 1
fi
Expand All @@ -2252,14 +2273,15 @@ _CleanUpOldLogFiles_()
/usr/bin/find -L "$FW_LOG_DIR" -name '*.log' -mtime +30 -exec rm {} \;

# Restore the most recent log file #
if [ -n "$topFile" ] && [ -f "$savedTopFile" ]
if [ -n "$topLogFile" ] && \
[ -n "$savedTopLogFile" ] && \
[ -s "$savedTopLogFile" ]
then
if ! mv -f "$savedTopFile" "$topFile"
if ! mv -f "$savedTopLogFile" "$topLogFile" 2>/dev/null
then
return 1
fi
fi

return 0
}

Expand Down Expand Up @@ -12057,9 +12079,9 @@ _DoInitializationStartup_()
_SetDefaultBuildType_
}

##------------------------------------------##
## Modified by ExtremeFiretop [2026-Jun-10] ##
##------------------------------------------##
##----------------------------------------##
## Modified by Martinski W. [2026-Jun-11] ##
##----------------------------------------##
#######################################################################
# TEMPORARY hack to check if the Gnuton F/W built-in 'webs_update.sh'
# script is the most recent version that includes required fixes.
Expand All @@ -12077,10 +12099,9 @@ _Gnuton_Check_Webs_Update_Script_()
return 0
fi

local theWebsUpdateFile="webs_update.sh"
local fixedGnutonWebsUpdateFilePath="${SETTINGS_DIR}/$theWebsUpdateFile"
local dwnldGnutonWebsUpdateFilePath="${SETTINGS_DIR}/${theWebsUpdateFile}.GNUTON.$$"
local localVersTag remoteVersTag diffRC
local fixedGnutonWebsUpdateFilePath="${SETTINGS_DIR}/$FW_WebsUpdateFile"
local dwnldGnutonWebsUpdateFilePath="${SETTINGS_DIR}/${FW_WebsUpdateFile}.GNUTON.$$"
local localVersTag remoteVersTag diffRC mountPoint

# Get local VERSTAG (if any) #
localVersTag="$(_Get_GnutonWebUpdate_ScriptVersTag_ "$FW_UpdateCheckScript")"
Expand All @@ -12089,132 +12110,101 @@ _Gnuton_Check_Webs_Update_Script_()
# Get the fixed version of the script targeted for Gnuton F/W #
if _CurlFileDownload_ "gnuton_webs_update.sh" "$dwnldGnutonWebsUpdateFilePath"
then
if ! chmod 755 "$dwnldGnutonWebsUpdateFilePath"
if ! chmod 755 "$dwnldGnutonWebsUpdateFilePath" 2>/dev/null
then
Say "${REDct}**ERROR**${NOct}: Unable to set permissions on the downloaded GNUton webs_update.sh file."
Say "${REDct}**ERROR**${NOct}: Unable to set permissions on the downloaded GNUton \"${FW_WebsUpdateFile}\" file."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

remoteVersTag="$(_Get_GnutonWebUpdate_ScriptVersTag_ "$dwnldGnutonWebsUpdateFilePath")"
[ -z "$remoteVersTag" ] && remoteVersTag=0
else
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

# Distinguish files that differ from an actual comparison error.
# Distinguish files that differ from an actual comparison error #
diff -q "$FW_UpdateCheckScript" "$dwnldGnutonWebsUpdateFilePath" >/dev/null 2>&1

diffRC=$?
diffRC="$?"

if [ "$diffRC" -gt 1 ]
then
Say "${REDct}**ERROR**${NOct}: Unable to compare the GNUton webs_update.sh files."
Say "${REDct}**ERROR**${NOct}: Unable to compare the GNUton \"${FW_WebsUpdateFile}\" files."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

# (Re)bind/mount only if remote is newer version OR files differ #
if [ "$remoteVersTag" -gt "$localVersTag" ] || \
[ "$diffRC" -eq 1 ]
{ [ "$remoteVersTag" -eq "$localVersTag" ] && [ "$diffRC" -ne 0 ] ; }
then
# Remove all existing bind-mount layers, including duplicates.
while awk -v target="$FW_UpdateCheckScript" '
$2 == target { found=1 }
END { exit !found }
' /proc/mounts
do
if ! umount "$FW_UpdateCheckScript"
then
Say "${REDct}**ERROR**${NOct}: Unable to unmount \"$FW_UpdateCheckScript\"."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi
for mountPoint in $(grep "$FW_UpdateCheckScript" /proc/mounts | awk -F' ' '{print $2}')
do umount "$FW_UpdateCheckScript" 2>/dev/null
done

if ! mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath"
if grep -q "$FW_UpdateCheckScript" /proc/mounts
then
Say "${REDct}**ERROR**${NOct}: Unable to install the fixed GNUton webs_update.sh file."
Say "${REDct}**ERROR**${NOct}: Unable to unmount \"$FW_UpdateCheckScript\"."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

if [ ! -f "$fixedGnutonWebsUpdateFilePath" ]
if ! mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath" 2>/dev/null
then
Say "${REDct}**ERROR**${NOct}: GNUton webs_update.sh source file is missing before bind mount."
Say "${REDct}**ERROR**${NOct}: Unable to install the fixed GNUton \"${FW_WebsUpdateFile}\" file."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

if [ ! -e "$FW_UpdateCheckScript" ]
if [ ! -s "$fixedGnutonWebsUpdateFilePath" ]
then
Say "${REDct}**ERROR**${NOct}: Bind-mount target \"$FW_UpdateCheckScript\" does not exist."
Say "${REDct}**ERROR**${NOct}: GNUton \"${FW_WebsUpdateFile}\" source file is missing before bind mount."
return 1
fi

if ! mount -o bind \
"$fixedGnutonWebsUpdateFilePath" \
"$FW_UpdateCheckScript"
if [ ! -s "$FW_UpdateCheckScript" ]
then
Say "${REDct}**ERROR**${NOct}: Unable to bind-mount the fixed GNUton webs_update.sh file."
Say "${REDct}**ERROR**${NOct}: Bind-mount target \"$FW_UpdateCheckScript\" does not exist."
return 1
fi

Say "${YLWct}Set up a fixed version of the \"${theWebsUpdateFile}\" script file.${NOct}"
if ! mount -o bind "$fixedGnutonWebsUpdateFilePath" "$FW_UpdateCheckScript"
then
Say "${REDct}**ERROR**${NOct}: Unable to bind-mount the fixed GNUton \"${FW_WebsUpdateFile}\" file."
return 1
fi
Say "${YLWct}Set up a fixed version of the \"${FW_WebsUpdateFile}\" script file.${NOct}"
else
rm -f "$dwnldGnutonWebsUpdateFilePath"
fi

return 0
}

##---------------------------------------##
## Added by ExtremeFiretop [2026-Jun-10] ##
##---------------------------------------##
# Serialize startup-time operations that use shared log and GNUton files.
##----------------------------------------##
## Modified by Martinski W. [2026-Jun-11] ##
##----------------------------------------##
# Startup/Initialization operations done by possible concurrent processes #
_RunLockedInitializationChecks_()
{
local retCode=0

[ ! -d /tmp/var ] && mkdir -p /tmp/var

# Open the dedicated lock file on its own file descriptor.
if ! eval "exec ${initMutexFLock_FD}>\"${initMutexFLock_FN}\""
then
Say "${REDct}**ERROR**${NOct}: Unable to open the initialization lock file."
return 1
fi

# Use a blocking exclusive lock so concurrent MerlinAU processes wait.
if ! flock -x "$initMutexFLock_FD" 2>/dev/null
then
Say "${REDct}**ERROR**${NOct}: Unable to acquire the initialization lock."
eval "exec ${initMutexFLock_FD}>&-"
return 1
fi

# These operations access shared paths and must not overlap.
if [ -d "$FW_LOG_DIR" ] && \
! _CleanUpOldLogFiles_
if ! _CleanUpOldLogFiles_
then
Say "${YLWct}WARNING:${NOct} Unable to clean up old firmware-update log files."
retCode=1
fi

# Set variable to 'false' to stop the check #
checkWebsUpdateScriptForGnuton="$isGNUtonFW"
if ! _Gnuton_Check_Webs_Update_Script_
then
Say "${YLWct}WARNING:${NOct} Unable to check or install the GNUton webs_update.sh patch."
Say "${YLWct}WARNING:${NOct} Unable to check or install the GNUton \"${FW_WebsUpdateFile}\" patch."
retCode=1
fi

flock -u "$initMutexFLock_FD" 2>/dev/null
eval "exec ${initMutexFLock_FD}>&-"

return "$retCode"
}


if [ "$SCRIPT_BRANCH" = "master" ]
then SCRIPT_VERS_INFO=""
else SCRIPT_VERS_INFO="[$versionDev_TAG]"
Expand All @@ -12224,6 +12214,15 @@ FW_InstalledVersion="$(_GetCurrentFWInstalledLongVersion_)"
FW_InstalledVerStr="${GRNct}${FW_InstalledVersion}${NOct}"
FW_NewUpdateVerInit=TBD

if _AcquireInitMutexFLock_
then
_RunLockedInitializationChecks_
_ReleaseInitMutexFLock_
else
Say "${REDct}**ERROR**${NOct}: Unable to acquire the initialization lock. Exiting..."
exit 1
fi

##----------------------------------------##
## Modified by Martinski W. [2025-Apr-07] ##
##----------------------------------------##
Expand All @@ -12234,8 +12233,6 @@ then
then Say "Exiting..." ; exit 1
fi

_RunLockedInitializationChecks_

inMenuMode=true
_DoInitializationStartup_
_CheckFor_VersionFile_
Expand All @@ -12261,9 +12258,8 @@ fi
if [ $# -gt 0 ]
then
if ! _AcquireLock_ cliOptsLock
then Say "Exiting..." ; exit 1 ; fi

_RunLockedInitializationChecks_
then Say "Exiting..." ; exit 1
fi

[ "$1" = "amtmupdate" ] && isVerbose=false

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# MerlinAU - AsusWRT-Merlin Firmware Auto Updater

## v1.6.4
## 2026-June-10
## 2026-June-11

## WebUI:
<img width="775" height="1640" alt="image" src="https://github.com/user-attachments/assets/846f889b-b39f-4ffe-a37a-8892ad9b2f7f" />
Expand Down