Copilot commented on code in PR #1720:
URL: https://github.com/apache/cloudberry/pull/1720#discussion_r3246658168


##########
.github/workflows/package-convenience-binaries.yml:
##########
@@ -0,0 +1,657 @@
+# --------------------------------------------------------------------
+#
+# 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.
+#
+# --------------------------------------------------------------------
+# GitHub Actions Workflow: Apache Cloudberry Convenience Package Build
+# --------------------------------------------------------------------
+# Description:
+#
+#   This workflow manually builds convenience DEB/RPM packages from an
+#   ASF-approved Apache Cloudberry source release tarball.
+#
+# Workflow Overview:
+# 1. **Verify Source Release Job**:
+#    - Validates version and Apache download URLs.
+#    - Downloads source tarball, .asc, and .sha512.
+#    - Imports the project KEYS file and verifies signature and checksum.
+#    - Publishes the verified source release as an internal workflow artifact.
+#
+# 2. **Build Package Job (Matrix)**:
+#    - Downloads the verified source release artifact.
+#    - Builds Cloudberry inside the target Cloudberry Docker build image.
+#    - Produces DEB/RPM packages and matching .sha512 checksum files.
+#    - Uploads per-platform package artifacts.
+#
+# Scope:
+# - Intended for official Apache Cloudberry source releases managed by the
+#   release manager.
+# - Supports an official source release mode and a temporary git ref test mode.
+# - Produces convenience binaries only; detached .asc signatures for DEB/RPM
+#   remain a release manager local step.
+# --------------------------------------------------------------------
+
+name: Apache Cloudberry Convenience Package Build
+
+on:
+  workflow_dispatch:
+    inputs:
+      source_mode:
+        description: 'Source mode: official release validation or git ref test'
+        required: true
+        default: official_source_release
+        type: choice
+        options:
+          - official_source_release
+          - git_ref_test
+
+      # ============================================================
+      # Fields for official_source_release mode
+      # ============================================================
+      version:
+        description: '[Required for all modes] Release version, e.g. 
2.2.0-incubating'
+        required: true
+        type: string
+      source_url:
+        description: '[official_source_release only] Apache source tarball URL 
from downloads.apache.org'
+        required: false
+        type: string
+      source_asc_url:
+        description: '[official_source_release only] Detached GPG signature 
URL for the source tarball (.asc)'
+        required: false
+        type: string
+      source_sha512_url:
+        description: '[official_source_release only] SHA-512 checksum URL for 
the source tarball (.sha512)'
+        required: false
+        type: string
+
+      # ============================================================
+      # Fields for git_ref_test mode
+      # ============================================================
+      git_ref:
+        description: '[git_ref_test only] Git tag or commit SHA to archive as 
source input'
+        required: false
+        type: string
+
+permissions:
+  contents: read

Review Comment:
   Workflow sets `permissions: contents: read` only, but it uses 
`actions/upload-artifact` and `actions/download-artifact`. With the current 
permissions block, `GITHUB_TOKEN` will not have `actions: write` (needed to 
upload artifacts) / `actions: read` (to download). Align the permissions with 
other CI workflows here (e.g., include `actions: write`, and any other required 
scopes).
   



