Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-linux (pull_request) Failing after 1m49s
Build App (Preview) / build-macos (pull_request) Successful in 2m57s
Build App (Preview) / build-windows (pull_request) Successful in 16m16s
Build App (Preview) / prune-previews (pull_request) Skipped
Two defects in the update channel, both visible in 0.4.20 and 0.4.21. **The channel tag does not survive.** `publish-update-channel.sh` created the GitHub release, uploaded both assets and verified each URL returned 200 — the job log shows it succeeding at 00:38. By 13:04 the tag was gone and every installed copy was checking a 404. Gitea push-mirrors this repo to GitHub every four hours, and a mirror push deletes remote refs with no local counterpart. `linux-latest` was created by GitHub's release API and never existed as a Gitea tag, so the mirror removed it. Versioned tags were never affected because `create-tag` creates them in Gitea first. So the tag is now anchored in Gitea, and before the GitHub release rather than after, so there is no window where the two disagree. Its absence fails the step instead of warning, because it is the only thing keeping the channel alive. Worth stating plainly: publishing correctly is not evidence the channel still works, and the verification that passed at 00:38 could not have caught a failure that arrives twelve hours later. **Every release carried the AppImage twice.** The channel's stable-named copy sat beside the versioned one, where the release job's `*.AppImage` glob picked it up — so v0.4.21 published `Triple-C_0.4.21_amd64.AppImage` and `Triple-C_x86_64.AppImage`, byte-identical at 86,686,200 bytes each, and `sync-to-github` copied both to the mirror. 80 MB of duplicate per release, under a name that reads like a different build. That is how it was noticed. The channel pair now lives in `bundle/appimage/update-channel/`, out of the glob's reach, and a guard fails the build if more than one AppImage is left beside the release. Verified by planting a second one: it fails. One appimagetool quirk found while moving it — zsyncmake writes the .zsync into the working directory, not beside the image it describes, so it has to be collected rather than assumed in place. The existing guard caught that too. Verified against the real 0.4.19 artifact: exactly one AppImage at top level, the channel pair in its own directory, update string still resolving to the fixed tag, and the wayland fallback intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YPqHpjV4EL6RNEwrRKqQm
131 lines
6.0 KiB
Bash
Executable File
131 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Publish the AppImage and its .zsync to the fixed `linux-latest` tag on the
|
||
# GitHub mirror — the URL every installed copy checks for updates.
|
||
#
|
||
# This exists because the update URL has to be one that never moves.
|
||
# `releases/latest` does move: it follows whatever release is newest, and the
|
||
# Gitea-to-GitHub backfill creates one GitHub release per Gitea tag, including
|
||
# the `-win` and `-mac` tags that carry no AppImage. Pointing a million
|
||
# installed copies at a URL that can resolve to a release with no AppImage in
|
||
# it is a failure that shows up on users' machines and nowhere else.
|
||
#
|
||
# So this tag holds exactly two files, replaced in place on every release.
|
||
# The versioned per-release artifacts are published separately and are what a
|
||
# human downloads; this is what the updater reads.
|
||
#
|
||
# It writes to GitHub rather than Gitea because that mirror is where updates
|
||
# are pulled from. Needs GH_PAT with contents write on the mirror.
|
||
#
|
||
# **The tag has to exist in Gitea, not just on GitHub, and that is the whole
|
||
# reason this script touches Gitea at all.** Gitea push-mirrors this repo to
|
||
# GitHub, and a mirror push deletes remote refs that have no local counterpart.
|
||
# A tag created only by GitHub's release API therefore survives until the next
|
||
# mirror run and then vanishes — which is exactly what happened to 0.4.20 and
|
||
# 0.4.21: the release was created and both URLs verified 200 at 00:38, and the
|
||
# 13:04 mirror deleted the tag, leaving every installed copy checking a 404.
|
||
# Versioned tags never had this problem because `create-tag` creates them in
|
||
# Gitea first. So does this one, now, and before the GitHub release rather than
|
||
# after, so there is no window where the two disagree.
|
||
#
|
||
# Note what this means for verification: publishing correctly is not evidence
|
||
# the channel still works hours later. The Gitea tag is what makes it durable,
|
||
# so its absence is treated as a failure rather than a warning.
|
||
#
|
||
# Usage: GH_PAT=... GITEA_TOKEN=... GITEA_SHA=... publish-update-channel.sh <dir>
|
||
|
||
set -euo pipefail
|
||
|
||
REPO="shadowdao/triple-c"
|
||
TAG="linux-latest"
|
||
API="https://api.github.com/repos/$REPO"
|
||
ASSETS=("Triple-C_x86_64.AppImage" "Triple-C_x86_64.AppImage.zsync")
|
||
|
||
GITEA_API="${GITEA_API:-https://repo.anhonesthost.net/api/v1}"
|
||
GITEA_REPO="${GITEA_REPO:-CyberCoveLLC/Triple-C}"
|
||
|
||
: "${GH_PAT:?GH_PAT is required to publish the update channel}"
|
||
: "${GITEA_TOKEN:?GITEA_TOKEN is required to anchor the $TAG tag against the mirror}"
|
||
: "${GITEA_SHA:?GITEA_SHA is required to point the $TAG tag at this build}"
|
||
dir="${1:?usage: publish-update-channel.sh <artifacts directory>}"
|
||
cd "$dir"
|
||
|
||
for asset in "${ASSETS[@]}"; do
|
||
[ -e "$asset" ] || { echo "Missing $asset in $dir" >&2; exit 1; }
|
||
done
|
||
|
||
gh() { curl -sf -H "Authorization: Bearer $GH_PAT" -H "Accept: application/vnd.github+json" "$@"; }
|
||
tea() { curl -sf -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" "$@"; }
|
||
|
||
# Anchor the tag in Gitea first — see the header. Moved rather than left
|
||
# alone: it has to name this build, and the mirror will carry whatever Gitea
|
||
# holds over the top of GitHub's copy.
|
||
echo "==> Anchoring the $TAG tag in Gitea at ${GITEA_SHA:0:9}"
|
||
tea -X DELETE "$GITEA_API/repos/$GITEA_REPO/tags/$TAG" >/dev/null 2>&1 || true
|
||
tea -X POST "$GITEA_API/repos/$GITEA_REPO/tags" \
|
||
-d "{\"tag_name\": \"$TAG\", \"target\": \"$GITEA_SHA\", \"message\": \"Rolling Linux update channel\"}" \
|
||
>/dev/null
|
||
|
||
# Not best-effort. Without this tag the mirror removes GitHub's and the
|
||
# channel dies silently somewhere between now and four hours from now.
|
||
tea "$GITEA_API/repos/$GITEA_REPO/tags/$TAG" >/dev/null 2>&1 \
|
||
|| { echo "FAILED: the $TAG tag does not exist in Gitea; the mirror would delete GitHub's copy." >&2; exit 1; }
|
||
|
||
echo "==> Looking for the $TAG release"
|
||
release="$(gh "$API/releases/tags/$TAG" 2>/dev/null || true)"
|
||
release_id="$(printf '%s' "$release" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("id",""))' 2>/dev/null || true)"
|
||
|
||
if [ -z "$release_id" ]; then
|
||
echo "==> Creating it"
|
||
# Not a prerelease, but deliberately not the "latest" release either: this
|
||
# tag is a channel, and it must never displace the versioned release a
|
||
# person lands on from the releases page.
|
||
release="$(gh -X POST "$API/releases" -d "$(python3 -c '
|
||
import json
|
||
print(json.dumps({
|
||
"tag_name": "'"$TAG"'",
|
||
"name": "Linux update channel",
|
||
"body": "Rolling AppImage build that Triple-C’s in-app updater reads. "
|
||
"The two files here are replaced on every release; for a specific "
|
||
"version, use the versioned releases instead.",
|
||
"draft": False,
|
||
"prerelease": False,
|
||
"make_latest": "false",
|
||
}))')")"
|
||
release_id="$(printf '%s' "$release" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')"
|
||
fi
|
||
|
||
echo "==> Removing superseded assets from release $release_id"
|
||
printf '%s' "$release" | python3 -c '
|
||
import sys, json
|
||
keep = set(sys.argv[1:])
|
||
for a in json.load(sys.stdin).get("assets", []):
|
||
if a["name"] in keep:
|
||
print(a["id"])
|
||
' "${ASSETS[@]}" | while read -r asset_id; do
|
||
[ -n "$asset_id" ] || continue
|
||
gh -X DELETE "$API/releases/assets/$asset_id" >/dev/null || true
|
||
done
|
||
|
||
for asset in "${ASSETS[@]}"; do
|
||
echo "==> Uploading $asset ($(du -h "$asset" | cut -f1))"
|
||
curl -sf -X POST \
|
||
-H "Authorization: Bearer $GH_PAT" \
|
||
-H "Content-Type: application/octet-stream" \
|
||
--data-binary "@$asset" \
|
||
"https://uploads.github.com/repos/$REPO/releases/$release_id/assets?name=$asset" >/dev/null
|
||
done
|
||
|
||
# The updater is only as good as this URL, and a silent failure here means
|
||
# every installed copy quietly stops updating. Confirm both are actually
|
||
# fetchable at the address the AppImage was built to check.
|
||
echo "==> Verifying the published URLs"
|
||
for asset in "${ASSETS[@]}"; do
|
||
url="https://github.com/$REPO/releases/download/$TAG/$asset"
|
||
code="$(curl -s -o /dev/null -w '%{http_code}' -L "$url")"
|
||
[ "$code" = "200" ] || { echo "FAILED: $url returned $code" >&2; exit 1; }
|
||
echo " $code $url"
|
||
done
|
||
|
||
echo "OK: $TAG updated, and anchored in Gitea so the mirror preserves it."
|