Skip to content
Open
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
121 changes: 121 additions & 0 deletions .github/workflows/update-merlin-sha256.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
name: Update Merlin SHA256 Checksums

on:
schedule:
# Every 15 minutes, offset from the top of the hour to reduce scheduler congestion.
- cron: '7,22,37,52 * * * *'
workflow_dispatch:

permissions:
contents: write

concurrency:
group: update-merlin-sha256
cancel-in-progress: false

jobs:
scrape-and-commit:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
ssh-key: ${{ secrets.DEPLOY_KEY }}

- name: Fetch, parse, and validate SHA256 signatures
shell: bash
run: |
set -euo pipefail

readonly SOURCE_URL='https://www.asuswrt-merlin.net/download'
readonly TARGET_FILE='merlin-sha256.txt'
readonly MIN_EXPECTED_ENTRIES=5

page_file="$(mktemp)"
candidate_file="$(mktemp)"
trap 'rm -f "$page_file" "$candidate_file"' EXIT

echo "Fetching SHA256 signatures from ${SOURCE_URL}..."
curl --fail --location --silent --show-error \
--retry 4 --retry-delay 5 --retry-connrefused \
--connect-timeout 15 --max-time 60 \
--user-agent 'MerlinAutoUpdate checksum mirror (+https://github.com/ExtremeFiretop/MerlinAutoUpdate-Router)' \
--output "$page_file" \
"$SOURCE_URL"

# Keep the same source section MerlinAU consumes today, but write to a
# temporary candidate so a scrape/parser failure cannot destroy the
# last-known-good mirror in the repository.
sed -n '/<.*>SHA256 signatures:<\/.*>/,/<\/pre>/p' "$page_file" | \
sed -n '/<pre[^>]*>/,/<\/pre>/p' | \
sed -e 's/^.*<pre[^>]*>//' \
-e 's/<[^>]*>//g' \
-e 's/^[[:space:]]*//' \
-e 's/[[:space:]]*$//' | \
tr -d '\r' | \
sed '/^[[:space:]]*$/d' > "$candidate_file"

echo "Validating candidate checksum list..."
awk -v min_entries="$MIN_EXPECTED_ENTRIES" '
BEGIN {
valid = 1
count = 0
}
{
count++

if (NF != 2) {
printf "Invalid field count on line %d: %s\n", NR, $0 > "/dev/stderr"
valid = 0
next
}

if (length($1) != 64 || $1 ~ /[^0-9A-Fa-f]/) {
printf "Invalid SHA256 on line %d: %s\n", NR, $1 > "/dev/stderr"
valid = 0
}

if (seen[$2]++) {
printf "Duplicate firmware filename on line %d: %s\n", NR, $2 > "/dev/stderr"
valid = 0
}
}
END {
if (count < min_entries) {
printf "Only %d checksum entries were parsed; expected at least %d.\n", count, min_entries > "/dev/stderr"
valid = 0
}

if (!valid)
exit 1
}
' "$candidate_file"

echo "Validated $(wc -l < "$candidate_file") checksum entries."
echo "Candidate preview:"
head -n 5 "$candidate_file"

# Replace the working-tree copy only after the candidate has passed
# every validation check. A failed run therefore leaves the repository
# and its last-known-good checksum mirror unchanged.
mv -f "$candidate_file" "$TARGET_FILE"

- name: Commit and push changes
shell: bash
run: |
set -euo pipefail

git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

git add merlin-sha256.txt

if git diff --cached --quiet; then
echo 'No checksum changes detected. Nothing to commit.'
exit 0
fi

git commit -m 'Automated update: refresh Merlin SHA256 checksums'
git push
123 changes: 92 additions & 31 deletions MerlinAU.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,11 +9,11 @@
set -u

