This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch 2.7.1-release
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/2.7.1-release by this push:
new f4e9e8b015 Add release script for Apache ShenYu 2.7.1
f4e9e8b015 is described below
commit f4e9e8b015a6968e86fde64faa36d228c3e61385
Author: liuhy <[email protected]>
AuthorDate: Tue Jun 16 19:49:26 2026 +0800
Add release script for Apache ShenYu 2.7.1
---
release.sh | 1115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1115 insertions(+)
diff --git a/release.sh b/release.sh
new file mode 100755
index 0000000000..cca4c7e829
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,1115 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ----------------------------------------------------------------------------
+# Apache ShenYu Release Script
+# Version: 2.7.1
+#
+# Usage:
+# ./release.sh <step> [options]
+#
+# Steps:
+# 1 - Prerequisites check
+# 2 - Maven release:prepare dry-run
+# 3 - Maven release:prepare (actual)
+# 4 - Maven release:perform
+# 5 - Publish to SVN dev repository
+# 6 - Verify release artifacts
+# 7 - Print voting email templates
+# 8 - Final release (after vote passes)
+# all - Run steps 1-6 sequentially
+# clean - Rollback: delete branch, tags, and clean Maven release artifacts
+#
+# Environment variables (can also be passed via options):
+# RELEASE_VERSION - Version to release (default: 2.7.1)
+# NEXT_VERSION - Next SNAPSHOT version (default: 2.7.2-SNAPSHOT)
+# GPG_KEY - GPG key ID (default: auto-detect from git user)
+# APACHE_USER - Apache LDAP username
+# GPG_PASSPHRASE - GPG key passphrase
+# SHENYU_HOME - Path to shenyu source (default: script directory)
+# SVN_DEV_DIR - Local SVN dev checkout dir (default:
~/svn_release/dev/shenyu)
+# SVN_RELEASE_DIR - Local SVN release checkout dir (default:
~/svn_release/release/shenyu)
+# ----------------------------------------------------------------------------
+
+set -euo pipefail
+
+# ========================= Configuration =========================
+
+RELEASE_VERSION="${RELEASE_VERSION:-2.7.1}"
+NEXT_VERSION="${NEXT_VERSION:-2.7.2-SNAPSHOT}"
+APACHE_USER="${APACHE_USER:-liuhongyu}"
+GPG_KEY="${GPG_KEY:-}"
+GPG_PASSPHRASE="${GPG_PASSPHRASE:-}"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+SHENYU_HOME="${SHENYU_HOME:-${SCRIPT_DIR}}"
+SVN_DEV_DIR="${SVN_DEV_DIR:-${HOME}/svn_release/dev/shenyu}"
+SVN_RELEASE_DIR="${SVN_RELEASE_DIR:-${HOME}/svn_release/release/shenyu}"
+RELEASE_BRANCH="${RELEASE_VERSION}-release"
+GIT_TAG="v${RELEASE_VERSION}"
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# ========================= Helper Functions =========================
+
+log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
+log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
+log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
+log_step() { echo -e "\n${BLUE}========== STEP $1: $2 ==========${NC}\n"; }
+
+confirm() {
+ local prompt="$1"
+ local default="${2:-n}"
+ local answer
+ if [[ "$default" == "y" ]]; then
+ read -rp "$(echo -e "${YELLOW}${prompt} [Y/n]${NC} ")" answer
+ [[ -z "$answer" || "$answer" =~ ^[Yy] ]]
+ else
+ read -rp "$(echo -e "${YELLOW}${prompt} [y/N]${NC} ")" answer
+ [[ "$answer" =~ ^[Yy] ]]
+ fi
+}
+
+require_var() {
+ local name="$1"
+ local value="$2"
+ if [[ -z "$value" ]]; then
+ log_error "Required variable $name is not set. Export it or pass it as
an option."
+ exit 1
+ fi
+}
+
+check_command() {
+ if ! command -v "$1" &>/dev/null; then
+ log_error "Command '$1' is required but not found. Please install it
first."
+ exit 1
+ fi
+}
+
+detect_gpg_key() {
+ if [[ -z "$GPG_KEY" ]]; then
+ # Try to find the key for the Apache user
+ GPG_KEY=$(gpg --list-secret-keys --keyid-format long 2>/dev/null \
+ | grep -A1 "uid.*${APACHE_USER}@apache.org" \
+ | grep "sec" | awk '{print $2}' | cut -d'/' -f2 | head -1)
+ if [[ -z "$GPG_KEY" ]]; then
+ # Fallback: use the first secret key
+ GPG_KEY=$(gpg --list-secret-keys --keyid-format long 2>/dev/null \
+ | grep "sec" | awk '{print $2}' | cut -d'/' -f2 | head -1)
+ fi
+ fi
+ echo "$GPG_KEY"
+}
+
+mvn_release_args() {
+ local args="-DskipTests"
+ if [[ -n "$GPG_PASSPHRASE" ]]; then
+ args="$args -Dgpg.passphrase=${GPG_PASSPHRASE}"
+ fi
+ echo "$args"
+}
+
+# ========================= Step 1: Prerequisites Check
=========================
+
+step_1_prerequisites() {
+ log_step "1" "Prerequisites Check"
+
+ local ok=true
+
+ # Check required commands
+ for cmd in java mvn git gpg svn curl; do
+ if check_command "$cmd" 2>/dev/null; then
+ local version
+ version=$("$cmd" --version 2>/dev/null | head -1 || echo "unknown")
+ log_info "Found $cmd: $version"
+ else
+ log_error "Missing required command: $cmd"
+ ok=false
+ fi
+ done
+
+ # Check Java version (need Java 17+)
+ local java_version
+ java_version=$(java -version 2>&1 | head -1 | grep -oE '[0-9]+' | head -1)
+ if [[ "$java_version" -lt 17 ]]; then
+ log_error "Java 17+ is required, found Java $java_version"
+ ok=false
+ else
+ log_info "Java version: $java_version (OK)"
+ fi
+
+ # Check Maven version
+ local mvn_version
+ mvn_version=$(mvn --version 2>/dev/null | head -1)
+ log_info "Maven: $mvn_version"
+
+ # Check GPG keys
+ GPG_KEY=$(detect_gpg_key)
+ if [[ -z "$GPG_KEY" ]]; then
+ log_error "No GPG secret key found. Create one with: gpg
--full-gen-key"
+ ok=false
+ else
+ log_info "GPG key: $GPG_KEY"
+ fi
+
+ # Check GPG key on keyserver
+ log_info "Checking GPG key on keyserver..."
+ if curl -s
"https://keyserver.ubuntu.com/pks/lookup?op=index&search=${GPG_KEY}" | grep -q
"pub"; then
+ log_info "GPG key found on keyserver.ubuntu.com (OK)"
+ else
+ log_warn "GPG key NOT found on keyserver.ubuntu.com. Upload with: gpg
--send-key $GPG_KEY"
+ fi
+
+ # Check Maven settings.xml
+ local settings_file="${HOME}/.m2/settings.xml"
+ if [[ -f "$settings_file" ]]; then
+ if grep -q "apache.releases.https" "$settings_file" && grep -q
"apache.snapshots.https" "$settings_file"; then
+ log_info "Maven settings.xml has Apache repository credentials
(OK)"
+ else
+ log_error "Maven settings.xml missing Apache repository
credentials (apache.releases.https / apache.snapshots.https)"
+ ok=false
+ fi
+ else
+ log_error "Maven settings.xml not found at $settings_file"
+ ok=false
+ fi
+
+ # Check git repository
+ if [[ -d "${SHENYU_HOME}/.git" ]]; then
+ local current_branch
+ current_branch=$(cd "$SHENYU_HOME" && git branch --show-current)
+ log_info "Git repo found, current branch: $current_branch"
+ if [[ "$current_branch" != "$RELEASE_BRANCH" ]]; then
+ log_warn "Current branch is '$current_branch', expected
'$RELEASE_BRANCH'"
+ if confirm "Switch to branch $RELEASE_BRANCH?"; then
+ (cd "$SHENYU_HOME" && git checkout "$RELEASE_BRANCH") || true
+ fi
+ fi
+ else
+ log_error "Not a git repository at $SHENYU_HOME"
+ ok=false
+ fi
+
+ # Check for uncommitted changes
+ local git_status
+ git_status=$(cd "$SHENYU_HOME" && git status --porcelain)
+ if [[ -n "$git_status" ]]; then
+ log_warn "Uncommitted changes found:"
+ echo "$git_status"
+ if ! confirm "Continue with uncommitted changes?"; then
+ exit 1
+ fi
+ else
+ log_info "No uncommitted changes (OK)"
+ fi
+
+ # Prompt for GPG passphrase if not set
+ if [[ -z "$GPG_PASSPHRASE" ]]; then
+ log_warn "GPG_PASSPHRASE is not set."
+ log_warn "You can either:"
+ log_warn " 1. Export it: export GPG_PASSPHRASE='your-passphrase'"
+ log_warn " 2. Ensure gpg-agent has it cached"
+ log_warn " 3. Use --pinentry-mode loopback by adding to
~/.gnupg/gpg.conf"
+ echo ""
+ if confirm "Enter GPG passphrase now? (will be hidden)"; then
+ read -rs -p "GPG Passphrase: " GPG_PASSPHRASE
+ echo ""
+ export GPG_PASSPHRASE
+ fi
+ fi
+
+ # Check Apache user
+ if [[ -z "$APACHE_USER" ]]; then
+ log_error "APACHE_USER is not set"
+ ok=false
+ else
+ log_info "Apache user: $APACHE_USER"
+ fi
+
+ echo ""
+ if [[ "$ok" == true ]]; then
+ log_info "✅ All prerequisites passed!"
+ else
+ log_error "❌ Some prerequisites failed. Please fix them before
proceeding."
+ exit 1
+ fi
+}
+
+# ========================= Step 2: Maven release:prepare dry-run
=========================
+
+step_2_dry_run() {
+ log_step "2" "Maven release:prepare dry-run"
+
+ require_var "RELEASE_VERSION" "$RELEASE_VERSION"
+
+ cd "$SHENYU_HOME"
+
+ log_info "Running Maven release:prepare dry-run for version
${RELEASE_VERSION}..."
+ log_info "Next development version will be: ${NEXT_VERSION}"
+
+ mvn release:prepare \
+ -Prelease \
+ -Darguments="$(mvn_release_args)" \
+ -DautoVersionSubmodules=true \
+ -DdryRun=true \
+ -DreleaseVersion="${RELEASE_VERSION}" \
+ -DdevelopmentVersion="${NEXT_VERSION}" \
+ -Dusername="${APACHE_USER}"
+
+ if [[ $? -eq 0 ]]; then
+ log_info "✅ Dry-run succeeded! Cleaning up dry-run artifacts..."
+ mvn release:clean -q
+ log_info "Ready to proceed with actual release:prepare."
+ else
+ log_error "❌ Dry-run failed. Please fix the errors above before
proceeding."
+ exit 1
+ fi
+}
+
+# ========================= Step 3: Maven release:prepare (actual)
=========================
+
+step_3_prepare() {
+ log_step "3" "Maven release:prepare (actual)"
+
+ cd "$SHENYU_HOME"
+
+ if ! confirm "This will modify pom.xml versions and create git tags.
Continue?"; then
+ log_info "Aborted."
+ exit 0
+ fi
+
+ log_info "Cleaning previous release artifacts..."
+ mvn release:clean -q
+
+ log_info "Running Maven release:prepare for version ${RELEASE_VERSION}..."
+ log_info "Next development version: ${NEXT_VERSION}"
+ log_info "pushChanges=false - you will push manually after review."
+
+ mvn release:prepare \
+ -Prelease \
+ -Darguments="$(mvn_release_args)" \
+ -DautoVersionSubmodules=true \
+ -DpushChanges=false \
+ -DreleaseVersion="${RELEASE_VERSION}" \
+ -DdevelopmentVersion="${NEXT_VERSION}" \
+ -Dusername="${APACHE_USER}"
+
+ if [[ $? -ne 0 ]]; then
+ log_error "❌ release:prepare failed. Rolling back..."
+ mvn release:rollback -q 2>/dev/null || true
+ exit 1
+ fi
+
+ log_info "✅ release:prepare succeeded!"
+ log_info ""
+ log_info "Review the changes:"
+ log_info " git log --oneline -5"
+ log_info " git tag -l 'v${RELEASE_VERSION}'"
+ log_info ""
+
+ if confirm "Push release branch and tags to origin?"; then
+ git push origin "${RELEASE_BRANCH}"
+ git push origin --tags
+ log_info "✅ Branch and tags pushed."
+ else
+ log_warn "Branch and tags NOT pushed. Push manually with:"
+ log_warn " git push origin ${RELEASE_BRANCH}"
+ log_warn " git push origin --tags"
+ fi
+}
+
+# ========================= Step 4: Maven release:perform
=========================
+
+step_4_perform() {
+ log_step "4" "Maven release:perform"
+
+ cd "$SHENYU_HOME"
+
+ if ! confirm "This will deploy artifacts to Apache Nexus staging
repository. Continue?"; then
+ log_info "Aborted."
+ exit 0
+ fi
+
+ log_info "Running Maven release:perform for version ${RELEASE_VERSION}..."
+
+ mvn release:perform \
+ -Prelease \
+ -Darguments="$(mvn_release_args)" \
+ -DautoVersionSubmodules=true \
+ -Dusername="${APACHE_USER}"
+
+ if [[ $? -ne 0 ]]; then
+ log_error "❌ release:perform failed."
+ exit 1
+ fi
+
+ log_info "✅ release:perform succeeded!"
+ log_info ""
+ log_warn "⚠️ IMPORTANT: Go to Apache Nexus and Close the staging
repository:"
+ log_warn " https://repository.apache.org/#stagingRepositories"
+ log_warn " Find the staging repo for org.apache.shenyu, select it, and
click 'Close'."
+ log_warn " Note the repository ID (e.g., orgapacheshenyu-XXXX) for the
vote email."
+}
+
+# ========================= Step 5: Publish to SVN dev repository
=========================
+
+step_5_svn_dev() {
+ log_step "5" "Publish to SVN dev repository"
+
+ require_var "RELEASE_VERSION" "$RELEASE_VERSION"
+ require_var "APACHE_USER" "$APACHE_USER"
+
+ # Step 5a: Update KEYS file
+ log_info "--- Step 5a: Update KEYS file ---"
+
+ GPG_KEY=$(detect_gpg_key)
+
+ if [[ -d "${HOME}/svn_release/release/shenyu" ]]; then
+ log_info "Updating existing KEYS checkout..."
+ cd "${HOME}/svn_release/release/shenyu"
+ svn update
+ else
+ log_info "Checking out SVN release directory..."
+ mkdir -p "${HOME}/svn_release/release"
+ cd "${HOME}/svn_release/release"
+ svn --username="${APACHE_USER}" co
https://dist.apache.org/repos/dist/release/shenyu
+ cd shenyu
+ fi
+
+ # Check if our key is already in KEYS
+ if grep -q "$GPG_KEY" KEYS 2>/dev/null; then
+ log_info "GPG key already in KEYS file (OK)"
+ else
+ log_info "Appending GPG public key to KEYS file..."
+ gpg -a --export "${GPG_KEY}" >> KEYS
+ if confirm "Commit KEYS update to SVN?"; then
+ svn --username="${APACHE_USER}" commit -m "append to KEYS"
+ fi
+ fi
+
+ # Step 5b: Copy distribution files to SVN dev
+ log_info "--- Step 5b: Copy distribution files to SVN dev ---"
+
+ if [[ -d "${SVN_DEV_DIR}" ]]; then
+ log_info "Updating existing SVN dev checkout..."
+ cd "${SVN_DEV_DIR}"
+ svn update
+ else
+ log_info "Checking out SVN dev directory..."
+ mkdir -p "$(dirname "${SVN_DEV_DIR}")"
+ cd "$(dirname "${SVN_DEV_DIR}")"
+ svn --username="${APACHE_USER}" co
https://dist.apache.org/repos/dist/dev/shenyu
+ cd shenyu
+ fi
+
+ local version_dir="${SVN_DEV_DIR}/${RELEASE_VERSION}"
+ if [[ -d "$version_dir" ]]; then
+ log_warn "Version directory already exists: $version_dir"
+ if ! confirm "Remove and recreate?"; then
+ log_info "Keeping existing directory."
+ else
+ svn delete "$version_dir" -m "remove old ${RELEASE_VERSION}"
2>/dev/null || true
+ rm -rf "$version_dir"
+ fi
+ fi
+
+ mkdir -p "$version_dir"
+
+ local dist_dir="${SHENYU_HOME}/shenyu-dist"
+ log_info "Copying distribution files from ${dist_dir}..."
+
+ # Source distribution
+ if ls "${dist_dir}/shenyu-src-dist/target/"*.zip* 1>/dev/null 2>&1; then
+ cp -f "${dist_dir}/shenyu-src-dist/target/"*.zip* "$version_dir/"
+ log_info " ✅ Source distribution copied"
+ else
+ log_error " ❌ Source distribution not found at
${dist_dir}/shenyu-src-dist/target/"
+ log_error " Make sure release:perform has been run and
distributions were built."
+ fi
+
+ # Bootstrap distribution
+ if ls "${dist_dir}/shenyu-bootstrap-dist/target/"*.tar.gz* 1>/dev/null
2>&1; then
+ cp -f "${dist_dir}/shenyu-bootstrap-dist/target/"*.tar.gz*
"$version_dir/"
+ log_info " ✅ Bootstrap distribution copied"
+ else
+ log_error " ❌ Bootstrap distribution not found"
+ fi
+
+ # Admin distribution
+ if ls "${dist_dir}/shenyu-admin-dist/target/"*.tar.gz* 1>/dev/null 2>&1;
then
+ cp -f "${dist_dir}/shenyu-admin-dist/target/"*.tar.gz* "$version_dir/"
+ log_info " ✅ Admin distribution copied"
+ else
+ log_error " ❌ Admin distribution not found"
+ fi
+
+ # List files in version directory
+ log_info "Files to be committed:"
+ ls -la "$version_dir/"
+
+ # Step 5c: Commit to SVN
+ log_info "--- Step 5c: Commit to SVN ---"
+
+ cd "${SVN_DEV_DIR}"
+ svn add "${RELEASE_VERSION}/" 2>/dev/null || true
+
+ if confirm "Commit release ${RELEASE_VERSION} to SVN dev?"; then
+ svn --username="${APACHE_USER}" commit -m "release ${RELEASE_VERSION}"
+ log_info "✅ SVN dev commit done."
+ else
+ log_warn "SVN commit skipped. Commit manually:"
+ log_warn " cd ${SVN_DEV_DIR} && svn --username=${APACHE_USER} commit
-m 'release ${RELEASE_VERSION}'"
+ fi
+}
+
+# ========================= Step 6: Verify release artifacts
=========================
+
+step_6_verify() {
+ log_step "6" "Verify release artifacts"
+
+ require_var "RELEASE_VERSION" "$RELEASE_VERSION"
+
+ local version_dir="${SVN_DEV_DIR}/${RELEASE_VERSION}"
+ if [[ ! -d "$version_dir" ]]; then
+ log_error "Version directory not found: $version_dir"
+ log_error "Run step 5 first, or check SVN_DEV_DIR setting."
+ exit 1
+ fi
+
+ cd "$version_dir"
+
+ local all_ok=true
+
+ # 6a: Verify sha512 checksums
+ log_info "--- 6a: Verify SHA512 checksums ---"
+ for sha_file in *.sha512; do
+ if [[ -f "$sha_file" ]]; then
+ if shasum -a 512 -c "$sha_file" 2>/dev/null; then
+ log_info " ✅ $sha_file verified"
+ else
+ log_error " ❌ $sha_file FAILED"
+ all_ok=false
+ fi
+ else
+ log_warn " No .sha512 files found. Generating checksums..."
+ for artifact in apache-shenyu-${RELEASE_VERSION}-src.zip \
+
apache-shenyu-${RELEASE_VERSION}-bootstrap-bin.tar.gz \
+ apache-shenyu-${RELEASE_VERSION}-admin-bin.tar.gz;
do
+ if [[ -f "$artifact" ]]; then
+ shasum -a 512 "$artifact" > "${artifact}.sha512"
+ log_info " Generated ${artifact}.sha512"
+ fi
+ done
+ fi
+ done
+
+ # 6b: Verify GPG signatures
+ log_info "--- 6b: Verify GPG signatures ---"
+ if ! curl -sf https://downloads.apache.org/shenyu/KEYS -o KEYS.download
2>/dev/null; then
+ log_warn "Could not download KEYS from Apache. Using local keys."
+ fi
+ gpg --import KEYS.download 2>/dev/null || true
+ rm -f KEYS.download
+
+ for asc_file in *.asc; do
+ if [[ -f "$asc_file" ]]; then
+ local artifact="${asc_file%.asc}"
+ if [[ -f "$artifact" ]]; then
+ if gpg --verify "$asc_file" "$artifact" 2>/dev/null; then
+ log_info " ✅ $asc_file signature verified"
+ else
+ log_error " ❌ $asc_file signature FAILED"
+ all_ok=false
+ fi
+ fi
+ fi
+ done
+
+ # 6c: Verify source dist contains required files
+ log_info "--- 6c: Verify source distribution contents ---"
+ local src_zip="apache-shenyu-${RELEASE_VERSION}-src.zip"
+ if [[ -f "$src_zip" ]]; then
+ local tmp_dir
+ tmp_dir=$(mktemp -d)
+ unzip -q "$src_zip" -d "$tmp_dir"
+
+ local src_dir="${tmp_dir}/apache-shenyu-${RELEASE_VERSION}-src"
+
+ # Check LICENSE and NOTICE
+ for file in LICENSE NOTICE; do
+ if [[ -f "${src_dir}/${file}" ]]; then
+ log_info " ✅ ${file} exists"
+ else
+ log_error " ❌ ${file} MISSING"
+ all_ok=false
+ fi
+ done
+
+ # Check year in NOTICE
+ local current_year
+ current_year=$(date +%Y)
+ if grep -q "$current_year" "${src_dir}/NOTICE" 2>/dev/null; then
+ log_info " ✅ NOTICE year is current ($current_year)"
+ else
+ log_warn " ⚠️ NOTICE year may need updating (expected
$current_year)"
+ fi
+
+ # Clean up
+ rm -rf "$tmp_dir"
+ else
+ log_error "Source zip not found: $src_zip"
+ all_ok=false
+ fi
+
+ # 6d: Verify binary distributions
+ log_info "--- 6d: Verify binary distributions ---"
+ for bin_file in apache-shenyu-${RELEASE_VERSION}-bootstrap-bin.tar.gz \
+ apache-shenyu-${RELEASE_VERSION}-admin-bin.tar.gz; do
+ if [[ -f "$bin_file" ]]; then
+ local tmp_dir
+ tmp_dir=$(mktemp -d)
+ tar xzf "$bin_file" -C "$tmp_dir"
+
+ for file in LICENSE NOTICE; do
+ if find "$tmp_dir" -name "$file" | head -1 | grep -q .; then
+ log_info " ✅ ${bin_file}: ${file} exists"
+ else
+ log_error " ❌ ${bin_file}: ${file} MISSING"
+ all_ok=false
+ fi
+ done
+
+ rm -rf "$tmp_dir"
+ else
+ log_warn " Binary not found: $bin_file"
+ fi
+ done
+
+ # 6e: Compare SVN source with GitHub tag
+ log_info "--- 6e: Verify SVN source matches GitHub tag ---"
+ log_info "To manually verify, run:"
+ echo ""
+ echo " wget
https://github.com/apache/shenyu/archive/v${RELEASE_VERSION}.zip"
+ echo " unzip v${RELEASE_VERSION}.zip"
+ echo " unzip apache-shenyu-${RELEASE_VERSION}-src.zip"
+ echo " diff -r -x 'shenyu-dashboard' -x 'shenyu-examples' -x
'shenyu-integrated-test' -x 'static' \\"
+ echo " apache-shenyu-${RELEASE_VERSION}-src shenyu-${RELEASE_VERSION}"
+ echo ""
+
+ echo ""
+ if [[ "$all_ok" == true ]]; then
+ log_info "✅ All verification checks passed!"
+ else
+ log_warn "⚠️ Some verification checks failed. Review the output
above."
+ fi
+}
+
+# ========================= Step 7: Print voting email templates
=========================
+
+step_7_vote_templates() {
+ log_step "7" "Voting email templates"
+
+ # Get the release commit ID
+ local commit_id
+ commit_id=$(cd "$SHENYU_HOME" && git rev-list -1 "v${RELEASE_VERSION}"
2>/dev/null || echo "COMMIT_ID_HERE")
+
+ # Detect staging repo ID
+ log_info "After closing the Nexus staging repository, find the repository
ID (e.g., orgapacheshenyu-XXXX)"
+ log_info "and replace STAGING_REPO_ID below with it."
+ echo ""
+
+ local staging_repo_id="STAGING_REPO_ID"
+
+ cat <<'VOTE_EMAIL'
+╔══════════════════════════════════════════════════════════════════╗
+║ VOTE EMAIL TEMPLATE ║
+╚══════════════════════════════════════════════════════════════════╝
+
+Send to: [email protected]
+Subject: [VOTE] Release Apache ShenYu RELEASE_VERSION
+
+--- BEGIN EMAIL ---
+
+Hello ShenYu Community,
+
+This is a call for vote to release Apache ShenYu version RELEASE_VERSION
+
+Release notes:
+https://github.com/apache/shenyu/blob/master/RELEASE-NOTES.md
+
+The release candidates:
+https://dist.apache.org/repos/dist/dev/shenyu/RELEASE_VERSION/
+
+Maven 2 staging repository:
+https://repository.apache.org/content/repositories/STAGING_REPO_ID/org/apache/shenyu/shenyu/RELEASE_VERSION/
+
+Git tag for the release:
+https://github.com/apache/shenyu/tree/vRELEASE_VERSION/
+
+Release Commit ID:
+https://github.com/apache/shenyu/commit/COMMIT_ID
+
+Keys to verify the Release Candidate:
+https://downloads.apache.org/shenyu/KEYS
+
+Look at here for how to verify this release candidate:
+https://shenyu.apache.org/community/release-guide/#check-release
+
+The vote will be open for at least 72 hours or until necessary number of votes
is reached.
+
+Please vote accordingly:
+[ ] +1 approve
+[ ] +0 no opinion
+[ ] -1 disapprove with the reason
+
+Checklist for reference:
+[ ] Download links are valid.
+[ ] Checksums and PGP signatures are valid.
+[ ] Source code distributions have correct names matching the current release.
+[ ] LICENSE and NOTICE files are correct for each ShenYu repo.
+[ ] All files have license headers if necessary.
+[ ] No compiled archives bundled in source archive.
+
+--- END EMAIL ---
+
+VOTE_EMAIL
+
+ cat <<'RESULT_EMAIL'
+╔══════════════════════════════════════════════════════════════════╗
+║ VOTE RESULT EMAIL TEMPLATE ║
+╚══════════════════════════════════════════════════════════════════╝
+
+Send to: [email protected]
+Subject: [RESULT][VOTE] Release Apache ShenYu RELEASE_VERSION
+
+--- BEGIN EMAIL ---
+
+We've received 3 +1 binding votes and X +1 non-binding votes:
+
++1, (binding voter 1) (binding)
++1, (binding voter 2) (binding)
++1, (binding voter 3) (binding)
++1, (non-binding voter 1) (non-binding)
++1, (non-binding voter 2) (non-binding)
+
+Vote thread:
+https://lists.apache.org/thread/XXXXX
+
+Thanks everyone for taking the time to verify and vote for the release!
+
+--- END EMAIL ---
+
+RESULT_EMAIL
+
+ cat <<'ANNOUNCE_EMAIL'
+╔══════════════════════════════════════════════════════════════════╗
+║ ANNOUNCE EMAIL TEMPLATE ║
+╚══════════════════════════════════════════════════════════════════╝
+
+Send to: [email protected], [email protected]
+Subject: [ANNOUNCE] Apache ShenYu RELEASE_VERSION available
+
+NOTE: [email protected] requires plain text format only.
+
+--- BEGIN EMAIL ---
+
+Hi,
+
+Apache ShenYu Team is glad to announce the new release of Apache ShenYu
RELEASE_VERSION.
+
+Apache ShenYu is an asynchronous, high-performance, cross-language, responsive
API gateway.
+
+Support various languages (http protocol), support Dubbo, Spring-Cloud, Grpc,
Motan, Sofa, Tars and other protocols.
+Plugin design idea, plugin hot swap, easy to expand.
+Flexible flow filtering to meet various flow control.
+Built-in rich plugin support, authentication, limiting, fuse, firewall, etc.
+Dynamic flow configuration, high performance.
+Support cluster deployment, A/B Test, blue-green release.
+
+Download Links: https://shenyu.apache.org/download/
+Release Notes: https://github.com/apache/shenyu/blob/master/RELEASE-NOTES.md
+Website: https://shenyu.apache.org/
+
+ShenYu Resources:
+- Issue: https://github.com/apache/shenyu/issues
+- Mailing list: [email protected]
+- Documents: https://shenyu.apache.org/docs/index/
+
+- Apache ShenYu Team
+
+--- END EMAIL ---
+
+ANNOUNCE_EMAIL
+
+ # Print with actual values
+ log_info "Replace the following placeholders in the templates:"
+ log_info " RELEASE_VERSION → ${RELEASE_VERSION}"
+ log_info " STAGING_REPO_ID → (find at
https://repository.apache.org/#stagingRepositories after closing)"
+ log_info " COMMIT_ID → ${commit_id}"
+}
+
+# ========================= Step 8: Final release (after vote passes)
=========================
+
+step_8_final_release() {
+ log_step "8" "Final release (after vote passes)"
+
+ require_var "RELEASE_VERSION" "$RELEASE_VERSION"
+ require_var "APACHE_USER" "$APACHE_USER"
+
+ local prev_version
+ prev_version=$(echo "$RELEASE_VERSION" | awk -F. '{printf "%d.%d.%d", $1,
$2, $3-1}')
+ # For 2.7.1, previous is 2.7.0
+
+ if ! confirm "Has the vote passed with at least 3 +1 binding votes?"; then
+ log_info "Cannot proceed with final release until vote passes."
+ exit 0
+ fi
+
+ # 8a: SVN release
+ log_info "--- 8a: Move artifacts from SVN dev to release ---"
+ log_info "Running: svn mv
https://dist.apache.org/repos/dist/dev/shenyu/${RELEASE_VERSION}
https://dist.apache.org/repos/dist/release/shenyu/ -m 'transfer packages for
${RELEASE_VERSION}'"
+ if confirm "Execute SVN move?"; then
+ svn mv
"https://dist.apache.org/repos/dist/dev/shenyu/${RELEASE_VERSION}" \
+ "https://dist.apache.org/repos/dist/release/shenyu/" \
+ -m "transfer packages for ${RELEASE_VERSION}" \
+ --username="${APACHE_USER}"
+ log_info "✅ SVN move done."
+ fi
+
+ log_info "--- 8a2: Delete previous version from SVN release ---"
+ log_info "Previous version: ${prev_version}"
+ if confirm "Delete previous version ${prev_version} from SVN release?";
then
+ svn delete
"https://dist.apache.org/repos/dist/release/shenyu/${prev_version}" \
+ -m "remove ${prev_version}" \
+ --username="${APACHE_USER}" 2>/dev/null || log_warn
"Previous version not found or already deleted."
+ fi
+
+ # 8b: Maven release
+ log_info ""
+ log_info "--- 8b: Release Maven staging repository ---"
+ log_info "Go to https://repository.apache.org/#stagingRepositories"
+ log_info "Find the staging repo for org.apache.shenyu, select it, and
click 'Release'."
+ log_info ""
+ log_info "Press Enter after you have released the staging repository..."
+ read -r
+
+ # 8c: GitHub release
+ log_info "--- 8c: GitHub release ---"
+ log_info "Go to https://github.com/apache/shenyu/releases"
+ log_info "Edit the v${RELEASE_VERSION} release and click 'Publish'."
+ log_info ""
+ log_info "Press Enter after you have published the GitHub release..."
+ read -r
+
+ # 8d: Docker release (automatic via GitHub Actions)
+ log_info "--- 8d: Docker release ---"
+ log_info "Docker images are automatically published via GitHub Actions
workflow (docker-publish-dockerhub.yml)."
+ log_info "Monitor: https://github.com/apache/shenyu/actions"
+ log_info ""
+ log_info "If the workflow fails, you can build manually:"
+ echo ""
+ echo " git checkout v${RELEASE_VERSION}"
+ echo " cd shenyu-dist"
+ echo " mvn clean package -Prelease"
+ echo " docker buildx create --name shenyu"
+ echo " docker buildx use shenyu"
+ echo " docker login"
+ echo " docker buildx build \\"
+ echo " -t apache/shenyu-admin:latest \\"
+ echo " -t apache/shenyu-admin:${RELEASE_VERSION} \\"
+ echo " --build-arg APP_NAME=apache-shenyu-${RELEASE_VERSION}-admin-bin
\\"
+ echo " --platform=linux/arm64,linux/amd64 \\"
+ echo " -f ./shenyu-admin-dist/docker/Dockerfile --push
./shenyu-admin-dist"
+ echo " docker buildx build \\"
+ echo " -t apache/shenyu-bootstrap:latest \\"
+ echo " -t apache/shenyu-bootstrap:${RELEASE_VERSION} \\"
+ echo " --build-arg
APP_NAME=apache-shenyu-${RELEASE_VERSION}-bootstrap-bin \\"
+ echo " --platform=linux/arm64,linux/amd64 \\"
+ echo " -f ./shenyu-bootstrap-dist/docker/Dockerfile --push
./shenyu-bootstrap-dist"
+ echo ""
+
+ # 8e: Merge release branch back to master
+ log_info "--- 8e: Merge release branch back to master ---"
+ if confirm "Merge ${RELEASE_BRANCH} back to master?"; then
+ (cd "$SHENYU_HOME" && \
+ git checkout master && \
+ git merge "origin/${RELEASE_BRANCH}" && \
+ git pull && \
+ git push origin master)
+ log_info "✅ Merged and pushed to master."
+
+ if confirm "Delete release branch ${RELEASE_BRANCH}?"; then
+ (cd "$SHENYU_HOME" && \
+ git push origin --delete "${RELEASE_BRANCH}" 2>/dev/null ||
true && \
+ git branch -d "${RELEASE_BRANCH}" 2>/dev/null || true)
+ log_info "✅ Release branch deleted."
+ fi
+ fi
+
+ # 8f: Update download pages
+ log_info "--- 8f: Update download pages ---"
+ log_info "⚠️ Manual steps required:"
+ log_info " 1. Update English download page:
https://shenyu.apache.org/download/"
+ log_info " 2. Update Chinese download page:
https://shenyu.apache.org/zh/download/"
+ log_info " - Use https://www.apache.org/dyn/closer.lua for download
links (NOT closer.cgi or mirrors.cgi)"
+ log_info " - GPG signature and hash links must use:
https://downloads.apache.org/shenyu/"
+ log_info " 3. Archive documentation for ${RELEASE_VERSION}"
+ log_info " 4. Update version page"
+ log_info " 5. Update events page with new release event"
+ log_info " 6. Update news page with new release news"
+ log_info ""
+ log_info "Wait at least 1 hour for Apache mirror to sync before updating
download pages."
+
+ # 8g: Send announce email
+ log_info "--- 8g: Send announce email ---"
+ log_info "Send the announce email (see template from step 7) to:"
+ log_info " - [email protected]"
+ log_info " - [email protected] (plain text format only!)"
+
+ log_info ""
+ log_info "🎉 Release ${RELEASE_VERSION} process complete!"
+}
+
+# ========================= Clean / Rollback =========================
+
+step_clean() {
+ log_step "CLEAN" "Rollback release artifacts"
+
+ require_var "RELEASE_VERSION" "$RELEASE_VERSION"
+
+ cd "$SHENYU_HOME"
+
+ log_warn "This will clean up Maven release artifacts and optionally delete
the release branch and tag."
+
+ # Maven release:clean
+ if confirm "Run mvn release:clean?"; then
+ mvn release:clean -q
+ log_info "✅ Maven release artifacts cleaned."
+ fi
+
+ # Delete local tag
+ if git tag -l "v${RELEASE_VERSION}" | grep -q .; then
+ if confirm "Delete local tag v${RELEASE_VERSION}?"; then
+ git tag -d "v${RELEASE_VERSION}"
+ log_info "✅ Local tag deleted."
+ fi
+ fi
+
+ # Delete remote tag
+ if git ls-remote --tags origin "v${RELEASE_VERSION}" | grep -q .; then
+ if confirm "Delete remote tag v${RELEASE_VERSION}?"; then
+ git push origin --delete tag "v${RELEASE_VERSION}"
+ log_info "✅ Remote tag deleted."
+ fi
+ fi
+
+ # Delete release branch
+ if git branch --list "${RELEASE_BRANCH}" | grep -q .; then
+ if confirm "Delete local branch ${RELEASE_BRANCH}?"; then
+ git branch -D "${RELEASE_BRANCH}"
+ log_info "✅ Local branch deleted."
+ fi
+ fi
+
+ if git ls-remote --heads origin "${RELEASE_BRANCH}" | grep -q .; then
+ if confirm "Delete remote branch ${RELEASE_BRANCH}?"; then
+ git push origin --delete "${RELEASE_BRANCH}"
+ log_info "✅ Remote branch deleted."
+ fi
+ fi
+
+ # Drop Nexus staging repo
+ log_warn "⚠️ Remember to drop the Nexus staging repository if it was
closed:"
+ log_warn " https://repository.apache.org/#stagingRepositories"
+
+ # Delete SVN dev artifacts
+ if confirm "Delete SVN dev artifacts for ${RELEASE_VERSION}?"; then
+ svn delete
"https://dist.apache.org/repos/dist/dev/shenyu/${RELEASE_VERSION}" \
+ -m "delete ${RELEASE_VERSION}" \
+ --username="${APACHE_USER}" 2>/dev/null || log_warn "SVN
delete failed or already deleted."
+ fi
+
+ log_info "✅ Cleanup complete."
+}
+
+# ========================= Usage =========================
+
+usage() {
+ cat <<EOF
+Apache ShenYu Release Script - v${RELEASE_VERSION}
+
+Usage: $(basename "$0") <step> [options]
+
+Steps:
+ 1 Prerequisites check
+ 2 Maven release:prepare dry-run
+ 3 Maven release:prepare (actual - modifies pom.xml and creates tags)
+ 4 Maven release:perform (deploys to Apache Nexus staging)
+ 5 Publish to SVN dev repository
+ 6 Verify release artifacts
+ 7 Print voting email templates
+ 8 Final release (after vote passes)
+ all Run steps 1-6 sequentially
+ clean Rollback: delete branch, tags, and clean Maven release artifacts
+
+Options:
+ -v, --version VERSION Release version (default: ${RELEASE_VERSION})
+ -n, --next-version VERSION Next SNAPSHOT version (default: ${NEXT_VERSION})
+ -u, --user USERNAME Apache LDAP username (default: ${APACHE_USER})
+ -k, --gpg-key KEY_ID GPG key ID
+ -p, --gpg-passphrase PASS GPG passphrase
+ -d, --home DIR ShenYu source directory (default:
${SHENYU_HOME})
+
+Environment variables:
+ RELEASE_VERSION, NEXT_VERSION, APACHE_USER, GPG_KEY, GPG_PASSPHRASE,
SHENYU_HOME
+
+Examples:
+ # Check prerequisites
+ $(basename "$0") 1
+
+ # Dry run with GPG passphrase
+ $(basename "$0") 2 -p 'my-passphrase'
+
+ # Full release (steps 1-6)
+ $(basename "$0") all -p 'my-passphrase'
+
+ # Print vote email templates
+ $(basename "$0") 7
+
+ # Final release after vote
+ $(basename "$0") 8
+
+ # Rollback
+ $(basename "$0") clean
+EOF
+}
+
+# ========================= Parse Arguments =========================
+
+parse_args() {
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -v|--version)
+ RELEASE_VERSION="$2"
+ RELEASE_BRANCH="${RELEASE_VERSION}-release"
+ GIT_TAG="v${RELEASE_VERSION}"
+ shift 2
+ ;;
+ -n|--next-version)
+ NEXT_VERSION="$2"
+ shift 2
+ ;;
+ -u|--user)
+ APACHE_USER="$2"
+ shift 2
+ ;;
+ -k|--gpg-key)
+ GPG_KEY="$2"
+ shift 2
+ ;;
+ -p|--gpg-passphrase)
+ GPG_PASSPHRASE="$2"
+ shift 2
+ ;;
+ -d|--home)
+ SHENYU_HOME="$2"
+ shift 2
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+}
+
+# ========================= Main =========================
+
+main() {
+ # Handle help flag even as first arg
+ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+ fi
+
+ if [[ $# -lt 1 ]]; then
+ usage
+ exit 1
+ fi
+
+ local step="$1"
+ shift
+ parse_args "$@"
+
+ # Export GPG passphrase for subprocess
+ export GPG_PASSPHRASE
+
+ echo ""
+ echo -e
"${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${BLUE}║ Apache ShenYu ${RELEASE_VERSION} Release Script
║${NC}"
+ echo -e
"${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}"
+ echo ""
+ log_info "Release version: ${RELEASE_VERSION}"
+ log_info "Next version: ${NEXT_VERSION}"
+ log_info "Apache user: ${APACHE_USER}"
+ log_info "Source dir: ${SHENYU_HOME}"
+ log_info "GPG key: $(detect_gpg_key)"
+ echo ""
+
+ case "$step" in
+ 1) step_1_prerequisites ;;
+ 2) step_2_dry_run ;;
+ 3) step_3_prepare ;;
+ 4) step_4_perform ;;
+ 5) step_5_svn_dev ;;
+ 6) step_6_verify ;;
+ 7) step_7_vote_templates ;;
+ 8) step_8_final_release ;;
+ all)
+ step_1_prerequisites
+ step_2_dry_run
+ step_3_prepare
+ step_4_perform
+ step_5_svn_dev
+ step_6_verify
+ echo ""
+ log_info "✅ Steps 1-6 complete! Next steps:"
+ log_info " 1. Close the Nexus staging repository at
https://repository.apache.org/#stagingRepositories"
+ log_info " 2. Run './release.sh 7' to print voting email
templates"
+ log_info " 3. Send vote email to [email protected]"
+ log_info " 4. After vote passes, run './release.sh 8' to finalize
the release"
+ ;;
+ clean)
+ step_clean ;;
+ *)
+ echo "Unknown step: $step"
+ usage
+ exit 1
+ ;;
+ esac
+}
+
+main "$@"