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]