Skip to content
Closed
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
168 changes: 168 additions & 0 deletions .github/scripts/publish-device-releases.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
set -euo pipefail

if [ ! -d out ]; then
echo "Expected build output directory 'out' to exist."
exit 1
fi

if ! command -v gh >/dev/null 2>&1; then
echo "GitHub CLI is required to publish device releases."
exit 1
fi

repo="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}"
version="${FIRMWARE_VERSION:-${GIT_TAG_VERSION:-dev}}"
commit_hash="$(git rev-parse --short HEAD)"
release_prefix="${RELEASE_PREFIX:-firmware}"
release_name_prefix="${RELEASE_NAME_PREFIX:-Firmware}"

manifest_dir="$(mktemp -d)"
manifest_path="${manifest_dir}/firmware-manifest.json"
work_dir="$(mktemp -d)"

cleanup() {
rm -rf "${manifest_dir}" "${work_dir}"
}
trap cleanup EXIT

slugify() {
printf '%s' "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//'
}

asset_ext() {
local file="$1"
if [[ "$file" == *-merged.bin ]]; then
printf 'merged.bin'
return
fi
printf '%s' "${file##*.}"
}

json_escape() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
value="${value//$'\n'/\\n}"
value="${value//$'\r'/}"
printf '%s' "$value"
}

mapfile -t outputs < <(find out -maxdepth 1 -type f | sort)
if [ "${#outputs[@]}" -eq 0 ]; then
echo "No firmware files found in out."
exit 1
fi

declare -A envs=()
version_suffix="-${version}-${commit_hash}"

for file in "${outputs[@]}"; do
base="$(basename "$file")"
stem="${base%.*}"
if [[ "$base" == *-merged.bin ]]; then
stem="${base%-merged.bin}"
fi
env_name="${stem%${version_suffix}}"
if [ "$env_name" = "$stem" ]; then
echo "Skipping '${base}', it does not match expected version suffix '${version_suffix}'."
continue
fi
envs["$env_name"]=1
done

printf '{\n "version": "%s",\n "commit": "%s",\n "generated_at": "%s",\n "firmwares": [\n' \
"$(json_escape "$version")" \
"$(json_escape "$commit_hash")" \
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" > "$manifest_path"

first_env=1
for env_name in $(printf '%s\n' "${!envs[@]}" | sort); do
tag="latest-$(slugify "${release_prefix}-${env_name}")"
release_name="${release_name_prefix}: ${env_name}"
target_dir="${work_dir}/${env_name}"
mkdir -p "$target_dir"

mapfile -t env_files < <(find out -maxdepth 1 -type f -name "${env_name}-${version}-${commit_hash}*" | sort)
if [ "${#env_files[@]}" -eq 0 ]; then
continue
fi

upload_args=()
asset_json=""
first_asset=1
for file in "${env_files[@]}"; do
ext="$(asset_ext "$(basename "$file")")"
asset_name="${env_name}.${ext}"
stable_file="${target_dir}/${asset_name}"
cp "$file" "$stable_file"
upload_args+=("${stable_file}")

download_url="https://github.com/${repo}/releases/download/${tag}/${asset_name}"
if [ "$first_asset" -eq 0 ]; then
asset_json="${asset_json},"
fi
asset_json="${asset_json}
{
\"name\": \"$(json_escape "$asset_name")\",
\"type\": \"$(json_escape "$ext")\",
\"download_url\": \"$(json_escape "$download_url")\"
}"
first_asset=0
done

