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 "$@"


Reply via email to