## Set version for each Production Release ##
readonly SCRIPT_VERSION=1.6.6
readonly SCRIPT_VERSTAG="26081603"
readonly SCRIPT_VERSION=1.6.7
readonly SCRIPT_VERSTAG="26082409"
readonly SCRIPT_NAME="MerlinAU"
## Set to "master" for Production Releases ##
SCRIPT_BRANCH="master"
SCRIPT_BRANCH="dev"

##----------------------------------------##
## Modified by Martinski W. [2024-Jul-03] ##
Expand All@@ -26,6 +26,9 @@ SCRIPT_URL_REPO="${SCRIPT_URL_BASE}/$SCRIPT_BRANCH"
readonly FW_SFURL_BASE="https://sourceforge.net/projects/asuswrt-merlin/files"
readonly FW_SFURL_RELEASE_SUFFIX="Release"
readonly FW_GITURL_RELEASE="https://api.github.com/repos/gnuton/asuswrt-merlin.ng/releases/latest"
readonly FW_SHA256_URL="https://www.asuswrt-merlin.net/download"
# The scheduled checksum mirror is maintained on the repository's default branch. #
readonly FW_SHA256_MIRROR_URL="${SCRIPT_URL_BASE}/main/merlin-sha256.txt"

##----------------------------------------##
## Modified by Martinski W. [2024-May-31] ##
Expand DownExpand Up@@ -5971,45 +5974,103 @@ _CopyGnutonFiles_()
return 0
}

##----------------------------------------##
## Modified by Martinski W. [2025-Feb-17] ##
##----------------------------------------##
##------------------------------------------##
## Modified by ExtremeFiretop [2026-Aug-24] ##
##------------------------------------------##
##------------------------------------------##
## Modified by ExtremeFiretop [2026-Aug-24] ##
##------------------------------------------##
_GetFirmwareSHA256FromList_()
{
local checksumList="$1"
local firmwareName="$2"

# Return a checksum only when there is exactly one exact filename match
# and its digest is a syntactically valid SHA256 value. Using awk avoids
# treating firmware filenames as regular expressions.
printf '%s\n' "$checksumList" | awk -v firmwareName="$firmwareName" '
$2 == firmwareName && length($1) == 64 && $1 !~ /[^0-9A-Fa-f]/ {
matchCount++
checksum = tolower($1)
}
END {
if (matchCount == 1)
print checksum
}'
}

_CheckOnlineFirmwareSHA256_()
{
# Fetch the latest SHA256 checksums from ASUSWRT-Merlin website #
checksums="$(curl -Ls --retry 4 --retry-delay 5 --retry-connrefused \
https://www.asuswrt-merlin.net/download |
sed -n '/<.*>SHA256 signatures:<\/.*>/,/<\/pre>/p' |
sed -n '/<pre[^>].*>/,/<\/pre>/p' |
sed -e 's/<[^>].*>//g; s/^[[:space:]]*//; s/[[:space:]]*$//')"
local checksums=""
local dl_sig=""
local fw_name=""
local fw_sig=""
local checksumSource=""

if [ -z "$checksums" ]
if [ ! -f "$firmware_file" ]
then
Say "${REDct}**ERROR**${NOct}: Could not download the firmware SHA256 signatures from the website."
Say "${REDct}**ERROR**${NOct}: Firmware image file NOT found!"
_DoCleanUp_ 1
return 1
fi

if [ -f "$firmware_file" ]
fw_name="$(basename "$firmware_file")"
fw_sig="$(openssl sha256 "$firmware_file" | awk -F ' ' '{print tolower($2)}')"

# PRIMARY: Fetch the checksum directly from the ASUSWRT-Merlin website.
checksums="$(curl -Lfs --retry 4 --retry-delay 5 --retry-connrefused \
"$FW_SHA256_URL" 2>/dev/null |
sed -n '/<.*>SHA256 signatures:<\/.*>/,/<\/pre>/p' |
sed -n '/<pre[^>]*>/,/<\/pre>/p' |
sed -e 's/<[^>]*>//g; s/^[[:space:]]*//; s/[[:space:]]*$//')"

