From a036d3ef466cee60453c9e5d14e9815b9fab01d2 Mon Sep 17 00:00:00 2001 From: Yoshihiro OKUMURA Date: Wed, 4 Nov 2026 20:15:15 +0900 Subject: [PATCH] fix(scripts): report Gitea API errors in release uploads The macOS and Linux release scripts called the Gitea API with `curl -sf`, which discards the response on failure. A rejected token surfaced only as a JSON decode error from the release id lookup. - Route API calls through gitea_api, which keeps the HTTP status and the response body - On failure print the status and Gitea's message, with a hint about the token scope on 401/403, and exit - Look up the release first and create it only on 404 instead of ignoring every creation error - Check the status of each asset upload --- scripts/build-release-linux.sh | 57 +++++++++++++++++++++++++--------- scripts/build-release-macos.sh | 57 +++++++++++++++++++++++++--------- 2 files changed, 84 insertions(+), 30 deletions(-) diff --git a/scripts/build-release-linux.sh b/scripts/build-release-linux.sh index 44e2e97..1eadcb1 100755 --- a/scripts/build-release-linux.sh +++ b/scripts/build-release-linux.sh @@ -80,26 +80,53 @@ fi : "${GITEA_SERVER_URL:?GITEA_SERVER_URL is required for upload}" : "${GITEA_REPOSITORY:?GITEA_REPOSITORY is required for upload}" -echo "" -echo "==> Creating Gitea release ${TAG} ..." -curl -sf -X POST \ - -H "Authorization: Bearer ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases" \ - -d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}" > /dev/null || true +API_BODY="$(mktemp)" +trap 'rm -f "${API_BODY}"' EXIT -RELEASE_ID="$(curl -sf \ - -H "Authorization: Bearer ${GITEA_TOKEN}" \ - "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${TAG}" \ - | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')" +# Call the Gitea repository API. The HTTP status is left in API_STATUS and +# the response body in ${API_BODY}; curl itself failing (DNS, TLS, ...) +# aborts the script with curl's own message. +gitea_api() { + local method="$1" path="$2" + shift 2 + API_STATUS="$(curl -sS -o "${API_BODY}" -w '%{http_code}' -X "${method}" \ + -H "Authorization: Bearer ${GITEA_TOKEN}" "$@" \ + "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}${path}")" +} + +# Abort with the HTTP status and the error message Gitea returned +api_fail() { + local message + message="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["message"])' \ + < "${API_BODY}" 2>/dev/null || head -c 500 "${API_BODY}")" + echo "Error: $1 failed (HTTP ${API_STATUS}): ${message}" >&2 + if [[ "${API_STATUS}" == 401 || "${API_STATUS}" == 403 ]]; then + echo "Check GITEA_TOKEN: it must be valid and have the write:repository scope." >&2 + fi + exit 1 +} + +echo "" +gitea_api GET "/releases/tags/${TAG}" +if [[ "${API_STATUS}" == 200 ]]; then + echo "==> Using existing Gitea release ${TAG}" +elif [[ "${API_STATUS}" == 404 ]]; then + echo "==> Creating Gitea release ${TAG} ..." + gitea_api POST "/releases" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}" + [[ "${API_STATUS}" == 201 ]] || api_fail "Creating release ${TAG}" +else + api_fail "Looking up release ${TAG}" +fi + +RELEASE_ID="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])' < "${API_BODY}")" echo "==> Uploading assets (release id: ${RELEASE_ID}) ..." for ARCHIVE in "${ARCHIVES[@]}"; do echo " Uploading ${ARCHIVE} ..." - curl -sf -X POST \ - -H "Authorization: Bearer ${GITEA_TOKEN}" \ - -F "attachment=@${ARCHIVE}" \ - "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets" > /dev/null + gitea_api POST "/releases/${RELEASE_ID}/assets" -F "attachment=@${ARCHIVE}" + [[ "${API_STATUS}" == 201 ]] || api_fail "Uploading ${ARCHIVE}" echo " Done." done diff --git a/scripts/build-release-macos.sh b/scripts/build-release-macos.sh index 932dcc3..ba70883 100755 --- a/scripts/build-release-macos.sh +++ b/scripts/build-release-macos.sh @@ -68,26 +68,53 @@ fi : "${GITEA_SERVER_URL:?GITEA_SERVER_URL is required for upload}" : "${GITEA_REPOSITORY:?GITEA_REPOSITORY is required for upload}" -echo "" -echo "==> Creating Gitea release ${TAG} ..." -curl -sf -X POST \ - -H "Authorization: Bearer ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases" \ - -d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}" > /dev/null || true +API_BODY="$(mktemp)" +trap 'rm -f "${API_BODY}"' EXIT -RELEASE_ID="$(curl -sf \ - -H "Authorization: Bearer ${GITEA_TOKEN}" \ - "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${TAG}" \ - | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')" +# Call the Gitea repository API. The HTTP status is left in API_STATUS and +# the response body in ${API_BODY}; curl itself failing (DNS, TLS, ...) +# aborts the script with curl's own message. +gitea_api() { + local method="$1" path="$2" + shift 2 + API_STATUS="$(curl -sS -o "${API_BODY}" -w '%{http_code}' -X "${method}" \ + -H "Authorization: Bearer ${GITEA_TOKEN}" "$@" \ + "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}${path}")" +} + +# Abort with the HTTP status and the error message Gitea returned +api_fail() { + local message + message="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["message"])' \ + < "${API_BODY}" 2>/dev/null || head -c 500 "${API_BODY}")" + echo "Error: $1 failed (HTTP ${API_STATUS}): ${message}" >&2 + if [[ "${API_STATUS}" == 401 || "${API_STATUS}" == 403 ]]; then + echo "Check GITEA_TOKEN: it must be valid and have the write:repository scope." >&2 + fi + exit 1 +} + +echo "" +gitea_api GET "/releases/tags/${TAG}" +if [[ "${API_STATUS}" == 200 ]]; then + echo "==> Using existing Gitea release ${TAG}" +elif [[ "${API_STATUS}" == 404 ]]; then + echo "==> Creating Gitea release ${TAG} ..." + gitea_api POST "/releases" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}" + [[ "${API_STATUS}" == 201 ]] || api_fail "Creating release ${TAG}" +else + api_fail "Looking up release ${TAG}" +fi + +RELEASE_ID="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])' < "${API_BODY}")" echo "==> Uploading assets (release id: ${RELEASE_ID}) ..." for ARCHIVE in "${ARCHIVES[@]}"; do echo " Uploading ${ARCHIVE} ..." - curl -sf -X POST \ - -H "Authorization: Bearer ${GITEA_TOKEN}" \ - -F "attachment=@${ARCHIVE}" \ - "${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets" > /dev/null + gitea_api POST "/releases/${RELEASE_ID}/assets" -F "attachment=@${ARCHIVE}" + [[ "${API_STATUS}" == 201 ]] || api_fail "Uploading ${ARCHIVE}" echo " Done." done