diff --git a/.forgejo/workflows/desktop.yml b/.forgejo/workflows/desktop.yml index f3984fa..77bd7c2 100644 --- a/.forgejo/workflows/desktop.yml +++ b/.forgejo/workflows/desktop.yml @@ -336,6 +336,9 @@ jobs: if [ "${GITHUB_REF_NAME}" = "dev" ]; then export RELEASE_TAG=dev export RELEASE_NOTES="Development build from ${GITHUB_SHA}" + # Rolling channel: drop the previous build's bundles once the manifest + # points at this one. Nothing can reach them, and they're ~100 MB a push. + export PRUNE_OLD_ASSETS=true APP_VERSION="$version" bash desktop/packaging/write-manifest.sh else export RELEASE_TAG="${GITHUB_REF_NAME}" diff --git a/desktop/packaging/write-manifest.sh b/desktop/packaging/write-manifest.sh index a93d91a..4e4030b 100644 --- a/desktop/packaging/write-manifest.sh +++ b/desktop/packaging/write-manifest.sh @@ -58,8 +58,18 @@ download_url() { printf '%s/%s/releases/download/%s/%s' "$GITHUB_SERVER_URL" "$G # install something it can't verify. platform_entry() { local target="$1" pattern="$2" bundle sig_name - bundle="$(printf '%s\n' "$names" | grep -E "$pattern" | head -1 || true)" - [ -n "$bundle" ] || { echo " no bundle matching $pattern — skipping $target" >&2; return; } + # Matched on THIS build's version, not just the file extension. + # + # The rolling dev release accumulates every build's assets, and picking the first + # extension match returned the OLDEST one — so the manifest advertised the new + # version while pointing at an old binary. The client would install the older + # build, still be told the newer version was available, and update forever. The + # signature check couldn't catch it either: the old bundle's signature is + # perfectly valid FOR THE OLD BUNDLE. + bundle="$(printf '%s\n' "$names" | grep -F "_${APP_VERSION}_" | grep -E "$pattern" | head -1 || true)" + # A hard failure, not a skip: reaching here means this platform's build didn't + # upload, and the whole point is to never advertise a bundle that isn't there. + [ -n "$bundle" ] || { echo " no $APP_VERSION bundle matching $pattern — skipping $target" >&2; return; } sig_name="$bundle.sig" if ! printf '%s\n' "$names" | grep -qxF "$sig_name"; then echo " $bundle has no $sig_name — skipping $target (was the build signed?)" >&2 @@ -140,3 +150,31 @@ curl -fsS -X POST "${AUTH[@]}" "$API/releases/$target_id/assets?name=latest.json -F "attachment=@$work/latest.json" >/dev/null echo "==> Done. $MANIFEST_TAG now advertises $APP_VERSION for ${#entries[@]} platform(s)." + +# --- prune superseded builds from a rolling channel --------------------------- +# +# The dev release is republished on every push and its assets otherwise accumulate +# forever — an AppImage alone is ~100 MB, so a week of pushes is gigabytes on the +# Git host for builds nobody can reach (the manifest only ever names the newest). +# +# Only for a rolling channel. A versioned release must keep its assets: that IS the +# archive, and the stable pointer's URLs aim at it. +if [ "${PRUNE_OLD_ASSETS:-false}" = "true" ]; then + echo "==> Pruning superseded assets from $RELEASE_TAG" + # Re-read: the manifest upload above changed the asset list. + current="$(curl -sS "${AUTH[@]}" "$API/releases/$release_id/assets")" + printf '%s' "$current" \ + | grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+[^}]*"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | while IFS= read -r row; do + asset_id="$(printf '%s' "$row" | grep -oE '[0-9]+' | head -1)" + asset_name="$(printf '%s' "$row" | sed -E 's/.*"name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')" + # Keep the manifest itself and everything belonging to the current build. + case "$asset_name" in + latest.json) continue ;; + *"$APP_VERSION"*) continue ;; + esac + echo " removing $asset_name" + curl -fsS -X DELETE "${AUTH[@]}" "$API/releases/$release_id/assets/$asset_id" >/dev/null || \ + echo " (couldn't remove $asset_name — leaving it)" >&2 + done +fi