From 9441bccf5857028751825e4b74a5fcb491095b4c Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 10 Sep 2026 13:58:46 -0400 Subject: [PATCH] fix(release): tooling checkout so old tags can publish; never move a tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dispatched build of v0.1.1 kept failing at publish with 'scripts/publish-release.sh: No such file or directory' — the workflow is read from the dispatch ref (main) but the workspace was the v0.1.1 tag, whose tree predates scripts/ and .forgejo/. - second checkout of the workflow's own ref into .ci-tools; publish runs .ci-tools/scripts/publish-release.sh (and .ci-tools/scripts/release-body.md) - exclude ./.ci-tools from the build context tar - target_commitish is now opt-in in publish-release.sh and only set for a push event, so a re-publish can never move an existing tag Verified locally end-to-end against a throwaway tag: create, idempotent re-run (stale assets deleted + replaced), public download, sha256sum -c, --version. Test release and tag deleted afterwards. --- .forgejo/workflows/release.yml | 25 +++++++++++++++++++------ scripts/publish-release.sh | 16 +++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index a27acea..5748373 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -29,7 +29,7 @@ jobs: runs-on: docker timeout-minutes: 120 steps: - - name: Checkout + - name: Checkout the source tag uses: https://code.forgejo.org/actions/checkout@v4 with: # A tag push builds that tag; a manual dispatch builds the tag the @@ -37,6 +37,15 @@ jobs: # release it is attached to). ref: ${{ github.event.inputs.tag || github.ref_name }} + - name: Checkout the CI tooling + uses: https://code.forgejo.org/actions/checkout@v4 + with: + # scripts/ and .forgejo/ only exist on the branch (older tags predate + # them), and the workflow itself is read from the dispatched ref — so + # fetch the same ref into .ci-tools and run the scripts from there. + ref: ${{ github.ref_name }} + path: .ci-tools + - name: Build ${{ env.TARGET }} in a rust container run: | set -euo pipefail @@ -62,7 +71,8 @@ jobs: && cargo build --release --locked \ && strip /target/release/onionwire \ && ls -l /target/release/onionwire' - tar czf - --exclude=./target --exclude=./.git --exclude=./.worktrees . \ + tar czf - --exclude=./target --exclude=./.git --exclude=./.worktrees \ + --exclude=./.ci-tools . \ | docker start -a -i "$BUILD_CONTAINER" mkdir -p dist docker cp "$BUILD_CONTAINER:/target/release/onionwire" "dist/onionwire-$TARGET" @@ -80,16 +90,19 @@ jobs: - name: Publish to the Forgejo release env: FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} - TARGET_COMMITISH: ${{ github.sha }} REPO_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + EVENT_NAME: ${{ github.event_name }} + EVENT_SHA: ${{ github.sha }} REF: ${{ github.ref_name }} INPUT_TAG: ${{ github.event.inputs.tag }} run: | set -euo pipefail cd "${GITHUB_WORKSPACE}" tag="${INPUT_TAG:-$REF}" - echo "publishing $tag from $REPO_API" - scripts/publish-release.sh \ + # Only a tag push may create the tag; a re-publish must not move it. + if [ "$EVENT_NAME" = "push" ]; then export TARGET_COMMITISH="$EVENT_SHA"; fi + echo "publishing $tag from $REPO_API (event=$EVENT_NAME)" + .ci-tools/scripts/publish-release.sh \ "$tag" "OnionWire $tag" \ - scripts/release-body.md \ + .ci-tools/scripts/release-body.md \ "dist/onionwire-$TARGET" "dist/onionwire-$TARGET.sha256" diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index 60486c4..23d8cdc 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -4,7 +4,9 @@ # Usage: publish-release.sh [...] # Env: FORGEJO_TOKEN user PAT with repo write (required) # REPO_API default https://forgejo.siriusdevops.com/api/v1/repos/sirius/onionwire -# TARGET_COMMITISH default: push-triggered commit (CI) else "main" +# TARGET_COMMITISH optional; set it only when the release may have to +# create the tag (e.g. a push event's commit sha). +# Leave empty to never move an existing tag. # # Idempotent: re-running for the same tag reuses the release and replaces # same-named assets instead of failing with 409. @@ -17,7 +19,7 @@ shift 3 : "${FORGEJO_TOKEN:?FORGEJO_TOKEN is not set}" api="${REPO_API:-https://forgejo.siriusdevops.com/api/v1/repos/sirius/onionwire}" -target="${TARGET_COMMITISH:-main}" +target="${TARGET_COMMITISH:-}" jqp() { python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; } @@ -25,14 +27,18 @@ jqp() { python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; } python3 - "$body_file" "$tag" "$target" "$name" > /tmp/release-body.json <<'PY' import json, sys body = open(sys.argv[1]).read().replace("@TAG@", sys.argv[2]) -print(json.dumps({ +payload = { "tag_name": sys.argv[2], - "target_commitish": sys.argv[3], "name": sys.argv[4], "body": body, "draft": False, "prerelease": False, -})) +} +# Only send target_commitish when asked: on an existing tag it is a request to +# move the tag, which is never what a re-publish wants. +if sys.argv[3]: + payload["target_commitish"] = sys.argv[3] +print(json.dumps(payload)) PY code=$(curl -s -o /tmp/release-rel.json -w '%{http_code}' \