3e779be6f3
CI / test (3.14, ubuntu-24.04-arm) (push) Waiting to run
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
265 lines
12 KiB
YAML
265 lines
12 KiB
YAML
name: OBS Publish
|
|
|
|
# OBS builds RPMs asynchronously on shared openSUSE infrastructure
|
|
# (~30-60 min per rebuild). Trying to trigger the rebuild AND download the
|
|
# freshly-built RPMs in the same tag-push run was structurally broken: the
|
|
# download ran minutes after the trigger, long before OBS had published the
|
|
# new version, so every release push failed with "No RPMs were downloaded".
|
|
#
|
|
# This workflow is split to match OBS's async reality:
|
|
# * `trigger` (tag push / dispatch) — fire the OBS rebuild, then exit
|
|
# green. It never tries to download, so a release push can't go red here.
|
|
# * `attach` (hourly schedule / dispatch) — once OBS has published the
|
|
# RPMs for the latest release's version, download them and attach them to
|
|
# the existing GitHub Release. It is a no-op (green) when the RPMs aren't
|
|
# built yet (retries next hour) or are already attached (no churn).
|
|
#
|
|
# Single-writer rule: this workflow NEVER creates the GitHub Release or edits
|
|
# its title/notes — release.yml (REL- marker tag) is the sole creator and the
|
|
# only writer of the release body. `attach` only uploads assets to a release
|
|
# that already exists, so it can't cause the "edited N times" / placeholder-
|
|
# notes churn the old per-workflow `gh release create --notes …` stubs did.
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*.*.*"
|
|
schedule:
|
|
# Hourly sweep: attach OBS RPMs to the latest release once OBS finishes
|
|
# building them. A no-op the rest of the time (already attached / not
|
|
# built yet), so it never edits a release it has nothing new for.
|
|
- cron: "17 * * * *"
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Release tag to trigger / attach RPMs to (e.g. v0.6.0)"
|
|
required: true
|
|
force:
|
|
description: "Force OBS runservice even if Tumbleweed RPM exists (use when source bumped without version change)"
|
|
required: false
|
|
default: "false"
|
|
|
|
env:
|
|
OBS_PROJECT: home:Kernalix7
|
|
OBS_PACKAGE: winpodx
|
|
OBS_API: https://api.opensuse.org
|
|
# /public/ is OBS's anonymous read-only API mirror — no auth needed for
|
|
# public home: projects. The runservice token can't read /build/.
|
|
OBS_PUBLIC_API: https://api.opensuse.org/public
|
|
OBS_DOWNLOAD: https://download.opensuse.org/repositories/home:/Kernalix7
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
# Fire the OBS rebuild and exit. Runs on tag push and manual dispatch only.
|
|
# Never downloads — so a release push is always green here; the RPMs land
|
|
# asynchronously via the `attach` job once OBS finishes the server-side build.
|
|
trigger:
|
|
if: github.event_name != 'schedule'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
- name: Resolve tag
|
|
id: tag
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "push" ]; then
|
|
tag="${{ github.ref_name }}"
|
|
else
|
|
tag="${{ inputs.tag }}"
|
|
fi
|
|
# OBS feeds the version into both deb and rpm specs; rpm forbids
|
|
# '-' in Version: (Version/Release delimiter). Strip the RTM
|
|
# gate suffix so `0.3.0-RTM1` lands as `0.3.0`.
|
|
version="${tag#v}"
|
|
version="${version%%-RTM*}"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Install curl + jq
|
|
run: sudo apt-get update && sudo apt-get install -y curl jq
|
|
|
|
- name: Probe whether OBS already has target version built
|
|
# Avoid triggering an unnecessary OBS rebuild when the target version
|
|
# is already published. A single rebuild costs ~30-60min on OBS shared
|
|
# infrastructure and is pure waste when the RPMs we'd download are
|
|
# byte-identical to what's already on the server. Probe a representative
|
|
# shipping repo (Tumbleweed) for a winpodx-<version>-*.rpm filename in
|
|
# the public download index; if found, OBS has the build cached.
|
|
id: probe
|
|
run: |
|
|
version="${{ steps.tag.outputs.version }}"
|
|
if [ "${{ inputs.force }}" = "true" ]; then
|
|
echo "force=true requested — bypassing Tumbleweed RPM probe and triggering runservice."
|
|
echo "skip_runservice=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
listing_url="${OBS_DOWNLOAD}/openSUSE_Tumbleweed/noarch/"
|
|
html=$(curl -sS -f -L "$listing_url" 2>/dev/null || echo "")
|
|
if echo "$html" | grep -qE "winpodx-${version}-[0-9]+\.[0-9]+\.noarch\.rpm"; then
|
|
echo "OBS already has openSUSE_Tumbleweed RPM for v${version} — skipping runservice."
|
|
echo "skip_runservice=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "OBS doesn't yet have openSUSE_Tumbleweed RPM for v${version} — will trigger runservice."
|
|
echo "skip_runservice=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Trigger OBS service run (token)
|
|
if: steps.probe.outputs.skip_runservice == 'false'
|
|
env:
|
|
OBS_TOKEN: ${{ secrets.OBS_TOKEN }}
|
|
run: |
|
|
if [ -z "$OBS_TOKEN" ]; then
|
|
echo "OBS_TOKEN secret is not set" >&2
|
|
exit 1
|
|
fi
|
|
curl -sS -f -X POST \
|
|
-H "Authorization: Token $OBS_TOKEN" \
|
|
"$OBS_API/trigger/runservice?project=$OBS_PROJECT&package=$OBS_PACKAGE"
|
|
echo "OBS rebuild triggered for v${{ steps.tag.outputs.version }}. RPMs will be attached by the hourly 'attach' job once OBS finishes (~30-60 min)."
|
|
|
|
# Download OBS-built RPMs once they're published and attach them to the
|
|
# existing GitHub Release. Runs hourly (sweeps the latest release) and on
|
|
# manual dispatch (targets inputs.tag). Never creates or edits the release.
|
|
attach:
|
|
if: github.event_name != 'push'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
- name: Install curl + jq
|
|
run: sudo apt-get update && sudo apt-get install -y curl jq
|
|
|
|
- name: Resolve target tag
|
|
id: tag
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
tag="${{ inputs.tag }}"
|
|
else
|
|
# schedule: sweep the most recent published release.
|
|
tag=$(gh release list --repo "${{ github.repository }}" --limit 1 \
|
|
--json tagName --jq '.[0].tagName' 2>/dev/null || echo "")
|
|
fi
|
|
if [ -z "$tag" ]; then
|
|
echo "No target tag (no releases yet?) — nothing to attach."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
version="${tag#v}"
|
|
version="${version%%-RTM*}"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Skip if release already has OBS RPMs attached
|
|
id: gate
|
|
if: steps.tag.outputs.skip != 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
tag="${{ steps.tag.outputs.tag }}"
|
|
# release.yml owns creation — only attach to a release that exists.
|
|
if ! gh release view "$tag" --repo "${{ github.repository }}" >/dev/null 2>&1; then
|
|
echo "Release $tag does not exist yet (release.yml owns creation) — nothing to attach."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
# Tumbleweed is always built and is the canary for "OBS RPMs already
|
|
# attached". If its asset is present, the sweep already ran for this
|
|
# release — exit green without re-uploading (no edit churn).
|
|
assets=$(gh release view "$tag" --repo "${{ github.repository }}" \
|
|
--json assets --jq '.assets[].name' 2>/dev/null || echo "")
|
|
if echo "$assets" | grep -q "openSUSE_Tumbleweed\.rpm$"; then
|
|
echo "OBS RPMs already attached to $tag — no-op."
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Discover repos and download RPMs
|
|
id: download
|
|
if: steps.gate.outputs.skip == 'false'
|
|
run: |
|
|
mkdir -p dist-rpms
|
|
version="${{ steps.tag.outputs.version }}"
|
|
|
|
# Parse enabled repos from project _meta (public, no auth)
|
|
meta=$(curl -sS -f "$OBS_PUBLIC_API/source/$OBS_PROJECT/_meta")
|
|
repos=$(echo "$meta" | grep -oP '(?<=<repository name=")[^"]+')
|
|
if [ -z "$repos" ]; then
|
|
echo "No repositories configured on $OBS_PROJECT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for repo in $repos; do
|
|
# openSUSE_Factory_{ARM,PowerPC,RISCV,zSystems} builds the same
|
|
# noarch content as openSUSE_Tumbleweed — publishing all five would
|
|
# just list four identical RPMs with misleading architecture-looking
|
|
# names. Skip the non-x86 Factory rebuilds; Tumbleweed users on
|
|
# aarch64/ppc64/riscv64/s390x install the Tumbleweed noarch RPM.
|
|
case "$repo" in
|
|
openSUSE_Factory_*) echo "Skipping $repo (noarch duplicate of Tumbleweed)"; continue ;;
|
|
esac
|
|
# Tumbleweed / Fedora / Slowroll all produce the same filename
|
|
# (winpodx-<ver>-<rel>.noarch.rpm) because their release tags lack
|
|
# a distro suffix. Without renaming, only the last-iterated repo's
|
|
# file survives in dist-rpms/. Append the repo name to every RPM so
|
|
# each distro's build gets a stable, unique asset name on the
|
|
# Release — matches the RHEL convention (.el9.rpm / .el10.rpm).
|
|
# Normalize short Leap repo names ("15.6", "16.0") to the familiar
|
|
# openSUSE_Leap_* form so the final filename is self-describing
|
|
# instead of ending in a bare version number like ".15.6.rpm".
|
|
case "$repo" in
|
|
1[0-9].[0-9]|1[0-9].[0-9][0-9]) repo_slug="openSUSE_Leap_$repo" ;;
|
|
*) repo_slug=$(echo "$repo" | tr '/:' '__') ;;
|
|
esac
|
|
for arch in x86_64 noarch; do
|
|
base="$OBS_DOWNLOAD/$repo/$arch"
|
|
# MirrorCache listing hrefs have a "./" prefix
|
|
# (href="./winpodx-<ver>-<rel>.noarch.rpm"); match the filename
|
|
# directly instead of anchoring on `href="winpodx-`.
|
|
listing=$(curl -sSL "$base/" || true)
|
|
[ -z "$listing" ] && continue
|
|
# Anchor the filename match to the current tag's version so a
|
|
# stale older RPM still in the OBS publish index (Tumbleweed /
|
|
# Slowroll rolling-release repos are slow to garbage-collect)
|
|
# doesn't get downloaded and re-uploaded as a v${version}
|
|
# release asset.
|
|
rpms=$(echo "$listing" | grep -oE "winpodx-${version}-[^\"]+\.rpm" | sort -u)
|
|
for f in $rpms; do
|
|
case "$f" in
|
|
*debuginfo*|*debugsource*|*.src.rpm) continue ;;
|
|
esac
|
|
out="dist-rpms/${f%.rpm}.${repo_slug}.rpm"
|
|
echo "Fetching $repo/$arch/$f -> $(basename "$out")"
|
|
curl -sS -fL -o "$out" "$base/$f" || true
|
|
done
|
|
done
|
|
done
|
|
|
|
ls -la dist-rpms/
|
|
if ! ls dist-rpms/*.rpm >/dev/null 2>&1; then
|
|
echo "::notice::OBS hasn't published RPMs for v${version} yet — they build asynchronously (~30-60 min). The hourly sweep will retry; or re-run this workflow once the build finishes."
|
|
echo "have_rpms=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "have_rpms=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Attach RPMs to GitHub release
|
|
if: steps.download.outputs.have_rpms == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
gh release upload "${{ steps.tag.outputs.tag }}" dist-rpms/*.rpm --clobber \
|
|
--repo "${{ github.repository }}"
|
|
echo "Attached OBS RPMs to ${{ steps.tag.outputs.tag }}."
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
if: steps.download.outputs.have_rpms == 'true'
|
|
with:
|
|
name: rpms-${{ steps.tag.outputs.version }}
|
|
path: dist-rpms/
|