From ca90facbdd8e42fd9aa237d475639f88ed763a30 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 1 May 2026 00:58:40 +0300 Subject: [PATCH] Add .forgejo/workflows/release.yml: tag v* publishes Forgejo release Triggered on `push` of any `v*` tag. Single `release` job on the `go` runner produces and uploads four assets to the matching Forgejo release: - drover-vX.Y.Z-windows-amd64.exe (cross-compiled portable binary) - drover-vX.Y.Z-windows-amd64.zip (portable bundle + WinDivert + docs) - drover-vX.Y.Z-setup.exe (Inno Setup installer via Wine) - SHA256SUMS.txt (sha256 of the three above) The asset names match what internal/updater/updater.go looks for, so selfupdate keeps working on tagged releases. Notes mirroring build.yml constraints: - manual git clone instead of actions/checkout (no Node in golang image; JS-based actions fail with `node: not found`) - apt-get installs wine/wine32:i386/xvfb/zip/jq in-job - Inno Setup 6.4.0 pinned, /VERYSILENT /CURRENTUSER install under Wine - prerelease auto-detected from a hyphen in the version (rc/beta/alpha) - curl uses -fsS so 409 (release exists) and other API errors fail loud - secrets.GITHUB_TOKEN handles both clone and Forgejo REST API writes Co-Authored-By: Claude Opus 4.7 (1M context) --- .forgejo/workflows/release.yml | 184 +++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 .forgejo/workflows/release.yml diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..ccec1d2 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,184 @@ +name: Release + +on: + push: + tags: + - 'v*' + +# We only ever publish a tag once. Don't cancel a running release — +# if the user re-pushed the same tag, that's a manual action and +# should fail loudly via Forgejo's "release exists" 409 from the API. +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: go + steps: + - name: Checkout + run: | + git clone --no-checkout "https://forgejo-runner:${GITHUB_TOKEN}@git.okcu.io/${GITHUB_REPOSITORY}.git" /tmp/src + git -C /tmp/src checkout "$GITHUB_SHA" + cp -a /tmp/src/. . + + - name: Extract version from tag + id: version + run: | + TAG="${GITHUB_REF#refs/tags/}" + VERSION="${TAG#v}" + echo "tag=${TAG}" | tee -a "$GITHUB_OUTPUT" + echo "version=${VERSION}" | tee -a "$GITHUB_OUTPUT" + + - name: Cross-compile drover.exe (windows/amd64) + env: + GOOS: windows + GOARCH: amd64 + CGO_ENABLED: '0' + run: | + set -e + BUILD_DATE="$(date -u +%Y-%m-%d)" + SHORT_SHA="${GITHUB_SHA:0:7}" + mkdir -p dist + go build -trimpath -ldflags="-s -w \ + -X main.Version=${{ steps.version.outputs.version }} \ + -X main.Commit=${SHORT_SHA} \ + -X main.BuildDate=${BUILD_DATE}" \ + -o "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" \ + ./cmd/drover + + - name: Build portable zip + run: | + set -e + # Stage files for the zip in a clean directory so the archive root + # contains exactly what users see when they unpack. + apt-get update >/dev/null + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends zip >/dev/null + STAGE="$(mktemp -d)" + cp "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" "$STAGE/drover.exe" + mkdir -p "$STAGE/windivert" + cp third_party/windivert/WinDivert.dll "$STAGE/windivert/" + cp third_party/windivert/WinDivert64.sys "$STAGE/windivert/" + cp third_party/windivert/LICENSE-LGPL "$STAGE/windivert/" + cp third_party/windivert/README.md "$STAGE/windivert/" + cp LICENSE "$STAGE/LICENSE.txt" + cp README.md "$STAGE/README.md" + # zip from inside STAGE so paths are relative + ( cd "$STAGE" && zip -r9 - . ) > "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.zip" + + - name: Install Wine + Inno Setup + run: | + set -e + dpkg --add-architecture i386 + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + wine wine64 wine32:i386 cabextract xvfb curl ca-certificates \ + >/dev/null + # Headless Wine prefix; suppress wineboot first-run banner. + export WINEPREFIX="$HOME/.wine" + export WINEDLLOVERRIDES="mscoree,mshtml=" + export DISPLAY=":99" + Xvfb :99 -screen 0 1024x768x16 & + sleep 1 + wineboot --init >/dev/null 2>&1 || true + # Download Inno Setup 6.4 — pinned to a known-good build to keep + # builds reproducible. Using upstream installer is more robust + # against link rot than third-party redistributions. + INNO_VERSION="6.4.0" + curl -fsSL -o /tmp/innosetup.exe \ + "https://files.jrsoftware.org/is/6/innosetup-${INNO_VERSION}.exe" + # Silent install. /CURRENTUSER avoids needing admin in Wine. + xvfb-run -a wine /tmp/innosetup.exe /VERYSILENT /SUPPRESSMSGBOXES /CURRENTUSER /NORESTART \ + /DIR="C:\\InnoSetup" \ + >/dev/null 2>&1 || true + # Sanity-check ISCC was installed + ISCC_LIN="$HOME/.wine/drive_c/InnoSetup/ISCC.exe" + ls -la "$ISCC_LIN" + xvfb-run -a wine "C:\\InnoSetup\\ISCC.exe" /? 2>&1 | head -5 || true + + - name: Compile Inno Setup installer + env: + DISPLAY: ":99" + WINEDLLOVERRIDES: "mscoree,mshtml=" + run: | + set -e + export WINEPREFIX="$HOME/.wine" + # installer.iss references "..\drover.exe" relative to installer/, + # i.e. expects a plain drover.exe at repo root. Stage a copy of the + # versioned binary there before invoking ISCC. + cp "dist/drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" drover.exe + # Inno Setup wants Windows-style /D defines and the script path + # in DOS form. We invoke from repo root; iss script resolves its + # own ..\ paths from installer/. + mkdir -p installer/Output + xvfb-run -a wine "C:\\InnoSetup\\ISCC.exe" \ + "/DMyAppVersion=${{ steps.version.outputs.version }}" \ + "installer\\installer.iss" + # Inno Setup default OutputDir is "installer/Output/" (relative + # to the .iss). Move the produced exe to dist/ with the canonical + # asset name. + mv "installer/Output/drover-${{ steps.version.outputs.version }}-setup.exe" \ + "dist/drover-${{ steps.version.outputs.tag }}-setup.exe" + ls -la dist/ + + - name: Compute SHA256SUMS.txt + run: | + set -e + ( cd dist && sha256sum \ + "drover-${{ steps.version.outputs.tag }}-windows-amd64.exe" \ + "drover-${{ steps.version.outputs.tag }}-windows-amd64.zip" \ + "drover-${{ steps.version.outputs.tag }}-setup.exe" \ + ) > dist/SHA256SUMS.txt + cat dist/SHA256SUMS.txt + + - name: Create Forgejo release and upload assets + env: + FJ_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FJ_HOST: git.okcu.io + FJ_REPO: ${{ github.repository }} + TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} + run: | + set -e + # jq is used to build the JSON body safely. Install if missing. + command -v jq >/dev/null || { + apt-get update >/dev/null + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends jq >/dev/null + } + # Detect prerelease from tag (anything with a hyphen after the version + # number, e.g. v0.1.0-rc.1, v0.1.0-beta) is a prerelease. + PRERELEASE="false" + case "$VERSION" in + *-*) PRERELEASE="true" ;; + esac + + # 1. Create the release. If a release for this tag already exists, + # Forgejo returns 409 and we exit non-zero so the user sees the + # real reason in the log instead of getting silent overwrites. + BODY=$(jq -n \ + --arg tag "$TAG" \ + --arg name "Drover-Go $TAG" \ + --argjson prerelease "$PRERELEASE" \ + --arg body "Automated release built from commit $GITHUB_SHA. See [docs](https://git.okcu.io/$FJ_REPO/src/tag/$TAG/docs/) for installation and troubleshooting." \ + '{tag_name: $tag, name: $name, prerelease: $prerelease, body: $body, draft: false}') + RELEASE_JSON=$(curl -fsS -X POST \ + -H "Authorization: token $FJ_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$BODY" \ + "https://$FJ_HOST/api/v1/repos/$FJ_REPO/releases") + RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id') + echo "Created release id=$RELEASE_ID" + + # 2. Upload each asset via multipart/form-data + for asset in dist/*.exe dist/*.zip dist/SHA256SUMS.txt; do + [ -f "$asset" ] || continue + name="$(basename "$asset")" + echo "Uploading $name..." + curl -fsS -X POST \ + -H "Authorization: token $FJ_TOKEN" \ + -F "attachment=@$asset;filename=$name" \ + "https://$FJ_HOST/api/v1/repos/$FJ_REPO/releases/$RELEASE_ID/assets?name=$name" \ + >/dev/null + done + + echo "Release $TAG published with $(ls dist | wc -l) assets."