Files
DarkRoom/tools/publish-release.sh
dtourolle d6d27fb062
Benchmarks / CPU and I/O (per commit) (push) Successful in 2m28s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Successful in 45m9s
Build and test / Layer separation (push) Successful in 38s
Traceability / Requirement traces (push) Successful in 44s
🐳 Android image / Build and push (push) Successful in 5s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 5s
Build and test / windows-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Successful in 14m25s
Build and test / Windows (x86_64, cross) (push) Successful in 34m30s
Build and test / Publish the release (push) Skipped
Publish a Gitea Release from CI on every v* tag
Nothing made a release. CI built the APK and the installer on the master
push and kept them as workflow artefacts, the Linux binary was not kept
at all, and most tags went out with no downloads until they were
attached by hand.

build-and-test now also runs on v* tags. On a tag the desktop job keeps
its release binary, and a release job that needs desktop, Android and
Windows collects the three, names them with the version and runs
tools/publish-release.sh. The script titles and describes the release
from the annotated tag's message as the server holds it, writes
SHA256SUMS, and attaches what is not already there, so a re-run after
an interrupted upload finishes the job instead of duplicating it. The
same script is how a release is made or finished by hand.

Tried on v0.14.1, whose release was made by hand with the same files:
it found the release, reported all four files attached, and changed
nothing.
2026-09-24 03:43:42 +02:00

101 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Turn a pushed tag into a Gitea Release with the build's files attached.
#
# GITEA_TOKEN=... tools/publish-release.sh v0.14.1 FILE...
#
# The release is named and described by the tag's own message — its first
# line is the title, the rest the notes — so the notes are written once, in
# the annotated tag, and not restated anywhere. SHA256SUMS over the given
# files is written and attached alongside them.
#
# Run by CI's `release` job on every v* tag, and by hand with the same
# arguments when a release has to be made or finished from a workstation.
# It is safe to repeat: an existing release is reused, and a file already
# attached under the same name and size is skipped, so a second run after an
# interrupted upload attaches only what is missing.
#
# Before this existed nothing made a release: CI built the APK and the
# installer on the master push and kept them as workflow artefacts, and
# most tags went out with no downloads at all.
set -euo pipefail
GITEA_URL="${GITEA_URL:-https://gitea.tourolle.paris}"
GITEA_REPO="${GITEA_REPO:-dtourolle/DarkRoom}"
: "${GITEA_TOKEN:?set GITEA_TOKEN to a token that can write releases}"
if [[ $# -lt 2 ]]; then
echo "usage: tools/publish-release.sh <tag> <file>..." >&2
exit 2
fi
TAG="$1"
shift
API="${GITEA_URL}/api/v1/repos/${GITEA_REPO}"
AUTH=(-H "Authorization: token ${GITEA_TOKEN}")
for f in "$@"; do
[[ -f "${f}" ]] || { echo "error: ${f} does not exist" >&2; exit 1; }
done
# The checksums, beside the files they describe, named as they will be
# downloaded — `sha256sum -c SHA256SUMS` in a download folder has to work.
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT
(
for f in "$@"; do
printf '%s %s\n' "$(sha256sum "${f}" | cut -d' ' -f1)" "$(basename "${f}")"
done
) > "${WORK}/SHA256SUMS"
cat "${WORK}/SHA256SUMS"
# The notes, from the server's copy of the tag. A CI checkout of a tag can
# hold it as a lightweight ref with no message, so the local repository is
# not asked.
curl -fsS "${AUTH[@]}" "${API}/tags/${TAG}" > "${WORK}/tag.json" \
|| { echo "error: no tag ${TAG} on ${GITEA_REPO}" >&2; exit 1; }
RELEASE_ID="$(curl -sS "${AUTH[@]}" "${API}/releases/tags/${TAG}" \
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("id") or "")' 2>/dev/null || true)"
if [[ -n "${RELEASE_ID}" ]]; then
echo "==> release ${TAG} exists (id ${RELEASE_ID}); attaching what is missing"
else
python3 - "${TAG}" "${WORK}/tag.json" > "${WORK}/release.json" <<'PY'
import json, sys
tag, path = sys.argv[1], sys.argv[2]
message = (json.load(open(path)).get("message") or "").strip()
if not message:
sys.exit(f"error: {tag} has no message; releases are cut from annotated tags")
title, _, body = message.partition("\n")
print(json.dumps({"tag_name": tag, "name": title.strip(), "body": body.strip(),
"draft": False, "prerelease": False}))
PY
RELEASE_ID="$(curl -fsS "${AUTH[@]}" -H 'Content-Type: application/json' \
--data @"${WORK}/release.json" "${API}/releases" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')"
echo "==> created release ${TAG} (id ${RELEASE_ID})"
fi
curl -fsS "${AUTH[@]}" "${API}/releases/${RELEASE_ID}/assets" > "${WORK}/assets.json"
for f in "$@" "${WORK}/SHA256SUMS"; do
name="$(basename "${f}")"
size="$(stat -c%s "${f}")"
have="$(python3 -c 'import json,sys
for a in json.load(open(sys.argv[1])):
if a["name"] == sys.argv[2]: print(a["size"])' "${WORK}/assets.json" "${name}")"
if [[ "${have}" == "${size}" ]]; then
echo " ${name}: already attached"
continue
fi
if [[ -n "${have}" ]]; then
echo "error: ${name} is attached at ${have} bytes, not ${size}; remove it by hand" >&2
exit 1
fi
echo " ${name}: uploading ${size} bytes"
curl -fsS "${AUTH[@]}" -F "attachment=@${f}" \
"${API}/releases/${RELEASE_ID}/assets?name=${name}" > /dev/null
done
echo "==> ${GITEA_URL}/${GITEA_REPO}/releases/tag/${TAG}"