refresh latest (workflow_dispatch) #56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Refresh latest release | |
| run-name: refresh latest (${{ github.event.head_commit.message || inputs.reason || github.event_name }}) | |
| # Keeps a rolling `latest` GitHub Release whose zip asset always matches the | |
| # shipped files on `main` (RBF + ARM binary + _handler.sh + README). | |
| # | |
| # Triggers: | |
| # * push to main touching a shipped file -> catches manual RBF / handler / | |
| # README rolls (a dev push, so it fires normally). | |
| # * workflow_dispatch -> build.yml calls this via the PAT right after it | |
| # commits a rebuilt ARM binary back to main. That commit-back push uses the | |
| # default GITHUB_TOKEN, which by design does NOT re-trigger `on: push`, so | |
| # the explicit dispatch is how an ARM-binary-only change (RBF unchanged) | |
| # gets picked up. Mirrors build.yml's DB-rebuild dispatch pattern. | |
| # | |
| # The work is gated on a CONTENT SIGNATURE (sha256 over each shipped file's | |
| # hash -- independent of zip compression / runner image), stored as a | |
| # `content-sig:` line in the release body. A redundant double-fire is a cheap | |
| # no-op. Hand-cut version tags (v1.1, ...) are never touched -- only the | |
| # rolling `latest` pointer moves. update_all / db.json is unaffected (it tracks | |
| # main); this only serves pinning tools + manual zip downloaders. | |
| # Rule: [[release-tag-asset-currency]]. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - '_Other/*.rbf' | |
| - 'games/**' | |
| - 'docs/**' | |
| workflow_dispatch: | |
| inputs: | |
| reason: | |
| description: 'Why (shown as the run title in the Actions tab)' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: refresh-latest-release | |
| cancel-in-progress: false | |
| jobs: | |
| refresh: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build release zip + content signature from current main | |
| run: | | |
| python3 - <<'PY' | |
| import zipfile, os, glob, hashlib | |
| BIN = "games/OpenBOR/OpenBOR_7533" | |
| FILES = [ | |
| BIN, | |
| "games/OpenBOR/_handler.sh", | |
| "docs/OpenBOR/README.md", | |
| ] | |
| rbfs = sorted(glob.glob("_Other/*.rbf")) | |
| assert len(rbfs) == 1, "expected exactly one RBF, got %r" % rbfs | |
| FILES += rbfs | |
| EXE = {BIN, "games/OpenBOR/_handler.sh"} | |
| entries = sorted((p.replace(os.sep, "/"), p) for p in FILES) | |
| # Content signature: sha256 over "arcname:filehash" lines. Depends | |
| # ONLY on shipped file contents -> stable across runners / zlib | |
| # versions (unlike the compressed zip bytes). | |
| sig_parts = [] | |
| out = "MiSTer-OpenBOR-release.zip" | |
| with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z: | |
| for arc, path in entries: | |
| assert os.path.isfile(path), "missing shipped file: " + path | |
| data = open(path, "rb").read() | |
| sig_parts.append(arc + ":" + hashlib.sha256(data).hexdigest()) | |
| zi = zipfile.ZipInfo(arc, date_time=(1980, 1, 1, 0, 0, 0)) | |
| zi.compress_type = zipfile.ZIP_DEFLATED | |
| zi.external_attr = (0o755 if path in EXE else 0o644) << 16 | |
| z.writestr(zi, data) | |
| sig = hashlib.sha256("\n".join(sig_parts).encode()).hexdigest() | |
| open("content.sig", "w").write(sig) | |
| print("built", out, os.path.getsize(out), "bytes") | |
| print("content-sig", sig) | |
| PY | |
| - name: Refresh rolling 'latest' release when content changed | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -e | |
| REPO="${{ github.repository }}" | |
| ZIP="MiSTer-OpenBOR-release.zip" | |
| NEW=$(cat content.sig) | |
| CUR=$(gh release view latest -R "$REPO" --json body \ | |
| -q '.body' 2>/dev/null | sed -n 's/^content-sig: //p' | head -1) | |
| echo "current-main content-sig = $NEW" | |
| echo "published latest sig = ${CUR:-<none>}" | |
| if [ "$NEW" = "$CUR" ]; then | |
| echo "latest already matches current main -- no refresh needed." | |
| exit 0 | |
| fi | |
| git config user.email "ci@misterorganize" | |
| git config user.name "MiSTer Organize CI" | |
| # Move the rolling pointer tag to current main HEAD (never touches | |
| # hand-cut version tags). | |
| git tag -f latest | |
| git push -f origin latest | |
| NOTES=$(printf '%s\n' \ | |
| 'Rolling snapshot - always matches the current main branch.' \ | |
| '' \ | |
| 'Auto-refreshed by CI whenever the RBF, ARM binary, _handler.sh, or README changes on main. For a frozen build, pin a version tag (e.g. v1.1) instead of latest.' \ | |
| '' \ | |
| 'MiSTer Frontier auto-installs and updates through the update_all database; this zip is only a manual-install snapshot.' \ | |
| '' \ | |
| "content-sig: $NEW") | |
| if gh release view latest -R "$REPO" >/dev/null 2>&1; then | |
| gh release upload latest "$ZIP" --clobber -R "$REPO" | |
| gh release edit latest -R "$REPO" --target "$GITHUB_SHA" \ | |
| --title "latest (tracks main)" --notes "$NOTES" | |
| else | |
| gh release create latest "$ZIP" -R "$REPO" --target "$GITHUB_SHA" \ | |
| --title "latest (tracks main)" --notes "$NOTES" | |
| fi | |
| # Confirm the release now advertises the new signature. | |
| POST=$(gh release view latest -R "$REPO" --json body -q '.body' \ | |
| | sed -n 's/^content-sig: //p' | head -1) | |
| [ "$POST" = "$NEW" ] || { echo "ERROR: published sig != built sig"; exit 1; } | |
| echo "latest refreshed OK (content-sig $NEW)" |