name: Build & Release on: push: branches: [main] paths: ["bios/**", "platforms/**"] workflow_dispatch: inputs: force_release: description: "Force release even if rate limited" type: boolean default: false permissions: {} concurrency: group: build cancel-in-progress: true jobs: release: if: false # disabled until pack generation is validated in production runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: "3.12" - run: pip install pyyaml - name: Run tests run: python -m unittest tests.test_e2e -v - name: Rate limit if: github.event.inputs.force_release != 'true' id: rate run: | LAST=$(gh release list --repo "${{ github.repository }}" --json createdAt -q '.[0].createdAt' 2>/dev/null || echo "") if [ -n "$LAST" ] && [ "$LAST" != "null" ]; then LAST_TS=$(date -d "$LAST" +%s 2>/dev/null || echo 0) DIFF=$(( ($(date +%s) - LAST_TS) / 86400 )) if [ "$DIFF" -lt 7 ]; then echo "Skipping: last release ${DIFF} days ago" echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi fi echo "skip=false" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Restore large files from release if: steps.rate.outputs.skip != 'true' run: | mkdir -p .cache/large gh release download large-files -D .cache/large/ 2>/dev/null || true python3 -c " import hashlib, json, os, shutil db = json.load(open('database.json')) with open('.gitignore') as f: ignored = {l.strip() for l in f if l.strip().startswith('bios/')} cache = '.cache/large' if not os.path.isdir(cache): exit(0) idx = {} for fn in os.listdir(cache): fp = os.path.join(cache, fn) if os.path.isfile(fp): h = hashlib.sha1(open(fp, 'rb').read()).hexdigest() idx[h] = fp restored = 0 for sha1, entry in db['files'].items(): path = entry['path'] if path in ignored and not os.path.exists(path): src = idx.get(sha1) if src: os.makedirs(os.path.dirname(path), exist_ok=True) shutil.copy2(src, path) print(f'Restored: {path}') restored += 1 print(f'Total: {restored} files restored') " env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Refresh data directories if: steps.rate.outputs.skip != 'true' run: python scripts/refresh_data_dirs.py continue-on-error: true - name: Build packs if: steps.rate.outputs.skip != 'true' run: python scripts/generate_pack.py --all --output-dir dist/ - name: Split oversized packs if: steps.rate.outputs.skip != 'true' run: | # GitHub releases cap assets at 2 GB. Use the .zip.001 volume # convention: 7-Zip and PeaZip open the .001 part directly, # unlike .partNN names which they reject as corrupt archives. for f in dist/*.zip; do size=$(stat -c%s "$f") if [ "$size" -gt 2000000000 ]; then split --bytes=1900M --numeric-suffixes=1 --suffix-length=3 "$f" "$f." rm "$f" echo "Split $(basename "$f") into $(ls "$f".* | wc -l) parts" fi done - name: Release if: steps.rate.outputs.skip != 'true' run: | DATE=$(date +%Y.%m.%d) EXISTING=$(gh release list --repo "${{ github.repository }}" --json tagName -q ".[].tagName" | grep -c "^v${DATE}" || true) TAG="v${DATE}" [ "$EXISTING" -gt 0 ] && TAG="v${DATE}.$((EXISTING+1))" CHANGES=$(git log --oneline -15 --no-merges -- bios/ platforms/ | sed 's/^/- /') TOTAL=$(python3 -c "import json; print(json.load(open('database.json'))['total_files'])") SIZE=$(python3 -c "import json; print(f'{json.load(open(\"database.json\"))[\"total_size\"]/1024/1024:.0f}')") PACKS=$(ls dist/*.zip dist/*.zip.001 2>/dev/null | while read f; do echo "- **$(basename $f)** ($(du -m "$f" | cut -f1) MB)"; done) gh release create "$TAG" dist/*.zip* \ --repo "${{ github.repository }}" \ --title "BIOS Pack $TAG" \ --notes "${TOTAL} files, ${SIZE} MB, verified checksums. ### Packs ${PACKS} ### Install Download the pack matching your frontend, extract to the BIOS directory. Packs over 2 GB are split into numbered volumes (.zip.001, .zip.002). Download every part, then either open the .001 file directly with 7-Zip / PeaZip, or join the parts first: - Linux/macOS: \`cat PackName.zip.0* > PackName.zip\` - Windows (cmd): \`copy /b PackName.zip.001+PackName.zip.002 PackName.zip\` | Platform | Pack | Path | |----------|------|------| | RetroArch / Lakka | RetroArch_Lakka_BIOS_Pack.zip | system/ | | Batocera | Batocera_BIOS_Pack.zip | /userdata/bios/ | | Recalbox | Recalbox_BIOS_Pack.zip | /recalbox/share/bios/ | | RetroBat | RetroBat_BIOS_Pack.zip | bios/ | | RetroDECK | RetroDECK_BIOS_Pack.zip | ~/retrodeck/bios/ | | EmuDeck | EmuDeck_BIOS_Pack.zip | Emulation/bios/ | | RomM | RomM_BIOS_Pack.zip | bios/{platform_slug}/ | ### Changes ${CHANGES} " \ --latest env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Cleanup old releases if: steps.rate.outputs.skip != 'true' run: | gh release list --repo "${{ github.repository }}" --json tagName,createdAt \ --jq 'sort_by(.createdAt) | reverse | .[].tagName' | \ grep -v "^large-files$" | tail -n +4 | while read tag; do gh release delete "$tag" --repo "${{ github.repository }}" --yes --cleanup-tag done env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}