name: Debian/Ubuntu Packages on: push: tags: - "v*.*.*" workflow_dispatch: inputs: tag: description: "Release tag (e.g. v0.1.0)" required: true permissions: contents: write jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - image: debian:12 suffix: debian12 backports: true - image: debian:13 suffix: debian13 - image: ubuntu:24.04 suffix: ubuntu2404 - image: ubuntu:25.04 suffix: ubuntu2504 - image: ubuntu:25.10 suffix: ubuntu2510 - image: ubuntu:26.04 suffix: ubuntu2604 container: ${{ matrix.image }} steps: - name: Bootstrap container (git + apt) shell: bash run: | set -e export DEBIAN_FRONTEND=noninteractive apt-get update apt-get install -y --no-install-recommends ca-certificates git - uses: actions/checkout@v5 - name: Enable backports if needed if: matrix.backports shell: bash run: | set -e export DEBIAN_FRONTEND=noninteractive codename=$(. /etc/os-release && echo "$VERSION_CODENAME") echo "deb http://deb.debian.org/debian ${codename}-backports main" \ > /etc/apt/sources.list.d/backports.list apt-get update - name: Install build deps shell: bash run: | set -e export DEBIAN_FRONTEND=noninteractive pkgs="build-essential devscripts debhelper dh-python \ python3-all python3-setuptools python3-wheel \ python3-hatchling python3-pip python3-build" apt-get install -y --no-install-recommends $pkgs if [ "${{ matrix.backports }}" = "true" ]; then codename=$(. /etc/os-release && echo "$VERSION_CODENAME") apt-get install -y -t ${codename}-backports pybuild-plugin-pyproject else apt-get install -y --no-install-recommends pybuild-plugin-pyproject fi - name: Build .deb shell: bash run: | set -e dpkg-buildpackage -us -uc -b mkdir -p dist for f in ../winpodx_*.deb; do base=$(basename "$f" .deb) cp "$f" "dist/${base}_${{ matrix.suffix }}.deb" done ls -la dist/ - uses: actions/upload-artifact@v4 with: name: deb-${{ matrix.suffix }} path: dist/*.deb publish: needs: build runs-on: ubuntu-latest steps: - name: Resolve tag id: tag shell: bash run: | if [ "${{ github.event_name }}" = "push" ]; then echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" else echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" fi - uses: actions/download-artifact@v4 with: path: all-debs pattern: deb-* merge-multiple: true - name: List collected debs run: ls -la all-debs/ # Single-writer rule: release.yml (REL- marker tag) is the SOLE creator # of the GitHub Release and the only writer of its title/notes. This # packaging workflow never creates or edits the release — it waits for # release.yml to create it, then attaches its assets. (The old per- # workflow `gh release create --notes "Release artifacts…"` stubs both # raced to produce duplicate releases — kernalix7 hit this on v0.4.1 # push 2026-05-05 — and churned the notes with placeholder text that # release.yml then overwrote, so the release showed as "edited" many # times. Waiting instead of creating eliminates both.) # # release.yml runs the full test suite before creating the release, so # it typically appears ~5-6 min after the push; poll up to 12 min. If it # never appears (e.g. only the v-tag was pushed, no REL- marker), skip # the upload gracefully — the .debs remain available as build-job # artifacts — rather than failing the run. - name: Wait for release (release.yml owns creation) id: wait env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tag="${{ steps.tag.outputs.tag }}" for _ in $(seq 1 48); do if gh release view "$tag" --repo "${{ github.repository }}" >/dev/null 2>&1; then echo "Release $tag exists — attaching .debs." echo "exists=true" >> "$GITHUB_OUTPUT" exit 0 fi echo "Release $tag not created yet (release.yml owns creation) — waiting…" sleep 15 done echo "::warning::Release $tag never appeared (REL-$tag marker tag not pushed?). Skipping .deb upload; .debs remain as build-job artifacts." echo "exists=false" >> "$GITHUB_OUTPUT" - name: Upload .deb to release if: steps.wait.outputs.exists == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release upload "${{ steps.tag.outputs.tag }}" all-debs/*.deb --clobber \ --repo "${{ github.repository }}"