diff --git a/.gitea/workflows/ota-build.yml b/.gitea/workflows/ota-build.yml index 910193c..54f8f61 100644 --- a/.gitea/workflows/ota-build.yml +++ b/.gitea/workflows/ota-build.yml @@ -9,7 +9,7 @@ on: jobs: build-ota: runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v4 @@ -17,17 +17,14 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: "3.11" - name: Install PlatformIO run: | python -m pip install --upgrade pip pip install platformio - - name: Remove stale OTA package files - run: rm -f dist/*.tslpkg - - - name: Build filesystem-aware OTA package + - name: Build OTA package run: python scripts/build_ota_package.py - name: Prepare direct OTA files @@ -35,21 +32,104 @@ jobs: set -eu rm -rf dist/ota mkdir -p dist/ota - count=0 - for file in dist/*.tslpkg; do - [ -f "$file" ] || continue - cp "$file" dist/ota/ - count=$((count+1)) + found=0 + for pkg in dist/*.tslpkg; do + [ -f "$pkg" ] || continue + cp "$pkg" dist/ota/ + found=1 done - if [ "$count" -eq 0 ]; then + if [ "$found" -eq 0 ]; then echo 'No .tslpkg OTA package was produced.' >&2 exit 1 fi find dist/ota -maxdepth 1 -type f | sort - - name: Upload direct OTA files as artifacts + - 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: + body = resp.read() + if not body: + return None + text = body.decode() + try: + return json.loads(text) + except json.JSONDecodeError: + return text + + 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") or [] + 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