Author SHA1 Message Date
jknapp d3a67688f8 Merge pull request 'CI: publish a fixed-URL latest AppImage for Shelly' (#7) from ci/latest-appimage-url into main 2026-10-04 19:19:39 +00:00
shadowdaoandClaude Sonnet 5.5 12cfca8b17 Publish a fixed-URL "latest" AppImage for static-URL updaters
Gitea has no /releases/latest/download/ redirect and release URLs always
include the tag, so updaters that poll a constant URL (Shelly) cannot use
them. After publishing a vX.Y.Z release, replace the asset on a rolling
pre-release tagged "latest":

  .../releases/download/latest/WhatsThatMusic-latest-linux-x86_64.AppImage

Replacing the asset changes its ETag and Last-Modified, which Shelly
compares. The pre-release flag keeps Gitea's own "latest release" on the
newest vX.Y.Z. The logic lives in .gitea/scripts/update-latest.sh so it can
be run against the API outside CI. The publish job now checks out first,
since checkout cleans the workspace and would delete downloaded artifacts.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 12:17:46 -07:00
jknapp 0e5b20498a Merge pull request 'Fix now-playing scraper, quit hang, tray icon and tray race (v1.0.1)' (#6) from fix/now-playing-scraper-and-quit into main
Build release artifacts / linux (push) Successful in 4m9s
Build release artifacts / macos (push) Successful in 1m6s
Build release artifacts / windows (push) Successful in 1m51s
Build release artifacts / publish (push) Successful in 15s
2026-10-04 18:37:51 +00:00
2 changed files with 87 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
#!/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:
#
# <server>/<owner>/<repo>/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/<owner>/<repo>), 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"
+12
View File
@@ -129,6 +129,9 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# Checkout first: it cleans the workspace, which would delete downloaded artifacts.
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
path: artifacts path: artifacts
@@ -198,3 +201,12 @@ jobs:
exit 1 exit 1
fi fi
echo "Attached $found artifacts to release $release_id" echo "Attached $found artifacts to release $release_id"
# Constant-URL copy of the AppImage for updaters that poll a static URL
# (e.g. Shelly). Runs after the versioned release exists, so a failure here
# cannot leave a tag without its assets.
- name: Update rolling "latest" AppImage
env:
TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITEA_TOKEN }}
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
run: bash .gitea/scripts/update-latest.sh