body_file="${target_dir}/release-body.md"
cat > "$body_file" <<EOF
Automated latest build for \`${env_name}\`.

- Version: \`${version}\`
- Commit: \`${commit_hash}\`
- Source workflow: [${GITHUB_WORKFLOW}](${GITHUB_SERVER_URL}/${repo}/actions/runs/${GITHUB_RUN_ID})

These assets use stable filenames so Toolbox can link to them directly.
EOF

git tag -f "$tag" "$GITHUB_SHA"
git push -f origin "refs/tags/${tag}"

if gh release view "$tag" --repo "$repo" >/dev/null 2>&1; then
gh release edit "$tag" --repo "$repo" --title "$release_name" --notes-file "$body_file" --latest=false
else
gh release create "$tag" --repo "$repo" --title "$release_name" --notes-file "$body_file" --target "$GITHUB_SHA" --latest=false
fi

gh release upload "$tag" "${upload_args[@]}" --repo "$repo" --clobber

if [ "$first_env" -eq 0 ]; then
printf ',\n' >> "$manifest_path"
fi
printf ' {\n "id": "%s",\n "tag": "%s",\n "release_url": "%s",\n "assets": [%s\n ]\n }' \
"$(json_escape "$env_name")" \
"$(json_escape "$tag")" \
"$(json_escape "https://github.com/${repo}/releases/tag/${tag}")" \
"$asset_json" >> "$manifest_path"
first_env=0
done

printf '\n ]\n}\n' >> "$manifest_path"

manifest_tag="latest-$(slugify "${release_prefix}-manifest")"
manifest_body="${manifest_dir}/manifest-body.md"
cat > "$manifest_body" <<EOF
Machine-readable firmware download manifest for Toolbox.

Use \`firmware-manifest.json\` to map device build environments to stable GitHub Release asset URLs.
EOF

git tag -f "$manifest_tag" "$GITHUB_SHA"
git push -f origin "refs/tags/${manifest_tag}"

if gh release view "$manifest_tag" --repo "$repo" >/dev/null 2>&1; then
gh release edit "$manifest_tag" --repo "$repo" --title "${release_name_prefix} manifest" --notes-file "$manifest_body" --latest=false
else
gh release create "$manifest_tag" --repo "$repo" --title "${release_name_prefix} manifest" --notes-file "$manifest_body" --target "$GITHUB_SHA" --latest=false
fi

gh release upload "$manifest_tag" "$manifest_path" --repo "$repo" --clobber
52 changes: 52 additions & 0 deletions .github/workflows/build-mqtt-firmwares.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build MQTT Firmwares

permissions:
contents: write

on:
workflow_dispatch:
push:
tags:
- 'mqtt-*'

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Clone Repo
uses: actions/checkout@v4

- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment

- name: Build MQTT Firmwares
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-mqtt-firmwares

- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v4
with:
name: mqtt-firmwares
path: out

- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
name: MQTT Firmware ${{ env.GIT_TAG_VERSION }}
body: ""
draft: true
files: out/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish Device Releases
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
RELEASE_PREFIX: mqtt
RELEASE_NAME_PREFIX: MQTT Firmware
run: /usr/bin/env bash .github/scripts/publish-device-releases.sh
52 changes: 52 additions & 0 deletions .github/workflows/build-repeater-firmwares-mqtt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build Repeater Firmwares MQTT

permissions:
contents: write

on:
workflow_dispatch:
push:
tags:
- 'repeater-*'

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Clone Repo
uses: actions/checkout@v4

- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment

- name: Build Repeater Firmwares MQTT
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-repeater-firmwares-mqtt

- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v4
with:
name: repeater-firmwares-mqtt
path: out

- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
name: Repeater Firmware MQTT ${{ env.FIRMWARE_VERSION }}
body: ""
draft: true
files: out/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish Device Releases
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
RELEASE_PREFIX: repeater-mqtt
RELEASE_NAME_PREFIX: Repeater MQTT Firmware
run: /usr/bin/env bash .github/scripts/publish-device-releases.sh
52 changes: 52 additions & 0 deletions .github/workflows/build-room-server-firmwares-mqtt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build Room Server Firmwares MQTT

permissions:
contents: write

on:
workflow_dispatch:
push:
tags:
- 'room-server-*'

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Clone Repo
uses: actions/checkout@v4

- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment

- name: Build MQTT Firmwares
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-room-server-firmwares-mqtt

- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v4
with:
name: room-server-firmwares-mqtt
path: out

- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
name: Room Server Firmware MQTT ${{ env.FIRMWARE_VERSION }}
body: ""
draft: true
files: out/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish Device Releases
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
RELEASE_PREFIX: room-server-mqtt
RELEASE_NAME_PREFIX: Room Server MQTT Firmware
run: /usr/bin/env bash .github/scripts/publish-device-releases.sh
36 changes: 19 additions & 17 deletions .github/workflows/pr-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,26 @@ jobs:
fail-fast: false
matrix:
environment:
# ESP32-S3 (most common platform)
- Heltec_v3_companion_radio_ble
- Heltec_v3_repeater
- Heltec_v3_room_server
# nRF52
- RAK_4631_companion_radio_ble
- RAK_4631_repeater
- RAK_4631_room_server
# RP2040
- PicoW_repeater
# STM32
- wio-e5-mini_repeater
# ESP32-S3 (Heltec v3)
- Heltec_v3_repeater_observer_mqtt
- Heltec_v3_room_server_observer_mqtt
# ESP32-S3 (Heltec v4)
- heltec_v4_repeater_observer_mqtt
- heltec_v4_room_server_observer_mqtt
# ESP32-S3 (LilyGo T3S3)
- LilyGo_T3S3_sx1262_repeater_observer_mqtt
- LilyGo_T3S3_sx1262_room_server_observer_mqtt
# ESP32-S3 (T-Beam Supreme)
- T_Beam_S3_Supreme_SX1262_repeater_observer_mqtt
# ESP32 (T-Beam SX1262)
- Tbeam_SX1262_repeater_observer_mqtt
- Tbeam_SX1262_room_server_observer_mqtt
# ESP32 (T-Beam SX1276)
- Tbeam_SX1276_repeater_observer_mqtt
- Tbeam_SX1276_room_server_observer_mqtt
# ESP32-C6
- LilyGo_Tlora_C6_repeater_
# LR1110 (nRF52)
- wio_wm1110_repeater
# SX1276 (ESP32)
- Tbeam_SX1276_repeater
- LilyGo_Tlora_C6_repeater_observer_mqtt
- LilyGo_Tlora_C6_room_server_observer_mqtt

steps:
- name: Clone Repo
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ compile_commands.json
.venv/
venv/
platformio.local.ini
.claude/settings.local.json
Loading