CI: release from GitHub Actions to GitHub releases

The release workflow moves from .gitea to .github: macOS on the
macos-signing runner, Windows and Linux (WSL) on ely-windows. The
persistent clones fetch from GitHub. ci.sh creates or reuses the tag's
release through GitHub's API and replaces a rerun's assets of the same
name; the update manifests point at GitHub's download URLs.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude committed 2026-10-19 19:19:04 -04:00
1 parent 0d991e1c71
commit 41321fd05f
3 files changed
+38 -27

No files matched your search

+22 -17
View File
@@ -10,7 +10,7 @@ version=$(awk -F'"' '/^version = / { print $2; exit }' "$root/Cargo.toml")
echo "ci.sh: tag $tag is not v$version" >&2
exit 1
}
: "${ELY_RELEASE_TOKEN:?the Gitea token that uploads}"
: "${ELY_RELEASE_TOKEN:?the GitHub token that uploads}"
if [ "$os" = macos ]; then
: "${ELY_SIGN_IDENTITY:?the Developer ID Application certificate}"
: "${ELY_NOTARY_PROFILE:?the notarytool keychain profile}"
@@ -72,13 +72,14 @@ if [ "$os" = linux ]; then
fi
# The tag's release, made by whichever platform comes first.
api=https://git.zacharyzhang.com/api/v1/repos/ZacharyZhang-NY/ElyOffice
auth="Authorization: token $ELY_RELEASE_TOKEN"
release_id() { curl -fsS -H "$auth" "$api/releases/tags/$tag" | python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])'; }
repo=ELY-Open-Source/ElyOffice
api=https://api.github.com/repos/$repo
auth=(-H "Authorization: Bearer $ELY_RELEASE_TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28")
release_id() { curl -fsS "${auth[@]}" "$api/releases/tags/$tag" | python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' | tr -d '\r'; }
if ! id=$(release_id 2>/dev/null); then
body="{\"tag_name\": \"$tag\", \"name\": \"Ely Office $version\"}"
# Another platform may make it first; the read decides.
curl -fsS -H "$auth" -H "Content-Type: application/json" -d "$body" "$api/releases" >/dev/null || true
# Another platform may make it first (422); the read decides.
curl -fsS "${auth[@]}" -H "Content-Type: application/json" -d "$body" "$api/releases" >/dev/null || true
id=$(release_id)
fi
names=()
@@ -92,19 +93,23 @@ for file in "$dist"/*; do
fi
names+=("$name")
done
# Rebuilt bytes differ; never mix them with earlier uploads.
have=$(curl -fsS -H "$auth" "$api/releases/$id/assets" | python3 -c 'import json, sys; print("\n".join(a["name"] for a in json.load(sys.stdin)))')
taken=()
for name in "${names[@]}"; do
if grep -qxF "$name" <<<"$have"; then taken+=("$name"); fi
# Each asset's id and name, a page of 100 at a time; Windows Python writes CRLF.
have=
for ((page = 1; ; page++)); do
got=$(curl -fsS "${auth[@]}" "$api/releases/$id/assets?per_page=100&page=$page" |
python3 -c 'import json, sys; print("\n".join("%s\t%s" % (a["id"], a["name"]) for a in json.load(sys.stdin)))' | tr -d '\r')
[ -n "$got" ] || break
have+=$got$'\n'
done
if [ ${#taken[@]} -gt 0 ]; then
echo "ci.sh: already on $tag; delete them, then rerun: ${taken[*]}" >&2
exit 1
fi
# Rebuilt bytes replace a rerun's earlier upload; other platforms' files stay.
cd "$dist"
for name in "${names[@]}"; do
file=$dist/$name
for old in $(awk -F'\t' -v name="$name" '$2 == name { print $1 }' <<<"$have"); do
echo "ci.sh: replacing $name"
curl -fsS "${auth[@]}" -X DELETE "$api/releases/assets/$old" >/dev/null
done
echo "ci.sh: uploading $name"
curl -fsS -H "$auth" -F "attachment=@$file" "$api/releases/$id/assets?name=$name" >/dev/null
curl -fsS "${auth[@]}" -H "Content-Type: application/octet-stream" --data-binary "@$name" \
"https://uploads.github.com/repos/$repo/releases/$id/assets?name=$name" >/dev/null
done
echo "ci.sh: $os uploaded to $tag"