This repository was archived by the owner on Jun 26, 2026. It is now read-only.
Fix webflasher DFU reliability, config UX, and batch EEPROM commits (… #32
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release firmware | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| # Least privilege at the top level: the build job only needs to read the | |
| # repo and upload workflow artifacts (artifacts don't need contents:write). | |
| # The publish job re-declares contents:write at the job level (below), so | |
| # only that one job can edit releases / push to master. | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.board }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| board: | |
| - Faketec | |
| - RAK4631 | |
| - XIAO_nRF52840 | |
| - Heltec_T114 | |
| - RAK3401 | |
| - T-Echo | |
| - T1000E | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Cache PlatformIO toolchains and libraries | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: | | |
| ~/.cache/pip | |
| ~/.platformio/.cache | |
| ~/.platformio/packages | |
| ~/.platformio/platforms | |
| key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pio- | |
| - name: Install PlatformIO Core | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install --upgrade platformio | |
| - name: Build ${{ matrix.board }} | |
| run: pio run -e ${{ matrix.board }} | |
| - name: Stage release assets | |
| id: stage | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-assets | |
| if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then | |
| VERSION="manual-$(date +%Y%m%d-%H%M%S)" | |
| else | |
| VERSION="${GITHUB_REF_NAME}" | |
| fi | |
| BOARD="${{ matrix.board }}" | |
| BASE="reticulum-rnode-${BOARD}-${VERSION}" | |
| cp ".pio/build/${BOARD}/firmware.zip" "release-assets/${BASE}.zip" | |
| cp ".pio/build/${BOARD}/firmware.hex" "release-assets/${BASE}.hex" | |
| python scripts/hex2uf2.py ".pio/build/${BOARD}/firmware.hex" "release-assets/${BASE}.uf2" | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "base=${BASE}" >> "$GITHUB_OUTPUT" | |
| ls -la release-assets/ | |
| - name: Upload workflow artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: firmware-${{ matrix.board }}-${{ steps.stage.outputs.version }} | |
| path: release-assets/* | |
| retention-days: 30 | |
| publish: | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/') | |
| runs-on: ubuntu-latest | |
| # Manual approval gate. This job publishes UNSIGNED firmware to the public | |
| # web flasher (docs/firmware/ on GitHub Pages), so it must not run without | |
| # a human in the loop. Configure the gate once in: | |
| # Settings -> Environments -> "firmware-release" -> Required reviewers. | |
| # Until a reviewer is added there, this only namespaces the job; ADD THE | |
| # REVIEWER to actually arm the gate. | |
| environment: firmware-release | |
| permissions: | |
| contents: write | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| GH_TOKEN: ${{ github.token }} | |
| steps: | |
| - name: Checkout master | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Download all board artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| path: downloaded-artifacts | |
| pattern: firmware-* | |
| - name: Stage release assets (flat layout) | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-assets | |
| find downloaded-artifacts -type f \( -name '*.zip' -o -name '*.hex' -o -name '*.uf2' \) \ | |
| -exec cp -v {} release-assets/ \; | |
| echo "Release assets staged:" | |
| ls -la release-assets/ | |
| - name: Generate SHA256 checksums for all firmware artifacts | |
| # The firmware is flashed UNSIGNED, so a checksums manifest is the only | |
| # integrity reference a user (or the web flasher) has. Generated over | |
| # every staged artifact and shipped both as a release asset and under | |
| # docs/firmware/<tag>/ (copied by the staging step below, since it's | |
| # inside release-assets/). | |
| run: | | |
| set -euo pipefail | |
| cd release-assets | |
| sha256sum * > SHA256SUMS.txt | |
| echo "Checksums:" | |
| cat SHA256SUMS.txt | |
| - name: Delete existing Release for a clean slate | |
| # Only delete when a release already exists for this tag (so the FIRST | |
| # release of a tag doesn't fail), and make the substitution LOUD — a | |
| # re-pushed tag replacing already-downloaded assets is a distribution- | |
| # integrity foot-gun, so we warn rather than swallow it. No `|| true`: | |
| # an unexpected delete failure now fails the job instead of hiding. | |
| run: | | |
| set -euo pipefail | |
| if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then | |
| echo "::warning::Release ${GITHUB_REF_NAME} already exists — deleting and recreating. Assets published under this tag are being SUBSTITUTED." | |
| gh release delete "${GITHUB_REF_NAME}" --yes --cleanup-tag=false | |
| else | |
| echo "No existing release for ${GITHUB_REF_NAME}; creating fresh." | |
| fi | |
| - name: Create GitHub Release with all board assets | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF_NAME}" | |
| PRERELEASE_FLAG="" | |
| if [[ "$TAG" == *-* ]]; then | |
| PRERELEASE_FLAG="--prerelease" | |
| fi | |
| cat > release-body.md <<'EOF' | |
| ## Flashing | |
| The easiest way to flash is the **web flasher** — visit | |
| https://thatSFguy.github.io/reticulum-rnode/ | |
| and pick this version from the dropdown. No toolchain | |
| install required, works in any Chromium-based browser. | |
| ## Manual flashing | |
| Per-board assets attached to this release: | |
| | File | How to use | | |
| |-------------------------|-------------------------------------------------------------------------| | |
| | `*.zip` | `adafruit-nrfutil dfu serial -pkg <zip> -p <port>`, or the web flasher | | |
| | `*.uf2` | Drag-and-drop onto NICENANO/XIAOBOOT USB drive (double-tap reset first) | | |
| | `*.hex` | nRFConnect Programmer, J-Link Commander, or any SWD tool | | |
| Put the board in **bootloader mode** (double-tap reset within | |
| ~500 ms) before flashing over serial or UF2. | |
| EOF | |
| gh release create "$TAG" \ | |
| --title "$TAG" \ | |
| --notes-file release-body.md \ | |
| $PRERELEASE_FLAG \ | |
| release-assets/* | |
| - name: Stage firmware under docs/firmware/<tag>/ for webflasher | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF_NAME}" | |
| TARGET="docs/firmware/${TAG}" | |
| mkdir -p "${TARGET}" | |
| cp -v release-assets/* "${TARGET}/" | |
| ls -la "${TARGET}" | |
| - name: Regenerate manifest | |
| run: python scripts/gen_firmware_manifest.py | |
| - name: Commit webflasher assets and push | |
| 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 docs/firmware/ | |
| if git diff --cached --quiet; then | |
| echo "No firmware changes to commit." | |
| exit 0 | |
| fi | |
| git commit -m "ci: publish ${GITHUB_REF_NAME} firmware to docs/firmware/" | |
| git push origin HEAD:master |