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
2 changes: 0 additions & 2 deletions .github/actions/setup-base/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ runs:
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Install dependencies
shell: bash
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/build_debian_src.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ jobs:
with:
submodules: recursive
path: meshtasticd
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Install deps
shell: bash
Expand All @@ -42,6 +40,7 @@ jobs:
sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
- name: Import GPG key
if: github.event_name != 'pull_request' && github.event_name != 'pull_request_target'
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
Expand All @@ -60,7 +59,7 @@ jobs:
run: debian/ci_pack_sdeb.sh
env:
SERIES: ${{ inputs.series }}
GPG_KEY_ID: ${{ steps.gpg.outputs.keyid }}
GPG_KEY_ID: ${{ steps.gpg.outputs.keyid || '' }}
PKG_VERSION: ${{ steps.version.outputs.deb }}

- name: Store binaries as an artifact
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/build_firmware.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ jobs:
- uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Build ${{ inputs.platform }}
id: build
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/docker_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Get release version string
run: |
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/docker_manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Get release version string
run: |
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/hook_copr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{ github.ref }}
repository: ${{ github.repository }}

- name: Trigger COPR build
uses: vidplace7/copr-build@main
Expand Down
184 changes: 150 additions & 34 deletions .github/workflows/main_matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ on:
- "**.md"
- version.properties

# Note: This is different from "pull_request". Need to specify ref when doing checkouts.
pull_request_target:
pull_request:
branches:
- master
- develop
Expand Down Expand Up @@ -88,8 +87,6 @@ jobs:
- uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Check ${{ matrix.check.board }}
uses: meshtastic/gh-action-firmware@main
with:
Expand Down Expand Up @@ -173,9 +170,6 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- uses: actions/download-artifact@v8
with:
Expand Down Expand Up @@ -238,47 +232,169 @@ jobs:
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}

shame:
firmware-size-report:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
permissions:
contents: read
actions: read
Comment on lines +238 to +240
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job runs on pull_request_target and checks out/runs code from the PR branch while granting pull-requests: write (and actions: read). That combination allows untrusted PR code to use the elevated GITHUB_TOKEN to modify PR metadata/comments (and potentially exfiltrate token-scoped data). Consider generating the report using code from the base branch (checkout github.event.pull_request.base.sha or default branch) and only using PR artifacts as inputs, or split into two jobs so the commenting job never executes PR-provided code.

Copilot uses AI. Check for mistakes.
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v6
if: github.event_name == 'pull_request_target'
with:
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
fetch-depth: 0
- name: Download the current manifests

Comment on lines 244 to +245
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checkout uses github.event.pull_request.* fields unconditionally. On push events those fields are absent, and on pull_request_target it checks out untrusted PR code. If you keep this job running on both push (to create baselines) and pull_request_target (to comment), it’s safer/cleaner to conditionally set ref/repository only for PR events, and otherwise do a normal checkout of the current repo/commit (or the PR base SHA for the reporting logic).

Copilot uses AI. Check for mistakes.
- name: Download current manifests
uses: actions/download-artifact@v8
with:
path: ./manifests-new/
path: ./manifests/
pattern: manifest-*
merge-multiple: true
- name: Upload combined manifests for later commit and global stats crunching.

- name: Collect current firmware sizes
run: python3 bin/collect_sizes.py ./manifests/ ./current-sizes.json

