name: Build and Release OpenMQTTGateway Firmware on: push: branches: - development tags: - '*' workflow_dispatch: permissions: contents: write env: # PlatformIO environment to build (see environments.ini) PIO_ENV: heltec-rtl_433 # Chip / flash layout used to create the merged (factory) image ESP_CHIP: esp32 BOOTLOADER_OFFSET: '0x1000' jobs: build-and-release: name: Build Firmware & Publish Gitea Release runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Cache PlatformIO packages uses: actions/cache@v4 with: path: | ~/.platformio .pio/libdeps key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini', 'environments.ini') }} restore-keys: | ${{ runner.os }}-pio- - name: Install Python & PlatformIO run: | if ! python3 -m venv /tmp/pio-venv >/dev/null 2>&1; then rm -rf /tmp/pio-venv apt-get update && apt-get install -y python3 python3-venv python3-pip curl git python3 -m venv /tmp/pio-venv fi /tmp/pio-venv/bin/pip install --upgrade pip /tmp/pio-venv/bin/pip install -U platformio setuptools echo "/tmp/pio-venv/bin" >> "$GITHUB_PATH" - name: Determine version id: version run: | SHORT_SHA="${GITHUB_SHA::7}" if [ "${GITHUB_REF_TYPE}" = "tag" ]; then TAG_NAME="${GITHUB_REF_NAME}" else BASE_VERSION="$(git describe --tags --abbrev=0 2>/dev/null || echo v1.7.0)" TAG_NAME="${BASE_VERSION}-b${GITHUB_RUN_NUMBER}-${SHORT_SHA}" fi echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT" echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" - name: Set version tag in sources env: TAG_NAME: ${{ steps.version.outputs.tag_name }} run: | sed -i "s/version_tag/${TAG_NAME}/g" main/User_config.h scripts/latest_version.json - name: Build OpenMQTTGateway Firmware (${{ env.PIO_ENV }}) run: | pio run -e "${PIO_ENV}" - name: Prepare Release Artifacts env: TAG_NAME: ${{ steps.version.outputs.tag_name }} run: | BUILD_DIR=".pio/build/${PIO_ENV}" PREFIX="OpenMQTTGateway-${PIO_ENV}-${TAG_NAME}" BOOT_APP0="${HOME}/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin" mkdir -p dist # OTA / update image cp "${BUILD_DIR}/firmware.bin" dist/firmware.bin cp "${BUILD_DIR}/firmware.bin" "dist/${PREFIX}-firmware.bin" cp "${BUILD_DIR}/firmware.elf" "dist/${PREFIX}-firmware.elf" cp "${BUILD_DIR}/partitions.bin" "dist/${PREFIX}-partitions.bin" cp "${BUILD_DIR}/bootloader.bin" "dist/${PREFIX}-bootloader.bin" cp "${BOOT_APP0}" "dist/${PREFIX}-boot_app0.bin" # Merged factory image (flash at 0x0 on an erased chip) pio pkg exec -p tool-esptoolpy -- esptool.py --chip "${ESP_CHIP}" merge_bin \ -o "dist/${PREFIX}-factory.bin" \ "${BOOTLOADER_OFFSET}" "${BUILD_DIR}/bootloader.bin" \ 0x8000 "${BUILD_DIR}/partitions.bin" \ 0xe000 "${BOOT_APP0}" \ 0x10000 "${BUILD_DIR}/firmware.bin" (cd dist && sha256sum * > SHA256SUMS.txt) ls -lA dist - name: Create Gitea Release and Upload Binaries env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ steps.version.outputs.tag_name }} SHORT_SHA: ${{ steps.version.outputs.short_sha }} run: | python3 - << 'EOF' import json import os import subprocess import urllib.request import urllib.error server_url = os.environ["GITHUB_SERVER_URL"].rstrip("/") repo = os.environ["GITHUB_REPOSITORY"] token = os.environ["GITEA_TOKEN"] tag_name = os.environ["TAG_NAME"] sha = os.environ["GITHUB_SHA"] short_sha = os.environ["SHORT_SHA"] pio_env = os.environ["PIO_ENV"] bootloader_offset = os.environ["BOOTLOADER_OFFSET"] api_base = f"{server_url}/api/v1/repos/{repo}" headers = { "Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json", } release_notes = ( f"Automated OpenMQTTGateway firmware build.\n\n" f"- **Environment:** `{pio_env}`\n" f"- **Commit:** `{short_sha}`\n" f"- **OTA update:** Upload `firmware.bin` via the WebUI / OTA.\n" f"- **Fresh install:** Flash `*-factory.bin` at offset `0x0`, or flash " f"`*-bootloader.bin` @ `{bootloader_offset}`, `*-partitions.bin` @ `0x8000`, " f"`*-boot_app0.bin` @ `0xe000` and `*-firmware.bin` @ `0x10000`." ) # Check if a release for this tag already exists release_id = None existing_assets = {} req = urllib.request.Request(f"{api_base}/releases/tags/{tag_name}", headers=headers) try: with urllib.request.urlopen(req) as resp: data = json.loads(resp.read().decode("utf-8")) release_id = data["id"] existing_assets = {a["name"]: a["id"] for a in data.get("assets", [])} print(f"Found existing release ID {release_id} for tag {tag_name}") except urllib.error.HTTPError as e: if e.code != 404: raise # Create a new release if none exists if release_id is None: payload = json.dumps({ "tag_name": tag_name, "target_commitish": sha, "name": f"OpenMQTTGateway {tag_name} ({pio_env})", "body": release_notes, "draft": False, "prerelease": os.environ.get("GITHUB_REF_TYPE") != "tag", }).encode("utf-8") req = urllib.request.Request(f"{api_base}/releases", data=payload, headers=headers, method="POST") with urllib.request.urlopen(req) as resp: data = json.loads(resp.read().decode("utf-8")) release_id = data["id"] print(f"Created release ID {release_id} for tag {tag_name}") # Upload firmware binaries in dist/ for filename in sorted(os.listdir("dist")): if filename in existing_assets: del_req = urllib.request.Request( f"{api_base}/releases/{release_id}/assets/{existing_assets[filename]}", headers=headers, method="DELETE", ) urllib.request.urlopen(del_req).close() filepath = os.path.join("dist", filename) print(f"Uploading {filename}...") subprocess.run([ "curl", "--fail", "-sS", "-X", "POST", "-H", f"Authorization: token {token}", "-F", f"attachment=@{filepath}", f"{api_base}/releases/{release_id}/assets?name={filename}" ], check=True) print(f"Uploaded {filename} successfully.") EOF