#!/usr/bin/env bash # Maintains a rolling "latest" release whose AppImage always has the same # download URL, for updaters that poll a static URL (e.g. Shelly) instead of # understanding version tags: # # ///releases/download/latest/WhatsThatMusic-latest-linux-x86_64.AppImage # # Gitea serves ETag and Last-Modified on release assets, and replacing the # asset changes both, which is how those updaters detect a new build. # Gitea has no GitHub-style /releases/latest/download/ redirect, hence this. # # Marked as a pre-release so Gitea's own "latest release" stays the newest vX.Y.Z. # # Required env: TOKEN, API (…/api/v1/repos//), GITHUB_REF_NAME, GITHUB_SHA # Optional env: ARTIFACTS_DIR (default: artifacts) set -euo pipefail : "${TOKEN:?TOKEN is required}" "${API:?API is required}" : "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required}" "${GITHUB_SHA:?GITHUB_SHA is required}" ARTIFACTS_DIR="${ARTIFACTS_DIR:-artifacts}" LATEST_TAG="latest" LATEST_NAME="WhatsThatMusic-latest-linux-x86_64.AppImage" api() { local method="$1" path="$2"; shift 2 local out code body out=$(curl -s -w '\n%{http_code}' -X "$method" -H "Authorization: token $TOKEN" "$API$path" "$@") code=$(printf '%s' "$out" | tail -n1) body=$(printf '%s' "$out" | sed '$d') if [ "$code" -ge 400 ]; then echo "$method $path -> HTTP $code" >&2 echo "$body" >&2 return 1 fi printf '%s' "$body" } appimage=$(find "$ARTIFACTS_DIR" -type f -name '*.AppImage' | sort | head -n1) if [ -z "$appimage" ]; then echo "No AppImage under $ARTIFACTS_DIR; cannot update the $LATEST_TAG release." >&2 exit 1 fi body_json=$(node -e ' console.log(JSON.stringify({ body: "Rolling pointer to the newest AppImage, currently " + process.argv[1] + ". " + "The download URL for this asset never changes; versioned downloads are on each vX.Y.Z release.", }));' "$GITHUB_REF_NAME") rolling_id=$(api GET "/releases/tags/$LATEST_TAG" 2>/dev/null \ | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).id" 2>/dev/null || true) if [ -z "${rolling_id:-}" ]; then echo "No '$LATEST_TAG' release yet; creating it." create_json=$(node -e ' console.log(JSON.stringify({ tag_name: "latest", name: "Latest (rolling)", target_commitish: process.argv[1], prerelease: true, body: JSON.parse(process.argv[2]).body, }));' "$GITHUB_SHA" "$body_json") rolling_id=$(api POST "/releases" -H "Content-Type: application/json" -d "$create_json" \ | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).id") else api PATCH "/releases/$rolling_id" -H "Content-Type: application/json" -d "$body_json" >/dev/null fi # Replace the asset: the new upload gets a new ETag and Last-Modified. old_id=$(api GET "/releases/$rolling_id/assets" \ | node -pe "((JSON.parse(require('fs').readFileSync(0,'utf8'))).find(a => a.name === process.argv[1]) || {}).id || ''" "$LATEST_NAME") if [ -n "$old_id" ]; then api DELETE "/releases/$rolling_id/assets/$old_id" >/dev/null fi echo "Uploading $LATEST_NAME from $(basename "$appimage") ($(du -h "$appimage" | cut -f1))" api POST "/releases/$rolling_id/assets?name=$LATEST_NAME" -F "attachment=@$appimage" >/dev/null echo "Updated '$LATEST_TAG' release ($rolling_id) to $GITHUB_REF_NAME"