diff --git a/.gitea/workflows/build-release.yml b/.gitea/workflows/build-release.yml new file mode 100644 index 0000000..24beb70 --- /dev/null +++ b/.gitea/workflows/build-release.yml @@ -0,0 +1,192 @@ +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 +