name: Build and Release OpenMQTTGateway Firmware on: push: branches: - development tags: - '*' workflow_dispatch: permissions: contents: write packages: write 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 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 default environment and version id: prep run: | # Only the first active "default_envs" entry from platformio.ini is built PIO_ENV="$(sed -n 's/^[[:space:]]*default_envs[[:space:]]*=[[:space:]]*//p' platformio.ini \ | head -n 1 | cut -d',' -f1 | tr -d '[:space:]')" if [ -z "${PIO_ENV}" ]; then echo "No active default_envs found in platformio.ini" >&2 exit 1 fi SHORT_SHA="${GITHUB_SHA::7}" if [ "${GITHUB_REF_TYPE}" = "tag" ]; then TAG_NAME="${GITHUB_REF_NAME}" else BASE_VERSION="$(git describe --tags --abbrev=0 --exclude '*-b*' 2>/dev/null || echo v1.7.0)" TAG_NAME="${BASE_VERSION}-b${GITHUB_RUN_NUMBER}-${SHORT_SHA}" fi echo "Environment: ${PIO_ENV}, version: ${TAG_NAME}" echo "pio_env=${PIO_ENV}" >> "$GITHUB_OUTPUT" echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT" echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" - name: Build OpenMQTTGateway Firmware (${{ steps.prep.outputs.pio_env }}) env: PIO_ENV: ${{ steps.prep.outputs.pio_env }} TAG_NAME: ${{ steps.prep.outputs.tag_name }} run: | # Inject the firmware version (same mechanism as upstream scripts/ci_build_firmware.sh) export PLATFORMIO_BUILD_FLAGS="-DOMG_VERSION=\\\"${TAG_NAME}\\\"" mkdir -p ".pio/build/${PIO_ENV}" pio run -e "${PIO_ENV}" - name: Prepare Release Artifacts env: PIO_ENV: ${{ steps.prep.outputs.pio_env }} TAG_NAME: ${{ steps.prep.outputs.tag_name }} run: | mkdir -p dist # Only the firmware image is published cp ".pio/build/${PIO_ENV}/firmware.bin" "dist/OpenMQTTGateway-${PIO_ENV}-${TAG_NAME}.bin" ls -lA dist - name: Create Gitea Release and Publish Package env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_PKG_TOKEN: ${{ secrets.GITEATOKEN }} PIO_ENV: ${{ steps.prep.outputs.pio_env }} TAG_NAME: ${{ steps.prep.outputs.tag_name }} SHORT_SHA: ${{ steps.prep.outputs.short_sha }} run: | python3 - << 'EOF' import base64 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"] owner, repo_name = repo.split("/", 1) actor = os.environ.get("GITHUB_ACTOR") or owner token = os.environ["GITEA_TOKEN"] pkg_token = os.environ.get("GITEA_PKG_TOKEN") or token tag_name = os.environ["TAG_NAME"] sha = os.environ["GITHUB_SHA"] short_sha = os.environ["SHORT_SHA"] pio_env = os.environ["PIO_ENV"] 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 the `.bin` file via the WebUI / OTA." ) # 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": False, }).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 binary to Release and Gitea Package Registry # Note: Gitea's Generic Package Registry upload router is at /api/packages/ (not /api/v1/packages/) package_name = f"{repo_name}-{pio_env}" pkg_base = f"{server_url}/api/packages/{owner}/generic/{package_name}/{tag_name}" basic_auth = base64.b64encode(f"{actor}:{pkg_token}".encode("utf-8")).decode("ascii") 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} to Release...") subprocess.run([ "curl", "--fail", "-sS", "-o", "/dev/null", "-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} to Release successfully.") # Publish to Gitea Generic Package Registry (delete existing file first if re-running) pkg_file_url = f"{pkg_base}/{filename}" try: del_pkg_req = urllib.request.Request( pkg_file_url, headers={"Authorization": f"Basic {basic_auth}"}, method="DELETE", ) urllib.request.urlopen(del_pkg_req).close() except urllib.error.HTTPError as e: if e.code != 404: raise print(f"Publishing {filename} to Gitea Package {package_name}/{tag_name}...") subprocess.run([ "curl", "--fail", "-sS", "-o", "/dev/null", "-X", "PUT", "--user", f"{actor}:{pkg_token}", "--upload-file", filepath, pkg_file_url, ], check=True) print(f"Published {filename} to Gitea Package Registry successfully.") EOF