##########
.github/workflows/package-convenience-binaries.yml:
##########
@@ -0,0 +1,657 @@
+# --------------------------------------------------------------------
+#
+# 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.
+#
+# --------------------------------------------------------------------
+# GitHub Actions Workflow: Apache Cloudberry Convenience Package Build
+# --------------------------------------------------------------------
+# Description:
+#
+#   This workflow manually builds convenience DEB/RPM packages from an
+#   ASF-approved Apache Cloudberry source release tarball.
+#
+# Workflow Overview:
+# 1. **Verify Source Release Job**:
+#    - Validates version and Apache download URLs.
+#    - Downloads source tarball, .asc, and .sha512.
+#    - Imports the project KEYS file and verifies signature and checksum.
+#    - Publishes the verified source release as an internal workflow artifact.
+#
+# 2. **Build Package Job (Matrix)**:
+#    - Downloads the verified source release artifact.
+#    - Builds Cloudberry inside the target Cloudberry Docker build image.
+#    - Produces DEB/RPM packages and matching .sha512 checksum files.
+#    - Uploads per-platform package artifacts.
+#
+# Scope:
+# - Intended for official Apache Cloudberry source releases managed by the
+#   release manager.
+# - Supports an official source release mode and a temporary git ref test mode.
+# - Produces convenience binaries only; detached .asc signatures for DEB/RPM
+#   remain a release manager local step.
+# --------------------------------------------------------------------
+
+name: Apache Cloudberry Convenience Package Build
+
+on:
+  workflow_dispatch:
+    inputs:
+      source_mode:
+        description: 'Source mode: official release validation or git ref test'
+        required: true
+        default: official_source_release
+        type: choice
+        options:
+          - official_source_release
+          - git_ref_test
+
+      # ============================================================
+      # Fields for official_source_release mode
+      # ============================================================
+      version:
+        description: '[Required for all modes] Release version, e.g. 
2.2.0-incubating'
+        required: true
+        type: string
+      source_url:
+        description: '[official_source_release only] Apache source tarball URL 
from downloads.apache.org'
+        required: false
+        type: string
+      source_asc_url:
+        description: '[official_source_release only] Detached GPG signature 
URL for the source tarball (.asc)'
+        required: false
+        type: string
+      source_sha512_url:
+        description: '[official_source_release only] SHA-512 checksum URL for 
the source tarball (.sha512)'
+        required: false
+        type: string
+
+      # ============================================================
+      # Fields for git_ref_test mode
+      # ============================================================
+      git_ref:
+        description: '[git_ref_test only] Git tag or commit SHA to archive as 
source input'
+        required: false
+        type: string
+
+permissions:
+  contents: read
+
+concurrency:
+  group: package-build-${{ github.ref }}-${{ inputs.version }}
+  cancel-in-progress: true
+
+env:
+  LOG_RETENTION_DAYS: 14
+  KEYS_URL: https://downloads.apache.org/incubator/cloudberry/KEYS
+
+jobs:
+  verify-source-release:
+    name: Verify source release
+    runs-on: ubuntu-24.04
+    timeout-minutes: 30
+    outputs:
+      source_tarball_name: ${{ steps.validate.outputs.source_tarball_name }}
+      artifact_name: ${{ steps.validate.outputs.artifact_name }}
+      packaging_version: ${{ steps.validate.outputs.packaging_version }}
+    container:
+      image: apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest
+      options: >-
+        --user root
+        --hostname cdw
+
+    steps:
+      - name: Initialize verification container
+        shell: bash
+        run: |
+          set -euo pipefail
+          su - gpadmin -c "/tmp/init_system.sh"
+
+      - name: Validate manual inputs
+        id: validate
+        shell: bash
+        env:
+          SOURCE_MODE: ${{ github.event.inputs.source_mode }}
+          VERSION: ${{ github.event.inputs.version }}
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          set -euo pipefail
+
+          if [[ -z "$VERSION" ]]; then
+            echo "::error::version must not be empty"
+            exit 1
+          fi
+
+          source_tarball_name="apache-cloudberry-${VERSION}-src.tar.gz"
+          artifact_name="verified-source-release-${VERSION}"
+          packaging_version="${VERSION%-incubating}"
+
+          if [[ -z "$packaging_version" ]]; then
+            echo "::error::Unable to derive packaging version from 
version=${VERSION}"
+            exit 1
+          fi
+
+          if [[ "$SOURCE_MODE" == "official_source_release" ]]; then
+            validate_apache_url() {
+              local value="$1"
+              local label="$2"
+              local prefix="https://downloads.apache.org/incubator/cloudberry/";
+              if [[ -z "$value" ]]; then
+                echo "::error::${label} must not be empty in 
official_source_release mode"
+                exit 1
+              fi
+              if [[ "$value" != "$prefix"* ]]; then
+                echo "::error::${label} must use downloads.apache.org"
+                exit 1
+              fi
+            }
+
+            validate_apache_url "$SOURCE_URL" "source_url"
+            validate_apache_url "$SOURCE_ASC_URL" "source_asc_url"
+            validate_apache_url "$SOURCE_SHA512_URL" "source_sha512_url"
+
+            if [[ "$SOURCE_URL" != *"/${source_tarball_name}" ]]; then
+              echo "::error::source_url must end with /${source_tarball_name}"
+              exit 1
+            fi
+            if [[ "$SOURCE_ASC_URL" != *"/${source_tarball_name}.asc" ]]; then
+              echo "::error::source_asc_url must end with 
/${source_tarball_name}.asc"
+              exit 1
+            fi
+            if [[ "$SOURCE_SHA512_URL" != *"/${source_tarball_name}.sha512" 
]]; then
+              echo "::error::source_sha512_url must end with 
/${source_tarball_name}.sha512"
+              exit 1
+            fi
+          elif [[ "$SOURCE_MODE" == "git_ref_test" ]]; then
+            if [[ -z "$GIT_REF" ]]; then
+              echo "::error::git_ref must not be empty in git_ref_test mode"
+              exit 1
+            fi
+            artifact_name="git-ref-source-${VERSION}"
+          else
+            echo "::error::Unsupported source_mode: $SOURCE_MODE"
+            exit 1
+          fi
+
+          echo "source_tarball_name=${source_tarball_name}" >> "$GITHUB_OUTPUT"
+          echo "artifact_name=${artifact_name}" >> "$GITHUB_OUTPUT"
+          echo "packaging_version=${packaging_version}" >> "$GITHUB_OUTPUT"
+
+      - name: Download source release and verification files
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+        run: |
+          set -euo pipefail
+          mkdir -p verified-source
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}" \
+            "$SOURCE_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}.asc" \
+            "$SOURCE_ASC_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}.sha512" \
+            "$SOURCE_SHA512_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/KEYS" \
+            "${KEYS_URL}"
+
+      - name: Verify source release signature and checksum
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+        run: |
+          set -euo pipefail
+          export GNUPGHOME="${RUNNER_TEMP}/gnupg"
+          mkdir -p "${GNUPGHOME}"
+          chmod 700 "${GNUPGHOME}"
+
+          gpg --import verified-source/KEYS
+          gpg --verify \
+            "verified-source/${SOURCE_TARBALL_NAME}.asc" \
+            "verified-source/${SOURCE_TARBALL_NAME}"
+
+          (
+            cd verified-source
+            sha512sum -c "${SOURCE_TARBALL_NAME}.sha512"
+          )
+
+          tar -tzf "verified-source/${SOURCE_TARBALL_NAME}" >/dev/null
+
+      - name: Checkout git ref for test packaging
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ github.event.inputs.git_ref }}
+          fetch-depth: 1
+          submodules: recursive
+          path: repo
+
+      - name: Prepare git ref source artifact
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+          VERSION: ${{ github.event.inputs.version }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          set -euo pipefail
+          mkdir -p verified-source
+          source_root_name="apache-cloudberry-${VERSION}"
+          tmp_dir="$(mktemp -d)"
+          trap 'rm -rf "$tmp_dir"' EXIT
+
+          git -C repo archive --format=tar --prefix="${source_root_name}/" 
HEAD | tar -x -C "${tmp_dir}"
+
+          if [[ -s repo/.gitmodules ]]; then
+            git -C repo submodule foreach --recursive --quiet "
+              fullpath=\"\$toplevel/\$sm_path\"
+              destpath=\"${tmp_dir}/${source_root_name}/\$sm_path\"
+              mkdir -p \"\$destpath\"
+              git -C \"\$fullpath\" archive --format=tar 
--prefix=\"\$sm_path/\" HEAD | tar -x -C \"${tmp_dir}/${source_root_name}\"
+            "
+          fi
+
+          tar -C "${tmp_dir}" -czf "verified-source/${SOURCE_TARBALL_NAME}" 
"${source_root_name}"
+          tar -tzf "verified-source/${SOURCE_TARBALL_NAME}" >/dev/null
+
+      - name: Summarize verified source release
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          VERSION: ${{ github.event.inputs.version }}
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+        run: |
+          {
+            echo "# Verified source release"
+            echo "- Version: ${VERSION}"
+            echo "- Packaging version: ${{ 
steps.validate.outputs.packaging_version }}"
+            echo "- Source URL: ${SOURCE_URL}"
+            echo "- Signature URL: ${SOURCE_ASC_URL}"
+            echo "- Checksum URL: ${SOURCE_SHA512_URL}"
+            echo "- KEYS URL: ${KEYS_URL}"
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Summarize git ref test source
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        shell: bash
+        env:
+          VERSION: ${{ github.event.inputs.version }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          {
+            echo "# Git ref test source"
+            echo "- Version: ${VERSION}"
+            echo "- Packaging version: ${{ 
steps.validate.outputs.packaging_version }}"
+            echo "- Git ref: ${GIT_REF}"
+            echo "- Mode: git_ref_test"
+            echo "- Archive source: git archive (recursive with submodules)"
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Upload verified source release
+        uses: actions/upload-artifact@v4
+        with:
+          name: ${{ steps.validate.outputs.artifact_name }}
+          retention-days: ${{ env.LOG_RETENTION_DAYS }}
+          if-no-files-found: error
+          path: verified-source/*
+
+  build-packages:
+    name: ${{ matrix.platform.target_os }}-${{ matrix.platform.target_arch 
}}-${{ matrix.platform.package_type }}
+    needs: verify-source-release
+    runs-on: ${{ matrix.platform.runner }}
+    timeout-minutes: 30
+    container:
+      image: ${{ matrix.platform.build_container_image }}
+      options: >-
+        --user root
+        --hostname cdw
+        --shm-size=2gb
+        -v /usr/share:/host_usr_share
+        -v /usr/local:/host_usr_local
+        -v /opt:/host_opt
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          # RPM Package
+          - {target_os: rocky8, target_arch: x86_64, package_type: rpm, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky8-latest}
+          - {target_os: rocky8, target_arch: arm64, package_type: rpm, runner: 
ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky8-latest}
+          - {target_os: rocky9, target_arch: x86_64, package_type: rpm, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky9-latest}
+          - {target_os: rocky9, target_arch: arm64, package_type: rpm, runner: 
ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky9-latest}
+          # DEB Package
+          - {target_os: ubuntu22.04, target_arch: x86_64, package_type: deb, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu22.04-latest}
+          - {target_os: ubuntu22.04, target_arch: arm64, package_type: deb, 
runner: ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu22.04-latest}
+          - {target_os: ubuntu24.04, target_arch: x86_64, package_type: deb, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest}
+          - {target_os: ubuntu24.04, target_arch: arm64, package_type: deb, 
runner: ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest}
+
+    steps:
+      - name: Free disk space
+        shell: bash
+        run: |
+          set -euo pipefail
+          rm -rf /host_opt/hostedtoolcache || true
+          rm -rf /host_usr_local/lib/android || true
+          rm -rf /host_usr_share/dotnet || true
+          rm -rf /host_opt/ghc || true
+          rm -rf /host_usr_local/.ghcup || true
+          rm -rf /host_usr_share/swift || true
+          rm -rf /host_usr_local/share/powershell || true
+          rm -rf /host_usr_local/share/chromium || true
+          rm -rf /host_usr_share/miniconda || true
+          rm -rf /host_opt/az || true
+          rm -rf /host_usr_share/sbt || true
+          df -h /
+
+      - name: Initialize build container
+        shell: bash
+        run: |
+          set -euo pipefail
+          su - gpadmin -c "/tmp/init_system.sh"
+
+      - name: Download verified source release
+        uses: actions/download-artifact@v4
+        with:
+          name: ${{ needs.verify-source-release.outputs.artifact_name }}
+          path: ${{ github.workspace }}/verified-source
+
+      - name: Extract source release
+        id: extract
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ 
needs.verify-source-release.outputs.source_tarball_name }}
+        run: |
+          set -euo pipefail
+          
tarball_path="${GITHUB_WORKSPACE}/verified-source/${SOURCE_TARBALL_NAME}"
+          source_root_name="$(tar -tzf "${tarball_path}" | awk -F/ 'NR == 1 { 
first = $1 } END { if (NR == 0) exit 1; print first }')"
+          
+          # Extract to a temporary location first
+          tar -xzf "${tarball_path}" -C "${GITHUB_WORKSPACE}"
+          
+          # Create cloudberry directory and move extracted content there
+          # This ensures the unittest script can find ${SRC_DIR}/../cloudberry
+          mkdir -p "${GITHUB_WORKSPACE}/cloudberry"
+          mv "${GITHUB_WORKSPACE}/${source_root_name}"/* 
"${GITHUB_WORKSPACE}/cloudberry/"
+          mv "${GITHUB_WORKSPACE}/${source_root_name}"/.[!.]* 
"${GITHUB_WORKSPACE}/cloudberry/" 2>/dev/null || true
+          rmdir "${GITHUB_WORKSPACE}/${source_root_name}"
+          
+          source_dir="${GITHUB_WORKSPACE}/cloudberry"
+          chown gpadmin:gpadmin "${GITHUB_WORKSPACE}"
+          chown -R gpadmin:gpadmin "${source_dir}"
+          echo "source_dir=${source_dir}" >> "$GITHUB_OUTPUT"
+
+      - name: Build Cloudberry from verified source release
+        shell: bash
+        env:
+          SOURCE_DIR: ${{ steps.extract.outputs.source_dir }}
+        run: |
+          set -euo pipefail
+          
+          # Create build-logs directory and set ownership
+          mkdir -p "${SOURCE_DIR}/build-logs"
+          chown -R gpadmin:gpadmin "${SOURCE_DIR}/build-logs"
+          
+          # Use /usr/local/cloudberry-db for all platforms
+          export BUILD_DESTINATION="/usr/local/cloudberry-db"
+          
+          chmod +x 
"${SOURCE_DIR}"/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh
+          chmod +x 
"${SOURCE_DIR}"/devops/build/automation/cloudberry/scripts/build-cloudberry.sh
+          
+          if ! su - gpadmin -c "cd ${SOURCE_DIR} && SRC_DIR=${SOURCE_DIR} 
BUILD_USER=github-actions BUILD_DESTINATION=${BUILD_DESTINATION} 
${SOURCE_DIR}/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh";
 then
+            echo "::error::Configure script failed"
+            exit 1
+          fi
+          
+          if ! su - gpadmin -c "cd ${SOURCE_DIR} && SRC_DIR=${SOURCE_DIR} 
BUILD_DESTINATION=${BUILD_DESTINATION} 
${SOURCE_DIR}/devops/build/automation/cloudberry/scripts/build-cloudberry.sh"; 
then
+            echo "::error::Build script failed"
+            exit 1
+          fi
+
+      - name: Run Apache Cloudberry unit tests
+        shell: bash
+        env:
+          SOURCE_DIR: ${{ steps.extract.outputs.source_dir }}
+        run: |
+          set -euo pipefail
+          
+          # Use /usr/local/cloudberry-db for all platforms
+          export BUILD_DESTINATION="/usr/local/cloudberry-db"
+          
+          chmod +x 
"${SOURCE_DIR}"/devops/build/automation/cloudberry/scripts/unittest-cloudberry.sh
+          if ! su - gpadmin -c "cd ${SOURCE_DIR} && SRC_DIR=${SOURCE_DIR} 
BUILD_DESTINATION=${BUILD_DESTINATION} 
${SOURCE_DIR}/devops/build/automation/cloudberry/scripts/unittest-cloudberry.sh";
 then
+            echo "::error::Unittest script failed"
+            exit 1
+          fi
+
+      - name: Build convenience package
+        shell: bash
+        env:
+          RELEASE_VERSION: ${{ github.event.inputs.version }}
+          PACKAGING_VERSION: ${{ 
needs.verify-source-release.outputs.packaging_version }}
+          SOURCE_DIR: ${{ steps.extract.outputs.source_dir }}
+          PACKAGE_TYPE: ${{ matrix.platform.package_type }}
+          TARGET_OS: ${{ matrix.platform.target_os }}
+          TARGET_ARCH: ${{ matrix.platform.target_arch }}
+        run: |
+          set -euo pipefail
+          artifact_dir="${GITHUB_WORKSPACE}/package-artifacts"
+          mkdir -p "${artifact_dir}"
+
+          # For DEB: Clean debian directory and let build-deb.sh copy from 
/usr/local/cloudberry-db
+          if [[ "${PACKAGE_TYPE}" == "deb" ]]; then
+            rm -rf "${SOURCE_DIR}/debian"
+          fi
+
+          su - gpadmin -c "
+            set -euo pipefail
+            cd '${SOURCE_DIR}'
+            export BUILD_DESTINATION='/usr/local/cloudberry-db'
+            if [[ '${PACKAGE_TYPE}' == 'deb' ]]; then
+              '${SOURCE_DIR}/devops/build/packaging/deb/build-deb.sh' -v 
'${PACKAGING_VERSION}'
+            else
+              '${SOURCE_DIR}/devops/build/packaging/rpm/build-rpm.sh' -v 
'${PACKAGING_VERSION}' -r '1'
+            fi
+          "
+
+          if [[ "${PACKAGE_TYPE}" == "deb" ]]; then
+            find "$(dirname "${SOURCE_DIR}")" -maxdepth 1 -type f -name 
'*.deb' \
+              -exec cp -f {} "${artifact_dir}/" \;
+            primary_pattern='*.deb'
+          else
+            # Copy RPM packages excluding debuginfo and debugsource packages
+            find /home/gpadmin/rpmbuild/RPMS -type f -name '*.rpm' \
+              ! -name '*-debuginfo-*.rpm' \
+              ! -name '*-debugsource-*.rpm' \
+              -exec cp -f {} "${artifact_dir}/" \;
+            primary_pattern='*.rpm'
+          fi
+
+          shopt -s nullglob
+          packages=("${artifact_dir}"/${primary_pattern})
+          if (( ${#packages[@]} == 0 )); then
+            echo "::error::No ${PACKAGE_TYPE} packages were collected for 
${TARGET_OS}/${TARGET_ARCH}"
+            exit 1
+          fi
+
+          # Generate SHA512 checksums for all packages
+          for package in "${packages[@]}"; do
+            (
+              cd "${artifact_dir}"
+              sha512sum "$(basename "${package}")" > "$(basename 
"${package}").sha512"
+            )
+          done
+
+          {
+            echo "# ${TARGET_OS}/${TARGET_ARCH}"
+            echo "- Package type: ${PACKAGE_TYPE}"
+            echo "- Release version: ${RELEASE_VERSION}"
+            echo "- Packaging version: ${PACKAGING_VERSION}"
+            echo "- Container image: ${{ matrix.platform.build_container_image 
}}"
+            echo "- Packages and checksums:"
+            for package in "${packages[@]}"; do
+              pkg_name="$(basename "${package}")"
+              echo "  - ${pkg_name}"
+              echo "  - ${pkg_name}.sha512"
+            done
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Upload convenience package artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          name: packages-${{ matrix.platform.target_os }}-${{ 
matrix.platform.target_arch }}-${{ matrix.platform.package_type }}
+          retention-days: ${{ env.LOG_RETENTION_DAYS }}
+          if-no-files-found: error
+          path: package-artifacts/
+
+  package-install-test:
+    name: ${{ matrix.platform.label }}-install-test
+    needs: [build-packages]
+    runs-on: ${{ matrix.platform.runner }}
+    timeout-minutes: 30

Review Comment:
   `package-install-test` job has `timeout-minutes: 30`. Even if installation 
is quick, this job also runs a `gpdemo` smoke test plus package manager 
operations, and similar install/test jobs in this repo use much higher 
timeouts. Consider increasing the timeout to avoid flaky cancellations under 
load.
   



##########
.github/workflows/package-convenience-binaries.yml:
##########
@@ -0,0 +1,657 @@
+# --------------------------------------------------------------------
+#
+# 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.
+#
+# --------------------------------------------------------------------
+# GitHub Actions Workflow: Apache Cloudberry Convenience Package Build
+# --------------------------------------------------------------------
+# Description:
+#
+#   This workflow manually builds convenience DEB/RPM packages from an
+#   ASF-approved Apache Cloudberry source release tarball.
+#
+# Workflow Overview:
+# 1. **Verify Source Release Job**:
+#    - Validates version and Apache download URLs.
+#    - Downloads source tarball, .asc, and .sha512.
+#    - Imports the project KEYS file and verifies signature and checksum.
+#    - Publishes the verified source release as an internal workflow artifact.
+#
+# 2. **Build Package Job (Matrix)**:
+#    - Downloads the verified source release artifact.
+#    - Builds Cloudberry inside the target Cloudberry Docker build image.
+#    - Produces DEB/RPM packages and matching .sha512 checksum files.
+#    - Uploads per-platform package artifacts.
+#
+# Scope:
+# - Intended for official Apache Cloudberry source releases managed by the
+#   release manager.
+# - Supports an official source release mode and a temporary git ref test mode.
+# - Produces convenience binaries only; detached .asc signatures for DEB/RPM
+#   remain a release manager local step.
+# --------------------------------------------------------------------
+
+name: Apache Cloudberry Convenience Package Build
+
+on:
+  workflow_dispatch:
+    inputs:
+      source_mode:
+        description: 'Source mode: official release validation or git ref test'
+        required: true
+        default: official_source_release
+        type: choice
+        options:
+          - official_source_release
+          - git_ref_test
+
+      # ============================================================
+      # Fields for official_source_release mode
+      # ============================================================
+      version:
+        description: '[Required for all modes] Release version, e.g. 
2.2.0-incubating'
+        required: true
+        type: string
+      source_url:
+        description: '[official_source_release only] Apache source tarball URL 
from downloads.apache.org'
+        required: false
+        type: string
+      source_asc_url:
+        description: '[official_source_release only] Detached GPG signature 
URL for the source tarball (.asc)'
+        required: false
+        type: string
+      source_sha512_url:
+        description: '[official_source_release only] SHA-512 checksum URL for 
the source tarball (.sha512)'
+        required: false
+        type: string
+
+      # ============================================================
+      # Fields for git_ref_test mode
+      # ============================================================
+      git_ref:
+        description: '[git_ref_test only] Git tag or commit SHA to archive as 
source input'
+        required: false
+        type: string
+
+permissions:
+  contents: read
+
+concurrency:
+  group: package-build-${{ github.ref }}-${{ inputs.version }}
+  cancel-in-progress: true
+
+env:
+  LOG_RETENTION_DAYS: 14
+  KEYS_URL: https://downloads.apache.org/incubator/cloudberry/KEYS
+
+jobs:
+  verify-source-release:
+    name: Verify source release
+    runs-on: ubuntu-24.04
+    timeout-minutes: 30
+    outputs:
+      source_tarball_name: ${{ steps.validate.outputs.source_tarball_name }}
+      artifact_name: ${{ steps.validate.outputs.artifact_name }}
+      packaging_version: ${{ steps.validate.outputs.packaging_version }}
+    container:
+      image: apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest
+      options: >-
+        --user root
+        --hostname cdw
+
+    steps:
+      - name: Initialize verification container
+        shell: bash
+        run: |
+          set -euo pipefail
+          su - gpadmin -c "/tmp/init_system.sh"
+
+      - name: Validate manual inputs
+        id: validate
+        shell: bash
+        env:
+          SOURCE_MODE: ${{ github.event.inputs.source_mode }}
+          VERSION: ${{ github.event.inputs.version }}
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          set -euo pipefail
+
+          if [[ -z "$VERSION" ]]; then
+            echo "::error::version must not be empty"
+            exit 1
+          fi
+
+          source_tarball_name="apache-cloudberry-${VERSION}-src.tar.gz"
+          artifact_name="verified-source-release-${VERSION}"
+          packaging_version="${VERSION%-incubating}"
+
+          if [[ -z "$packaging_version" ]]; then
+            echo "::error::Unable to derive packaging version from 
version=${VERSION}"
+            exit 1
+          fi
+
+          if [[ "$SOURCE_MODE" == "official_source_release" ]]; then
+            validate_apache_url() {
+              local value="$1"
+              local label="$2"
+              local prefix="https://downloads.apache.org/incubator/cloudberry/";
+              if [[ -z "$value" ]]; then
+                echo "::error::${label} must not be empty in 
official_source_release mode"
+                exit 1
+              fi
+              if [[ "$value" != "$prefix"* ]]; then
+                echo "::error::${label} must use downloads.apache.org"
+                exit 1
+              fi
+            }
+
+            validate_apache_url "$SOURCE_URL" "source_url"
+            validate_apache_url "$SOURCE_ASC_URL" "source_asc_url"
+            validate_apache_url "$SOURCE_SHA512_URL" "source_sha512_url"
+
+            if [[ "$SOURCE_URL" != *"/${source_tarball_name}" ]]; then
+              echo "::error::source_url must end with /${source_tarball_name}"
+              exit 1
+            fi
+            if [[ "$SOURCE_ASC_URL" != *"/${source_tarball_name}.asc" ]]; then
+              echo "::error::source_asc_url must end with 
/${source_tarball_name}.asc"
+              exit 1
+            fi
+            if [[ "$SOURCE_SHA512_URL" != *"/${source_tarball_name}.sha512" 
]]; then
+              echo "::error::source_sha512_url must end with 
/${source_tarball_name}.sha512"
+              exit 1
+            fi
+          elif [[ "$SOURCE_MODE" == "git_ref_test" ]]; then
+            if [[ -z "$GIT_REF" ]]; then
+              echo "::error::git_ref must not be empty in git_ref_test mode"
+              exit 1
+            fi
+            artifact_name="git-ref-source-${VERSION}"
+          else
+            echo "::error::Unsupported source_mode: $SOURCE_MODE"
+            exit 1
+          fi
+
+          echo "source_tarball_name=${source_tarball_name}" >> "$GITHUB_OUTPUT"
+          echo "artifact_name=${artifact_name}" >> "$GITHUB_OUTPUT"
+          echo "packaging_version=${packaging_version}" >> "$GITHUB_OUTPUT"
+
+      - name: Download source release and verification files
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+        run: |
+          set -euo pipefail
+          mkdir -p verified-source
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}" \
+            "$SOURCE_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}.asc" \
+            "$SOURCE_ASC_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}.sha512" \
+            "$SOURCE_SHA512_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/KEYS" \
+            "${KEYS_URL}"
+
+      - name: Verify source release signature and checksum
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+        run: |
+          set -euo pipefail
+          export GNUPGHOME="${RUNNER_TEMP}/gnupg"
+          mkdir -p "${GNUPGHOME}"
+          chmod 700 "${GNUPGHOME}"
+
+          gpg --import verified-source/KEYS
+          gpg --verify \
+            "verified-source/${SOURCE_TARBALL_NAME}.asc" \
+            "verified-source/${SOURCE_TARBALL_NAME}"
+
+          (
+            cd verified-source
+            sha512sum -c "${SOURCE_TARBALL_NAME}.sha512"
+          )
+
+          tar -tzf "verified-source/${SOURCE_TARBALL_NAME}" >/dev/null
+
+      - name: Checkout git ref for test packaging
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ github.event.inputs.git_ref }}
+          fetch-depth: 1
+          submodules: recursive
+          path: repo
+
+      - name: Prepare git ref source artifact
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+          VERSION: ${{ github.event.inputs.version }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          set -euo pipefail
+          mkdir -p verified-source
+          source_root_name="apache-cloudberry-${VERSION}"
+          tmp_dir="$(mktemp -d)"
+          trap 'rm -rf "$tmp_dir"' EXIT
+
+          git -C repo archive --format=tar --prefix="${source_root_name}/" 
HEAD | tar -x -C "${tmp_dir}"
+
+          if [[ -s repo/.gitmodules ]]; then
+            git -C repo submodule foreach --recursive --quiet "
+              fullpath=\"\$toplevel/\$sm_path\"
+              destpath=\"${tmp_dir}/${source_root_name}/\$sm_path\"
+              mkdir -p \"\$destpath\"
+              git -C \"\$fullpath\" archive --format=tar 
--prefix=\"\$sm_path/\" HEAD | tar -x -C \"${tmp_dir}/${source_root_name}\"
+            "
+          fi
+
+          tar -C "${tmp_dir}" -czf "verified-source/${SOURCE_TARBALL_NAME}" 
"${source_root_name}"
+          tar -tzf "verified-source/${SOURCE_TARBALL_NAME}" >/dev/null
+
+      - name: Summarize verified source release
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          VERSION: ${{ github.event.inputs.version }}
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+        run: |
+          {
+            echo "# Verified source release"
+            echo "- Version: ${VERSION}"
+            echo "- Packaging version: ${{ 
steps.validate.outputs.packaging_version }}"
+            echo "- Source URL: ${SOURCE_URL}"
+            echo "- Signature URL: ${SOURCE_ASC_URL}"
+            echo "- Checksum URL: ${SOURCE_SHA512_URL}"
+            echo "- KEYS URL: ${KEYS_URL}"
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Summarize git ref test source
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        shell: bash
+        env:
+          VERSION: ${{ github.event.inputs.version }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          {
+            echo "# Git ref test source"
+            echo "- Version: ${VERSION}"
+            echo "- Packaging version: ${{ 
steps.validate.outputs.packaging_version }}"
+            echo "- Git ref: ${GIT_REF}"
+            echo "- Mode: git_ref_test"
+            echo "- Archive source: git archive (recursive with submodules)"
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Upload verified source release
+        uses: actions/upload-artifact@v4
+        with:
+          name: ${{ steps.validate.outputs.artifact_name }}
+          retention-days: ${{ env.LOG_RETENTION_DAYS }}
+          if-no-files-found: error
+          path: verified-source/*
+
+  build-packages:
+    name: ${{ matrix.platform.target_os }}-${{ matrix.platform.target_arch 
}}-${{ matrix.platform.package_type }}
+    needs: verify-source-release
+    runs-on: ${{ matrix.platform.runner }}
+    timeout-minutes: 30
+    container:
+      image: ${{ matrix.platform.build_container_image }}
+      options: >-
+        --user root
+        --hostname cdw
+        --shm-size=2gb
+        -v /usr/share:/host_usr_share
+        -v /usr/local:/host_usr_local
+        -v /opt:/host_opt
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          # RPM Package
+          - {target_os: rocky8, target_arch: x86_64, package_type: rpm, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky8-latest}
+          - {target_os: rocky8, target_arch: arm64, package_type: rpm, runner: 
ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky8-latest}
+          - {target_os: rocky9, target_arch: x86_64, package_type: rpm, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky9-latest}
+          - {target_os: rocky9, target_arch: arm64, package_type: rpm, runner: 
ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-rocky9-latest}
+          # DEB Package
+          - {target_os: ubuntu22.04, target_arch: x86_64, package_type: deb, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu22.04-latest}
+          - {target_os: ubuntu22.04, target_arch: arm64, package_type: deb, 
runner: ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu22.04-latest}
+          - {target_os: ubuntu24.04, target_arch: x86_64, package_type: deb, 
runner: ubuntu-24.04, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest}
+          - {target_os: ubuntu24.04, target_arch: arm64, package_type: deb, 
runner: ubuntu-24.04-arm, build_container_image: 
apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest}
+
+    steps:
+      - name: Free disk space
+        shell: bash
+        run: |
+          set -euo pipefail
+          rm -rf /host_opt/hostedtoolcache || true
+          rm -rf /host_usr_local/lib/android || true
+          rm -rf /host_usr_share/dotnet || true
+          rm -rf /host_opt/ghc || true
+          rm -rf /host_usr_local/.ghcup || true
+          rm -rf /host_usr_share/swift || true
+          rm -rf /host_usr_local/share/powershell || true
+          rm -rf /host_usr_local/share/chromium || true
+          rm -rf /host_usr_share/miniconda || true
+          rm -rf /host_opt/az || true
+          rm -rf /host_usr_share/sbt || true
+          df -h /
+
+      - name: Initialize build container
+        shell: bash
+        run: |
+          set -euo pipefail
+          su - gpadmin -c "/tmp/init_system.sh"
+
+      - name: Download verified source release
+        uses: actions/download-artifact@v4
+        with:
+          name: ${{ needs.verify-source-release.outputs.artifact_name }}
+          path: ${{ github.workspace }}/verified-source
+
+      - name: Extract source release
+        id: extract
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ 
needs.verify-source-release.outputs.source_tarball_name }}
+        run: |
+          set -euo pipefail
+          
tarball_path="${GITHUB_WORKSPACE}/verified-source/${SOURCE_TARBALL_NAME}"
+          source_root_name="$(tar -tzf "${tarball_path}" | awk -F/ 'NR == 1 { 
first = $1 } END { if (NR == 0) exit 1; print first }')"
+          
+          # Extract to a temporary location first
+          tar -xzf "${tarball_path}" -C "${GITHUB_WORKSPACE}"
+          
+          # Create cloudberry directory and move extracted content there
+          # This ensures the unittest script can find ${SRC_DIR}/../cloudberry
+          mkdir -p "${GITHUB_WORKSPACE}/cloudberry"
+          mv "${GITHUB_WORKSPACE}/${source_root_name}"/* 
"${GITHUB_WORKSPACE}/cloudberry/"
+          mv "${GITHUB_WORKSPACE}/${source_root_name}"/.[!.]* 
"${GITHUB_WORKSPACE}/cloudberry/" 2>/dev/null || true
+          rmdir "${GITHUB_WORKSPACE}/${source_root_name}"
+          
+          source_dir="${GITHUB_WORKSPACE}/cloudberry"
+          chown gpadmin:gpadmin "${GITHUB_WORKSPACE}"
+          chown -R gpadmin:gpadmin "${source_dir}"
+          echo "source_dir=${source_dir}" >> "$GITHUB_OUTPUT"
+
+      - name: Build Cloudberry from verified source release
+        shell: bash
+        env:
+          SOURCE_DIR: ${{ steps.extract.outputs.source_dir }}
+        run: |
+          set -euo pipefail
+          
+          # Create build-logs directory and set ownership
+          mkdir -p "${SOURCE_DIR}/build-logs"
+          chown -R gpadmin:gpadmin "${SOURCE_DIR}/build-logs"
+          
+          # Use /usr/local/cloudberry-db for all platforms
+          export BUILD_DESTINATION="/usr/local/cloudberry-db"
+          
+          chmod +x 
"${SOURCE_DIR}"/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh
+          chmod +x 
"${SOURCE_DIR}"/devops/build/automation/cloudberry/scripts/build-cloudberry.sh
+          
+          if ! su - gpadmin -c "cd ${SOURCE_DIR} && SRC_DIR=${SOURCE_DIR} 
BUILD_USER=github-actions BUILD_DESTINATION=${BUILD_DESTINATION} 
${SOURCE_DIR}/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh";
 then
+            echo "::error::Configure script failed"
+            exit 1
+          fi
+          
+          if ! su - gpadmin -c "cd ${SOURCE_DIR} && SRC_DIR=${SOURCE_DIR} 
BUILD_DESTINATION=${BUILD_DESTINATION} 
${SOURCE_DIR}/devops/build/automation/cloudberry/scripts/build-cloudberry.sh"; 
then
+            echo "::error::Build script failed"
+            exit 1
+          fi
+
+      - name: Run Apache Cloudberry unit tests
+        shell: bash
+        env:
+          SOURCE_DIR: ${{ steps.extract.outputs.source_dir }}
+        run: |
+          set -euo pipefail
+          
+          # Use /usr/local/cloudberry-db for all platforms
+          export BUILD_DESTINATION="/usr/local/cloudberry-db"
+          
+          chmod +x 
"${SOURCE_DIR}"/devops/build/automation/cloudberry/scripts/unittest-cloudberry.sh
+          if ! su - gpadmin -c "cd ${SOURCE_DIR} && SRC_DIR=${SOURCE_DIR} 
BUILD_DESTINATION=${BUILD_DESTINATION} 
${SOURCE_DIR}/devops/build/automation/cloudberry/scripts/unittest-cloudberry.sh";
 then
+            echo "::error::Unittest script failed"
+            exit 1
+          fi
+
+      - name: Build convenience package
+        shell: bash
+        env:
+          RELEASE_VERSION: ${{ github.event.inputs.version }}
+          PACKAGING_VERSION: ${{ 
needs.verify-source-release.outputs.packaging_version }}
+          SOURCE_DIR: ${{ steps.extract.outputs.source_dir }}
+          PACKAGE_TYPE: ${{ matrix.platform.package_type }}
+          TARGET_OS: ${{ matrix.platform.target_os }}
+          TARGET_ARCH: ${{ matrix.platform.target_arch }}
+        run: |
+          set -euo pipefail
+          artifact_dir="${GITHUB_WORKSPACE}/package-artifacts"
+          mkdir -p "${artifact_dir}"
+
+          # For DEB: Clean debian directory and let build-deb.sh copy from 
/usr/local/cloudberry-db
+          if [[ "${PACKAGE_TYPE}" == "deb" ]]; then
+            rm -rf "${SOURCE_DIR}/debian"
+          fi
+
+          su - gpadmin -c "
+            set -euo pipefail
+            cd '${SOURCE_DIR}'
+            export BUILD_DESTINATION='/usr/local/cloudberry-db'
+            if [[ '${PACKAGE_TYPE}' == 'deb' ]]; then
+              '${SOURCE_DIR}/devops/build/packaging/deb/build-deb.sh' -v 
'${PACKAGING_VERSION}'
+            else
+              '${SOURCE_DIR}/devops/build/packaging/rpm/build-rpm.sh' -v 
'${PACKAGING_VERSION}' -r '1'
+            fi
+          "
+
+          if [[ "${PACKAGE_TYPE}" == "deb" ]]; then
+            find "$(dirname "${SOURCE_DIR}")" -maxdepth 1 -type f -name 
'*.deb' \
+              -exec cp -f {} "${artifact_dir}/" \;
+            primary_pattern='*.deb'
+          else
+            # Copy RPM packages excluding debuginfo and debugsource packages
+            find /home/gpadmin/rpmbuild/RPMS -type f -name '*.rpm' \
+              ! -name '*-debuginfo-*.rpm' \
+              ! -name '*-debugsource-*.rpm' \
+              -exec cp -f {} "${artifact_dir}/" \;
+            primary_pattern='*.rpm'
+          fi
+
+          shopt -s nullglob
+          packages=("${artifact_dir}"/${primary_pattern})
+          if (( ${#packages[@]} == 0 )); then
+            echo "::error::No ${PACKAGE_TYPE} packages were collected for 
${TARGET_OS}/${TARGET_ARCH}"
+            exit 1
+          fi
+
+          # Generate SHA512 checksums for all packages
+          for package in "${packages[@]}"; do
+            (
+              cd "${artifact_dir}"
+              sha512sum "$(basename "${package}")" > "$(basename 
"${package}").sha512"
+            )
+          done
+
+          {
+            echo "# ${TARGET_OS}/${TARGET_ARCH}"
+            echo "- Package type: ${PACKAGE_TYPE}"
+            echo "- Release version: ${RELEASE_VERSION}"
+            echo "- Packaging version: ${PACKAGING_VERSION}"
+            echo "- Container image: ${{ matrix.platform.build_container_image 
}}"
+            echo "- Packages and checksums:"
+            for package in "${packages[@]}"; do
+              pkg_name="$(basename "${package}")"
+              echo "  - ${pkg_name}"
+              echo "  - ${pkg_name}.sha512"
+            done
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Upload convenience package artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          name: packages-${{ matrix.platform.target_os }}-${{ 
matrix.platform.target_arch }}-${{ matrix.platform.package_type }}
+          retention-days: ${{ env.LOG_RETENTION_DAYS }}
+          if-no-files-found: error
+          path: package-artifacts/
+
+  package-install-test:
+    name: ${{ matrix.platform.label }}-install-test
+    needs: [build-packages]
+    runs-on: ${{ matrix.platform.runner }}
+    timeout-minutes: 30
+    container:
+      image: ${{ matrix.platform.test_container_image }}
+      options: >-
+        --user root
+        --hostname cdw
+        --shm-size=2gb
+        -v /usr/share:/host_usr_share
+        -v /usr/local:/host_usr_local
+        -v /opt:/host_opt
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          # RPM Package
+          - {target_os: rocky8, target_arch: x86_64, package_type: rpm, 
runner: ubuntu-24.04, test_container_image: 
apache/incubator-cloudberry:cbdb-test-rocky8-latest, label: rocky8-x86_64-rpm}
+          - {target_os: rocky8, target_arch: arm64, package_type: rpm, runner: 
ubuntu-24.04-arm, test_container_image: 
apache/incubator-cloudberry:cbdb-test-rocky8-latest, label: rocky8-arm64-rpm}
+          - {target_os: rocky9, target_arch: x86_64, package_type: rpm, 
runner: ubuntu-24.04, test_container_image: 
apache/incubator-cloudberry:cbdb-test-rocky9-latest, label: rocky9-x86_64-rpm}
+          - {target_os: rocky9, target_arch: arm64, package_type: rpm, runner: 
ubuntu-24.04-arm, test_container_image: 
apache/incubator-cloudberry:cbdb-test-rocky9-latest, label: rocky9-arm64-rpm}
+          # DEB Package
+          - {target_os: ubuntu22.04, target_arch: x86_64, package_type: deb, 
runner: ubuntu-24.04, test_container_image: 
apache/incubator-cloudberry:cbdb-test-ubuntu22.04-latest, label: 
ubuntu22.04-x86_64-deb}
+          - {target_os: ubuntu22.04, target_arch: arm64, package_type: deb, 
runner: ubuntu-24.04-arm, test_container_image: 
apache/incubator-cloudberry:cbdb-test-ubuntu22.04-latest, label: 
ubuntu22.04-arm64-deb}
+          - {target_os: ubuntu24.04, target_arch: x86_64, package_type: deb, 
runner: ubuntu-24.04, test_container_image: 
apache/incubator-cloudberry:cbdb-test-ubuntu24.04-latest, label: 
ubuntu24.04-x86_64-deb}
+          - {target_os: ubuntu24.04, target_arch: arm64, package_type: deb, 
runner: ubuntu-24.04-arm, test_container_image: 
apache/incubator-cloudberry:cbdb-test-ubuntu24.04-latest, label: 
ubuntu24.04-arm64-deb}
+
+    steps:
+      - name: Initialize test container
+        shell: bash
+        run: |
+          set -euo pipefail
+          su - gpadmin -c "/tmp/init_system.sh"
+
+      - name: Download package artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: packages-${{ matrix.platform.target_os }}-${{ 
matrix.platform.target_arch }}-${{ matrix.platform.package_type }}
+          path: ${{ github.workspace }}/package-artifacts
+
+      - name: Verify package checksum
+        id: verify-package
+        shell: bash
+        env:
+          PACKAGE_TYPE: ${{ matrix.platform.package_type }}
+        run: |
+          set -euo pipefail
+          artifact_dir="${GITHUB_WORKSPACE}/package-artifacts"
+          
+          # Find package file using ls
+          if [[ "${PACKAGE_TYPE}" == "deb" ]]; then
+            package_file=$(ls "${artifact_dir}"/*.deb 2>/dev/null | head -n 1)
+          else
+            package_file=$(ls "${artifact_dir}"/*.rpm 2>/dev/null | head -n 1)
+          fi

Review Comment:
   With `set -euo pipefail`, `package_file=$(ls ... | head -n 1)` will cause 
the step to exit immediately if no matching files exist (because `ls` returns 
non-zero and `pipefail` propagates it), so the later explicit "No package 
found" error handling won’t run. Use a glob/array or `find`-based selection 
that doesn’t rely on `ls` failing, so you can emit the intended diagnostic when 
artifacts are missing.
   



##########
.github/workflows/package-convenience-binaries.yml:
##########
@@ -0,0 +1,657 @@
+# --------------------------------------------------------------------
+#
+# 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.
+#
+# --------------------------------------------------------------------
+# GitHub Actions Workflow: Apache Cloudberry Convenience Package Build
+# --------------------------------------------------------------------
+# Description:
+#
+#   This workflow manually builds convenience DEB/RPM packages from an
+#   ASF-approved Apache Cloudberry source release tarball.
+#
+# Workflow Overview:
+# 1. **Verify Source Release Job**:
+#    - Validates version and Apache download URLs.
+#    - Downloads source tarball, .asc, and .sha512.
+#    - Imports the project KEYS file and verifies signature and checksum.
+#    - Publishes the verified source release as an internal workflow artifact.
+#
+# 2. **Build Package Job (Matrix)**:
+#    - Downloads the verified source release artifact.
+#    - Builds Cloudberry inside the target Cloudberry Docker build image.
+#    - Produces DEB/RPM packages and matching .sha512 checksum files.
+#    - Uploads per-platform package artifacts.
+#
+# Scope:
+# - Intended for official Apache Cloudberry source releases managed by the
+#   release manager.
+# - Supports an official source release mode and a temporary git ref test mode.
+# - Produces convenience binaries only; detached .asc signatures for DEB/RPM
+#   remain a release manager local step.
+# --------------------------------------------------------------------
+
+name: Apache Cloudberry Convenience Package Build
+
+on:
+  workflow_dispatch:
+    inputs:
+      source_mode:
+        description: 'Source mode: official release validation or git ref test'
+        required: true
+        default: official_source_release
+        type: choice
+        options:
+          - official_source_release
+          - git_ref_test
+
+      # ============================================================
+      # Fields for official_source_release mode
+      # ============================================================
+      version:
+        description: '[Required for all modes] Release version, e.g. 
2.2.0-incubating'
+        required: true
+        type: string
+      source_url:
+        description: '[official_source_release only] Apache source tarball URL 
from downloads.apache.org'
+        required: false
+        type: string
+      source_asc_url:
+        description: '[official_source_release only] Detached GPG signature 
URL for the source tarball (.asc)'
+        required: false
+        type: string
+      source_sha512_url:
+        description: '[official_source_release only] SHA-512 checksum URL for 
the source tarball (.sha512)'
+        required: false
+        type: string
+
+      # ============================================================
+      # Fields for git_ref_test mode
+      # ============================================================
+      git_ref:
+        description: '[git_ref_test only] Git tag or commit SHA to archive as 
source input'
+        required: false
+        type: string
+
+permissions:
+  contents: read
+
+concurrency:
+  group: package-build-${{ github.ref }}-${{ inputs.version }}
+  cancel-in-progress: true
+
+env:
+  LOG_RETENTION_DAYS: 14
+  KEYS_URL: https://downloads.apache.org/incubator/cloudberry/KEYS
+
+jobs:
+  verify-source-release:
+    name: Verify source release
+    runs-on: ubuntu-24.04
+    timeout-minutes: 30
+    outputs:
+      source_tarball_name: ${{ steps.validate.outputs.source_tarball_name }}
+      artifact_name: ${{ steps.validate.outputs.artifact_name }}
+      packaging_version: ${{ steps.validate.outputs.packaging_version }}
+    container:
+      image: apache/incubator-cloudberry:cbdb-build-ubuntu24.04-latest
+      options: >-
+        --user root
+        --hostname cdw
+
+    steps:
+      - name: Initialize verification container
+        shell: bash
+        run: |
+          set -euo pipefail
+          su - gpadmin -c "/tmp/init_system.sh"
+
+      - name: Validate manual inputs
+        id: validate
+        shell: bash
+        env:
+          SOURCE_MODE: ${{ github.event.inputs.source_mode }}
+          VERSION: ${{ github.event.inputs.version }}
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          set -euo pipefail
+
+          if [[ -z "$VERSION" ]]; then
+            echo "::error::version must not be empty"
+            exit 1
+          fi
+
+          source_tarball_name="apache-cloudberry-${VERSION}-src.tar.gz"
+          artifact_name="verified-source-release-${VERSION}"
+          packaging_version="${VERSION%-incubating}"
+
+          if [[ -z "$packaging_version" ]]; then
+            echo "::error::Unable to derive packaging version from 
version=${VERSION}"
+            exit 1
+          fi
+
+          if [[ "$SOURCE_MODE" == "official_source_release" ]]; then
+            validate_apache_url() {
+              local value="$1"
+              local label="$2"
+              local prefix="https://downloads.apache.org/incubator/cloudberry/";
+              if [[ -z "$value" ]]; then
+                echo "::error::${label} must not be empty in 
official_source_release mode"
+                exit 1
+              fi
+              if [[ "$value" != "$prefix"* ]]; then
+                echo "::error::${label} must use downloads.apache.org"
+                exit 1
+              fi
+            }
+
+            validate_apache_url "$SOURCE_URL" "source_url"
+            validate_apache_url "$SOURCE_ASC_URL" "source_asc_url"
+            validate_apache_url "$SOURCE_SHA512_URL" "source_sha512_url"
+
+            if [[ "$SOURCE_URL" != *"/${source_tarball_name}" ]]; then
+              echo "::error::source_url must end with /${source_tarball_name}"
+              exit 1
+            fi
+            if [[ "$SOURCE_ASC_URL" != *"/${source_tarball_name}.asc" ]]; then
+              echo "::error::source_asc_url must end with 
/${source_tarball_name}.asc"
+              exit 1
+            fi
+            if [[ "$SOURCE_SHA512_URL" != *"/${source_tarball_name}.sha512" 
]]; then
+              echo "::error::source_sha512_url must end with 
/${source_tarball_name}.sha512"
+              exit 1
+            fi
+          elif [[ "$SOURCE_MODE" == "git_ref_test" ]]; then
+            if [[ -z "$GIT_REF" ]]; then
+              echo "::error::git_ref must not be empty in git_ref_test mode"
+              exit 1
+            fi
+            artifact_name="git-ref-source-${VERSION}"
+          else
+            echo "::error::Unsupported source_mode: $SOURCE_MODE"
+            exit 1
+          fi
+
+          echo "source_tarball_name=${source_tarball_name}" >> "$GITHUB_OUTPUT"
+          echo "artifact_name=${artifact_name}" >> "$GITHUB_OUTPUT"
+          echo "packaging_version=${packaging_version}" >> "$GITHUB_OUTPUT"
+
+      - name: Download source release and verification files
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+        run: |
+          set -euo pipefail
+          mkdir -p verified-source
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}" \
+            "$SOURCE_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}.asc" \
+            "$SOURCE_ASC_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/${SOURCE_TARBALL_NAME}.sha512" \
+            "$SOURCE_SHA512_URL"
+          curl --fail --location --silent --show-error \
+            --output "verified-source/KEYS" \
+            "${KEYS_URL}"
+
+      - name: Verify source release signature and checksum
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+        run: |
+          set -euo pipefail
+          export GNUPGHOME="${RUNNER_TEMP}/gnupg"
+          mkdir -p "${GNUPGHOME}"
+          chmod 700 "${GNUPGHOME}"
+
+          gpg --import verified-source/KEYS
+          gpg --verify \
+            "verified-source/${SOURCE_TARBALL_NAME}.asc" \
+            "verified-source/${SOURCE_TARBALL_NAME}"
+
+          (
+            cd verified-source
+            sha512sum -c "${SOURCE_TARBALL_NAME}.sha512"
+          )
+
+          tar -tzf "verified-source/${SOURCE_TARBALL_NAME}" >/dev/null
+
+      - name: Checkout git ref for test packaging
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ github.event.inputs.git_ref }}
+          fetch-depth: 1
+          submodules: recursive
+          path: repo
+
+      - name: Prepare git ref source artifact
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        shell: bash
+        env:
+          SOURCE_TARBALL_NAME: ${{ steps.validate.outputs.source_tarball_name 
}}
+          VERSION: ${{ github.event.inputs.version }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          set -euo pipefail
+          mkdir -p verified-source
+          source_root_name="apache-cloudberry-${VERSION}"
+          tmp_dir="$(mktemp -d)"
+          trap 'rm -rf "$tmp_dir"' EXIT
+
+          git -C repo archive --format=tar --prefix="${source_root_name}/" 
HEAD | tar -x -C "${tmp_dir}"
+
+          if [[ -s repo/.gitmodules ]]; then
+            git -C repo submodule foreach --recursive --quiet "
+              fullpath=\"\$toplevel/\$sm_path\"
+              destpath=\"${tmp_dir}/${source_root_name}/\$sm_path\"
+              mkdir -p \"\$destpath\"
+              git -C \"\$fullpath\" archive --format=tar 
--prefix=\"\$sm_path/\" HEAD | tar -x -C \"${tmp_dir}/${source_root_name}\"
+            "
+          fi
+
+          tar -C "${tmp_dir}" -czf "verified-source/${SOURCE_TARBALL_NAME}" 
"${source_root_name}"
+          tar -tzf "verified-source/${SOURCE_TARBALL_NAME}" >/dev/null
+
+      - name: Summarize verified source release
+        if: github.event.inputs.source_mode == 'official_source_release'
+        shell: bash
+        env:
+          VERSION: ${{ github.event.inputs.version }}
+          SOURCE_URL: ${{ github.event.inputs.source_url }}
+          SOURCE_ASC_URL: ${{ github.event.inputs.source_asc_url }}
+          SOURCE_SHA512_URL: ${{ github.event.inputs.source_sha512_url }}
+        run: |
+          {
+            echo "# Verified source release"
+            echo "- Version: ${VERSION}"
+            echo "- Packaging version: ${{ 
steps.validate.outputs.packaging_version }}"
+            echo "- Source URL: ${SOURCE_URL}"
+            echo "- Signature URL: ${SOURCE_ASC_URL}"
+            echo "- Checksum URL: ${SOURCE_SHA512_URL}"
+            echo "- KEYS URL: ${KEYS_URL}"
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Summarize git ref test source
+        if: github.event.inputs.source_mode == 'git_ref_test'
+        shell: bash
+        env:
+          VERSION: ${{ github.event.inputs.version }}
+          GIT_REF: ${{ github.event.inputs.git_ref }}
+        run: |
+          {
+            echo "# Git ref test source"
+            echo "- Version: ${VERSION}"
+            echo "- Packaging version: ${{ 
steps.validate.outputs.packaging_version }}"
+            echo "- Git ref: ${GIT_REF}"
+            echo "- Mode: git_ref_test"
+            echo "- Archive source: git archive (recursive with submodules)"
+          } >> "$GITHUB_STEP_SUMMARY"
+
+      - name: Upload verified source release
+        uses: actions/upload-artifact@v4
+        with:
+          name: ${{ steps.validate.outputs.artifact_name }}
+          retention-days: ${{ env.LOG_RETENTION_DAYS }}
+          if-no-files-found: error
+          path: verified-source/*
+
+  build-packages:
+    name: ${{ matrix.platform.target_os }}-${{ matrix.platform.target_arch 
}}-${{ matrix.platform.package_type }}
+    needs: verify-source-release
+    runs-on: ${{ matrix.platform.runner }}
+    timeout-minutes: 30

Review Comment:
   `build-packages` job has `timeout-minutes: 30`, but this job builds 
Cloudberry from source, runs unit tests, and then packages (similar jobs 
elsewhere in this repo use 120 minutes). This timeout is likely to kill 
successful builds; consider increasing it to match existing build workflows.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to