#!/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 ..." >&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}"