From 6007b5e63d76962675fb121b8fa8c2de89242fdc Mon Sep 17 00:00:00 2001 From: Nerdy-Technician Date: Wed, 30 Sep 2026 22:40:03 +0100 Subject: [PATCH 1/2] ci: add security scanning, pinned actions and Dependabot auto-merge - CI: add actionlint, dependency review and gitleaks secret scan alongside ShellCheck and PSScriptAnalyzer - CodeQL (Actions, security-extended) on PRs, main and weekly - OpenSSF Scorecard on main and weekly - Pin every GitHub Action to a commit SHA - Release workflows: pass tag/version values via env instead of inline expressions, and scope contents: write to the job - Dependabot: drop unused pip ecosystem, group Actions updates - Auto-merge Dependabot PRs once all checks pass - Add SECURITY.md and MIT LICENSE --- .github/dependabot.yml | 15 ++----- .github/workflows/ci.yml | 54 +++++++++++++++++++++++- .github/workflows/codeql.yml | 31 ++++++++++++++ .github/workflows/package-release.yml | 38 +++++++++++------ .github/workflows/release-descriptor.yml | 22 ++++++---- .github/workflows/scorecard.yml | 31 ++++++++++++++ LICENSE | 21 +++++++++ SECURITY.md | 16 +++++++ 8 files changed, 193 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/scorecard.yml create mode 100644 LICENSE create mode 100644 SECURITY.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e84fbb4..3f0bd66 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,19 +1,12 @@ version: 2 updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 - allow: - - dependency-type: "direct" - labels: - - "deps" - - "security" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "actions" - - "security" \ No newline at end of file + - "security" + groups: + actions: + patterns: ["*"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd601bd..2f127f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: pull_request: push: branches: [main] + workflow_dispatch: permissions: contents: read @@ -26,7 +27,7 @@ jobs: Linux/Monitoring/Check - Wazuh_is_running.sh Linux/Network/Network - Port Scan.sh steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: ShellCheck (errors only) run: | shellcheck --version @@ -45,7 +46,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Analyze PowerShell scripts shell: pwsh run: | @@ -70,3 +71,52 @@ jobs: } Write-Output "Analyzed $($files.Count) PowerShell files, $failed error(s)." if ($failed -gt 0) { exit 1 } + + actionlint: + name: Lint workflows + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Run actionlint + uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 + + dependency-review: + name: Dependency review + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: high + + secret-scan: + name: Secret scan + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + - name: Run gitleaks + run: | + docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f git /repo --redact --no-banner + + # Merges Dependabot PRs once every check above has passed. + dependabot-automerge: + name: Auto-merge Dependabot + needs: [shellcheck, powershell, actionlint, dependency-review, secret-scan] + if: github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + PR_URL: ${{ github.event.pull_request.html_url }} + steps: + - name: Squash-merge PR + run: gh pr merge "$PR_URL" --squash --delete-branch diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..85a97f4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,31 @@ +name: CodeQL + +on: + pull_request: + push: + branches: [main] + schedule: + - cron: "23 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (actions) + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + security-events: write + actions: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + languages: actions + queries: security-extended + - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + category: /language:actions diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index 8ad3dad..3bed114 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -19,7 +19,7 @@ on: default: 'false' permissions: - contents: write + contents: read env: WINDOWS_ARCHIVE: Nerdy-RMMScripts-Windows.zip @@ -28,17 +28,21 @@ env: jobs: build-and-package: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout repository (full history) - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Debug event context + env: + TAG_INPUT: ${{ inputs.tag }} run: | - echo "Event: ${{ github.event_name }}" - echo "Ref: ${{ github.ref }}" - echo "Tag Input: ${{ inputs.tag }}" + echo "Event: $GITHUB_EVENT_NAME" + echo "Ref: $GITHUB_REF" + echo "Tag Input: $TAG_INPUT" git tag --list --sort=-creatordate | head -n 15 echo "Commit message:" && git log -1 --pretty=%B @@ -62,19 +66,26 @@ jobs: - name: Run Windows packaging script (PowerShell Core) shell: pwsh + env: + PREV_TAG: ${{ steps.tags.outputs.previous_tag }} run: | - $prev = '${{ steps.tags.outputs.previous_tag }}' + $prev = $env:PREV_TAG if ($prev) { ./release/Create-Windows-Release.ps1 -PreviousTag $prev } else { ./release/Create-Windows-Release.ps1 } - name: Make Linux script executable run: chmod +x release/create-linux-release.sh - name: Run Linux packaging script + env: + PREV_TAG: ${{ steps.tags.outputs.previous_tag }} run: | - prev='${{ steps.tags.outputs.previous_tag }}' + prev="$PREV_TAG" if [ -n "$prev" ]; then ./release/create-linux-release.sh -p "$prev"; else ./release/create-linux-release.sh; fi - name: Compute checksums & JSON manifest + env: + CURRENT_TAG: ${{ steps.tags.outputs.current_tag }} + PREV_TAG: ${{ steps.tags.outputs.previous_tag }} run: | cd release echo "Generating SHA256SUMS.txt" @@ -82,7 +93,7 @@ jobs: for f in Nerdy-RMMScripts-Windows.zip Nerdy-RMMScripts-Linux.zip ReleaseNotes-*.md; do if [ -f "$f" ]; then sha256sum "$f" >> SHA256SUMS.txt; fi done - echo '{"generated":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","tag":"'${{ steps.tags.outputs.current_tag }}'","previous":"'${{ steps.tags.outputs.previous_tag }}'","archives":[' > manifest.json + echo '{"generated":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","tag":"'"$CURRENT_TAG"'","previous":"'"$PREV_TAG"'","archives":[' > manifest.json first=1 for a in Nerdy-RMMScripts-Windows.zip Nerdy-RMMScripts-Linux.zip; do if [ -f "$a" ]; then @@ -104,7 +115,7 @@ jobs: echo "Manifest:" && cat manifest.json - name: Upload artifacts (always) - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: packaged-scripts path: | @@ -143,9 +154,10 @@ jobs: - name: Build changes markdown id: changes + env: + PREV: ${{ steps.tags.outputs.previous_tag }} + CUR: ${{ steps.version.outputs.new_tag || steps.tags.outputs.current_tag }} run: | - PREV='${{ steps.tags.outputs.previous_tag }}' - CUR='${{ steps.version.outputs.new_tag || steps.tags.outputs.current_tag }}' if [ -n "$PREV" ]; then TABLE=$(git diff --name-status "$PREV" "$CUR" | awk 'BEGIN{print "| Status | File |"; print "|--------|------|";} {s=$1; f=$2; if(s=="R"){f=$3} emoji=""; if(s=="A")emoji="🆕"; else if(s=="M")emoji="✏️"; else if(s=="D")emoji="🗑️"; else if(s=="R")emoji="♻️"; else if(s=="C")emoji="📄"; print "| " emoji " " s " | " f " |"}') COUNTS=$(git diff --name-status "$PREV" "$CUR" | awk '{c[$1]++} END{for(k in c){emoji=""; if(k=="A")emoji="🆕"; else if(k=="M")emoji="✏️"; else if(k=="D")emoji="🗑️"; else if(k=="R")emoji="♻️"; else if(k=="C")emoji="📄"; print "- " emoji " " k ":" c[k]}}') @@ -162,7 +174,7 @@ jobs: - name: Auto create release from push if: steps.version.outputs.skip != 'true' && steps.version.outputs.new_tag && github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: tag_name: ${{ steps.version.outputs.new_tag }} name: ${{ steps.version.outputs.new_tag }} @@ -190,7 +202,7 @@ jobs: - name: Attach assets to release if: github.event_name == 'release' && github.event.inputs.dryRun != 'true' - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: files: | release/${{ env.WINDOWS_ARCHIVE }} diff --git a/.github/workflows/release-descriptor.yml b/.github/workflows/release-descriptor.yml index d5444c8..2bd6bc8 100644 --- a/.github/workflows/release-descriptor.yml +++ b/.github/workflows/release-descriptor.yml @@ -7,9 +7,7 @@ on: workflow_dispatch: permissions: - contents: write - actions: read - packages: read + contents: read concurrency: group: release-descriptor @@ -18,9 +16,11 @@ concurrency: jobs: release: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 @@ -43,19 +43,21 @@ jobs: - name: Build archives + diff if: steps.version.outputs.skip != 'true' + env: + VERSION: ${{ steps.version.outputs.version }} run: | set -euo pipefail mkdir -p release_out zip -r release_out/Nerdy-RMMScripts-Windows.zip Windows -x "*/.git/*" zip -r release_out/Nerdy-RMMScripts-Linux.zip Linux -x "*/.git/*" - PREV="$(git tag --sort=-creatordate | grep -v "^${{ steps.version.outputs.version }}$" | grep '^v' | head -1 || true)" + PREV="$(git tag --sort=-creatordate | grep -v "^${VERSION}$" | grep '^v' | head -1 || true)" if [ -n "${PREV}" ]; then git diff --name-status "${PREV}"...HEAD > release_out/diff.txt; else git diff --name-status HEAD > release_out/diff.txt; fi A=$(grep '^A' release_out/diff.txt | wc -l | tr -d ' ' || true) M=$(grep '^M' release_out/diff.txt | wc -l | tr -d ' ' || true) D=$(grep '^D' release_out/diff.txt | wc -l | tr -d ' ' || true) R=$(grep '^R' release_out/diff.txt | wc -l | tr -d ' ' || true) { - echo "## 🚀 Release ${{ steps.version.outputs.version }}" + echo "## 🚀 Release ${VERSION}" [ -n "${PREV}" ] && echo "Previous Tag: ${PREV}" || echo "Initial Release" echo echo "### 📊 Changes" @@ -78,16 +80,18 @@ jobs: - name: Create tag if: steps.version.outputs.skip != 'true' + env: + VERSION: ${{ steps.version.outputs.version }} run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "${{ steps.version.outputs.version }}" - git push origin "${{ steps.version.outputs.version }}" + git tag "${VERSION}" + git push origin "${VERSION}" - name: Publish release if: steps.version.outputs.skip != 'true' - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: tag_name: ${{ steps.version.outputs.version }} name: ${{ steps.version.outputs.version }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..6e6dc35 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,31 @@ +name: OpenSSF Scorecard + +on: + push: + branches: [main] + schedule: + - cron: "41 5 * * 1" + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + security-events: write + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + - uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + sarif_file: results.sarif diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2478ef --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Nerdy Technician + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..674ad54 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Reporting a vulnerability + +Please **do not** open a public issue for security problems. + +Report vulnerabilities privately through GitHub: +[Report a vulnerability](https://github.com/Nerdy-Technician/Nerdy-RMMScripts/security/advisories/new) + +Include the affected script, what the issue is, and steps to reproduce. You'll get a response as soon as possible, and credit once a fix is out if you'd like it. + +## Scope + +This repository contains RMM scripts for Windows (PowerShell/Batch) and Linux (Bash). Reports about a script that could expose credentials, weaken a system's security, or be abused when run by an RMM agent are especially welcome. + +Always review a script before running it in your environment. From 613b8efac29a0b21dc1156b6e8ddb338ef713836 Mon Sep 17 00:00:00 2001 From: Nerdy-Technician Date: Wed, 30 Sep 2026 22:41:47 +0100 Subject: [PATCH 2/2] ci: fix shellcheck findings in release workflows --- .github/workflows/package-release.yml | 42 +++++++++++++----------- .github/workflows/release-descriptor.yml | 8 ++--- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index 3bed114..c696bd2 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -61,8 +61,8 @@ jobs: PREV_TAG="$t"; break done echo "Previous tag: $PREV_TAG" - echo "current_tag=$CURRENT_TAG" >> $GITHUB_OUTPUT - echo "previous_tag=$PREV_TAG" >> $GITHUB_OUTPUT + echo "current_tag=$CURRENT_TAG" >> "$GITHUB_OUTPUT" + echo "previous_tag=$PREV_TAG" >> "$GITHUB_OUTPUT" - name: Run Windows packaging script (PowerShell Core) shell: pwsh @@ -93,13 +93,13 @@ jobs: for f in Nerdy-RMMScripts-Windows.zip Nerdy-RMMScripts-Linux.zip ReleaseNotes-*.md; do if [ -f "$f" ]; then sha256sum "$f" >> SHA256SUMS.txt; fi done - echo '{"generated":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","tag":"'"$CURRENT_TAG"'","previous":"'"$PREV_TAG"'","archives":[' > manifest.json + echo '{"generated":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","tag":"'"$CURRENT_TAG"'","previous":"'"$PREV_TAG"'","archives":[' > manifest.json first=1 for a in Nerdy-RMMScripts-Windows.zip Nerdy-RMMScripts-Linux.zip; do if [ -f "$a" ]; then size=$(stat -c %s "$a") - if [ $first -eq 0 ]; then echo ',' >> manifest.json; fi - echo '{"name":"'$a'","size":'$size'}' >> manifest.json + if [ "$first" -eq 0 ]; then echo ',' >> manifest.json; fi + echo '{"name":"'"$a"'","size":'"$size"'}' >> manifest.json first=0 fi done @@ -107,8 +107,8 @@ jobs: first=1 for n in ReleaseNotes-*.md; do size=$(stat -c %s "$n") - if [ $first -eq 0 ]; then echo ',' >> manifest.json; fi - echo '{"name":"'$n'","size":'$size'}' >> manifest.json + if [ "$first" -eq 0 ]; then echo ',' >> manifest.json; fi + echo '{"name":"'"$n"'","size":'"$size"'}' >> manifest.json first=0 done echo ']}' >> manifest.json @@ -132,7 +132,7 @@ jobs: run: | COMMIT_MSG=$(git log -1 --pretty=%B) if echo "$COMMIT_MSG" | grep -qi '\[skip release\]'; then - echo "skip=true" >> $GITHUB_OUTPUT + echo "skip=true" >> "$GITHUB_OUTPUT" echo "Release skipped by commit message directive."; exit 0 fi @@ -141,14 +141,14 @@ jobs: NEW_TAG="v0.1.0" else LATEST=$(echo "$TAGS" | sort -V | tail -n1) - MAJOR=$(echo $LATEST | sed -E 's/v([0-9]+)\.[0-9]+\.[0-9]+/\1/') - MINOR=$(echo $LATEST | sed -E 's/v[0-9]+\.([0-9]+)\.[0-9]+/\1/') - PATCH=$(echo $LATEST | sed -E 's/v[0-9]+\.[0-9]+\.([0-9]+)/\1/') + MAJOR=$(echo "$LATEST" | sed -E 's/v([0-9]+)\.[0-9]+\.[0-9]+/\1/') + MINOR=$(echo "$LATEST" | sed -E 's/v[0-9]+\.([0-9]+)\.[0-9]+/\1/') + PATCH=$(echo "$LATEST" | sed -E 's/v[0-9]+\.[0-9]+\.([0-9]+)/\1/') PATCH=$((PATCH+1)) NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" fi echo "Computed next tag: $NEW_TAG" - echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT + echo "new_tag=$NEW_TAG" >> "$GITHUB_OUTPUT" git tag "$NEW_TAG" git push origin "$NEW_TAG" @@ -161,15 +161,17 @@ jobs: if [ -n "$PREV" ]; then TABLE=$(git diff --name-status "$PREV" "$CUR" | awk 'BEGIN{print "| Status | File |"; print "|--------|------|";} {s=$1; f=$2; if(s=="R"){f=$3} emoji=""; if(s=="A")emoji="🆕"; else if(s=="M")emoji="✏️"; else if(s=="D")emoji="🗑️"; else if(s=="R")emoji="♻️"; else if(s=="C")emoji="📄"; print "| " emoji " " s " | " f " |"}') COUNTS=$(git diff --name-status "$PREV" "$CUR" | awk '{c[$1]++} END{for(k in c){emoji=""; if(k=="A")emoji="🆕"; else if(k=="M")emoji="✏️"; else if(k=="D")emoji="🗑️"; else if(k=="R")emoji="♻️"; else if(k=="C")emoji="📄"; print "- " emoji " " k ":" c[k]}}') - echo "table<> $GITHUB_OUTPUT - echo "$TABLE" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - echo "counts<> $GITHUB_OUTPUT - echo "$COUNTS" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - echo "initial=false" >> $GITHUB_OUTPUT + { + echo "table<> "$GITHUB_OUTPUT" else - echo "initial=true" >> $GITHUB_OUTPUT + echo "initial=true" >> "$GITHUB_OUTPUT" fi - name: Auto create release from push diff --git a/.github/workflows/release-descriptor.yml b/.github/workflows/release-descriptor.yml index 2bd6bc8..cd13c27 100644 --- a/.github/workflows/release-descriptor.yml +++ b/.github/workflows/release-descriptor.yml @@ -52,10 +52,10 @@ jobs: zip -r release_out/Nerdy-RMMScripts-Linux.zip Linux -x "*/.git/*" PREV="$(git tag --sort=-creatordate | grep -v "^${VERSION}$" | grep '^v' | head -1 || true)" if [ -n "${PREV}" ]; then git diff --name-status "${PREV}"...HEAD > release_out/diff.txt; else git diff --name-status HEAD > release_out/diff.txt; fi - A=$(grep '^A' release_out/diff.txt | wc -l | tr -d ' ' || true) - M=$(grep '^M' release_out/diff.txt | wc -l | tr -d ' ' || true) - D=$(grep '^D' release_out/diff.txt | wc -l | tr -d ' ' || true) - R=$(grep '^R' release_out/diff.txt | wc -l | tr -d ' ' || true) + A=$(grep -c '^A' release_out/diff.txt || true) + M=$(grep -c '^M' release_out/diff.txt || true) + D=$(grep -c '^D' release_out/diff.txt || true) + R=$(grep -c '^R' release_out/diff.txt || true) { echo "## 🚀 Release ${VERSION}" [ -n "${PREV}" ] && echo "Previous Tag: ${PREV}" || echo "Initial Release"