- name: Upload size report artifact
uses: actions/upload-artifact@v7
id: upload-manifest
with:
name: manifests-${{ github.sha }}
name: firmware-sizes-${{ github.sha }}
overwrite: true
path: manifests-new/*.mt.json
- name: Find the merge base
if: github.event_name == 'pull_request_target'
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
path: ./current-sizes.json
retention-days: 90

- name: Download baseline sizes from develop
if: github.event_name == 'pull_request'
continue-on-error: true
id: baseline-develop
env:
base: ${{ github.base_ref }}
head: ${{ github.sha }}
# Currently broken (for-loop through EVERY artifact -- rate limiting)
# - name: Download the old manifests
# if: github.event_name == 'pull_request_target'
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
# env:
# GH_TOKEN: ${{ github.token }}
# merge_base: ${{ env.MERGE_BASE }}
# repo: ${{ github.repository }}
# - name: Do scan and post comment
# if: github.event_name == 'pull_request_target'
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
GH_TOKEN: ${{ github.token }}
run: |
# Find the latest successful CI run on develop and get its firmware-sizes artifact
RUN_ID=$(gh run list -R "${{ github.repository }}" \
--workflow CI --branch develop --status success \
--limit 1 --json databaseId --jq '.[0].databaseId // empty')
if [ -n "$RUN_ID" ]; then
Comment on lines +272 to +275
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gh run list ... --jq '.[0].databaseId' will output null (a non-empty string) when there are no matching runs. The subsequent if [ -n "$RUN_ID" ] check will treat null as valid and the download logic will fail. Consider changing the jq to '.[0].databaseId // empty' (or explicitly checking RUN_ID != "null").

Copilot uses AI. Check for mistakes.
ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | .name' | head -1)
if [ -n "$ARTIFACT_NAME" ]; then
gh run download "$RUN_ID" -R "${{ github.repository }}" \
--name "$ARTIFACT_NAME" --dir ./baseline-develop/
cp ./baseline-develop/current-sizes.json ./develop-sizes.json
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi

- name: Download baseline sizes from master
if: github.event_name == 'pull_request'
continue-on-error: true
id: baseline-master
env:
GH_TOKEN: ${{ github.token }}
run: |
RUN_ID=$(gh run list -R "${{ github.repository }}" \
--workflow CI --branch master --status success \
--limit 1 --json databaseId --jq '.[0].databaseId // empty')
if [ -n "$RUN_ID" ]; then
ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | .name' | head -1)
if [ -n "$ARTIFACT_NAME" ]; then
gh run download "$RUN_ID" -R "${{ github.repository }}" \
--name "$ARTIFACT_NAME" --dir ./baseline-master/
cp ./baseline-master/current-sizes.json ./master-sizes.json
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi

- name: Generate size comparison report
if: github.event_name == 'pull_request'
id: report
run: |
ARGS="./current-sizes.json"
if [ -f ./develop-sizes.json ]; then
ARGS="$ARGS --baseline develop:./develop-sizes.json"
fi
if [ -f ./master-sizes.json ]; then
ARGS="$ARGS --baseline master:./master-sizes.json"
fi
REPORT=$(python3 bin/size_report.py $ARGS)
if [ -z "$REPORT" ]; then
echo "has_report=false" >> "$GITHUB_OUTPUT"
else
echo "has_report=true" >> "$GITHUB_OUTPUT"
{
echo '<!-- firmware-size-report -->'
echo '# Firmware Size Report'
echo ''
echo "$REPORT"
echo ''
echo '---'
echo "*Updated for ${{ github.sha }}*"
} > ./size-report.md
cat ./size-report.md >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload size report
if: github.event_name == 'pull_request' && steps.report.outputs.has_report == 'true'
uses: actions/upload-artifact@v7
with:
name: size-report
path: ./size-report.md
retention-days: 5

firmware-size-comment:
if: github.event_name == 'pull_request' && github.repository == 'meshtastic/firmware'
continue-on-error: true
permissions:
pull-requests: write
runs-on: ubuntu-latest
needs: [firmware-size-report]
steps:
- name: Download size report
id: download
uses: actions/download-artifact@v8
continue-on-error: true
with:
name: size-report

- name: Post or update PR comment
if: steps.download.outcome == 'success'
uses: actions/github-script@v8
Comment on lines +366 to +368
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says this should be added to PR descriptions, but this workflow posts/updates a PR comment instead. If the intent is to update the PR body, use the Pull Requests API (pulls.update) to update pull_request.body (and still keep the marker to make it idempotent).

Copilot uses AI. Check for mistakes.
with:
script: |
const fs = require('fs');
const marker = '<!-- firmware-size-report -->';
const body = fs.readFileSync('./size-report.md', 'utf8');
const prNumber = context.payload.pull_request.number;

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});

const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}

release-artifacts:
runs-on: ubuntu-latest
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/package_pio_deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Setup Python
uses: actions/setup-python@v6
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/package_ppa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ jobs:
with:
submodules: recursive
path: meshtasticd
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Install deps
shell: bash
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/test_native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
submodules: recursive

- name: Setup native build
Expand Down Expand Up @@ -72,8 +70,6 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
submodules: recursive

- name: Setup native build
Expand Down Expand Up @@ -128,9 +124,6 @@ jobs:
if: always()
steps:
- uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
Expand Down
53 changes: 53 additions & 0 deletions bin/collect_sizes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python3

"""Collect firmware binary sizes from manifest (.mt.json) files into a single report."""

import json
import os
import sys


def collect_sizes(manifest_dir):
"""Scan manifest_dir for .mt.json files and return {board: size_bytes} dict."""
sizes = {}
for fname in sorted(os.listdir(manifest_dir)):
if not fname.endswith(".mt.json"):
continue
path = os.path.join(manifest_dir, fname)
with open(path) as f:
data = json.load(f)
board = data.get("platformioTarget", fname.replace(".mt.json", ""))
# Find the main firmware .bin size (largest .bin, excluding OTA/littlefs/bleota)
bin_size = None
for entry in data.get("files", []):
name = entry.get("name", "")
if name.startswith("firmware-") and name.endswith(".bin"):
bin_size = entry["bytes"]
break
# Fallback: any .bin that isn't ota/littlefs/bleota
if bin_size is None:
for entry in data.get("files", []):
name = entry.get("name", "")
if name.endswith(".bin") and not any(
x in name for x in ["littlefs", "bleota", "ota"]
):
bin_size = entry["bytes"]
break
if bin_size is not None:
sizes[board] = bin_size
return sizes


if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <manifest_dir> <output.json>", file=sys.stderr)
sys.exit(1)

manifest_dir = sys.argv[1]
output_path = sys.argv[2]

sizes = collect_sizes(manifest_dir)
with open(output_path, "w") as f:
json.dump(sizes, f, indent=2, sort_keys=True)

print(f"Collected sizes for {len(sizes)} targets -> {output_path}")
Loading