Files
3m-ui/.github/workflows/release.yml
T
kazeyukiro eb78dec2d8 ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the
previous SemVer tag and this release, then append install notes.
2026-09-27 03:46:22 +00:00

222 lines
9.0 KiB
YAML

name: Release stable or release candidate
# Ways to release:
# 1) Push a SemVer tag: git tag v1.3.5 && git push origin v1.3.5
# 2) GitHub web UI → Actions → "Release stable or release candidate" → Run workflow
# Version: 1.3.5 or v1.3.5 (required)
# Ref: branch or SHA to tag (default main)
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: "Version to release (1.3.5 or v1.3.5; RC e.g. 1.4.0-rc.1)"
required: true
type: string
ref:
description: "Git ref to tag (branch name or commit SHA). Default: main"
required: false
type: string
default: main
permissions:
contents: read
concurrency:
group: release-formal-${{ inputs.version || github.ref_name }}
cancel-in-progress: false
jobs:
metadata:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
tag: ${{ steps.meta.outputs.tag }}
sha: ${{ steps.meta.outputs.sha }}
prerelease: ${{ steps.meta.outputs.prerelease }}
latest: ${{ steps.meta.outputs.latest }}
image_tag: ${{ steps.meta.outputs.image_tag }}
steps:
- name: Normalize version to SemVer tag
id: tagname
env:
RAW: ${{ inputs.version || github.ref_name }}
run: |
set -euo pipefail
RAW="${RAW#refs/tags/}"
case "$RAW" in
v*) TAG="$RAW" ;;
*) TAG="v$RAW" ;;
esac
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Normalized release tag: $TAG"
- name: Checkout source (web UI uses ref; tag push uses the tag)
if: github.event_name == 'workflow_dispatch'
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || 'main' }}
fetch-depth: 0
- name: Checkout tag (tag-push path)
if: github.event_name == 'push'
uses: actions/checkout@v4
with:
ref: ${{ steps.tagname.outputs.tag }}
fetch-depth: 0
- name: Create annotated tag when releasing from the web UI
if: github.event_name == 'workflow_dispatch'
env:
TAG: ${{ steps.tagname.outputs.tag }}
run: |
set -euo pipefail
SHA="$(git rev-parse HEAD)"
echo "Source ref resolves to $SHA"
if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then
# Peel annotated tags to the underlying commit.
EXISTING="$(git rev-parse "refs/tags/$TAG^{commit}" 2>/dev/null || git rev-parse "refs/tags/$TAG")"
if [ "$EXISTING" != "$SHA" ]; then
echo "Tag $TAG already points at $EXISTING, not $SHA. Refusing to move an immutable release tag." >&2
exit 1
fi
echo "Tag $TAG already exists on $SHA"
else
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Release $TAG" "$SHA"
git push origin "refs/tags/$TAG"
echo "Created and pushed tag $TAG → $SHA"
fi
- name: Resolve exact tag and channel
id: meta
env:
TAG: ${{ steps.tagname.outputs.tag }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
python3 scripts/release-metadata.py "$TAG"
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
if gh release view "$TAG" >/dev/null 2>release-error; then
echo "Release $TAG already exists. Published versions cannot be replaced." >&2
exit 1
fi
if ! grep -qiE 'release not found|HTTP 404' release-error; then
cat release-error >&2
exit 1
fi
build:
needs: metadata
uses: ./.github/workflows/release-build.yml
with:
source_sha: ${{ needs.metadata.outputs.sha }}
version: ${{ needs.metadata.outputs.tag }}
release:
needs: [metadata, build]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.sha }}
fetch-depth: 0
fetch-tags: true
- uses: actions/download-artifact@v4
with:
path: release-files
merge-multiple: true
- name: Collect scripts and checksum every public asset
env:
VERSION: ${{ needs.metadata.outputs.tag }}
REPOSITORY: ${{ github.repository }}
run: |
cp scripts/{install.sh,update.sh,uninstall.sh,3m-ui.sh,3m-ui} release-files/
cp docker-compose*.yml release-files/
cp distribution/mihomo.env release-files/
sed -i "s#ghcr.io/kazeyukiro/3m-ui#ghcr.io/${REPOSITORY,,}#g" release-files/docker-compose*.yml
sed -i "s#kazeyukiro/3m-ui#${REPOSITORY}#g" release-files/install.sh
printf '%s\n' "$VERSION" > release-files/VERSION
printf '%s\n' "$REPOSITORY" > release-files/REPOSITORY
cd release-files
sha256sum * > SHA256SUMS
- uses: sigstore/cosign-installer@v3
- name: Sign checksums using this workflow identity
working-directory: release-files
run: |
cosign sign-blob --yes --output-certificate SHA256SUMS.pem \
--output-signature SHA256SUMS.sig SHA256SUMS
- name: Publish once without overwriting assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.metadata.outputs.tag }}
PRERELEASE: ${{ needs.metadata.outputs.prerelease }}
LATEST: ${{ needs.metadata.outputs.latest }}
SHA: ${{ needs.metadata.outputs.sha }}
run: |
set -euo pipefail
# Previous formal SemVer tag (ignore pre / the tag we are publishing).
PREV="$(git tag -l 'v*' --sort=-version:refname | grep -E '^v[0-9]' | grep -vxF "$TAG" | head -n1 || true)"
{
if [ -n "${PREV}" ]; then
echo "## Changes since ${PREV}"
echo
RANGE="${PREV}..${SHA}"
else
echo "## Changes"
echo
RANGE="${SHA}"
fi
# Categorize conventional commits (same shape as v1.3.7 notes).
feats=$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges | grep -E '^- feat' || true)
fixes=$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges | grep -E '^- fix' || true)
perfs=$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges | grep -E '^- perf' || true)
docs=$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges | grep -E '^- docs' || true)
chores=$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges | grep -E '^- (chore|ci|build|refactor|style|test)' || true)
other=$(git log "$RANGE" --pretty=format:'- %s (%h)' --no-merges | grep -Ev '^- (feat|fix|perf|docs|chore|ci|build|refactor|style|test)' || true)
if [ -n "$feats" ]; then echo "### Features"; echo "$feats"; echo; fi
if [ -n "$fixes" ]; then echo "### Fixes"; echo "$fixes"; echo; fi
if [ -n "$perfs" ]; then echo "### Performance"; echo "$perfs"; echo; fi
if [ -n "$docs" ]; then echo "### Docs"; echo "$docs"; echo; fi
if [ -n "$chores" ]; then echo "### Chore / CI"; echo "$chores"; echo; fi
if [ -n "$other" ]; then echo "### Other"; echo "$other"; echo; fi
if [ -z "$feats$fixes$perfs$docs$chores$other" ]; then
echo "_No conventional commits in range._"
echo
fi
echo "---"
echo
echo "Complete Linux installation bundles are provided for amd64 and arm64."
echo "Each bundle includes the panel, its pinned Mihomo core and lifecycle scripts."
echo "Other architectures are standalone panel builds for advanced installations."
echo "Verify downloads against SHA256SUMS. SHA256SUMS.pem and SHA256SUMS.sig"
echo "provide Sigstore keyless verification for this release workflow."
echo "Docker images are published to ghcr.io/<owner>/<repository>:<tag>."
echo "The latest image and default installer follow stable releases only."
} > release-notes.md
cat release-notes.md
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists; refusing to overwrite."
exit 1
fi
gh release create "$TAG" --verify-tag --draft --title "$TAG" \
--notes-file release-notes.md
gh release upload "$TAG" release-files/*
gh release edit "$TAG" --draft=false --prerelease="$PRERELEASE" --latest="$LATEST"
image:
needs: [metadata, release]
permissions:
contents: read
packages: write
uses: ./.github/workflows/release-image.yml
with:
source_sha: ${{ needs.metadata.outputs.sha }}
version: ${{ needs.metadata.outputs.tag }}
image_tag: ${{ needs.metadata.outputs.image_tag }}
latest: ${{ needs.metadata.outputs.latest == 'true' }}