if [ -n "$checksums" ]
then
fw_sig="$(openssl sha256 "$firmware_file" | awk -F ' ' '{print $2}')"
# Extract the corresponding signature for the firmware file from the fetched checksums #
dl_sig="$(echo "$checksums" | grep "$(basename "$firmware_file")" | awk -F ' ' '{print $1}')"
if [ "$fw_sig" != "$dl_sig" ]
dl_sig="$(_GetFirmwareSHA256FromList_ "$checksums" "$fw_name")"
fi

if [ -n "$dl_sig" ]
then
checksumSource="ASUSWRT-Merlin website"
else
Say "${YLWct}**WARNING**${NOct}: Independently published checksum could not be retrieved from the ASUSWRT-Merlin website."

# SECONDARY: Use the repository mirror only when the official source
# did not yield a usable checksum. Never use the checksum bundled in
# the firmware archive for an online update.
checksums="$(curl -Lfs --retry 4 --retry-delay 5 --retry-connrefused \
"$FW_SHA256_MIRROR_URL" 2>/dev/null)"

if [ -n "$checksums" ]
then
dl_sig="$(_GetFirmwareSHA256FromList_ "$checksums" "$fw_name")"
fi

if [ -z "$dl_sig" ]
then
Say "${REDct}**ERROR**${NOct}: SHA256 signature from extracted firmware file does not match the SHA256 signature from the website."
Say "${REDct}**ERROR**${NOct}: No unique valid SHA256 signature for ${fw_name} was available from either independent online source."
Say "Online firmware update was aborted; bundled archive checksum fallback is intentionally disabled."
_DoCleanUp_ 1
_SendEMailNotification_ FAILED_FW_CHECKSUM_STATUS
return 1
else
Say "SHA256 signature check for firmware image file passed successfully."
return 0
fi
else
Say "${REDct}**ERROR**${NOct}: Firmware image file NOT found!"

checksumSource="MerlinAU GitHub checksum mirror"
Say "${YLWct}**WARNING**${NOct}: Using the MerlinAU GitHub checksum mirror for verification. (15 MINUTE DELAY!)"
fi

# If the primary source supplied a checksum but it mismatches, this fails
# immediately. The mirror is never used to bypass a checksum mismatch.
if [ "$fw_sig" != "$dl_sig" ]
then
Say "${REDct}**ERROR**${NOct}: SHA256 signature from extracted firmware file does not match the SHA256 signature from the ${checksumSource}."
_DoCleanUp_ 1
_SendEMailNotification_ FAILED_FW_CHECKSUM_STATUS
return 1
fi

Say "SHA256 signature check for firmware image file passed successfully using the ${checksumSource}."
return 0
}

##----------------------------------------##
Expand DownExpand Up@@ -10371,9 +10432,9 @@ _DelFWAutoUpdateHook_()
fi
}

##----------------------------------------##
## Modified by Martinski W. [2024-May-17] ##
##----------------------------------------##
##------------------------------------------##
## Modified by ExtremeFiretop [2026-Aug-24] ##
##------------------------------------------##
_AddFWAutoUpdateHook_()
{
local hookScriptFile jobHookAdded=false
Expand All@@ -10398,12 +10459,12 @@ _AddFWAutoUpdateHook_()

if "$jobHookAdded"
then Say "Cron job hook was added successfully to '$hookScriptFile' script."
else Say "Cron job hook already exists in '$hookScriptFile' script."
else DoPrintf "Cron job hook already exists in '$hookScriptFile' script.\n"
fi
}

##------------------------------------------##
## Modified by ExtremeFiretop [2024-Nov-18] ##
## Modified by ExtremeFiretop [2026-Aug-24] ##
##------------------------------------------##
_AddScriptAutoUpdateHook_()
{
Expand All@@ -10429,7 +10490,7 @@ _AddScriptAutoUpdateHook_()

if "$jobHookAdded"
then Say "Cron job hook was added successfully to '$hookScriptFile' script."
else Say "Cron job hook already exists in '$hookScriptFile' script."
else DoPrintf "Cron job hook already exists in '$hookScriptFile' script.\n"
fi
}

