Skip to content

Nightly upstream sync #66

Nightly upstream sync

Nightly upstream sync #66

# 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."