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
2 changes: 1 addition & 1 deletion .github/workflows/Create-NewReleases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.3
with:
fetch-depth: 0
ref: 'main' # Ensure we're tagging the main branch after the merge
Expand Down
199 changes: 163 additions & 36 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.3
readonly SCRIPT_VERSTAG="26052123"
readonly SCRIPT_VERSION=1.6.4
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,6 +539,33 @@ _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] ##
##-------------------------------------##
Expand Down Expand Up @@ -773,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 @@ -2217,31 +2245,46 @@ readonly POST_REBOOT_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $POST_REBOOT_SCRIPT_
readonly POST_UPDATE_EMAIL_SCRIPT_JOB="$ScriptFilePath postUpdateEmail &"
readonly POST_UPDATE_EMAIL_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $POST_UPDATE_EMAIL_SCRIPT_JOB $hookScriptTagStr"

##-------------------------------------##
## Added by Martinski W. [2026-Feb-07] ##
##-------------------------------------##
##------------------------------------------##
## Modified by ExtremeFiretop [2026-Jun-10] ##
##------------------------------------------##
_CleanUpOldLogFiles_()
{
[ ! -d "$FW_LOG_DIR" ] && return 1
local numLogFiles topLogFile
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)"
[ -n "$topFile" ] && mv -f "$topFile" "${topFile}.SAVED.TEMP.LOG"
topLogFile="$(ls -1t "$FW_LOG_DIR"/*.log 2>/dev/null | head -n1)"

if [ -n "$topLogFile" ] && [ -s "$topLogFile" ]
then
savedTopLogFile="${topLogFile}.SAVED.$$.TEMP.LOG"
if ! mv -f "$topLogFile" "$savedTopLogFile" 2>/dev/null
then
return 1
fi
fi

# Delete logs older than 30 days #
/usr/bin/find -L "$FW_LOG_DIR" -name '*.log' -mtime +30 -exec rm {} \;

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

_CleanUpOldLogFiles_

##----------------------------------------##
## Modified by Martinski W. [2024-Jan-27] ##
##----------------------------------------##
Expand Down Expand Up @@ -2860,7 +2903,7 @@ _CurlFileDownload_()
then return 1
fi
local retCode=1
local tempFilePathDL="${2}.DL.TMP"
local tempFilePathDL="${2}.DL.$$.TMP"
local srceFilePathDL="${SCRIPT_URL_REPO}/$1"

curl -LSs --retry 4 --retry-delay 5 --retry-connrefused \
Expand All @@ -2877,7 +2920,11 @@ _CurlFileDownload_()
then updatedWebUIPage=true
else updatedWebUIPage=false
fi
mv -f "$tempFilePathDL" "$2"
if ! mv -f "$tempFilePathDL" "$2"
then
rm -f "$tempFilePathDL"
return 1
fi
retCode=0
fi

Expand Down Expand Up @@ -12033,7 +12080,7 @@ _DoInitializationStartup_()
}

##----------------------------------------##
## Modified by Martinski W. [2025-Jul-29] ##
## Modified by Martinski W. [2026-Jun-11] ##
##----------------------------------------##
#######################################################################
# TEMPORARY hack to check if the Gnuton F/W built-in 'webs_update.sh'
Expand All @@ -12052,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
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 @@ -12064,39 +12110,119 @@ _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
chmod 755 "$dwnldGnutonWebsUpdateFilePath"
if ! chmod 755 "$dwnldGnutonWebsUpdateFilePath" 2>/dev/null
then
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
return 1 #NOT available so do nothing#
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

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

if [ "$diffRC" -gt 1 ]
then
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" ] || \
! diff -q "$FW_UpdateCheckScript" "$dwnldGnutonWebsUpdateFilePath" >/dev/null 2>&1
{ [ "$remoteVersTag" -eq "$localVersTag" ] && [ "$diffRC" -ne 0 ] ; }
then
umount "$FW_UpdateCheckScript" 2>/dev/null
mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath"
mount -o bind "$fixedGnutonWebsUpdateFilePath" "$FW_UpdateCheckScript"
Say "${YLWct}Set up a fixed version of the \"${theWebsUpdateFile}\" script file.${NOct}"
for mountPoint in $(grep "$FW_UpdateCheckScript" /proc/mounts | awk -F' ' '{print $2}')
do umount "$FW_UpdateCheckScript" 2>/dev/null
done

if grep -q "$FW_UpdateCheckScript" /proc/mounts
then
Say "${REDct}**ERROR**${NOct}: Unable to unmount \"$FW_UpdateCheckScript\"."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

if ! mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath" 2>/dev/null
then
Say "${REDct}**ERROR**${NOct}: Unable to install the fixed GNUton \"${FW_WebsUpdateFile}\" file."
rm -f "$dwnldGnutonWebsUpdateFilePath"
return 1
fi

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

if [ ! -s "$FW_UpdateCheckScript" ]
then
Say "${REDct}**ERROR**${NOct}: Bind-mount target \"$FW_UpdateCheckScript\" does not exist."
return 1
fi

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
}

##----------------------------------------##
## Modified by Martinski W. [2026-Jun-11] ##
##----------------------------------------##
# Startup/Initialization operations done by possible concurrent processes #
_RunLockedInitializationChecks_()
{
local retCode=0

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 \"${FW_WebsUpdateFile}\" patch."
retCode=1
fi

return "$retCode"
}

if [ "$SCRIPT_BRANCH" = "master" ]
then SCRIPT_VERS_INFO=""
else SCRIPT_VERS_INFO="[$versionDev_TAG]"
fi

## Set variable to 'false' to stop the check ##
checkWebsUpdateScriptForGnuton="$isGNUtonFW"
_Gnuton_Check_Webs_Update_Script_

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 Down Expand Up @@ -12132,7 +12258,8 @@ fi
if [ $# -gt 0 ]
then
if ! _AcquireLock_ cliOptsLock
then Say "Exiting..." ; exit 1 ; fi
then Say "Exiting..." ; exit 1
fi

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

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

## v1.6.3
## 2026-May-22
## v1.6.4
## 2026-June-21

## WebUI:
<img width="775" height="1640" alt="image" src="https://github.com/user-attachments/assets/846f889b-b39f-4ffe-a37a-8892ad9b2f7f" />
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.3
1.6.4