#!/usr/bin/env bash # Publish a release to the Gitea/Forgejo host, uploading the built artifacts. # # Requires: # - GITEA_TOKEN (token with write access) OR credentials usable for basic auth # (e.g. the same ones stored in ~/.git-credentials by `git push`) # - build artifacts from tools/build_releases.sh (+ optional installer/AppImage) # # Usage: # GITEA_TOKEN=xxx ./tools/publish_release.sh [version] # (version defaults to config/version from project.godot) set -euo pipefail REPO="${REPO:-zwitschi/security-game}" HOST="${HOST:-https://git.allucanget.biz}" VERSION="${1:-$(grep -oP 'config/version="\K[^"]+' project.godot)}" TAG="v$VERSION" # AUTH is a curl argument list. Populated by the first available method: # 1. GITEA_TOKEN explicit token (repo secret in CI) # 2. GITEA_AUTH_HEADER a ready-made "Authorization: ..." header # 3. ~/.git-credentials basic auth for HOST # 4. the checkout's git extraheader (Gitea Actions runners inject one for the # job; this is what makes tag builds publish without any repo secret) AUTH=() if [[ -n "${GITEA_TOKEN:-}" ]]; then AUTH=(-H "Authorization: token $GITEA_TOKEN") elif [[ -n "${GITEA_AUTH_HEADER:-}" ]]; then AUTH=(-H "$GITEA_AUTH_HEADER") elif [[ -f "$HOME/.git-credentials" ]]; then CRED="$(python3 -c " import os for l in open(os.path.expanduser('~/.git-credentials')): if '$HOST'.split('//')[1] in l: print(l.split('://',1)[1].split('@')[0]); break")" if [[ -z "$CRED" ]]; then echo "error: no credentials for $HOST in ~/.git-credentials" >&2 exit 1 fi # curl basic auth. (Previously only CRED was set, so ${AUTH[@]} was unbound # under `set -u` and every API call failed silently.) AUTH=(-u "$CRED") fi if [[ ${#AUTH[@]} -eq 0 ]]; then # Gitea Actions: actions/checkout configures a local extraheader with the # runner's credentials. Reuse it for the API (the same server, so the header is # valid for $HOST too). GIT_HEADER="$(git config --local --get-regexp '^http\..*\.extraheader$' 2>/dev/null | head -n1 | cut -d' ' -f2- || true)" if [[ -n "${GIT_HEADER:-}" ]]; then AUTH=(-H "$GIT_HEADER") fi fi if [[ ${#AUTH[@]} -eq 0 ]]; then echo "error: no credentials. Set GITEA_TOKEN, GITEA_AUTH_HEADER, or provide ~/.git-credentials." >&2 exit 1 fi # Artifacts that must exist (run tools/build_releases.sh first). portables=( "build/step-line-linux-$VERSION.x86_64" "build/step-line-windows-$VERSION.exe" "build/step-line-macos-$VERSION.zip" ) extras=( "build/step-line-setup-$VERSION.exe" "build/step-line-$VERSION-x86_64.AppImage" "build/step-line-web-$VERSION.zip" ) all_artifacts=("${portables[@]}") for f in "${extras[@]}"; do if [[ -f "$f" ]]; then all_artifacts+=("$f") else echo "note: optional artifact missing (skipping): $f" fi done API="$HOST/api/v1/repos/$REPO/releases" echo "==> Creating release $TAG on $HOST/$REPO" RELEASE_ID="$(curl -s -X POST "$API" "${AUTH[@]}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Step Line $VERSION — playable prototype. See README for controls.\",\"draft\":false,\"prerelease\":false}" \ | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)" # If the release already exists (tag already pushed), reuse it. if [[ -z "$RELEASE_ID" ]]; then RELEASE_ID="$(curl -s "${AUTH[@]}" "$API/tags/$TAG" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)" fi if [[ -z "$RELEASE_ID" ]]; then echo "error: could not create or find release $TAG" >&2 exit 1 fi echo "Release id: $RELEASE_ID" for f in "${all_artifacts[@]}"; do if [[ ! -f "$f" ]]; then echo "error: missing artifact $f — run ./tools/build_releases.sh first" >&2 exit 1 fi echo "==> Uploading $(basename "$f")" # Fail loudly: a silent 4xx here used to leave a release with no artifacts while # the job still reported success. code="$(curl -sS -o /tmp/release-upload.json -w '%{http_code}' --retry 2 \ -X POST "$API/$RELEASE_ID/assets" "${AUTH[@]}" -F "attachment=@$f" || true)" if [[ "$code" != "201" && "$code" != "200" ]]; then echo "error: upload of $(basename "$f") failed with HTTP $code" >&2 cat /tmp/release-upload.json >&2 || true echo "" >&2 exit 1 fi done # Verify the release actually carries the artifacts we just sent. have="$(curl -sS "${AUTH[@]}" "$API/$RELEASE_ID" | python3 -c " import sys, json try: d = json.load(sys.stdin) except Exception: print(0); raise SystemExit print(len(d.get('assets', [])))" 2>/dev/null || echo 0)" if [[ "${have:-0}" -lt "${#all_artifacts[@]}" ]]; then echo "error: release has $have assets, expected ${#all_artifacts[@]}" >&2 exit 1 fi echo "Verified $have artifacts on the release" echo "" echo "Release live: $HOST/$REPO/releases/tag/$TAG"