Expand Down
21 changes: 21 additions & 0 deletions merlin-sha256.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
a04d65e566b70a2533f1dcde9e0e1e66133438268f66165caeb973a2aaa8e065 GT-AX11000_3004_388.12_2_rog_ubi.w
24f8679af3726386321aea8ae49af2e7a560f46a8f1fca5a93e31a31d72e43d2 GT-AX11000_3004_388.12_2_ubi.w
b78b365ec8060351c5143782ad9a438f67ab39f37b454fbe06113e99a62c6c25 GT-AXE11000_3004_388.12_2_pureubi.w
c4ef1675ecd772f381c6e5a696d643db89e28f64451d140f53bb8fea62d0089d GT-AXE11000_3004_388.12_2_rog_pureubi.w
352f4a89e86f02deb89d5569db0e354c89956f28225458f8e14f4fe6bacc2123 RT-AX58U_3004_388.12_2_puresqubi.w
936e04cccc6da05b94c19c3cb9c6d8b9fbb0a6542adc7b953aea3c0c7c068903 RT-AX68U_3004_388.12_2_pureubi.w
864cb94504bd0f036f730089c779aafbf5827721af10d5533924f94d3c20982a RT-AX86U_3004_388.12_2_pureubi.w
a902d77915c609cf5ee6ac6069c52e3ab460cd4b915f0febcb269175cc842a6d RT-AX88U_3004_388.12_2_ubi.w
5dc2284ac99bcb280b8f57c98d817ae17c1ae5e60102b8cb581680894739f467 GT-AX11000_PRO_3006_102.8_4_nand_squashfs.pkgtb
9497ab9a5956da9b6c9b86a1c0172e0eef9d8e02c9531598332f0a911a92e925 GT-AX6000_3006_102.8_4_nand_squashfs.pkgtb
ea266310a61dc9018fed850e1dba38b37335740fd5e0053683a1133d69837f4a GT-AXE16000_3006_102.8_4_nand_squashfs.pkgtb
f9e896c3b46a2913e60ea2ac638b4ca624d4d239b3a5416864da95f8b4ed1873 GT-BE19000AI_3006_102.8_4_emmc_squashfs.pkgtb
11932c048a9ecc2be8de93a19af985d685bb21999091d70a1b946fbfa57b7960 GT-BE98_PRO_3006_102.8_4_nand_squashfs.pkgtb
78f296e30bde73b842f3e211b3205bdab7e9b947f1b0b40d43a248cc86badaa7 RT-AX86U_PRO_3006_102.8_4_nand_squashfs.pkgtb
8eca0813db27ef8518535cd00060475c58c7928d8878e87d34df4476dde05550 RT-AX88U_PRO_3006_102.8_4_nand_squashfs.pkgtb
566bb3a9a6331293d7a28b2dbb0676dfa846a5f11bafa3a99c848b894c087606 RT-BE58_GO_3006_102.8_4_nand_squashfs.pkgtb
7cf298f161aca6de3ad4ff3840f08eae3c11a39c067da825e444439aac67dabc RT-BE86U_3006_102.8_4_nand_squashfs.pkgtb
28954e19157261fe97adcb266591de839c202d188d485484a87089149c61dea3 RT-BE88U_3006_102.8_4_nand_squashfs.pkgtb
1fc818b504f51a378fcbd1b8acde0d1e7950d28527c364cc8c4937c9a4072fbe RT-BE92U_3006_102.8_4_nand_squashfs.pkgtb
f4483a27071d4bc074b2e42760e736a8d079a5f0fb9cab1684553de11ce5f453 RT-BE96U_3006_102.8_4_nand_squashfs.pkgtb
12b00d614e5073e78c8e87ae84c688c2d93e7afcb4d0f36d479731b1cbb9f54e XT12_3006_102.8_4_nand_squashfs.pkgtb