Files
drover-go/.gitea/workflows/release.yml
T
root 9d174d8db1
Build / test (push) Successful in 1m12s
Build / build-windows (push) Successful in 56s
Release / release (push) Successful in 2m59s
release.yml: drop apt cache (Gitea restore times out on 300MB)
Apt cache save works (28s in v0.1.4) but the next run can't restore
it: 'getCacheEntry failed: Request timeout' — Gitea cache backend
chokes on the ~300MB archive. Drop the cache step rather than burn
30s every run on a save that the next run can't read.

Real fix: bake wine + innoextract + xauth + wine32:i386 into a
custom CI image on git.okcu.io's registry, use container.image to
pull it. Defer until release frequency justifies the setup work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 03:26:37 +03:00

238 lines
11 KiB
YAML

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:
# Debian-based Docker images ship /etc/apt/apt.conf.d/docker-clean
# which deletes /var/cache/apt/archives after every apt-get install.
# That defeats actions/cache for the apt cache; disable it before
# any apt operations.
- name: Disable apt auto-clean (preserve cache for actions/cache)
run: |
rm -f /etc/apt/apt.conf.d/docker-clean
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
- name: Install Node (required by actions/cache)
run: |
apt-get update >/dev/null
apt-get install -y --no-install-recommends nodejs >/dev/null
- 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/. .
# Cache Go modules — saves ~10-20s on warm runs.
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: go-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: go-${{ runner.os }}-
# NOTE: actions/cache for the apt archive (~300 MB) is disabled. The
# save step works (~28s in v0.1.4) but restore times out on the
# next run — Gitea's cache server can't push 300 MB back fast
# enough. The Wine + Inno Setup install stays at ~1m20s. The
# right fix is a pre-baked Docker image (golang:1.25 + wine +
# innoextract + xauth + wine32:i386) pushed to git.okcu.io as
# the job's container.image. Tracked as future work.
- 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
# -H=windowsgui makes drover.exe a GUI subsystem binary so the
# double-click experience doesn't flash a console window. main.go
# calls AttachConsole on startup so CLI runs still print to the
# parent terminal when launched from cmd/PowerShell.
go build -trimpath -ldflags="-s -w -H=windowsgui \
-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."