This is an automated email from the ASF dual-hosted git repository.
hello-stephen 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 9b26dc6be40 [improvement](release) Improve release helper workflow
(#65488)
9b26dc6be40 is described below
commit 9b26dc6be4090d40733640edd5df2b2c475d08dc
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Tue Jul 14 10:25:36 2026 +0800
[improvement](release) Improve release helper workflow (#65488)
Problem Summary: The release helper scripts covered packaging and vote
preparation, but the workflow did not include a completion step for
publishing a passed release candidate, and the README was not structured
enough for a release manager or AI agent to turn a version such as Doris
4.0.7 into concrete next steps. This PR adds a release completion
script, updates the release configuration for final SVN artifact paths,
and rewrites the README as a step-by-step runbook with clear boundaries
for source artifacts, convenience binaries, manual emails, and PMC-only
release SVN publishing.
---
tools/release-tools/01-check-env.sh | 2 +-
tools/release-tools/04-release-complete.sh | 230 ++++++++++++++
tools/release-tools/README.md | 336 ++++++++++++++++-----
tools/release-tools/release.env | 13 +-
tools/release-tools/tests/run.sh | 26 ++
.../tests/test-check-env-required-tools.sh | 29 ++
.../tests/test-readme-release-complete-guidance.sh | 27 ++
.../tests/test-release-complete-checksum.sh | 199 ++++++++++++
8 files changed, 783 insertions(+), 79 deletions(-)
diff --git a/tools/release-tools/01-check-env.sh
b/tools/release-tools/01-check-env.sh
index c6e47048e91..431b7cfd10c 100755
--- a/tools/release-tools/01-check-env.sh
+++ b/tools/release-tools/01-check-env.sh
@@ -46,7 +46,7 @@ problems=0
echo "== Apache Doris ${TAG} - signing environment check =="
# 1. required tools
-for t in git gpg svn sha512sum curl gzip; do
+for t in git gpg svn svnmucc 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
diff --git a/tools/release-tools/04-release-complete.sh
b/tools/release-tools/04-release-complete.sh
new file mode 100755
index 00000000000..84691d55a4c
--- /dev/null
+++ b/tools/release-tools/04-release-complete.sh
@@ -0,0 +1,230 @@
+#!/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 04 - publish the passed RC source artifact to the Apache release SVN
+# and generate the [ANNOUNCE] email draft.
+#
+# The release SVN commit is public and requires PMC permission. This script
+# pauses for confirmation before changing SVN, and it never sends email.
+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 ]]; }
+
+mail_only=0
+usage() {
+ cat <<EOF
+Usage: $0 [--mail-only]
+
+Publishes Apache Doris ${TAG} source artifacts from dev SVN to release SVN as
+Apache Doris ${VERSION}, then writes the [ANNOUNCE] email draft.
+
+Options:
+ --mail-only Only write announce-email.txt and announce-email.eml.
+EOF
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --mail-only) mail_only=1; shift ;;
+ -h|--help) usage; exit 0 ;;
+ *) die "unknown argument: $1" ;;
+ esac
+done
+
+svn_auth=(--non-interactive --no-auth-cache)
+[[ -n "${ASF_USERNAME:-}" ]] && svn_auth+=(--username "$ASF_USERNAME")
+[[ -n "${ASF_PASSWORD:-}" ]] && svn_auth+=(--password "$ASF_PASSWORD")
+svnmucc_auth=(--non-interactive --no-auth-cache)
+[[ -n "${ASF_USERNAME:-}" ]] && svnmucc_auth+=(-u "$ASF_USERNAME")
+[[ -n "${ASF_PASSWORD:-}" ]] && svnmucc_auth+=(-p "$ASF_PASSWORD")
+
+RELEASE_PKG_BASE="${RELEASE_PKG_BASE:-apache-doris-${VERSION}-src}"
+RELEASE_SERIES="${RELEASE_SERIES:-${VERSION%.*}}"
+RELEASE_SVN_DIR="${RELEASE_SVN_DIR:-${RELEASE_SVN_BASE}/${RELEASE_SERIES}/${VERSION}}"
+RELEASE_SVN_PARENT_DIR="${RELEASE_SVN_PARENT_DIR:-${RELEASE_SVN_DIR%/*}}"
+DOWNLOAD_PAGE_URL="${DOWNLOAD_PAGE_URL:-https://doris.apache.org/download/}"
+ANNOUNCE_RELEASE_NOTES_URL="${ANNOUNCE_RELEASE_NOTES_URL:-}"
+
+require_tool() {
+ command -v "$1" >/dev/null 2>&1 || die "missing tool: $1"
+}
+
+write_announce_email() {
+ local rn subject body_file eml_file
+
+ rn="${ANNOUNCE_RELEASE_NOTES_URL:-${RELEASE_NOTES_URL:-}}"
+ if [[ -z "$rn" ]]; then
+ read -r -p "Release Notes URL for announce email: " rn
+ fi
+ [[ -n "$rn" ]] || die "release notes url required"
+
+ mkdir -p "$WORK_DIR"
+ subject="[ANNOUNCE] Apache Doris ${VERSION} release"
+ body_file="$WORK_DIR/announce-email.txt"
+ eml_file="$WORK_DIR/announce-email.eml"
+
+ read -r -d '' BODY <<EOF || true
+Hi All,
+
+We are pleased to announce the release of Apache Doris ${VERSION}.
+
+Apache Doris is a real-time analytics and hybrid search database for AI agents.
+
+The release is available at:
+${DOWNLOAD_PAGE_URL}
+
+The source artifacts are available at:
+${RELEASE_SVN_DIR}/
+
+Thanks to everyone who has contributed to this release, and the release note
can be found here:
+${rn}
+
+Best Regards,
+
+On behalf of the Doris team,
+${SIGNER_NAME}
+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.)"
+}
+
+publish_to_release_svn() {
+ local src_tar src_asc src_sha512 dst_tar dst_asc dst_sha512 checksum_dir
src_tar_file final_sha512_file release_parent_missing
+ local -a svnmucc_ops
+
+ require_tool svn
+ require_tool svnmucc
+ require_tool gpg
+ require_tool sha512sum
+
+ src_tar="${DEV_SVN_DIR}/${PKG_BASE}.tar.gz"
+ src_asc="${src_tar}.asc"
+ src_sha512="${src_tar}.sha512"
+ dst_tar="${RELEASE_PKG_BASE}.tar.gz"
+ dst_asc="${dst_tar}.asc"
+ dst_sha512="${dst_tar}.sha512"
+
+ echo "== Apache Doris ${TAG} - complete release =="
+ echo "Source dev SVN folder: ${DEV_SVN_DIR}/"
+ echo "Target release SVN folder: ${RELEASE_SVN_DIR}/"
+ echo
+ echo "Source files:"
+ echo " ${PKG_BASE}.tar.gz"
+ echo " ${PKG_BASE}.tar.gz.asc"
+ echo " ${PKG_BASE}.tar.gz.sha512"
+ echo "Target files:"
+ echo " ${dst_tar}"
+ echo " ${dst_asc}"
+ echo " ${dst_sha512}"
+ echo
+ warn "Only PMC members can write to the release SVN directory."
+
+ svn info "${svn_auth[@]}" "$src_tar" >/dev/null || die "source artifact not
found: $src_tar"
+ svn info "${svn_auth[@]}" "$src_asc" >/dev/null || die "source signature not
found: $src_asc"
+ svn info "${svn_auth[@]}" "$src_sha512" >/dev/null || die "source checksum
not found: $src_sha512"
+ if svn info "${svn_auth[@]}" "$RELEASE_SVN_DIR" >/dev/null 2>&1; then
+ die "release SVN folder already exists: $RELEASE_SVN_DIR (use --mail-only
to regenerate the email)"
+ fi
+ release_parent_missing=0
+ if svn info "${svn_auth[@]}" "$RELEASE_SVN_PARENT_DIR" >/dev/null 2>&1; then
+ ok "release SVN parent exists: ${RELEASE_SVN_PARENT_DIR}"
+ else
+ release_parent_missing=1
+ warn "release SVN parent will be created: ${RELEASE_SVN_PARENT_DIR}"
+ fi
+
+ checksum_dir="$(mktemp -d)"
+ trap 'rm -rf "$checksum_dir"' EXIT
+
+ src_tar_file="${PKG_BASE}.tar.gz"
+ final_sha512_file="${checksum_dir}/${dst_sha512}"
+ svn cat "${svn_auth[@]}" "$src_tar" > "${checksum_dir}/${src_tar_file}"
+ svn cat "${svn_auth[@]}" "$src_sha512" >
"${checksum_dir}/${src_tar_file}.sha512"
+ svn cat "${svn_auth[@]}" "$src_asc" > "${checksum_dir}/${src_tar_file}.asc"
+ (
+ cd "$checksum_dir"
+ sha512sum --check "${src_tar_file}.sha512"
+ gpg --verify "${src_tar_file}.asc" "$src_tar_file"
+ cp "$src_tar_file" "$dst_tar"
+ sha512sum "$dst_tar" > "$dst_sha512"
+ sha512sum --check "$dst_sha512"
+ )
+ ok "source RC checksum and signature verified: ${src_tar_file}"
+ ok "final sha512 ok: ${dst_sha512}"
+
+ echo "--- svnmucc operations ---"
+ svnmucc_ops=()
+ if [[ "$release_parent_missing" -eq 1 ]]; then
+ echo "mkdir ${RELEASE_SVN_PARENT_DIR}"
+ svnmucc_ops+=(mkdir "$RELEASE_SVN_PARENT_DIR")
+ fi
+ echo "mkdir ${RELEASE_SVN_DIR}"
+ svnmucc_ops+=(mkdir "$RELEASE_SVN_DIR")
+ echo "mv ${src_tar}"
+ echo " -> ${RELEASE_SVN_DIR}/${dst_tar}"
+ svnmucc_ops+=(mv "$src_tar" "${RELEASE_SVN_DIR}/${dst_tar}")
+ echo "mv ${src_asc}"
+ echo " -> ${RELEASE_SVN_DIR}/${dst_asc}"
+ svnmucc_ops+=(mv "$src_asc" "${RELEASE_SVN_DIR}/${dst_asc}")
+ echo "put ${final_sha512_file}"
+ echo " -> ${RELEASE_SVN_DIR}/${dst_sha512}"
+ svnmucc_ops+=(put "$final_sha512_file" "${RELEASE_SVN_DIR}/${dst_sha512}")
+ echo "rm ${DEV_SVN_DIR}"
+ svnmucc_ops+=(rm "$DEV_SVN_DIR")
+ echo
+ echo "Will commit the URL operations above in one SVN revision."
+ confirm "FINAL confirm - publish release SVN and remove dev RC now?" || {
warn "stopping before SVN commit."; exit 0; }
+
+ if ! svnmucc "${svnmucc_auth[@]}" -m "Release Doris ${VERSION}"
"${svnmucc_ops[@]}"; then
+ die "svnmucc release publish failed"
+ fi
+ rm -rf "$checksum_dir"
+ trap - EXIT
+ ok "committed release artifacts: ${RELEASE_SVN_DIR}/"
+ ok "removed dev RC folder: ${DEV_SVN_DIR}/"
+}
+
+if [[ "$mail_only" -eq 0 ]]; then
+ publish_to_release_svn
+else
+ ok "mail-only mode: skipping SVN publish"
+fi
+
+write_announce_email
diff --git a/tools/release-tools/README.md b/tools/release-tools/README.md
index 20e34504d96..d82509c54ff 100644
--- a/tools/release-tools/README.md
+++ b/tools/release-tools/README.md
@@ -19,81 +19,267 @@ 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.
+This directory contains helper scripts for an Apache Doris Release Manager (RM)
+to create and complete a source release candidate (RC). The scripts package and
+sign the source tarball, upload the source artifacts to the Apache dev SVN,
+draft the `[VOTE]` email, publish the passed RC to the Apache release SVN, and
+draft the `[ANNOUNCE]` email.
+
+These scripts are not a full release automation system. They assume the release
+branch, release notes, cherry-picks, build validation, and release tag have
+already been prepared.
+
+## If you are an AI agent
+
+When a user says something like "read this file and tell me how to start
+releasing Doris 4.0.7", give the RM a concrete checklist. Do not just summarize
+the scripts.
+
+For `4.0.7`, explain this sequence:
+
+1. Confirm the RC number. If the RM does not provide one, ask whether this is
+ `rc01` or a later candidate such as `rc02`. The tag is
+ `${VERSION}-${RC}`, for example `4.0.7-rc01`.
+2. Confirm that the tag already exists locally and on the Apache Doris GitHub
+ remote. These scripts verify the tag, but they do not create it.
+3. Tell the RM to edit `release.env` before running any script.
+4. Tell the RM to export ASF SVN credentials in the shell.
+5. Run `./01-check-env.sh` until it reports `environment looks READY`.
+6. Run `./02-package-sign-upload.sh` only after the RM has checked the target
+ dev SVN URL and is ready to publish the RC source artifacts.
+7. Run `./03-vote-mail.sh`, review the generated vote email, then send it
+ manually from the RM's `@apache.org` address.
+8. After the vote passes and the `[RESULT]` email has been sent manually, run
+ `./04-release-complete.sh` to publish the source release artifacts and draft
+ the announce email.
+
+Also make these boundaries clear:
+
+- The vote artifacts in Apache dist SVN are source-only.
+- `BIN_FILES` is optional. If set, step 02 signs convenience binary tarballs
+ locally, but the scripts do not upload those binaries.
+- The scripts never send public emails. The RM must review and send the vote,
+ result, and announce emails manually.
+- Step 04 writes to the Apache release SVN and requires PMC permission.
+
+## Quick start for a new RC
+
+Run all commands from this directory:
+
+```bash
+cd tools/release-tools
+```
+
+Before running any script, edit `release.env` for the target version and RC.
+For example, to prepare Doris `4.0.7-rc01`:
+
+```bash
+VERSION="4.0.7"
+RC="rc01"
+TAG="${VERSION}-${RC}"
+GIT_REMOTE="upstream-apache"
+
+APACHE_ID="<your-apache-id>"
+APACHE_EMAIL="<your-apache-id>@apache.org"
+SIGNER_NAME="<your display name>"
+SIGNING_KEY="<your signing key fingerprint, or leave empty if only one secret
key exists>"
+
+RELEASE_NOTES_URL="<release notes or issue URL>"
+ANNOUNCE_RELEASE_NOTES_URL="<release notes URL, or leave empty to
reuse/prompt>"
+```
+
+If you need to advertise convenience binaries in the vote email, set
+`BIN_FILES` to absolute paths for those prebuilt tarballs. Leave `BIN_FILES=()`
+for the source-only flow.
+
+Export ASF credentials in the same shell. The scripts use these credentials for
+SVN upload and KEYS publishing:
+
+```bash
+export ASF_USERNAME=<your-apache-id>
+export ASF_PASSWORD='<your-apache-ldap-password>'
+```
+
+Then run the release flow:
+
+```bash
+./01-check-env.sh
+./02-package-sign-upload.sh
+./03-vote-mail.sh
+```
+
+Review the generated vote email in `WORK_DIR`, then send it manually to
+`[email protected]` from your `@apache.org` address.
+
+After the vote passes:
+
+1. Tally the votes and send the `[RESULT]` email manually.
+2. Run `./04-release-complete.sh`.
+3. Check that the new source artifacts are visible on `downloads.apache.org`.
+4. Update the Doris download page and release metadata as needed, then check
+ that the public download page points at the new release.
+5. Wait for the download and CDN propagation window.
+6. Review the generated announce email in `WORK_DIR`.
+7. Only after those checks pass, send the `[ANNOUNCE]` email manually.
+
+Use this command only when the release SVN step has already been done and you
+only need to regenerate the announce email:
+
+```bash
+./04-release-complete.sh --mail-only
+```
## 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.
+
+- The release tag has already been created and pushed to `apache/doris`.
+ Branch preparation, patch merges, validation, and tag creation are out of
+ scope for these scripts.
+- The local Doris checkout has fetched that tag.
+- `release.env` is filled in for this exact release.
+- These tools are on `PATH`: `git`, `gpg`, `svn`, `svnmucc`, `sha512sum`,
+ `curl`, and `gzip`.
+- A GPG signing key is available locally, or the RM is ready to let
+ `01-check-env.sh` import an existing key or generate and publish a new key.
+- ASF credentials are exported as `ASF_USERNAME` and `ASF_PASSWORD`.
+- The RM can write to the required Apache SVN locations. Step 04 requires PMC
+ permission for the release SVN commit.
+
+## Configuration reference
+
+All scripts source `release.env`. Edit it once per RC.
+
+Set these fields first:
+
+- `VERSION`: Final release version, for example `4.0.7`.
+- `RC`: Candidate number, for example `rc01` or `rc02`.
+- `TAG`: Derived as `${VERSION}-${RC}`. Do not set it to the final version
+ without the RC suffix.
+- `GIT_REMOTE`: Git remote that points at `github.com/apache/doris`.
+- `APACHE_ID`: Apache committer id.
+- `APACHE_EMAIL`: Apache email address.
+- `SIGNER_NAME`: Display name used in generated emails.
+- `SIGNING_KEY`: Fingerprint passed to `gpg -u`. Leave it empty only when this
+ machine has exactly one usable local secret key.
+- `RELEASE_NOTES_URL`: Link used in the vote email. If empty, step 03 prompts.
+- `ANNOUNCE_RELEASE_NOTES_URL`: Link used in the announce email. If empty, step
+ 04 reuses `RELEASE_NOTES_URL` or prompts.
+- `BIN_FILES`: Optional absolute paths to prebuilt convenience binary tarballs.
+
+The default `REPO_DIR` is the enclosing Doris checkout because these scripts
+live under `tools/release-tools`. Override it only when you deliberately run
the
+scripts against another clone.
+
+The default SVN URLs target the official Doris dist repositories:
+
+- Dev RC source artifacts:
+ `https://dist.apache.org/repos/dist/dev/doris/<version>-<rc>/`
+- Final source release artifacts:
+ `https://dist.apache.org/repos/dist/release/doris/<major.minor>/<version>/`
+
+Step 02 uploads source artifacts with the RC suffix:
+
+- `apache-doris-<version>-<rc>-src.tar.gz`
+- `apache-doris-<version>-<rc>-src.tar.gz.asc`
+- `apache-doris-<version>-<rc>-src.tar.gz.sha512`
+
+Step 04 publishes final source artifacts without the RC suffix:
+
+- `apache-doris-<version>-src.tar.gz`
+- `apache-doris-<version>-src.tar.gz.asc`
+- `apache-doris-<version>-src.tar.gz.sha512`
+
+All generated files go to `WORK_DIR`, which defaults to
+`tools/release-tools/<version>-<rc>`.
+
+## Script details
+
+### 01-check-env.sh
+
+Use this script to prepare and verify the signing environment. It checks
+required tools, `GPG_TTY`, GPG SHA512 digest preferences, the signing key, live
+published `KEYS`, test signing, and ASF credentials.
+
+This script is mostly read-only. It prompts before every state-changing action,
+including editing `gpg.conf`, importing or generating a key, and publishing the
+key to the Doris KEYS files.
+
+Expected success line:
+
+```text
+environment looks READY for <version>-<rc>
+```
+
+### 02-package-sign-upload.sh
+
+Use this script to build, sign, checksum, and upload the RC source artifacts.
+
+It verifies that the local tag exists, the tag exists on `GIT_REMOTE`, and both
+tags point to the same commit. It then creates the source tarball with
+`git archive`, writes `.asc` and `.sha512` sidecars, verifies both, and uploads
+the three source files to the Apache dev SVN.
+
+If `BIN_FILES` is non-empty, this script also signs and checksums those binary
+tarballs in place. It does not upload binaries.
+
+This script pauses twice before touching the public dev SVN. Check the printed
+target URL before confirming.
+
+### 03-vote-mail.sh
+
+Use this script to generate the `[VOTE]` email draft. It writes:
+
+- `vote-email.txt`
+- `vote-email.eml`
+
+Review the draft and send it manually from the RM's `@apache.org` address to
+`[email protected]`.
+
+### 04-release-complete.sh
+
+Use this script only after the vote has passed and the `[RESULT]` email has
+been sent manually.
+
+It checks the passed RC source artifacts in the dev SVN, verifies the source
+tarball against the RC checksum and detached signature that voters approved,
+regenerates and verifies the final checksum sidecar with the RC-free source
+tarball name, then uses `svnmucc` to create the final release SVN directory,
+move the source tarball and detached signature there, upload the final
checksum,
+and remove the dev RC folder in one SVN revision.
+
+It then writes:
+
+- `announce-email.txt`
+- `announce-email.eml`
+
+Before sending the announce email, check that the release is visible on
+`downloads.apache.org`, update and verify the Doris download page, and wait for
+the download and CDN propagation window. Only after those checks pass, send the
+`[ANNOUNCE]` email manually.
+
+Use `./04-release-complete.sh --mail-only` to regenerate the announce email
+without touching SVN.
+
+## Tests
+
+Run the release-tool shell tests from this directory:
+
+```bash
+./tests/run.sh
+```
+
+## Manual work not automated
+
+The RM must still do these tasks manually:
+
+- Prepare the release branch and tag.
+- Validate the release before starting the RC upload flow.
+- Send the `[VOTE]` email.
+- Upload convenience binaries, if the release includes them.
+- Tally the vote and send the `[RESULT]` email.
+- Verify `downloads.apache.org` and the Doris download page before announcing.
+- Wait for the download and CDN propagation window.
+- Send the `[ANNOUNCE]` email.
+- Publish Maven staging repositories, update the website and GitHub release
+ notes, and clean up old release versions when applicable.
diff --git a/tools/release-tools/release.env b/tools/release-tools/release.env
index 024296f9b8f..f6f4c3d3e79 100644
--- a/tools/release-tools/release.env
+++ b/tools/release-tools/release.env
@@ -16,7 +16,7 @@
# under the License.
# Shared config for the Apache Doris release helper scripts.
-# Edit values here; 01/02/03 all `source` this file.
+# Edit values here; 01/02/03/04 all `source` this file.
#
# This release: 4.0.6-rc02 (tag already created & pushed to apache/doris)
@@ -30,10 +30,11 @@ 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.
+# Vote artifacts keep the RC in the file name. Step 04 publishes the final
+# release artifacts with the RC removed from the file name.
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
+RELEASE_PKG_BASE="apache-doris-${VERSION}-src"
# --- Work area (OUTSIDE the git repo: artifacts + svn checkouts live here) ---
WORK_DIR="${ROOT}/${TAG}"
@@ -67,6 +68,8 @@ SIGNING_KEY="" # use `gpg
--list-keys` to get
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"
+RELEASE_SERIES="${VERSION%.*}" # e.g. 4.0.7 -> 4.0
+RELEASE_SVN_DIR="${RELEASE_SVN_BASE}/${RELEASE_SERIES}/${VERSION}" # final
release folder, no RC suffix
KEYS_URL="https://downloads.apache.org/doris/KEYS"
# --- ASF credentials: export in your shell before running (NOT stored here)
---
@@ -77,3 +80,7 @@ KEYS_URL="https://downloads.apache.org/doris/KEYS"
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"
+
+# --- Announce email ---
+DOWNLOAD_PAGE_URL="https://doris.apache.org/download/"
+ANNOUNCE_RELEASE_NOTES_URL="" # leave empty -> step 04 will reuse
RELEASE_NOTES_URL or prompt
diff --git a/tools/release-tools/tests/run.sh b/tools/release-tools/tests/run.sh
new file mode 100755
index 00000000000..fec52ed2938
--- /dev/null
+++ b/tools/release-tools/tests/run.sh
@@ -0,0 +1,26 @@
+#!/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.
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
+for test_script in "${ROOT}"/tests/test-*.sh; do
+ echo "== ${test_script##*/} =="
+ "$test_script"
+done
diff --git a/tools/release-tools/tests/test-check-env-required-tools.sh
b/tools/release-tools/tests/test-check-env-required-tools.sh
new file mode 100755
index 00000000000..cde2a1ee455
--- /dev/null
+++ b/tools/release-tools/tests/test-check-env-required-tools.sh
@@ -0,0 +1,29 @@
+#!/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.
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+required_tool_loop="$(awk '/^for t in /,/; do/' "${ROOT}/01-check-env.sh")"
+
+for tool in git gpg svn svnmucc sha512sum curl gzip; do
+ if ! grep -qw "$tool" <<<"$required_tool_loop"; then
+ echo "01-check-env.sh does not require tool: $tool" >&2
+ exit 1
+ fi
+done
diff --git a/tools/release-tools/tests/test-readme-release-complete-guidance.sh
b/tools/release-tools/tests/test-readme-release-complete-guidance.sh
new file mode 100755
index 00000000000..18597a15dde
--- /dev/null
+++ b/tools/release-tools/tests/test-readme-release-complete-guidance.sh
@@ -0,0 +1,27 @@
+#!/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.
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+readme="${ROOT}/README.md"
+
+grep -q 'downloads.apache.org' "$readme"
+grep -q 'download page' "$readme"
+grep -q 'propagation' "$readme"
+grep -q 'Only after those checks pass, send the `\[ANNOUNCE\]` email' "$readme"
diff --git a/tools/release-tools/tests/test-release-complete-checksum.sh
b/tools/release-tools/tests/test-release-complete-checksum.sh
new file mode 100755
index 00000000000..0bde8c9ed31
--- /dev/null
+++ b/tools/release-tools/tests/test-release-complete-checksum.sh
@@ -0,0 +1,199 @@
+#!/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.
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+tmp="$(mktemp -d)"
+trap 'rm -rf "$tmp"' EXIT
+
+cp "${ROOT}/04-release-complete.sh" "$tmp/"
+cat > "$tmp/release.env" <<'EOF'
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+VERSION="9.9.9"
+RC="rc01"
+TAG="${VERSION}-${RC}"
+PKG_BASE="apache-doris-${TAG}-src"
+RELEASE_PKG_BASE="apache-doris-${VERSION}-src"
+WORK_DIR="${ROOT}/${TAG}"
+DEV_SVN_BASE="https://dist.example.test/dev/doris"
+DEV_SVN_DIR="${DEV_SVN_BASE}/${TAG}"
+RELEASE_SVN_BASE="https://dist.example.test/release/doris"
+RELEASE_SERIES="${VERSION%.*}"
+DOWNLOAD_PAGE_URL="https://doris.example.test/download/"
+ANNOUNCE_RELEASE_NOTES_URL="https://doris.example.test/release-notes"
+RELEASE_NOTES_URL=""
+DEV_LIST="[email protected]"
+SIGNER_NAME="Release Manager"
+EOF
+
+cat > "$tmp/svn" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+cmd="$1"
+shift
+case "$cmd" in
+ info)
+ url="${@: -1}"
+ case "$url" in
+
https://dist.example.test/release/doris/9.9|https://dist.example.test/release/doris/9.9/9.9.9)
+ exit 1
+ ;;
+ esac
+ ;;
+ cat)
+ url="${@: -1}"
+ printf '%s\n' "$url" >> "$FAKE_SVN_CAT_LOG"
+ case "$url" in
+ *.tar.gz)
+ printf 'release artifact bytes\n'
+ ;;
+ *.sha512)
+ if [[ "${FAKE_BAD_SHA512:-0}" -eq 1 ]]; then
+
digest="00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ else
+ digest="$(printf 'release artifact bytes\n' | sha512sum | awk
'{print $1}')"
+ fi
+ printf '%s apache-doris-9.9.9-rc01-src.tar.gz\n' "$digest"
+ ;;
+ *.asc)
+ printf 'fake detached signature\n'
+ ;;
+ *)
+ echo "unexpected svn cat url: $url" >&2
+ exit 1
+ ;;
+ esac
+ ;;
+ *)
+ echo "unexpected svn command: $cmd" >&2
+ exit 1
+ ;;
+esac
+EOF
+chmod +x "$tmp/svn"
+
+cat > "$tmp/mktemp" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+[[ "$1" == "-d" ]]
+dir="${FAKE_MKTEMP_ROOT}/checksum-dir-${RANDOM}-${RANDOM}"
+mkdir -p "$dir"
+printf '%s\n' "$dir" >> "$FAKE_MKTEMP_LOG"
+printf '%s\n' "$dir"
+EOF
+chmod +x "$tmp/mktemp"
+
+cat > "$tmp/gpg" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+printf '%s\n' "$*" > "$FAKE_GPG_LOG"
+[[ "$1" == "--verify" ]]
+[[ "$2" == "apache-doris-9.9.9-rc01-src.tar.gz.asc" ]]
+[[ "$3" == "apache-doris-9.9.9-rc01-src.tar.gz" ]]
+[[ -f "$2" ]]
+[[ -f "$3" ]]
+EOF
+chmod +x "$tmp/gpg"
+
+cat > "$tmp/svnmucc" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+printf '%s\n' "$*" > "$FAKE_SVNMUCC_LOG"
+series_dir_created=0
+while (($#)); do
+ case "$1" in
+ mkdir)
+ if [[ "$2" == "https://dist.example.test/release/doris/9.9" ]]; then
+ series_dir_created=1
+ elif [[ "$2" == "https://dist.example.test/release/doris/9.9/9.9.9" &&
"$series_dir_created" -eq 0 ]]; then
+ echo "release series directory must be created before release leaf
directory" >&2
+ exit 1
+ fi
+ shift 2
+ ;;
+ mv)
+ shift 3
+ ;;
+ put)
+ checksum_file="$2"
+ cp "$checksum_file" "$FAKE_FINAL_SHA512"
+ shift 3
+ ;;
+ rm)
+ shift 2
+ ;;
+ -m|-u|-p)
+ shift 2
+ ;;
+ --non-interactive|--no-auth-cache)
+ shift
+ ;;
+ *)
+ shift
+ ;;
+ esac
+done
+EOF
+chmod +x "$tmp/svnmucc"
+
+export PATH="$tmp:$PATH"
+export FAKE_SVN_CAT_LOG="$tmp/svn-cat.log"
+export FAKE_GPG_LOG="$tmp/gpg.log"
+export FAKE_SVNMUCC_LOG="$tmp/svnmucc.log"
+export FAKE_FINAL_SHA512="$tmp/final.sha512"
+export FAKE_MKTEMP_ROOT="$tmp/mktemp-root"
+export FAKE_MKTEMP_LOG="$tmp/mktemp.log"
+mkdir -p "$FAKE_MKTEMP_ROOT"
+
+printf 'y\n' | bash "$tmp/04-release-complete.sh" >/dev/null
+
+if grep -q 'mv
https://dist.example.test/dev/doris/9.9.9-rc01/apache-doris-9.9.9-rc01-src.tar.gz.sha512'
"$FAKE_SVNMUCC_LOG"; then
+ echo "release completion must not move the RC checksum sidecar unchanged" >&2
+ exit 1
+fi
+
+grep -q 'apache-doris-9.9.9-rc01-src.tar.gz$' "$FAKE_SVN_CAT_LOG"
+grep -q 'apache-doris-9.9.9-rc01-src.tar.gz.sha512$' "$FAKE_SVN_CAT_LOG"
+grep -q 'apache-doris-9.9.9-rc01-src.tar.gz.asc$' "$FAKE_SVN_CAT_LOG"
+grep -q -- '--verify apache-doris-9.9.9-rc01-src.tar.gz.asc
apache-doris-9.9.9-rc01-src.tar.gz' "$FAKE_GPG_LOG"
+grep -q 'mkdir https://dist.example.test/release/doris/9.9' "$FAKE_SVNMUCC_LOG"
+grep -q
'https://dist.example.test/release/doris/9.9/9.9.9/apache-doris-9.9.9-src.tar.gz'
"$FAKE_SVNMUCC_LOG"
+grep -q 'put ' "$FAKE_SVNMUCC_LOG"
+grep -q 'apache-doris-9.9.9-src.tar.gz$' "$FAKE_FINAL_SHA512"
+if grep -q 'apache-doris-9.9.9-rc01-src.tar.gz' "$FAKE_FINAL_SHA512"; then
+ echo "final checksum sidecar still references the RC tarball name" >&2
+ exit 1
+fi
+
+success_checksum_dir="$(tail -n 1 "$FAKE_MKTEMP_LOG")"
+if [[ -e "$success_checksum_dir" ]]; then
+ echo "checksum temp directory was not removed after successful publish:
$success_checksum_dir" >&2
+ exit 1
+fi
+
+if FAKE_BAD_SHA512=1 bash "$tmp/04-release-complete.sh" >/dev/null 2>&1; then
+ echo "release completion unexpectedly succeeded with a bad RC checksum" >&2
+ exit 1
+fi
+
+failed_checksum_dir="$(tail -n 1 "$FAKE_MKTEMP_LOG")"
+if [[ -e "$failed_checksum_dir" ]]; then
+ echo "checksum temp directory was not removed after checksum verification
failure: $failed_checksum_dir" >&2
+ exit 1
+fi
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]