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 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git clone --no-checkout "https://x-access-token:${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 (via innoextract, no Wine GUI install) run: | set -e dpkg --add-architecture i386 apt-get update # innoextract unpacks Inno Setup .exe installers without running them # under Wine. We then copy the extracted compiler files into the Wine # prefix and call ISCC.exe via Wine — much more reliable than running # the whole interactive installer through Wine + Xvfb (which failed # silently in the previous iteration). # ISCC.exe is a 32-bit binary even from Inno Setup 6 — without # wine32:i386 wine bails with "init_peb starting ... in experimental # wow64 mode / failed to load syswow64/ntdll.dll error c0000135". DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ wine wine64 wine32:i386 innoextract xvfb xauth curl ca-certificates \ >/dev/null 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 # Pin Inno Setup version. The innoextract package in Debian # trixie (1.9.x) parses headers up through Inno Setup 6.2.x. # 6.3+ already fails with "Unexpected setup data version" / # "Stream error while parsing setup headers", 6.5+ with # "Setup loader checksum mismatch". Stay on 6.2.2 until either # innoextract gets bumped in Debian or we replace it with a # docker-image-based ISCC step. INNO_VERSION="6.2.2" INNO_TAG="is-6_2_2" curl -fsSL -o /tmp/innosetup.exe \ "https://github.com/jrsoftware/issrc/releases/download/${INNO_TAG}/innosetup-${INNO_VERSION}.exe" # innoextract unpacks the installer payload to ./app — that's the # full Inno Setup directory tree (ISCC.exe + support DLLs/scripts). mkdir -p /tmp/inno-extract ( cd /tmp/inno-extract && innoextract /tmp/innosetup.exe ) # Place it where Windows-style paths can resolve. Wine maps # C:\InnoSetup to ~/.wine/drive_c/InnoSetup so we copy there. mkdir -p "$HOME/.wine/drive_c/InnoSetup" cp -a /tmp/inno-extract/app/. "$HOME/.wine/drive_c/InnoSetup/" # Sanity check ls -la "$HOME/.wine/drive_c/InnoSetup/ISCC.exe" 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."