This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new f85e3a26b04 [chore](tools) Add release helper scripts for cutting a 
source release candidate (#64062)
f85e3a26b04 is described below

commit f85e3a26b04492c9d21909c6ebbfc0c9433ee318
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Thu Jun 4 15:00:40 2026 +0800

    [chore](tools) Add release helper scripts for cutting a source release 
candidate (#64062)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    
    Add `tools/release-tools/`, a set of helper scripts for a Release
    Manager (RM) to cut an Apache Doris **source** release candidate in
    three steps:
    
    - `01-check-env.sh` — check / prepare the GPG signing environment and
    ASF credentials.
    - `02-package-sign-upload.sh` — `git archive` the tag, GPG-sign,
    generate sha512, upload to the dev SVN.
    - `03-vote-mail.sh` — generate the `[VOTE]` email draft.
    - `release.env` — shared config (version, paths, signing key, SVN URLs,
    email); edit per release.
    - `README.md` — usage.
    
    The scripts are reusable across releases (everything version-specific
    lives in `release.env`). Branch prep, issue cleanup, patch merges and
    tag creation are out of scope.
---
 .asf.yaml                                     |   2 +-
 .gitleaks.toml                                |   7 +
 tools/release-tools/01-check-env.sh           | 205 ++++++++++++++++++++++++++
 tools/release-tools/02-package-sign-upload.sh | 109 ++++++++++++++
 tools/release-tools/03-vote-mail.sh           | 111 ++++++++++++++
 tools/release-tools/README.md                 |  99 +++++++++++++
 tools/release-tools/release.env               |  79 ++++++++++
 7 files changed, 611 insertions(+), 1 deletion(-)

diff --git a/.asf.yaml b/.asf.yaml
index 5ca12f6e9cf..70f2c884f19 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -25,7 +25,7 @@ github:
     - lakehouse
     - ai
     - agent
-    - elt
+    - observability
     - sql
     - snowflake
     - redshift
diff --git a/.gitleaks.toml b/.gitleaks.toml
index 8498a6e5184..7bc31857f9b 100644
--- a/.gitleaks.toml
+++ b/.gitleaks.toml
@@ -27,6 +27,13 @@ regexTarget = "line"
 paths = 
['''^fe/fe-filesystem/fe-filesystem-s3/src/test/java/org/apache/doris/filesystem/s3/S3ObjStorageTest\.java$''']
 regexes = ['''(access_key|secret_key).*"(ak-|sk-|canonical|sess-tok)''']
 
+[[rules.allowlists]]
+description = "Ignore the GPG signing-key fingerprint in the release-tools 
config template (a public key id, not a secret)"
+condition = "AND"
+regexTarget = "line"
+paths = ['''^tools/release-tools/release\.env$''']
+regexes = ['''SIGNING_KEY="[A-F0-9]{40}"''']
+
 [[rules]]
 id = "private-key"
 
diff --git a/tools/release-tools/01-check-env.sh 
b/tools/release-tools/01-check-env.sh
new file mode 100755
index 00000000000..c6e47048e91
--- /dev/null
+++ b/tools/release-tools/01-check-env.sh
@@ -0,0 +1,205 @@
+#!/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.
+
+# Step 01 - prepare / check the signing environment.
+# Read-mostly. The only state-changing paths (import key / generate key /
+# publish KEYS / edit gpg.conf) are opt-in and prompt before acting.
+set -euo pipefail
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=release.env
+source "${HERE}/release.env"
+
+ok()   { echo "[ OK ] $*"; }
+warn() { echo "[WARN] $*"; }
+err()  { echo "[FAIL] $*"; }
+die()  { err "$*"; exit 1; }
+confirm() { local a; read -r -p "$1 [y/N] " a; [[ "$a" == y || "$a" == Y ]]; }
+
+# does $1 (fingerprint) appear in a KEYS stream piped on stdin?
+key_in_keys() {
+  local fpr="$1" kr ret=1; kr="$(mktemp -d)"
+  if gpg --homedir "$kr" --import >/dev/null 2>&1 && gpg --homedir "$kr" 
--list-keys "$fpr" >/dev/null 2>&1; then ret=0; fi
+  rm -rf "$kr"; return "$ret"
+}
+
+# svn auth args (only added if exported)
+svn_auth=(--non-interactive --no-auth-cache)
+[[ -n "${ASF_USERNAME:-}" ]] && svn_auth+=(--username "$ASF_USERNAME")
+[[ -n "${ASF_PASSWORD:-}" ]] && svn_auth+=(--password "$ASF_PASSWORD")
+
+problems=0
+echo "== Apache Doris ${TAG} - signing environment check =="
+
+# 1. required tools
+for t in git gpg svn sha512sum curl gzip; do
+  if command -v "$t" >/dev/null 2>&1; then ok "tool: $t"; else err "missing 
tool: $t"; problems=$((problems+1)); fi
+done
+
+# 2. GPG_TTY (needed so the passphrase prompt can appear)
+export GPG_TTY="$(tty || true)"
+ok "GPG_TTY=${GPG_TTY:-<none>}"
+
+# 3. gpg.conf SHA512 preference (Apache recommendation)
+gpgconf_file="${GNUPGHOME:-$HOME/.gnupg}/gpg.conf"
+if grep -q "cert-digest-algo SHA512" "$gpgconf_file" 2>/dev/null; then
+  ok "gpg.conf has SHA512 digest preferences"
+else
+  warn "gpg.conf missing SHA512 preferences ($gpgconf_file)"
+  if confirm "Append recommended SHA512 lines to gpg.conf?"; then
+    {
+      echo "personal-digest-preferences SHA512"
+      echo "cert-digest-algo SHA512"
+      echo "default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 
AES CAST5 ZLIB BZIP2 ZIP Uncompressed"
+    } >> "$gpgconf_file"
+    ok "updated $gpgconf_file"
+  fi
+fi
+
+# 4. resolve a usable secret key (one primary fpr per secret key)
+list_secret_fprs() {
+  gpg --list-secret-keys --with-colons 2>/dev/null \
+    | awk -F: '$1=="sec"{w=1} $1=="fpr"&&w{print $10; w=0}'
+}
+resolve_secret_key() {
+  if [[ -n "${SIGNING_KEY}" ]]; then
+    gpg --list-secret-keys "${SIGNING_KEY}" >/dev/null 2>&1 && { echo 
"${SIGNING_KEY}"; return 0; }
+    return 1
+  fi
+  local fprs n; fprs="$(list_secret_fprs)"; n="$(printf '%s\n' "$fprs" | grep 
-c . || true)"
+  if   [[ "$n" -eq 1 ]]; then printf '%s\n' "$fprs"; return 0
+  elif [[ "$n" -gt 1 ]]; then return 2
+  fi
+  return 1
+}
+
+import_key_from_file() {
+  local p; read -r -p "Path to exported secret key file (.asc/.gpg): " p
+  [[ -f "$p" ]] || die "no such file: $p"
+  gpg --import "$p"
+}
+
+gen_key() {
+  local rn em
+  read -r -p "Real name (>=5 chars, e.g. morningman): " rn
+  read -r -p "Apache email ([email protected]): " em
+  echo "Generating RSA-4096 sign key, no expiry. You'll be asked for a 
passphrase - REMEMBER IT."
+  gpg --quick-generate-key "${rn} (CODE SIGNING KEY) <${em}>" rsa4096 sign 
never
+}
+
+# append the new public key to dev + release KEYS and commit (so voters can 
verify)
+# append the key to the dev + release KEYS files and commit (append-only).
+# idempotent: skips a repo whose KEYS already contains the key, so re-running
+# after a partial failure won't duplicate the entry.
+# caller is responsible for asking the user first.
+publish_key_to_keys() {
+  local fpr="$1" spec name base wc
+  mkdir -p "${WORK_DIR}"
+  for spec in "dev=${DEV_SVN_BASE}" "release=${RELEASE_SVN_BASE}"; do
+    name="${spec%%=*}"; base="${spec#*=}"
+    wc="${WORK_DIR}/keys-${name}-$$"
+    rm -rf "$wc"                       # avoid stale working-copy collisions
+    echo ">> ${base}/KEYS"
+    svn checkout --depth files "${svn_auth[@]}" "$base" "$wc"
+    if key_in_keys "$fpr" < "$wc/KEYS"; then
+      ok "key already in ${name} KEYS - skip"
+      continue
+    fi
+    # append only - never rewrite existing KEYS content
+    { echo; gpg --list-sigs "$fpr"; gpg --armor --export "$fpr"; } >> 
"$wc/KEYS"
+    echo "--- appended to ${name}/KEYS (tail) ---"; tail -n 18 "$wc/KEYS"
+    svn commit "${svn_auth[@]}" -m "Add KEY for ${APACHE_ID}" "$wc"
+    ok "committed KEYS to ${base}"
+  done
+  warn "MANUAL: paste this fingerprint into https://id.apache.org (OpenPGP 
field):"
+  gpg --fingerprint "$fpr" | sed -n '2p'
+}
+
+SIGNER=""
+if SIGNER="$(resolve_secret_key)"; then
+  ok "signing key resolved: ${SIGNER}"
+else
+  rc=$?
+  if [[ "$rc" -eq 2 ]]; then
+    err "multiple secret keys found - set SIGNING_KEY in release.env to choose 
one:"
+    gpg --list-secret-keys --keyid-format=long
+    problems=$((problems+1))
+  else
+    warn "no usable secret key in this environment. Options:"
+    echo "  1) import an existing exported secret key from a file"
+    echo "  2) generate a NEW key here and publish it to the Doris KEYS files"
+    echo "  3) skip (you will sign on another machine)"
+    read -r -p "choose [1/2/3]: " choice
+    case "$choice" in
+      1) import_key_from_file; SIGNER="$(resolve_secret_key || true)";;
+      2) gen_key; SIGNER="$(resolve_secret_key || true)"
+         if [[ -n "$SIGNER" ]] && confirm "Publish this new key to dev+release 
KEYS now?"; then
+           publish_key_to_keys "$SIGNER"
+         fi;;
+      3) warn "skipping local key; sign on another machine.";;
+      *) warn "no choice made.";;
+    esac
+    if [[ -n "$SIGNER" ]]; then ok "signing key resolved: ${SIGNER}"; else 
warn "still no signing key"; problems=$((problems+1)); fi
+  fi
+fi
+
+# 5. is the signing key published in the live KEYS? + 6. test signature
+if [[ -n "$SIGNER" ]]; then
+  fpr="$(gpg --list-keys --with-colons "$SIGNER" | awk -F: '/^fpr:/{print $10; 
exit}')"
+  ok "fingerprint: ${fpr}"
+
+  if curl -fsSL "$KEYS_URL" 2>/dev/null | key_in_keys "$fpr"; then
+    ok "key is present in published KEYS"
+  else
+    warn "key NOT found in published KEYS ($KEYS_URL) - voters can't verify 
until it's published"
+    if confirm "Append this key to the Doris dev+release KEYS and commit 
now?"; then
+      publish_key_to_keys "$fpr"
+      if svn cat "${svn_auth[@]}" "${RELEASE_SVN_BASE}/KEYS" 2>/dev/null | 
key_in_keys "$fpr"; then
+        ok "key now in release SVN KEYS (downloads.apache.org mirror syncs in 
a few min)"
+      else
+        err "key still not in release SVN KEYS after commit - check output 
above"
+        problems=$((problems+1))
+      fi
+    else
+      warn "skipped KEYS publish; key MUST be in published KEYS before the 
vote"
+      problems=$((problems+1))
+    fi
+  fi
+
+  tf="$(mktemp)"; echo "doris ${TAG} signing test" > "$tf"
+  if gpg -u "$SIGNER" --armor --detach-sign -o "$tf.asc" "$tf" >/dev/null 2>&1 
\
+     && gpg --verify "$tf.asc" "$tf" >/dev/null 2>&1; then
+    ok "test sign + verify succeeded (passphrase/agent working)"
+  else
+    err "test signing failed - check passphrase / GPG_TTY / pinentry"; 
problems=$((problems+1))
+  fi
+  rm -f "$tf" "$tf.asc"
+fi
+
+# 7. ASF SVN credentials
+if [[ -n "${ASF_USERNAME:-}" && -n "${ASF_PASSWORD:-}" ]]; then
+  ok "ASF_USERNAME/ASF_PASSWORD present in env"
+else
+  warn "ASF_USERNAME/ASF_PASSWORD not both set - needed for SVN upload (step 
02) and KEYS publish"
+fi
+
+echo
+if [[ "$problems" -eq 0 ]]; then
+  ok "environment looks READY for ${TAG}"
+else
+  err "${problems} problem(s) above - resolve before packaging"; exit 1
+fi
diff --git a/tools/release-tools/02-package-sign-upload.sh 
b/tools/release-tools/02-package-sign-upload.sh
new file mode 100755
index 00000000000..8d6b20cc641
--- /dev/null
+++ b/tools/release-tools/02-package-sign-upload.sh
@@ -0,0 +1,109 @@
+#!/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.
+
+# Step 02 - package the source tarball, sign it, and upload to the Apache dev 
SVN.
+# The SVN commit is outward-facing: it pauses twice for confirmation before 
commit.
+set -euo pipefail
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=release.env
+source "${HERE}/release.env"
+
+ok()   { echo "[ OK ] $*"; }
+warn() { echo "[WARN] $*"; }
+die()  { echo "[FAIL] $*" >&2; exit 1; }
+confirm() { local a; read -r -p "$1 [y/N] " a; [[ "$a" == y || "$a" == Y ]]; }
+export GPG_TTY="$(tty || true)"
+
+svn_auth=(--non-interactive --no-auth-cache)
+[[ -n "${ASF_USERNAME:-}" ]] && svn_auth+=(--username "$ASF_USERNAME")
+[[ -n "${ASF_PASSWORD:-}" ]] && svn_auth+=(--password "$ASF_PASSWORD")
+
+# resolve signer
+if [[ -n "${SIGNING_KEY}" ]]; then
+  SIGNER="${SIGNING_KEY}"
+else
+  SIGNER="$(gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: 
'$1=="sec"{w=1} $1=="fpr"&&w{print $10; exit}')"
+fi
+[[ -n "$SIGNER" ]] || die "no signing key found; run ./01-check-env.sh first"
+ok "signer: $SIGNER"
+
+# 1. verify the tag matches the apache remote (don't release a local-only tag)
+cd "$REPO_DIR"
+git rev-parse "$TAG" >/dev/null 2>&1 || die "local tag $TAG not found in 
$REPO_DIR"
+local_tag="$(git rev-parse "$TAG")"
+remote_tag="$(git ls-remote --tags "$GIT_REMOTE" "refs/tags/$TAG" | awk -v 
t="refs/tags/$TAG" '$2==t{print $1}')"
+[[ -n "$remote_tag" ]] || die "tag $TAG not found on remote $GIT_REMOTE"
+[[ "$local_tag" == "$remote_tag" ]] || die "tag mismatch: local=$local_tag 
$GIT_REMOTE=$remote_tag"
+ok "tag $TAG matches $GIT_REMOTE"
+
+# 2. package via git archive from the tag
+mkdir -p "$WORK_DIR"
+art="$WORK_DIR/${PKG_BASE}.tar.gz"
+echo "archiving $TAG -> $art"
+git archive --format=tar --prefix="$ARCHIVE_PREFIX" "$TAG" | gzip > "$art"
+ok "source tarball: $(du -h "$art" | cut -f1)  $art"
+
+# 3. sign + checksum (inside WORK_DIR so the files reference bare basenames)
+cd "$WORK_DIR"
+f="${PKG_BASE}.tar.gz"
+rm -f "$f.asc" "$f.sha512"
+gpg -u "$SIGNER" --armor --output "$f.asc" --detach-sign "$f"
+gpg --verify "$f.asc" "$f"
+ok "signature ok: $f.asc"
+sha512sum "$f" > "$f.sha512"
+sha512sum --check "$f.sha512"
+ok "sha512 ok: $f.sha512"
+echo; ls -l "$f" "$f.asc" "$f.sha512"; echo
+
+# 4. sign + checksum the prebuilt binary tarballs. Sidecar files are written 
NEXT TO
+#    each binary (not in WORK_DIR) and are NOT uploaded here -- you upload 
them manually.
+bin_count="${#BIN_FILES[@]}"
+if [[ "$bin_count" -gt 0 ]]; then
+  echo "signing $bin_count binary artifact(s)..."
+  for bin in "${BIN_FILES[@]}"; do
+    [[ -f "$bin" ]] || die "binary artifact not found: $bin (check BIN_FILES 
in release.env)"
+    bdir="$(cd "$(dirname "$bin")" && pwd)"
+    bname="$(basename "$bin")"
+    (
+      cd "$bdir"
+      rm -f "$bname.asc" "$bname.sha512"
+      gpg -u "$SIGNER" --armor --output "$bname.asc" --detach-sign "$bname"
+      gpg --verify "$bname.asc" "$bname"
+      sha512sum "$bname" > "$bname.sha512"
+      sha512sum --check "$bname.sha512"
+    )
+    ok "binary signed: $bdir/$bname (+ .asc, .sha512)"
+  done
+  echo
+fi
+
+# 5. upload to dev SVN (two confirmations; nothing public happens before them)
+echo "Target dev SVN folder: ${DEV_SVN_DIR}/"
+confirm "Checkout + add these 3 files for the above SVN URL?" || { warn 
"stopping before SVN."; exit 0; }
+wc="$WORK_DIR/dev-svn"
+rm -rf "$wc"
+svn checkout --depth empty "${svn_auth[@]}" "$DEV_SVN_BASE" "$wc"
+mkdir -p "$wc/$TAG"
+cp "$f" "$f.asc" "$f.sha512" "$wc/$TAG/"
+svn add "$wc/$TAG"
+echo "--- svn status ---"; svn status "$wc"; echo
+echo "Will commit the above to: ${DEV_SVN_DIR}/"
+confirm "FINAL confirm - svn commit now?" || { warn "left staged (uncommitted) 
at $wc"; exit 0; }
+svn commit "${svn_auth[@]}" -m "Add ${TAG}" "$wc"
+ok "committed. Vote artifacts now at: ${DEV_SVN_DIR}/"
+echo "Next: ./03-vote-mail.sh"
diff --git a/tools/release-tools/03-vote-mail.sh 
b/tools/release-tools/03-vote-mail.sh
new file mode 100755
index 00000000000..025e154ea7a
--- /dev/null
+++ b/tools/release-tools/03-vote-mail.sh
@@ -0,0 +1,111 @@
+#!/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.
+
+# Step 03 - generate the [VOTE] email draft for [email protected].
+# Draft only: it writes a .txt and .eml; you send it from your @apache.org 
mail.
+set -euo pipefail
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=release.env
+source "${HERE}/release.env"
+
+ok()  { echo "[ OK ] $*"; }
+die() { echo "[FAIL] $*" >&2; exit 1; }
+
+# release notes link (prompt if not preset)
+rn="${RELEASE_NOTES_URL}"
+if [[ -z "$rn" ]]; then read -r -p "Release Notes URL (the issue link): " rn; 
fi
+[[ -n "$rn" ]] || die "release notes url required"
+
+# signer fingerprint
+if [[ -n "${SIGNING_KEY}" ]]; then
+  SIGNER="${SIGNING_KEY}"
+else
+  SIGNER="$(gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: 
'$1=="sec"{w=1} $1=="fpr"&&w{print $10; exit}')"
+fi
+[[ -n "$SIGNER" ]] || die "no signing key found; run ./01-check-env.sh first"
+FPR="$(gpg --list-keys --with-colons "$SIGNER" | awk -F: '/^fpr:/{print $10; 
exit}')"
+
+mkdir -p "$WORK_DIR"
+subject="[VOTE] Release Apache Doris ${TAG}"
+body_file="$WORK_DIR/vote-email.txt"
+eml_file="$WORK_DIR/vote-email.eml"
+
+# Convenience-binary download section: derived from BIN_FILES, omitted when 
empty.
+bin_section=""
+if [[ "${#BIN_FILES[@]}" -gt 0 ]]; then
+  bin_section=$'\nThe convenience binaries can be downloaded here:\n'
+  for bin in "${BIN_FILES[@]}"; do
+    b="$(basename "$bin")"
+    bin_section+="${BIN_DOWNLOAD_BASE}/${b}"$'\n'
+    bin_section+="${BIN_DOWNLOAD_BASE}/${b}.asc"$'\n'
+    bin_section+="${BIN_DOWNLOAD_BASE}/${b}.sha512"$'\n'
+  done
+fi
+
+read -r -d '' BODY <<EOF || true
+Hi all,
+
+Please review and vote on Apache Doris ${TAG} release.
+
+The release candidate has been tagged in GitHub as ${TAG}, available here:
+https://github.com/apache/doris/releases/tag/${TAG}
+
+Release Notes are here:
+${rn}
+
+Thanks to everyone who has contributed to this release.
+
+The artifacts (source, signature and checksum) corresponding to this release
+candidate can be found here:
+${DEV_SVN_DIR}/
+${bin_section}
+This has been signed with PGP key ${FPR}, corresponding to ${APACHE_EMAIL}.
+KEYS file is available here:
+${KEYS_URL}
+It is also listed here:
+https://people.apache.org/keys/committer/${APACHE_ID}.asc
+
+To verify and build, you can refer to following link:
+${VERIFY_GUIDE_URL}
+
+The vote will be open for at least 72 hours.
+[ ] +1 Approve the release
+[ ] +0 No opinion
+[ ] -1 Do not release this package because ...
+
+Best Regards,
+${SIGNER_NAME} (${APACHE_ID})
+EOF
+
+printf '%s\n' "$BODY" > "$body_file"
+{
+  echo "To: ${DEV_LIST}"
+  echo "Subject: ${subject}"
+  echo "Content-Type: text/plain; charset=UTF-8"
+  echo
+  printf '%s\n' "$BODY"
+} > "$eml_file"
+
+ok "subject: ${subject}"
+ok "body:    ${body_file}"
+ok "eml:     ${eml_file}  (open in your apache.org mail client)"
+echo "----------------------------------------------------------------"
+cat "$body_file"
+echo "----------------------------------------------------------------"
+echo "Review, then SEND MANUALLY from your @apache.org address to ${DEV_LIST}."
+echo "(Not auto-sent by design - it's a public ASF list.)"
diff --git a/tools/release-tools/README.md b/tools/release-tools/README.md
new file mode 100644
index 00000000000..20e34504d96
--- /dev/null
+++ b/tools/release-tools/README.md
@@ -0,0 +1,99 @@
+<!--
+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.
+-->
+
+# Doris release helper scripts
+
+Helper scripts for an Apache Doris Release Manager (RM) to cut a **source** 
release candidate:
+package and sign the source tarball, upload it to the Apache dev SVN, and 
draft the `[VOTE]` email.
+
+## Prerequisites
+Have these ready before you run anything:
+- The release **tag is already created and pushed** to `apache/doris` — these 
scripts only verify it
+  (branch prep, patch merges and tag creation are out of scope).
+- A local clone of `apache/doris` with that tag fetched.
+- These tools on `PATH`: `git`, `gpg`, `svn`, `sha512sum`, `curl`, `gzip`.
+- A GPG signing key — or let step 01 import an existing one, or generate and 
publish a new one.
+- ASF credentials exported in your shell (used for the SVN upload and the KEYS 
publish):
+  ```bash
+  export ASF_USERNAME=<your-apache-id>
+  export ASF_PASSWORD='<your-apache-ldap-password>'
+  ```
+- `release.env` filled in for this release — see 
[Configuration](#configuration) for the fields.
+
+## Steps
+Run from this directory, in order:
+1. **Check the signing environment** — `./01-check-env.sh`. Re-run until it 
ends with
+   `environment looks READY`.
+2. **Package, sign & upload** — `./02-package-sign-upload.sh`. Builds and 
signs the source tarball
+   and uploads it to the dev SVN. It pauses twice for confirmation; nothing 
public happens before
+   the final confirm.
+3. **Draft the vote email** — `./03-vote-mail.sh`. Prompts for the Release 
Notes URL and prints the
+   draft; review it and send it yourself from your `@apache.org` address.
+
+---
+
+Everything below is reference detail.
+
+## Files
+- `release.env` — all configuration (version, paths, signing key, SVN URLs, 
email). **Edit this first.**
+- `01-check-env.sh` — check / prepare the GPG signing environment and ASF 
credentials.
+- `02-package-sign-upload.sh` — `git archive` the tag, GPG-sign, sha512, sign 
any prebuilt binaries
+  locally, then upload the source artifacts to the dev SVN.
+- `03-vote-mail.sh` — generate the `[VOTE]` email draft.
+
+## Configuration
+The scripts are reusable across releases — they hold no version; edit 
`release.env` each time.
+Set at least:
+- `VERSION` / `RC` — e.g. `4.0.6` and `rc02`; `TAG` is derived as 
`${VERSION}-${RC}`.
+- `GIT_REMOTE` — the git remote pointing at `github.com/apache/doris`.
+- `APACHE_ID` / `APACHE_EMAIL` / `SIGNER_NAME` — your committer id, 
`@apache.org` email, and the
+  display name used to sign the vote email.
+- `SIGNING_KEY` — fingerprint of the key to sign with (leave empty to 
auto-detect a single local secret key).
+- `BIN_FILES` — optional absolute paths to prebuilt binary tarballs to sign 
locally (see below). Leave
+  the list empty (the default) to skip binary signing and run the source-only 
flow. When set, step 03
+  advertises each binary in the vote email under `BIN_DOWNLOAD_BASE`; when 
empty that section is omitted.
+
+`REPO_DIR` defaults to the enclosing checkout (`${ROOT}/../../`) since these 
scripts live inside
+`apache/doris`; override it only if you run them against a different clone.
+
+The SVN URLs, dev mailing list and verify-guide link rarely change; the 
defaults target the official
+Doris dist repos. The **vote SVN artifacts are source-only**: 
`apache-doris-<tag>-src.tar.gz` + `.asc`
++ `.sha512`. All script output (source tarball, signatures, SVN checkouts, 
email draft) goes to
+`WORK_DIR` (`<this-dir>/<tag>`, i.e. `tools/release-tools/<tag>`, by default).
+
+## What each step does
+- **01** verifies the required tools, your `GPG_TTY` / `gpg.conf`, resolves 
(or helps you create) a
+  signing key, checks it is present in the live published `KEYS`, runs a test 
sign + verify, and
+  confirms your ASF credentials. It is read-mostly: every state-changing 
action (edit `gpg.conf`,
+  import / generate a key, publish `KEYS`) prompts before acting.
+- **02** checks the local tag matches `GIT_REMOTE`, builds the source tarball 
from the tag with
+  `git archive`, signs it and writes the `.sha512`. If `BIN_FILES` is 
non-empty it then GPG-signs and
+  sha512s each prebuilt binary tarball, writing the `.asc` / `.sha512` 
sidecars **next to** each binary
+  (these are NOT uploaded — you upload the binaries and their sidecars 
yourself). Finally it uploads
+  the three **source** files to `dev/doris/<tag>/` on the Apache dist SVN. 
**Eyeball the target URL**
+  at the confirm prompt before committing — nothing public happens until the 
final confirm.
+- **03** writes `vote-email.txt` and `vote-email.eml` into `WORK_DIR` and 
prints the draft. Review it
+  and send it yourself from your `@apache.org` address.
+
+## Not automated (on purpose)
+- Sending the vote email — it goes to a public ASF list, so you review and 
send it manually.
+- Uploading binary packages — step 02 can *sign* the tarballs listed in 
`BIN_FILES`, but binaries are
+  not part of the ASF source vote, so you upload them (with their 
`.asc`/`.sha512`) manually.
+- Post-vote steps — tallying the result, the result email, and moving the 
artifacts from `dev/` to
+  `release/` once the vote passes.
diff --git a/tools/release-tools/release.env b/tools/release-tools/release.env
new file mode 100644
index 00000000000..024296f9b8f
--- /dev/null
+++ b/tools/release-tools/release.env
@@ -0,0 +1,79 @@
+# 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.
+
+# Shared config for the Apache Doris release helper scripts.
+# Edit values here; 01/02/03 all `source` this file.
+#
+# This release: 4.0.6-rc02  (tag already created & pushed to apache/doris)
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+
+# --- Repo & version ---
+REPO_DIR="${ROOT}/../../"
+VERSION="4.0.6"
+RC="rc02"
+TAG="${VERSION}-${RC}"                 # 4.0.6-rc02
+GIT_REMOTE="upstream-apache"           # remote pointing at 
github.com/apache/doris
+
+# --- Artifact naming ---
+# Matches the live convention in release/doris/4.0/4.0.5/ :
+# the rc IS kept in the file name; only the release *folder* drops it.
+PKG_BASE="apache-doris-${TAG}-src"     # -> apache-doris-4.0.6-rc02-src.tar.gz
+ARCHIVE_PREFIX="${PKG_BASE}/"          # top-level dir inside the tarball
+
+# --- Work area (OUTSIDE the git repo: artifacts + svn checkouts live here) ---
+WORK_DIR="${ROOT}/${TAG}"
+
+# --- Prebuilt binary artifacts (OPTIONAL; signed locally; uploaded MANUALLY 
by you) ---
+# Step 02 writes a .asc signature and a .sha512 checksum NEXT TO each file 
listed here.
+# These are NOT uploaded by the scripts and are NOT part of the source-only 
vote SVN;
+# you upload the binaries together with their .asc/.sha512 yourself, wherever 
they go.
+# Leave the list empty (the default) for the source-only flow. To also sign 
binaries,
+# set absolute paths to your prebuilt binary tarballs, e.g.:
+#   BIN_FILES=(
+#     "${WORK_DIR}/apache-doris-${VERSION}-bin-x64.tar.gz"
+#     "${WORK_DIR}/apache-doris-${VERSION}-bin-x64-noavx2.tar.gz"
+#     "${WORK_DIR}/apache-doris-${VERSION}-bin-arm64.tar.gz"
+#   )
+BIN_FILES=()
+
+# Public download base for the convenience binaries advertised in the vote 
email.
+# Step 03 lists each BIN_FILES basename under this URL (+ .asc / .sha512);
+# when BIN_FILES is empty the email omits the convenience-binaries section.
+BIN_DOWNLOAD_BASE="https://apache-doris-releases.oss-accelerate.aliyuncs.com";
+
+# --- Signer identity ---
+APACHE_ID="morningman"
+APACHE_EMAIL="[email protected]"
+SIGNER_NAME="Mingyu Chen"               # display name used to sign the vote 
email
+# Key passed to `gpg -u`. Leave EMPTY to auto-detect the only local secret key.
+SIGNING_KEY=""                          # use `gpg --list-keys` to get
+
+# --- Apache dist SVN (vote artifacts are SOURCE-ONLY; no binaries) ---
+DEV_SVN_BASE="https://dist.apache.org/repos/dist/dev/doris";
+DEV_SVN_DIR="${DEV_SVN_BASE}/${TAG}"   # <-- vote folder. VERIFY this URL 
before committing.
+RELEASE_SVN_BASE="https://dist.apache.org/repos/dist/release/doris";
+KEYS_URL="https://downloads.apache.org/doris/KEYS";
+
+# --- ASF credentials: export in your shell before running (NOT stored here) 
---
+#   export ASF_USERNAME=morningman
+#   export ASF_PASSWORD='...'          # Apache LDAP password
+
+# --- Vote email ---
+DEV_LIST="[email protected]"
+RELEASE_NOTES_URL=""                   # leave empty -> step 03 will prompt 
you for the issue link
+VERIFY_GUIDE_URL="https://doris.apache.org/community/release-and-verify/release-verify";


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to