Clean Old Components Releases #6
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: Clean Old Components Releases | |
| on: | |
| schedule: | |
| - cron: '0 15 1,15 * *' # Run at midnight JST (15:00 UTC) on the 1st and 15th of each month | |
| workflow_dispatch: | |
| jobs: | |
| clean-releases: | |
| # Require write permissions so the job can delete releases via the API | |
| permissions: | |
| contents: write | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate a token for Rekku | |
| id: generate-rekku-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ vars.REKKU_APP_ID }} | |
| private-key: ${{ secrets.REKKU_PRIVATE_KEY }} | |
| repositories: "components" | |
| owner: "RetroDECK" | |
| - name: Clean Old Releases | |
| run: | | |
| set -euo pipefail | |
| # Create / reset log file for deleted releases | |
| LOGFILE=deleted-releases.log | |
| : > "$LOGFILE" | |
| # Define the threshold date (1 month ago, UTC) | |
| THRESHOLD_DATE=$(date -u -d "1 month ago" +%Y-%m-%dT%H:%M:%SZ) | |
| # Define the list of tags to be excluded | |
| EXCLUDE_TAGS=("cooker-0.9.0b-ShoujoMonster") | |
| # Get latest release ID (so we don't delete the very latest release) | |
| LATEST_RELEASE_ID=$(curl -s -H "Authorization: token ${{ steps.generate-rekku-token.outputs.token }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest" | jq -r '.id // empty') | |
| # Pagination: iterate pages until none returned | |
| per_page=100 | |
| page=1 | |
| while :; do | |
| echo "Fetching releases page $page" | |
| RESP=$(curl -s -H "Authorization: token ${{ steps.generate-rekku-token.outputs.token }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/releases?per_page=$per_page&page=$page") | |
| COUNT=$(echo "$RESP" | jq 'length') | |
| if [ "$COUNT" -eq 0 ]; then | |
| break | |
| fi | |
| for idx in $(seq 0 $((COUNT - 1))); do | |
| release=$(echo "$RESP" | jq ".[$idx]") | |
| release_id=$(echo "$release" | jq -r '.id') | |
| tag_name=$(echo "$release" | jq -r '.tag_name // empty') | |
| name=$(echo "$release" | jq -r '.name // empty') | |
| published_at=$(echo "$release" | jq -r '.published_at // empty') | |
| # Skip the latest release | |
| if [ -n "$LATEST_RELEASE_ID" ] && [ "$release_id" = "$LATEST_RELEASE_ID" ]; then | |
| echo "Skipping latest release: id=$release_id tag=$tag_name name=$name" | |
| continue | |
| fi | |
| # Skip excluded tags | |
| if [ -n "$tag_name" ]; then | |
| for ex in "${EXCLUDE_TAGS[@]}"; do | |
| if [ "$tag_name" = "$ex" ]; then | |
| echo "Skipping excluded tag: $tag_name (id=$release_id name=$name)" | |
| skip=1 | |
| break | |
| else | |
| skip=0 | |
| fi | |
| done | |
| if [ "$skip" -eq 1 ]; then | |
| continue | |
| fi | |
| fi | |
| # If there's no published_at (draft?), skip | |
| if [ -z "$published_at" ] || [ "$published_at" = "null" ]; then | |
| echo "Skipping release without published_at: id=$release_id tag=$tag_name name=$name" | |
| continue | |
| fi | |
| # Compare dates in epoch seconds for safety | |
| published_epoch=$(date -d "$published_at" +%s) | |
| threshold_epoch=$(date -d "$THRESHOLD_DATE" +%s) | |
| if [ "$published_epoch" -lt "$threshold_epoch" ]; then | |
| # Log details before deleting | |
| log_line="$(date -u +%Y-%m-%dT%H:%M:%SZ) | DELETING release id=$release_id tag=$tag_name name=$name published_at=$published_at" | |
| echo "$log_line" | tee -a "$LOGFILE" | |
| # Delete release (capture body + http code so we can diagnose permission failures) | |
| del_output=$(curl -s -w "\n%{http_code}" -X DELETE -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/$GITHUB_REPOSITORY/releases/$release_id") | |
| del_http_code=$(echo "$del_output" | tail -n1) | |
| del_body=$(echo "$del_output" | sed '$d') | |
| if [ "$del_http_code" = "204" ]; then | |
| echo "Delete succeeded: release id=$release_id" | tee -a "$LOGFILE" | |
| else | |
| echo "Delete failed (HTTP $del_http_code): release id=$release_id" | tee -a "$LOGFILE" | |
| if [ -n "$del_body" ]; then | |
| # Response bodies can contain JSON error messages which help explain 403 reasons | |
| echo "Response body: $del_body" | tee -a "$LOGFILE" | |
| fi | |
| fi | |
| else | |
| echo "Keeping release id=$release_id (tag=$tag_name name=$name published_at=$published_at) — not older than threshold" | |
| fi | |
| done | |
| page=$((page + 1)) | |
| done | |
| echo "Finished cleanup. Deleted/releases log: $LOGFILE" | |
| env: | |
| GITHUB_TOKEN: ${{ steps.generate-rekku-token.outputs.token }} | |
| - name: Upload deleted releases log | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: deleted-releases-log | |
| path: deleted-releases.log | |
| env: | |
| GITHUB_TOKEN: ${{ steps.generate-rekku-token.outputs.token }} |