diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index 0a398895..3000045d 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -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 diff --git a/MerlinAU.sh b/MerlinAU.sh index 237e1a54..f0552434 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -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] ## @@ -434,7 +434,7 @@ _ReleaseLock_() fi [ -s "$LockFilePath" ] && return 0 fi - rm -f "$LockFilePath" + printf '' > "$LockFilePath" } ## Defaults ## @@ -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 @@ -478,7 +478,7 @@ _AcquireLock_() _CreateLockFile_() { echo "$$|$lockTypeReq" > "$LockFilePath" ; } - if [ ! -f "$LockFilePath" ] + if [ ! -s "$LockFilePath" ] then _CreateLockFile_ ; return 0 fi @@ -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] ## ##-------------------------------------## @@ -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] ## @@ -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] ## ##----------------------------------------## @@ -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 \ @@ -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 @@ -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' @@ -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")" @@ -12064,24 +12110,99 @@ _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" ] @@ -12089,14 +12210,19 @@ 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] ## ##----------------------------------------## @@ -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 diff --git a/README.md b/README.md index 9f1dd278..26f58ecd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MerlinAU - AsusWRT-Merlin Firmware Auto Updater -## v1.6.3 -## 2026-May-22 +## v1.6.4 +## 2026-June-21 ## WebUI: image diff --git a/version.txt b/version.txt index 266146b8..9edc58bb 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.6.3 +1.6.4