From 6646c2982153af072465c822e37cce5dca0d72b7 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Mon, 28 Sep 2026 16:54:14 -0400 Subject: [PATCH 1/8] Migrate MerlinAU script distribution to GitHub Releases - publish dedicated install and update script assets with each release - use version-pinned GitHub Release assets for stable script downloads - keep development builds on raw GitHub - validate generated release assets before publishing - preserve install fallback to the repository source when a release asset is unavailable - update release workflow and documentation for the new distribution method --- .github/workflows/Create-NewReleases.yml | 16 ++++ MerlinAU.sh | 115 ++++++++++++++++++++++- README.md | 6 +- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index 5059e092..64874897 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -79,6 +79,19 @@ jobs: - name: Push Changes run: git push origin main + - name: Build and Validate Counted Release Assets + run: | + cp MerlinAU.sh MerlinAU-install.sh + cp MerlinAU.sh MerlinAU-update.sh + + sed -i 's/^readonly RELEASE_ASSET_KIND=.*/readonly RELEASE_ASSET_KIND="install"/' MerlinAU-install.sh + sed -i 's/^readonly RELEASE_ASSET_KIND=.*/readonly RELEASE_ASSET_KIND="update"/' Merlin + + grep -Fxq 'readonly RELEASE_ASSET_KIND="install"' MerlinAU-install.sh + grep -Fxq 'readonly RELEASE_ASSET_KIND="update"' MerlinAU-update.sh + grep -Fxq 'readonly SCRIPT_VERSION=${{ steps.nextver.outputs.tag }}' MerlinAU-install.sh + grep -Fxq 'readonly SCRIPT_VERSION=${{ steps.nextver.outputs.tag }}' MerlinAU-update.sh + - name: Create and Push Tag run: | git tag ${{ steps.nextver.outputs.tag }} @@ -92,3 +105,6 @@ jobs: name: "Release ${{ steps.nextver.outputs.tag }}" prerelease: false generate_release_notes: true + files: | + MerlinAU-install.sh + MerlinAU-update.sh diff --git a/MerlinAU.sh b/MerlinAU.sh index 3e3a059b..76363009 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -19,11 +19,11 @@ set -u ## Set version for each Production Release ## -readonly SCRIPT_VERSION=1.6.9 -readonly SCRIPT_VERSTAG="26092509" +readonly SCRIPT_VERSION=1.7.0 +readonly SCRIPT_VERSTAG="26092816" readonly SCRIPT_NAME="MerlinAU" ## Set to "master" for Production Releases ## -SCRIPT_BRANCH="master" +SCRIPT_BRANCH="dev" ##----------------------------------------## ## Modified by Martinski W. [2024-Jul-03] ## @@ -32,6 +32,15 @@ SCRIPT_BRANCH="master" readonly SCRIPT_URL_BASE="https://raw.githubusercontent.com/ExtremeFiretop/MerlinAutoUpdate-Router" SCRIPT_URL_REPO="${SCRIPT_URL_BASE}/$SCRIPT_BRANCH" +# Production release assets are used for install/update downloads so GitHub's +# native per-asset download counters can provide aggregate usage statistics. +# Development builds continue to use raw.githubusercontent.com. +readonly RELEASE_URL_BASE="https://github.com/ExtremeFiretop/MerlinAutoUpdate-Router/releases/download" +readonly RELEASE_INSTALL_ASSET="${SCRIPT_NAME}-install.sh" +readonly RELEASE_UPDATE_ASSET="${SCRIPT_NAME}-update.sh" +# Rewritten to "install" or "update" in generated GitHub Release assets. +readonly RELEASE_ASSET_KIND="source" + # Firmware URL Info # readonly FW_SFURL_BASE="https://sourceforge.net/projects/asuswrt-merlin/files" readonly FW_SFURL_RELEASE_SUFFIX="Release" @@ -2947,9 +2956,14 @@ _CurlFileDownload_() then return 1 fi local tempFilePathDL="${2}.DL.$$.TMP" - local srceFilePathURL="${SCRIPT_URL_REPO}/$1" + local srceFilePathURL local curlRetCode returnCODE statusSTRx httpStatusSTR + if [ $# -gt 2 ] && [ -n "$3" ] + then srceFilePathURL="$3" + else srceFilePathURL="${SCRIPT_URL_REPO}/$1" + fi + rm -f "$tempFilePathDL" printf '' > "$curlErrLogFPath" printf '' > "$curlTmpLogFPath" @@ -2995,6 +3009,59 @@ _CurlFileDownload_() return "$returnCODE" } +##------------------------------------------## +## Added by ExtremeFiretop [2026-Sep-28] ## +##------------------------------------------## +# Download a production script from a version-pinned GitHub Release asset. +# Validate the downloaded script's syntax, expected version, and asset type +# before replacing the installed script. This provides aggregate GitHub +# download counts without adding a third-party analytics/redirect service +# or a persistent client identifier. +_DownloadReleaseScriptAsset_() +{ + if [ $# -lt 3 ] || [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] + then return 1 + fi + + local assetKind="$1" releaseVersion="$2" destFilePath="$3" + local assetName releaseBaseURL tempScriptPath + local downloadedKind + + case "$assetKind" in + install) assetName="$RELEASE_INSTALL_ASSET" ;; + update) assetName="$RELEASE_UPDATE_ASSET" ;; + *) return 1 ;; + esac + + releaseBaseURL="${RELEASE_URL_BASE}/${releaseVersion}" + tempScriptPath="${destFilePath}.REL.$$.TMP" + rm -f "$tempScriptPath" + + if ! _CurlFileDownload_ "$assetName" "$tempScriptPath" \ + "${releaseBaseURL}/${assetName}" + then + rm -f "$tempScriptPath" + return 1 + fi + + downloadedKind="$(grep -m1 '^readonly RELEASE_ASSET_KIND=' "$tempScriptPath" | \ + cut -d'=' -f2- | tr -d '"[:space:]')" + + if [ "$downloadedKind" != "$assetKind" ] + then + Say "${REDct}**ERROR**${NOct}: Release asset type validation failed for [$assetName]." + rm -f "$tempScriptPath" + return 1 + fi + + if ! mv -f "$tempScriptPath" "$destFilePath" + then + rm -f "$tempScriptPath" + return 1 + fi + return 0 +} + ##----------------------------------------## ## Modified by maghuro [2026-Sep-24] ## ##----------------------------------------## @@ -3038,7 +3105,45 @@ _DownloadScriptFiles_() Say "${REDct}**ERROR**${NOct}: Unable to download latest WebUI ASP file for $SCRIPT_NAME." fi - if _CurlFileDownload_ "${SCRIPT_NAME}.sh" "$ScriptFilePath" + local scriptDownloadOK=false releaseVersion + + # Stable production installs/updates use GitHub Release assets so GitHub + # can count aggregate install/update downloads. Development stays on raw + # GitHub and is intentionally excluded from production download counts. + if [ "$SCRIPT_BRANCH" = "master" ] + then + if "$isUpdateAction" + then + releaseVersion="$DLRepoVersion" + [ -z "$releaseVersion" ] && releaseVersion="$(head -n1 "$SCRIPT_VERPATH")" + if _DownloadReleaseScriptAsset_ update "$releaseVersion" "$ScriptFilePath" + then scriptDownloadOK=true + fi + elif [ "$RELEASE_ASSET_KIND" = "install" ] + then + # The bootstrap itself was the counted install asset. Do not fetch + # the same asset a second time and inflate the install counter. + scriptDownloadOK=true + else + releaseVersion="$(head -n1 "$SCRIPT_VERPATH")" + if _DownloadReleaseScriptAsset_ install "$releaseVersion" "$ScriptFilePath" + then + scriptDownloadOK=true + else + # Preserve compatibility with a short release-publication race + # or an older AMTM bootstrap by falling back to the raw script. + Say "${YLWct}*WARNING*${NOct}: Counted install asset unavailable; falling back to repository source." + if _CurlFileDownload_ "${SCRIPT_NAME}.sh" "$ScriptFilePath" + then scriptDownloadOK=true + fi + fi + fi + elif _CurlFileDownload_ "${SCRIPT_NAME}.sh" "$ScriptFilePath" + then + scriptDownloadOK=true + fi + + if "$scriptDownloadOK" then dos2unix "$ScriptFilePath" chmod 755 "$ScriptFilePath" diff --git a/README.md b/README.md index 3f17e158..b0ab2416 100644 --- a/README.md +++ b/README.md @@ -194,9 +194,13 @@ Use your preferred SSH client to connect to the router. *Manual Installation* 1. To Download the script to your router, Copy and paste: ```bash -curl --retry 3 "https://raw.githubusercontent.com/ExtremeFiretop/MerlinAutoUpdate-Router/master/MerlinAU.sh" -o "/jffs/scripts/MerlinAU.sh" && chmod +x "/jffs/scripts/MerlinAU.sh" && sh /jffs/scripts/MerlinAU.sh install +curl -fL --retry 3 "https://github.com/ExtremeFiretop/MerlinAutoUpdate-Router/releases/latest/download/MerlinAU-install.sh" -o "/jffs/scripts/MerlinAU.sh" && chmod +x "/jffs/scripts/MerlinAU.sh" && sh /jffs/scripts/MerlinAU.sh install ``` - The script is now ready for use! + +### Aggregate Download Counts and Privacy +Stable MerlinAU releases use separate GitHub Release assets for fresh installs and script updates. This way, GitHub's native download counter can provide aggregate install/update activity without adding a third-party gateway, cookies, installation ID, or telemetry. +The counters represent **download events, not unique users or routers**. Reinstalls, forced updates, retries, or manual downloads can increment them more than once. ## Usage From 7e5594b1bebc561a88d6a4587a330b203dd564e5 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Mon, 28 Sep 2026 16:56:15 -0400 Subject: [PATCH 2/8] Update comment notes Update comment notes --- MerlinAU.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 76363009..00314ebc 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -3062,9 +3062,9 @@ _DownloadReleaseScriptAsset_() return 0 } -##----------------------------------------## -## Modified by maghuro [2026-Sep-24] ## -##----------------------------------------## +##------------------------------------------## +## Modified by ExtremeFiretop [2026-Sep-28] ## +##------------------------------------------## _DownloadScriptFiles_() { local retCode=0 isUpdateAction updatedWebUIPage theWebPage From b6437677e0b0293246688d446e34c7044e6849df Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Mon, 28 Sep 2026 17:50:58 -0400 Subject: [PATCH 3/8] Update notes Update notes --- MerlinAU.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 00314ebc..33b97892 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -3013,9 +3013,8 @@ _CurlFileDownload_() ## Added by ExtremeFiretop [2026-Sep-28] ## ##------------------------------------------## # Download a production script from a version-pinned GitHub Release asset. -# Validate the downloaded script's syntax, expected version, and asset type -# before replacing the installed script. This provides aggregate GitHub -# download counts without adding a third-party analytics/redirect service +# Validate the downloaded script's asset type before replacing the installed script. +# This provides aggregate GitHub# download counts without adding a third-party redirect service # or a persistent client identifier. _DownloadReleaseScriptAsset_() { From 8e3246ecaeb833c3c09e2555205c039beb4e2531 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Thu, 1 Oct 2026 13:35:57 -0400 Subject: [PATCH 4/8] Add GitHub Release download metrics tracking Add GitHub Release download metrics tracking --- .github/workflows/Track_Metrics_MerlinAU.yml | 64 +++++++ track_downloads.py | 180 +++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 .github/workflows/Track_Metrics_MerlinAU.yml create mode 100644 track_downloads.py diff --git a/.github/workflows/Track_Metrics_MerlinAU.yml b/.github/workflows/Track_Metrics_MerlinAU.yml new file mode 100644 index 00000000..52fca3d9 --- /dev/null +++ b/.github/workflows/Track_Metrics_MerlinAU.yml @@ -0,0 +1,64 @@ +name: Track Release Downloads + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + snapshot-metrics: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + + - name: Restore metrics history + shell: bash + run: | + mkdir -p metrics + + if git ls-remote --exit-code --heads origin metrics >/dev/null 2>&1; then + git fetch origin metrics:refs/remotes/origin/metrics + git show origin/metrics:metrics/release_downloads.csv \ + > metrics/release_downloads.csv 2>/dev/null || true + fi + + - name: Snapshot release download counts + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: python3 track_downloads.py + + - name: Publish metrics history + shell: bash + run: | + cp metrics/release_downloads.csv /tmp/release_downloads.csv + rm -rf metrics + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + if git show-ref --verify --quiet refs/remotes/origin/metrics; then + git switch --force-create metrics origin/metrics + else + git switch --orphan metrics + git rm -rf . >/dev/null 2>&1 || true + fi + + mkdir -p metrics + cp /tmp/release_downloads.csv metrics/release_downloads.csv + + git add metrics/release_downloads.csv + + if git diff --cached --quiet; then + echo "No metrics changes to commit." + exit 0 + fi + + git commit -m "Update release download metrics" + git push origin metrics diff --git a/track_downloads.py b/track_downloads.py new file mode 100644 index 00000000..c4dedcb9 --- /dev/null +++ b/track_downloads.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import csv +import json +import os +import sys +from datetime import datetime, timezone +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +ASSETS = { + "MerlinAU-install.sh": "install", + "MerlinAU-update.sh": "update", +} + +OUTPUT_PATH = Path("metrics/release_downloads.csv") + +FIELDNAMES = [ + "snapshot_date", + "snapshot_time_utc", + "tag", + "published_at", + "kind", + "asset_name", + "asset_id", + "download_count", +] + + +def github_get(url: str, token: str | None): + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "MerlinAU-release-metrics", + } + if token: + headers["Authorization"] = f"Bearer {token}" + + request = Request(url, headers=headers) + + try: + with urlopen(request, timeout=30) as response: + return json.load(response) + except HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace") + raise RuntimeError( + f"GitHub API request failed: HTTP {exc.code} for {url}\n{body}" + ) from exc + except URLError as exc: + raise RuntimeError(f"GitHub API request failed for {url}: {exc}") from exc + + +def get_releases(repository: str, token: str | None) -> list[dict]: + releases = [] + page = 1 + + while True: + url = ( + f"https://api.github.com/repos/{repository}/releases" + f"?per_page=100&page={page}" + ) + payload = github_get(url, token) + + if not isinstance(payload, list): + raise RuntimeError("Unexpected GitHub API response while listing releases.") + + releases.extend(payload) + + if len(payload) < 100: + break + + page += 1 + + return releases + + +def load_existing_rows(path: Path): + rows = {} + + if not path.exists(): + return rows + + with path.open("r", newline="", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + + missing = [field for field in FIELDNAMES if field not in (reader.fieldnames or [])] + if missing: + raise RuntimeError( + f"{path} is missing expected columns: {', '.join(missing)}" + ) + + for row in reader: + key = (row["snapshot_date"], row["tag"], row["asset_name"]) + rows[key] = {field: row.get(field, "") for field in FIELDNAMES} + + return rows + + +def write_rows(path: Path, rows) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + temp_path = path.with_suffix(path.suffix + ".tmp") + + ordered_rows = sorted( + rows.values(), + key=lambda row: ( + row["snapshot_date"], + row["published_at"], + row["tag"], + row["kind"], + row["asset_name"], + ), + ) + + with temp_path.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=FIELDNAMES) + writer.writeheader() + writer.writerows(ordered_rows) + + temp_path.replace(path) + + +def main() -> int: + repository = os.environ.get("GITHUB_REPOSITORY", "").strip() + token = os.environ.get("GITHUB_TOKEN", "").strip() or None + + if not repository or "/" not in repository: + print("ERROR: GITHUB_REPOSITORY must be set to owner/repository.", file=sys.stderr) + return 1 + + now = datetime.now(timezone.utc).replace(microsecond=0) + snapshot_date = now.date().isoformat() + snapshot_time = now.isoformat().replace("+00:00", "Z") + + rows = load_existing_rows(OUTPUT_PATH) + releases = get_releases(repository, token) + + matched_assets = 0 + + for release in releases: + if release.get("draft") or release.get("prerelease"): + continue + + tag = str(release.get("tag_name") or "") + published_at = str(release.get("published_at") or "") + + for asset in release.get("assets") or []: + asset_name = str(asset.get("name") or "") + kind = ASSETS.get(asset_name) + + if kind is None: + continue + + row = { + "snapshot_date": snapshot_date, + "snapshot_time_utc": snapshot_time, + "tag": tag, + "published_at": published_at, + "kind": kind, + "asset_name": asset_name, + "asset_id": str(asset.get("id") or ""), + "download_count": str(asset.get("download_count") or 0), + } + + # Same-day manual reruns refresh the row instead of duplicating it. + key = (snapshot_date, tag, asset_name) + rows[key] = row + matched_assets += 1 + + write_rows(OUTPUT_PATH, rows) + + print( + f"Recorded {matched_assets} counted release asset(s) for " + f"{snapshot_date} in {OUTPUT_PATH}." + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From bf96ddecc24835aac49ca09afe21c55d1cbce6e2 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Sat, 3 Oct 2026 20:05:39 -0400 Subject: [PATCH 5/8] Temp Test Temp Test --- MerlinAU.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/MerlinAU.sh b/MerlinAU.sh index 33b97892..2b5e805b 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -12992,6 +12992,32 @@ then _ReleaseLock_ cliFileLock fi ;; + releaseassettest) + testKind="${2:-update}" + testVersion="${3:-release-metrics-test}" + testDest="/tmp/${SCRIPT_NAME}-${testKind}-releaseasset-test.sh" + + printf "\nTesting GitHub Release asset runtime path...\n" + printf "Asset type: %s\n" "$testKind" + printf "Release version: %s\n" "$testVersion" + printf "Destination: %s\n\n" "$testDest" + + rm -f "$testDest" + + if _DownloadReleaseScriptAsset_ "$testKind" "$testVersion" "$testDest" + then + printf "Release asset download succeeded.\n" + printf "Downloaded marker: " + grep -m1 '^readonly RELEASE_ASSET_KIND=' "$testDest" + printf "Downloaded size: " + wc -c < "$testDest" + printf "Downloaded file: %s\n" "$testDest" + _DoExit_ 0 + else + printf "Release asset download FAILED.\n" + _DoExit_ 1 + fi + ;; forceupdate) if _AcquireLock_ cliFileLock then From a8c9b1bd8ba7ddb5e6b43025a1fa93dd724ba647 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Sat, 3 Oct 2026 20:09:59 -0400 Subject: [PATCH 6/8] Fix Test Fix Test --- MerlinAU.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 2b5e805b..7d103991 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -3043,8 +3043,7 @@ _DownloadReleaseScriptAsset_() return 1 fi - downloadedKind="$(grep -m1 '^readonly RELEASE_ASSET_KIND=' "$tempScriptPath" | \ - cut -d'=' -f2- | tr -d '"[:space:]')" + downloadedKind="$(grep -m1 '^readonly RELEASE_ASSET_KIND=' "$tempScriptPath" | cut -d'"' -f2)" if [ "$downloadedKind" != "$assetKind" ] then From f9e38976cea029a959fdff67ba0af3961ca450e5 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Sat, 3 Oct 2026 20:20:20 -0400 Subject: [PATCH 7/8] Revert "Temp Test" This reverts commit bf96ddecc24835aac49ca09afe21c55d1cbce6e2. --- MerlinAU.sh | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 7d103991..bc7358e8 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -12991,32 +12991,6 @@ then _ReleaseLock_ cliFileLock fi ;; - releaseassettest) - testKind="${2:-update}" - testVersion="${3:-release-metrics-test}" - testDest="/tmp/${SCRIPT_NAME}-${testKind}-releaseasset-test.sh" - - printf "\nTesting GitHub Release asset runtime path...\n" - printf "Asset type: %s\n" "$testKind" - printf "Release version: %s\n" "$testVersion" - printf "Destination: %s\n\n" "$testDest" - - rm -f "$testDest" - - if _DownloadReleaseScriptAsset_ "$testKind" "$testVersion" "$testDest" - then - printf "Release asset download succeeded.\n" - printf "Downloaded marker: " - grep -m1 '^readonly RELEASE_ASSET_KIND=' "$testDest" - printf "Downloaded size: " - wc -c < "$testDest" - printf "Downloaded file: %s\n" "$testDest" - _DoExit_ 0 - else - printf "Release asset download FAILED.\n" - _DoExit_ 1 - fi - ;; forceupdate) if _AcquireLock_ cliFileLock then From e725c4f9a51f2b97fddb9cc3db926ac741b6c977 Mon Sep 17 00:00:00 2001 From: ExtremeFiretop Date: Sat, 3 Oct 2026 20:33:01 -0400 Subject: [PATCH 8/8] Fixing Workflow Fixing Workflow --- .github/workflows/Create-NewReleases.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index 64874897..c48574ec 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -85,12 +85,10 @@ jobs: cp MerlinAU.sh MerlinAU-update.sh sed -i 's/^readonly RELEASE_ASSET_KIND=.*/readonly RELEASE_ASSET_KIND="install"/' MerlinAU-install.sh - sed -i 's/^readonly RELEASE_ASSET_KIND=.*/readonly RELEASE_ASSET_KIND="update"/' Merlin + sed -i 's/^readonly RELEASE_ASSET_KIND=.*/readonly RELEASE_ASSET_KIND="update"/' MerlinAU-update.sh grep -Fxq 'readonly RELEASE_ASSET_KIND="install"' MerlinAU-install.sh grep -Fxq 'readonly RELEASE_ASSET_KIND="update"' MerlinAU-update.sh - grep -Fxq 'readonly SCRIPT_VERSION=${{ steps.nextver.outputs.tag }}' MerlinAU-install.sh - grep -Fxq 'readonly SCRIPT_VERSION=${{ steps.nextver.outputs.tag }}' MerlinAU-update.sh - name: Create and Push Tag run: |