Nightly upstream sync #65
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Nightly upstream sync - rebases the eMuleBB aMule fork only when upstream | |
| # moved, builds the requested packaging tracks, and publishes a prerelease. | |
| name: Nightly upstream sync | |
| on: | |
| schedule: | |
| # GitHub schedules run in UTC. Use an off-hour minute to avoid the | |
| # top-of-hour global Actions queue spike. | |
| - cron: '23 2 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: Build and publish even when upstream has not moved. | |
| required: false | |
| type: boolean | |
| default: false | |
| tracks: | |
| description: >- | |
| Comma-separated packaging tracks. Default covers Windows plus Linux. | |
| Valid: windows, appimage, flatpak, macos. | |
| required: false | |
| type: string | |
| default: 'windows,appimage,flatpak' | |
| permissions: | |
| attestations: write | |
| artifact-metadata: write | |
| contents: write | |
| actions: read | |
| id-token: write | |
| concurrency: | |
| group: amule-nightly-upstream | |
| cancel-in-progress: false | |
| jobs: | |
| prepare: | |
| name: Check upstream and prepare rebased ref | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| changed: ${{ steps.prepare.outputs.changed }} | |
| build_ref: ${{ steps.prepare.outputs.build_ref }} | |
| nightly_tag: ${{ steps.prepare.outputs.nightly_tag }} | |
| origin_master_sha: ${{ steps.prepare.outputs.origin_master_sha }} | |
| upstream_sha: ${{ steps.prepare.outputs.upstream_sha }} | |
| upstream_workflow_changes: ${{ steps.prepare.outputs.upstream_workflow_changes }} | |
| tracks: ${{ steps.prepare.outputs.tracks }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: master | |
| - name: Rebase fork branch when upstream changed | |
| id: prepare | |
| env: | |
| FORCE: ${{ inputs.force || false }} | |
| TRACKS: ${{ inputs.tracks || 'windows,appimage,flatpak' }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "eMuleBB automation" | |
| git config user.email "actions@users.noreply.github.com" | |
| # aMule development moved to the amule-org organization after the | |
| # amule-project owner became unreachable; track the active upstream. | |
| git remote add upstream https://github.com/amule-org/amule.git | |
| git fetch origin master --tags | |
| git fetch origin automation/upstream-nightly || true | |
| git fetch upstream master --tags | |
| ORIGIN_MASTER_SHA="$(git rev-parse origin/master)" | |
| UPSTREAM_SHA="$(git rev-parse upstream/master)" | |
| UPSTREAM_BASE="$(git merge-base origin/master upstream/master)" | |
| UPSTREAM_WORKFLOW_CHANGES="$(git diff --name-only "${UPSTREAM_BASE}..upstream/master" -- .github/workflows || true)" | |
| TRACKS="${TRACKS:-windows,appimage,flatpak}" | |
| echo "origin/master: ${ORIGIN_MASTER_SHA}" | |
| echo "upstream/master: ${UPSTREAM_SHA}" | |
| echo "tracks: ${TRACKS}" | |
| if [[ -n "${UPSTREAM_WORKFLOW_CHANGES}" ]]; then | |
| { | |
| echo "### Upstream workflow changes held for manual review" | |
| echo | |
| echo "eMuleBB keeps .github/workflows fork-owned during automatic upstream rebases." | |
| echo "The following upstream workflow files changed and were restored from origin/master before publishing:" | |
| echo | |
| printf '%s\n' "${UPSTREAM_WORKFLOW_CHANGES}" | sed 's/^/- `/' | sed 's/$/`/' | |
| echo | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| fi | |
| if [[ "${FORCE}" != "true" ]] && git merge-base --is-ancestor "${UPSTREAM_SHA}" origin/master; then | |
| echo "No new upstream commits since master was last published." | |
| { | |
| echo "changed=false" | |
| echo "origin_master_sha=${ORIGIN_MASTER_SHA}" | |
| echo "upstream_sha=${UPSTREAM_SHA}" | |
| echo "upstream_workflow_changes=" | |
| echo "tracks=${TRACKS}" | |
| } >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| # .github/workflows is fork-owned. Auto-resolve any workflow-file | |
| # conflict during the rebase to the in-progress side so the rebase | |
| # never aborts on them (the ownership-restore step below overwrites | |
| # them from origin/master regardless of which side wins here). | |
| git config merge.ours.driver true | |
| printf '%s\n' '.github/workflows/** merge=ours' >> .git/info/attributes | |
| git checkout -B automation/upstream-nightly origin/master | |
| git rebase upstream/master | |
| # Always restore fork-owned workflows from origin/master after the | |
| # rebase, regardless of whether upstream touched them. This keeps the | |
| # published ref carrying eMuleBB CI even when a workflow conflict was | |
| # auto-resolved to the upstream side above. | |
| git rm -r --quiet .github/workflows || true | |
| git checkout origin/master -- .github/workflows | |
| if ! git diff --cached --quiet -- .github/workflows || ! git diff --quiet -- .github/workflows; then | |
| git add .github/workflows | |
| git commit -m "CI-042 preserve eMuleBB workflow ownership" | |
| fi | |
| SHORT_UPSTREAM="${UPSTREAM_SHA:0:8}" | |
| NIGHTLY_TAG="amule-nightly-$(date -u +%Y%m%d)-${SHORT_UPSTREAM}" | |
| git tag -f "${NIGHTLY_TAG}" | |
| git push origin HEAD:refs/heads/automation/upstream-nightly --force-with-lease | |
| git push origin "refs/tags/${NIGHTLY_TAG}" --force | |
| { | |
| echo "changed=true" | |
| echo "build_ref=refs/tags/${NIGHTLY_TAG}" | |
| echo "nightly_tag=${NIGHTLY_TAG}" | |
| echo "origin_master_sha=${ORIGIN_MASTER_SHA}" | |
| echo "upstream_sha=${UPSTREAM_SHA}" | |
| echo "upstream_workflow_changes<<EOF" | |
| printf '%s\n' "${UPSTREAM_WORKFLOW_CHANGES}" | |
| echo "EOF" | |
| echo "tracks=${TRACKS}" | |
| } >> "${GITHUB_OUTPUT}" | |
| build: | |
| name: Build nightly packages | |
| needs: prepare | |
| if: needs.prepare.outputs.changed == 'true' | |
| uses: ./.github/workflows/packaging.yml | |
| with: | |
| only: ${{ needs.prepare.outputs.tracks }} | |
| checkout_ref: ${{ needs.prepare.outputs.build_ref }} | |
| publish-master: | |
| name: Publish rebased master | |
| needs: | |
| - prepare | |
| - build | |
| if: needs.prepare.outputs.changed == 'true' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ needs.prepare.outputs.build_ref }} | |
| - name: Update master after green build | |
| run: | | |
| set -euo pipefail | |
| git push origin \ | |
| "HEAD:refs/heads/master" \ | |
| --force-with-lease="refs/heads/master:${{ needs.prepare.outputs.origin_master_sha }}" | |
| cleanup-failed-build: | |
| name: Remove failed nightly tag | |
| needs: | |
| - prepare | |
| - build | |
| - publish-master | |
| if: >- | |
| always() && | |
| needs.prepare.outputs.changed == 'true' && | |
| (needs.build.result != 'success' || needs.publish-master.result != 'success') | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Delete tag for failed nightly | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.prepare.outputs.nightly_tag }} | |
| run: | | |
| set -euo pipefail | |
| gh api \ | |
| --method DELETE \ | |
| "repos/${{ github.repository }}/git/refs/tags/${TAG}" || true | |
| release: | |
| name: Publish nightly prerelease | |
| needs: | |
| - prepare | |
| - build | |
| - publish-master | |
| if: needs.prepare.outputs.changed == 'true' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ needs.prepare.outputs.build_ref }} | |
| - name: Download package artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: downloaded | |
| - name: Stage release artifacts | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| find downloaded -maxdepth 4 -type f \ | |
| \( -name '*.zip' -o -name '*.AppImage' -o -name '*.flatpak' -o -name '*.dmg' \) \ | |
| -print -exec cp '{}' dist/ \; | |
| ls -la dist | |
| - name: Generate artifact attestations | |
| uses: actions/attest@v4 | |
| with: | |
| subject-path: dist/* | |
| - name: Publish or refresh prerelease | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.prepare.outputs.nightly_tag }} | |
| UPSTREAM_SHA: ${{ needs.prepare.outputs.upstream_sha }} | |
| UPSTREAM_WORKFLOW_CHANGES: ${{ needs.prepare.outputs.upstream_workflow_changes }} | |
| TRACKS: ${{ needs.prepare.outputs.tracks }} | |
| run: | | |
| set -euo pipefail | |
| NOTES="$(mktemp)" | |
| { | |
| echo "Automated eMuleBB aMule nightly built after upstream moved." | |
| echo | |
| echo "Upstream master: ${UPSTREAM_SHA}" | |
| echo "Build ref: ${TAG}" | |
| echo "Tracks: ${TRACKS}" | |
| echo | |
| echo "This prerelease is generated by the scheduled upstream sync workflow." | |
| echo "Release assets include GitHub artifact attestations." | |
| echo | |
| echo "Verify a downloaded asset with:" | |
| echo | |
| echo " gh attestation verify PATH_TO_ASSET -R ${{ github.repository }}" | |
| if [[ -n "${UPSTREAM_WORKFLOW_CHANGES}" ]]; then | |
| echo | |
| echo "Upstream workflow changes were detected but not merged automatically." | |
| echo "eMuleBB keeps .github/workflows fork-owned; review these manually:" | |
| printf '%s\n' "${UPSTREAM_WORKFLOW_CHANGES}" | sed 's/^/- /' | |
| fi | |
| } > "${NOTES}" | |
| if gh release view "${TAG}" >/dev/null 2>&1; then | |
| gh release upload "${TAG}" dist/* --clobber | |
| gh release edit "${TAG}" --title "aMule nightly ${TAG}" --notes-file "${NOTES}" --prerelease | |
| else | |
| gh release create "${TAG}" \ | |
| --title "aMule nightly ${TAG}" \ | |
| --notes-file "${NOTES}" \ | |
| --prerelease \ | |
| dist/* | |
| fi | |
| - name: Keep only latest nightly release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NIGHTLY_TAG_PREFIX: amule-nightly- | |
| run: | | |
| set -euo pipefail | |
| jq_filter='.[] | select(.isPrerelease and (.tagName | startswith("'"${NIGHTLY_TAG_PREFIX}"'"))) | [.createdAt, .tagName] | @tsv' | |
| mapfile -t old_tags < <( | |
| gh release list \ | |
| --repo "${{ github.repository }}" \ | |
| --limit 100 \ | |
| --json tagName,isPrerelease,createdAt \ | |
| --jq "${jq_filter}" | | |
| sort -r | | |
| tail -n +2 | | |
| cut -f2 | |
| ) | |
| if [[ "${#old_tags[@]}" -eq 0 ]]; then | |
| echo "No older nightly releases to delete." | |
| exit 0 | |
| fi | |
| for tag in "${old_tags[@]}"; do | |
| echo "Deleting old nightly release and tag: ${tag}" | |
| gh release delete "${tag}" \ | |
| --repo "${{ github.repository }}" \ | |
| --cleanup-tag \ | |
| --yes | |
| done | |
| no-change: | |
| name: No upstream changes | |
| needs: prepare | |
| if: needs.prepare.outputs.changed == 'false' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - run: echo "No upstream changes; nightly build and release skipped." |