name: OTA build on: push: branches: - '**' workflow_dispatch: jobs: build-ota: runs-on: ubuntu-latest timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install PlatformIO run: | python -m pip install --upgrade pip pip install platformio - name: Build OTA firmware binaries run: pio run - name: Prepare direct OTA files run: | set -eu rm -rf dist/ota project_name="${GITHUB_REPOSITORY#*/}" project_name="${project_name#PlatformIO_}" timestamp="$(date -u +%Y-%m-%dT%H-%M-%SZ)" mkdir -p dist/ota found=0 for fw in .pio/build/*/firmware.bin .pio/build/*/firmware.hex; do [ -f "$fw" ] || continue env_name="$(basename "$(dirname "$fw")")" ext="${fw##*.}" cp "$fw" "dist/ota/${project_name}_${env_name}_${timestamp}.${ext}" found=1 done if [ "$found" -eq 0 ]; then echo 'No firmware.bin or firmware.hex artifacts were produced.' >&2 exit 1 fi find dist/ota -maxdepth 1 -type f | sort - name: Upload backup OTA artifact ZIP uses: actions/upload-artifact@v3 with: name: ota-files path: dist/ota/* if-no-files-found: error - name: Publish raw OTA files to rolling release env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | python - <<'PY' import json import mimetypes import os import sys from pathlib import Path from urllib.request import Request, urlopen from urllib.error import HTTPError server = os.environ['GITHUB_SERVER_URL'] repo = os.environ['GITHUB_REPOSITORY'] token = os.environ.get('GITEA_TOKEN') if not token: print('GITEA_TOKEN missing', file=sys.stderr) sys.exit(1) api = f"{server}/api/v1/repos/{repo}" headers = { 'Authorization': f'token {token}', 'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'Hermes-Agent' } tag = 'ota-builds' release_name = 'OTA Builds' body = 'Rolling OTA files from the latest successful push build.' def req(url, method='GET', data=None, extra_headers=None): h = dict(headers) if extra_headers: h.update(extra_headers) payload = None if data is None else json.dumps(data).encode() r = Request(url, data=payload, headers=h, method=method) with urlopen(r, timeout=120) as resp: return json.loads(resp.read().decode()) if resp.readable() else None try: release = req(f"{api}/releases/tags/{tag}") except HTTPError as e: if e.code != 404: raise release = req(f"{api}/releases", method='POST', data={ 'tag_name': tag, 'target': os.environ.get('GITHUB_SHA', ''), 'name': release_name, 'body': body, 'draft': False, 'prerelease': True, }) release_id = release['id'] existing = req(f"{api}/releases/{release_id}/assets") for asset in existing: rid = asset['id'] req(f"{api}/releases/{release_id}/assets/{rid}", method='DELETE') for path in sorted(Path('dist/ota').glob('*')): if not path.is_file(): continue mime = mimetypes.guess_type(path.name)[0] or 'application/octet-stream' upload_headers = { 'Authorization': f'token {token}', 'Content-Type': mime, 'Accept': 'application/json', 'User-Agent': 'Hermes-Agent' } url = f"{api}/releases/{release_id}/assets?name={path.name}" data = path.read_bytes() r = Request(url, data=data, headers=upload_headers, method='POST') with urlopen(r, timeout=240) as resp: print(resp.read().decode()) PY