name: Release on: push: tags: - 'v*' workflow_dispatch: inputs: tag: description: 'Tag to release (e.g. v1.2.3)' required: true jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Test run: go test ./... - name: Lint run: go vet ./... release: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Build release binaries run: | VERSION="${{ github.event.inputs.tag || github.ref_name }}" for target in "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64"; do GOOS="${target%/*}" GOARCH="${target#*/}" EXT="" [ "$GOOS" = "windows" ] && EXT=".exe" NAME="relspec-${GOOS}-${GOARCH}${EXT}" GOOS="$GOOS" GOARCH="$GOARCH" go build \ -trimpath \ -ldflags "-X git.warky.dev/wdevs/relspecgo/cmd/relspec.version=${VERSION}" \ -o "$NAME" ./cmd/relspec echo "Built $NAME" done - name: Create release and upload assets run: | TAG="${{ github.event.inputs.tag || github.ref_name }}" API="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" # Collect commits since the previous tag (or last 20 if no prior tag) PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${TAG}$" | head -1) if [ -n "$PREV_TAG" ]; then RANGE="${PREV_TAG}..${TAG}" else RANGE="HEAD~20..HEAD" fi NOTES=$(git log "$RANGE" --pretty=format:"- %s" --no-merges) BODY="## What's changed"$'\n'"${NOTES}" # Escape for JSON BODY_JSON=$(printf '%s' "$BODY" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))') RELEASE=$(curl -s -X POST "$API" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"body\":${BODY_JSON}}") UPLOAD_URL=$(echo "$RELEASE" | grep -o '"upload_url":"[^"]*"' | cut -d'"' -f4 | sed 's/{[^}]*}//') if [ -z "$UPLOAD_URL" ]; then echo "Failed to create release: $RELEASE" exit 1 fi for f in relspec-*; do echo "Uploading $f..." curl -s -X POST "${UPLOAD_URL}?name=${f}" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${f}" > /dev/null done env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pkg-aur: needs: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Publish to AUR env: AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} run: | set -euo pipefail VERSION="${{ github.event.inputs.tag || github.ref_name }}" PKGVER="${VERSION#v}" AUR_KEY_PATH="$HOME/.ssh/aur" AUR_KNOWN_HOSTS="$HOME/.ssh/known_hosts" # Setup SSH for AUR mkdir -p ~/.ssh chmod 700 ~/.ssh if [ -z "${AUR_SSH_KEY:-}" ]; then echo "AUR_SSH_KEY is empty" exit 1 fi # Support raw multiline keys, escaped \\n secrets, or base64-encoded keys. CLEAN_AUR_SSH_KEY="$(printf '%s' "$AUR_SSH_KEY" | tr -d '\r')" if printf '%s' "$CLEAN_AUR_SSH_KEY" | grep -q "^-----BEGIN .*PRIVATE KEY-----$"; then printf '%s\n' "$CLEAN_AUR_SSH_KEY" > "$AUR_KEY_PATH" elif printf '%s' "$CLEAN_AUR_SSH_KEY" | grep -q '\\n'; then printf '%b\n' "$CLEAN_AUR_SSH_KEY" > "$AUR_KEY_PATH" else if printf '%s' "$CLEAN_AUR_SSH_KEY" | tr -d '[:space:]' | base64 --decode > "$AUR_KEY_PATH" 2>/dev/null; then : else printf '%s\n' "$CLEAN_AUR_SSH_KEY" > "$AUR_KEY_PATH" fi fi chmod 600 "$AUR_KEY_PATH" if ! ssh-keygen -y -f "$AUR_KEY_PATH" >/dev/null 2>&1; then echo "AUR_SSH_KEY is not a valid private key." echo "Store it as a raw private key, an escaped private key with \\n, or a base64-encoded private key." exit 1 fi ssh-keyscan -t rsa,ed25519 aur.archlinux.org >> "$AUR_KNOWN_HOSTS" chmod 644 "$AUR_KNOWN_HOSTS" # Clone AUR repo GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$AUR_KNOWN_HOSTS -i $AUR_KEY_PATH" \ git clone ssh://aur@aur.archlinux.org/relspec.git aur-repo CURRENT_PKGVER=$(awk -F= '/^pkgver=/ {print $2; exit}' aur-repo/PKGBUILD | tr -d "[:space:]") CURRENT_PKGREL=$(awk -F= '/^pkgrel=/ {print $2; exit}' aur-repo/PKGBUILD | tr -d "[:space:]") if [ "$CURRENT_PKGVER" = "$PKGVER" ]; then case "$CURRENT_PKGREL" in ''|*[!0-9]*) echo "Unsupported pkgrel in AUR repo: ${CURRENT_PKGREL}" exit 1 ;; *) PKGREL=$((CURRENT_PKGREL + 1)) ;; esac else PKGREL=1 fi echo "Publishing AUR package version ${PKGVER}-${PKGREL}" # Compute SHA256 of the source archive from the same URL the PKGBUILD will download. SHA=$(curl -fsSL "https://git.warky.dev/wdevs/relspecgo/archive/v${PKGVER}.zip" | sha256sum | cut -d' ' -f1) # Update PKGBUILD — keep remote source URL, bump version/checksum, and increment pkgrel for same-version rebuilds. sed -e "s/^pkgver=.*/pkgver=${PKGVER}/" \ -e "s/^pkgrel=.*/pkgrel=${PKGREL}/" \ -e "s/^sha256sums=.*/sha256sums=('${SHA}')/" \ linux/arch/PKGBUILD > aur-repo/PKGBUILD # Generate .SRCINFO inside an Arch container (docker cp avoids DinD volume mount issues) CID=$(docker run -d archlinux:latest sleep infinity) docker cp aur-repo/PKGBUILD $CID:/build/PKGBUILD || (docker exec $CID mkdir -p /build && docker cp aur-repo/PKGBUILD $CID:/build/PKGBUILD) docker exec $CID bash -c " pacman -Sy --noconfirm base-devel && useradd -m builder && chown -R builder:builder /build && runuser -u builder -- bash -c 'cd /build && makepkg --printsrcinfo > .SRCINFO' " docker cp $CID:/build/.SRCINFO aur-repo/.SRCINFO docker rm -f $CID # Commit and push to AUR master cd aur-repo git config user.email "hein@warky.dev" git config user.name "Hein" git add PKGBUILD .SRCINFO git commit -m "Update to v${PKGVER}-${PKGREL}" GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$AUR_KNOWN_HOSTS -i $AUR_KEY_PATH" \ git push origin HEAD:master pkg-deb: needs: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Build Debian packages run: | VERSION="${{ github.event.inputs.tag || github.ref_name }}" PKGVER="${VERSION#v}" for GOARCH in amd64 arm64; do GOOS=linux GOARCH=$GOARCH go build \ -trimpath \ -ldflags "-X git.warky.dev/wdevs/relspecgo/cmd/relspec.version=${PKGVER}" \ -o relspec ./cmd/relspec PKGDIR="relspec_${PKGVER}_${GOARCH}" mkdir -p "${PKGDIR}/DEBIAN" mkdir -p "${PKGDIR}/usr/bin" install -m755 relspec "${PKGDIR}/usr/bin/relspec" sed -e "s/VERSION/${PKGVER}/" \ -e "s/ARCH/${GOARCH}/" \ linux/debian/control > "${PKGDIR}/DEBIAN/control" dpkg-deb --build --root-owner-group "${PKGDIR}" echo "Built ${PKGDIR}.deb" done - name: Upload to release run: | TAG="${{ github.event.inputs.tag || github.ref_name }}" RELEASE=$(curl -s "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \ -H "Authorization: token ${GITHUB_TOKEN}") UPLOAD_URL=$(echo "$RELEASE" | grep -o '"upload_url":"[^"]*"' | cut -d'"' -f4 | sed 's/{[^}]*}//') for f in *.deb; do FNAME=$(basename "$f") echo "Uploading $FNAME..." curl -s -X POST "${UPLOAD_URL}?name=${FNAME}" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${f}" > /dev/null done env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pkg-rpm: needs: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Build RPM run: | set -euo pipefail VERSION="${{ github.event.inputs.tag || github.ref_name }}" PKGVER="${VERSION#v}" GO_VER="$(awk '/^go / { print $2; exit }' go.mod)" if [ -z "${GO_VER}" ]; then echo "Failed to determine Go version from go.mod" exit 1 fi # Source tarball — prefix=relspec-VERSION/ matches RPM %autosetup convention git archive --format=tar.gz --prefix=relspec-${PKGVER}/ HEAD \ > relspec-${PKGVER}.tar.gz # Patch spec version sed -i "s/^Version:.*/Version: ${PKGVER}/" linux/centos/relspec.spec mkdir -p linux/centos/out CID=$(docker create \ -e GO_VER="${GO_VER}" \ -e PKGVER="${PKGVER}" \ -w /build \ rockylinux:9 \ bash -lc " set -euo pipefail dnf install -y rpm-build git && curl -fsSL https://go.dev/dl/go\${GO_VER}.linux-amd64.tar.gz | tar -C /usr/local -xz && export PATH=\$PATH:/usr/local/go/bin && mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} && cp relspec-${PKGVER}.tar.gz ~/rpmbuild/SOURCES/ && cp linux/centos/relspec.spec ~/rpmbuild/SPECS/ && rpmbuild --nodeps -ba ~/rpmbuild/SPECS/relspec.spec ") cleanup() { docker rm -f "$CID" >/dev/null 2>&1 || true } trap cleanup EXIT docker cp relspec-${PKGVER}.tar.gz "$CID:/build/relspec-${PKGVER}.tar.gz" docker cp linux "$CID:/build/linux" docker start -a "$CID" docker cp "$CID:/root/rpmbuild/RPMS/." linux/centos/out/ trap - EXIT cleanup - name: Upload to release run: | TAG="${{ github.event.inputs.tag || github.ref_name }}" RELEASE=$(curl -s "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \ -H "Authorization: token ${GITHUB_TOKEN}") UPLOAD_URL=$(echo "$RELEASE" | grep -o '"upload_url":"[^"]*"' | cut -d'"' -f4 | sed 's/{[^}]*}//') while IFS= read -r f; do FNAME=$(basename "$f") echo "Uploading $FNAME..." curl -s -X POST "${UPLOAD_URL}?name=${FNAME}" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${f}" > /dev/null done < <(find linux/centos/out -name "*.rpm") env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}