ci(release): add overwrite release rebuild mode

This commit is contained in:
Aethersailor
2026-05-27 21:24:10 +08:00
parent 8cbc63cf2d
commit 6554bf6288
2 changed files with 166 additions and 37 deletions

View File

@@ -16,7 +16,17 @@ on:
branches:
- master
- dev
workflow_dispatch: {}
workflow_dispatch:
inputs:
release_tag:
description: "Existing release tag to rebuild, e.g. v1.2.3"
required: false
type: string
overwrite_existing_release:
description: "Overwrite assets/body of an existing GitHub Release"
required: false
default: false
type: boolean
schedule:
- cron: '0 19 * * 0'
@@ -53,12 +63,44 @@ jobs:
- name: Determine version
id: version
env:
DISPATCH_RELEASE_TAG: ${{ inputs.release_tag }}
OVERWRITE_EXISTING_RELEASE: ${{ inputs.overwrite_existing_release }}
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
echo "Detected version from tag: $VERSION"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" && "$OVERWRITE_EXISTING_RELEASE" == "true" ]]; then
git fetch --tags --force origin
VERSION="${DISPATCH_RELEASE_TAG:-}"
VERSION="${VERSION#"${VERSION%%[![:space:]]*}"}"
VERSION="${VERSION%"${VERSION##*[![:space:]]}"}"
if [[ "$VERSION" != v* ]]; then
VERSION="v$VERSION"
fi
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Invalid release tag '$VERSION'. Use vX.Y.Z, for example v1.2.3."
exit 1
fi
if ! git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
echo "::error::Tag '$VERSION' does not exist on origin."
exit 1
fi
TAG_COMMIT="$(git rev-list -n 1 "$VERSION")"
MASTER_COMMIT="$(git rev-parse HEAD)"
echo "Overwrite release tag: $VERSION"
echo "Tag commit: $TAG_COMMIT"
echo "Master commit used for rebuilt assets: $MASTER_COMMIT"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_release=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == refs/heads/master ]] && ([[ "${{ github.event_name }}" == "workflow_dispatch" ]] || [[ "${{ github.event_name }}" == "schedule" ]]); then
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LATEST_TAG" ]; then
@@ -505,7 +547,7 @@ jobs:
name: "🚀 Release Artifacts"
runs-on: ubuntu-latest
needs: [prepare, build-linux, build-windows-amd64, merge-manifest]
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.overwrite_existing_release == true)
permissions:
contents: write
steps:
@@ -515,32 +557,42 @@ jobs:
fetch-depth: 0
- name: Prepare Release Notes Context
env:
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
OVERWRITE_EXISTING_RELEASE: ${{ inputs.overwrite_existing_release }}
run: |
set -euo pipefail
git fetch --tags --force origin
CURRENT_TAG="${GITHUB_REF_NAME}"
CURRENT_COMMIT="$(git rev-list -n 1 "$CURRENT_TAG")"
PREVIOUS_TAG="$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' "${CURRENT_COMMIT}^" 2>/dev/null || true)"
CURRENT_TAG="$RELEASE_VERSION"
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${OVERWRITE_EXISTING_RELEASE}" = "true" ]; then
CURRENT_COMMIT="$(git rev-parse HEAD)"
TAG_COMMIT="$(git rev-list -n 1 "$CURRENT_TAG")"
PREVIOUS_TAG="$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' "${TAG_COMMIT}^" 2>/dev/null || true)"
else
CURRENT_COMMIT="$(git rev-list -n 1 "$CURRENT_TAG")"
PREVIOUS_TAG="$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' "${CURRENT_COMMIT}^" 2>/dev/null || true)"
fi
EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
if [ -n "$PREVIOUS_TAG" ]; then
RANGE_LABEL="$PREVIOUS_TAG..$CURRENT_TAG"
git log --pretty=format:'- %h %s' "$PREVIOUS_TAG..$CURRENT_TAG" > commits.md
git diff --name-only "$PREVIOUS_TAG" "$CURRENT_TAG" > changed-files.md
git diff --stat "$PREVIOUS_TAG" "$CURRENT_TAG" > diffstat.md
RANGE_LABEL="$PREVIOUS_TAG..$CURRENT_COMMIT"
git log --pretty=format:'- %h %s' "$PREVIOUS_TAG..$CURRENT_COMMIT" > commits.md
git diff --name-only "$PREVIOUS_TAG" "$CURRENT_COMMIT" > changed-files.md
git diff --stat "$PREVIOUS_TAG" "$CURRENT_COMMIT" > diffstat.md
else
RANGE_LABEL="$CURRENT_TAG"
git log --pretty=format:'- %h %s' "$CURRENT_TAG" > commits.md
git diff --name-only "$EMPTY_TREE" "$CURRENT_TAG^{tree}" > changed-files.md
git diff --stat "$EMPTY_TREE" "$CURRENT_TAG^{tree}" > diffstat.md
RANGE_LABEL="$CURRENT_COMMIT"
git log --pretty=format:'- %h %s' "$CURRENT_COMMIT" > commits.md
git diff --name-only "$EMPTY_TREE" "$CURRENT_COMMIT^{tree}" > changed-files.md
git diff --stat "$EMPTY_TREE" "$CURRENT_COMMIT^{tree}" > diffstat.md
fi
{
echo "# Release Context"
echo
echo "Current tag: $CURRENT_TAG"
echo "Current commit: $CURRENT_COMMIT"
echo "Previous tag: ${PREVIOUS_TAG:-none}"
echo "Range: $RANGE_LABEL"
echo
@@ -687,6 +739,14 @@ jobs:
- `SHA256SUMS`
EOF
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.overwrite_existing_release }}" = "true" ]; then
{
echo
echo "<!-- rebuilt-from: $(git rev-parse HEAD) -->"
echo "<!-- rebuilt-at: $(date -u +%Y-%m-%dT%H:%M:%SZ) -->"
} >> release-notes.md
fi
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
@@ -707,9 +767,11 @@ jobs:
- name: Create Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
with:
tag_name: ${{ needs.prepare.outputs.version }}
files: |
artifacts/linux-*/*.tar.gz
artifacts/openwrt-*/*.apk
artifacts/windows-*/*.zip
SHA256SUMS
body_path: release-notes.md
overwrite_files: true

View File

@@ -3,15 +3,23 @@ name: Sync Dev to Master
on:
workflow_dispatch:
inputs:
release_mode:
description: "Release mode: new creates a tag, overwrite rebuilds an existing release, sync_only only syncs branches."
required: true
default: "new"
type: choice
options:
- new
- overwrite
- sync_only
version:
description: "Release tag, e.g. v1.2.3. Leave empty to auto bump patch. Used only when create_release_tag is true."
description: "Release tag, e.g. v1.2.3. Empty auto bumps for new or uses latest existing tag for overwrite."
required: false
type: string
create_release_tag:
description: "Create and push a new release tag after syncing."
confirm_overwrite:
description: "Type OVERWRITE to confirm rebuilding an existing release."
required: false
default: true
type: boolean
type: string
concurrency:
group: sync-dev-to-master
@@ -84,10 +92,13 @@ jobs:
git push origin master
fi
- name: Create and Push Release Tag
if: ${{ inputs.create_release_tag != false }}
- name: Resolve Release Tag
id: release_tag
if: ${{ inputs.release_mode != 'sync_only' }}
env:
REQUESTED_VERSION: ${{ github.event.inputs.version }}
RELEASE_MODE: ${{ inputs.release_mode }}
REQUESTED_VERSION: ${{ inputs.version }}
CONFIRM_OVERWRITE: ${{ inputs.confirm_overwrite }}
run: |
set -euo pipefail
@@ -97,6 +108,11 @@ jobs:
REQUESTED_VERSION="${REQUESTED_VERSION#"${REQUESTED_VERSION%%[![:space:]]*}"}"
REQUESTED_VERSION="${REQUESTED_VERSION%"${REQUESTED_VERSION##*[![:space:]]}"}"
if [ "$RELEASE_MODE" = "overwrite" ] && [ "${CONFIRM_OVERWRITE:-}" != "OVERWRITE" ]; then
echo "::error::confirm_overwrite must be exactly 'OVERWRITE' when release_mode=overwrite."
exit 1
fi
if [ -n "$REQUESTED_VERSION" ]; then
TAG_NAME="$REQUESTED_VERSION"
if [[ "$TAG_NAME" != v* ]]; then
@@ -107,13 +123,23 @@ jobs:
LATEST_TAG="$(git tag --list 'v*.*.*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)"
if [ -z "$LATEST_TAG" ]; then
TAG_NAME="v0.1.0"
echo "No existing vX.Y.Z tag found. Starting at $TAG_NAME"
if [ "$RELEASE_MODE" = "new" ]; then
TAG_NAME="v0.1.0"
echo "No existing vX.Y.Z tag found. Starting at $TAG_NAME"
else
echo "::error::No existing vX.Y.Z tag found to overwrite."
exit 1
fi
else
VERSION="${LATEST_TAG#v}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
TAG_NAME="v${MAJOR}.${MINOR}.$((PATCH + 1))"
echo "Auto bumped release tag from $LATEST_TAG to $TAG_NAME"
if [ "$RELEASE_MODE" = "new" ]; then
VERSION="${LATEST_TAG#v}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
TAG_NAME="v${MAJOR}.${MINOR}.$((PATCH + 1))"
echo "Auto bumped release tag from $LATEST_TAG to $TAG_NAME"
else
TAG_NAME="$LATEST_TAG"
echo "No release tag requested. Using latest existing release tag: $TAG_NAME"
fi
fi
fi
@@ -122,21 +148,62 @@ jobs:
exit 1
fi
if git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
echo "::error::Tag '$TAG_NAME' already exists locally."
exit 1
if [ "$RELEASE_MODE" = "new" ]; then
if git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
echo "::error::Tag '$TAG_NAME' already exists locally."
exit 1
fi
if git ls-remote --exit-code --tags origin "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
echo "::error::Tag '$TAG_NAME' already exists on origin."
exit 1
fi
else
if ! git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
echo "::error::Tag '$TAG_NAME' does not exist locally."
exit 1
fi
if ! git ls-remote --exit-code --tags origin "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
echo "::error::Tag '$TAG_NAME' does not exist on origin."
exit 1
fi
TAG_COMMIT="$(git rev-list -n 1 "$TAG_NAME")"
MASTER_COMMIT="$(git rev-parse HEAD)"
echo "Overwrite release tag: $TAG_NAME"
echo "Tag commit: $TAG_COMMIT"
echo "Master commit used for rebuilt assets: $MASTER_COMMIT"
fi
if git ls-remote --exit-code --tags origin "refs/tags/$TAG_NAME" >/dev/null 2>&1; then
echo "::error::Tag '$TAG_NAME' already exists on origin."
exit 1
fi
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
- name: Create and Push Release Tag
if: ${{ inputs.release_mode == 'new' }}
env:
TAG_NAME: ${{ steps.release_tag.outputs.tag_name }}
run: |
set -euo pipefail
git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
git push origin "$TAG_NAME"
- name: Skip Release Tag
if: ${{ inputs.create_release_tag == false }}
- name: Dispatch Overwrite Release Build
if: ${{ inputs.release_mode == 'overwrite' }}
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
TAG_NAME: ${{ steps.release_tag.outputs.tag_name }}
run: |
echo "create_release_tag=false; synced dev to master without creating a release tag."
set -euo pipefail
gh workflow run build-dockerhub.yml \
--ref master \
-f release_tag="$TAG_NAME" \
-f overwrite_existing_release=true
echo "Dispatched release rebuild for $TAG_NAME from master."
- name: Skip Release
if: ${{ inputs.release_mode == 'sync_only' }}
run: |
echo "release_mode=sync_only; synced dev to master without creating or rebuilding a release."