From 67a2a3fc5db2a75a7f34b646370022eaba241423 Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Wed, 10 Jun 2026 14:36:36 -0700 Subject: [PATCH 01/13] ci: speed up RPM debuginfo package builds --- cspell.config.yaml | 1 + package/README.md | 2 +- package/rpm/xrpld.spec | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cspell.config.yaml b/cspell.config.yaml index 0d38c4be7b0..8273df6c986 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -301,6 +301,7 @@ words: - txs - ubsan - UBSAN + - ufdio - umant - unacquired - unambiguity diff --git a/package/README.md b/package/README.md index 63c2ab88fc0..76720e990b1 100644 --- a/package/README.md +++ b/package/README.md @@ -135,7 +135,7 @@ into the staging area, and invokes the platform build tool. 1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory. 2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`. -3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. The spec uses manual `install` commands to place files. +3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "xrpld_release ..."`. The spec uses manual `install` commands to place files, disables `dwz`, and writes uncompressed RPM payloads while generating debuginfo packages. 4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm` ### DEB diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec index 5595fd0d8da..4778db7cc49 100644 --- a/package/rpm/xrpld.spec +++ b/package/rpm/xrpld.spec @@ -11,6 +11,8 @@ BuildRequires: systemd-rpm-macros %undefine _debugsource_packages %debug_package +%global _binary_payload w.ufdio +%global _find_debuginfo_dwz_opts %{nil} %build_mtime_policy clamp_to_source_date_epoch From 57bcf6ea412c6818d78bb15d589618c29d5a9e7b Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Wed, 17 Jun 2026 08:23:07 -0700 Subject: [PATCH 02/13] fix: align RPM package release define --- package/README.md | 28 +++++++++++++++++++++++++++- package/build_pkg.sh | 2 +- package/rpm/xrpld.spec | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/package/README.md b/package/README.md index 76720e990b1..8e7b549d405 100644 --- a/package/README.md +++ b/package/README.md @@ -118,6 +118,28 @@ defaults. Run `./package/build_pkg.sh --help` for the same table: | `--pkg-release N` | `PKG_RELEASE` | `1` | package release number | | `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime | reproducibility timestamp | +`PKG_VERSION` is the canonical input version for both package formats. +`build_pkg.sh` splits pre-release versions such as `3.2.0-b1` or +`3.2.0-rc1` into a base version and suffix, then converts the suffix separator +from `-` to `~` for package metadata so pre-releases sort before the final +release. `PKG_RELEASE` remains the package release number. For RPM builds, +those two inputs are passed to `xrpld.spec` as `xrpld_version` and +`pkg_release`; `pkg_release` is derived directly from `PKG_RELEASE` and is not +a separate user-facing input. + +With `PKG_RELEASE=1`, the package metadata becomes: + +| Input version | RPM version/release | Debian version | +| ------------- | --------------------- | -------------- | +| `3.2.0` | `3.2.0-1%{?dist}` | `3.2.0-1` | +| `3.2.0-b1` | `3.2.0~b1-1%{?dist}` | `3.2.0~b1-1` | +| `3.2.0-rc1` | `3.2.0~rc1-1%{?dist}` | `3.2.0~rc1-1` | + +The RPM path intentionally uses `~` in `Version`, matching the Debian +pre-release ordering convention, so RPM filenames/NVRs begin with forms like +`xrpld-3.2.0~b1-...` and `xrpld-3.2.0~rc1-...` instead of encoding +pre-releases with an older `0..` RPM `Release` value. + The package format (`deb` or `rpm`) is inferred from the host's package manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). Hosts without one of those fail early. @@ -135,7 +157,11 @@ into the staging area, and invokes the platform build tool. 1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory. 2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`. -3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "xrpld_release ..."`. The spec uses manual `install` commands to place files, disables `dwz`, and writes uncompressed RPM payloads while generating debuginfo packages. +3. Converts `PKG_VERSION` to the RPM `xrpld_version` define, passes + `PKG_RELEASE` through as the `pkg_release` define, then runs + `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. + The spec uses manual `install` commands to place files, disables `dwz`, and + writes uncompressed RPM payloads while generating debuginfo packages. 4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm` ### DEB diff --git a/package/build_pkg.sh b/package/build_pkg.sh index e2ec8fee3d5..ea505fe70dd 100755 --- a/package/build_pkg.sh +++ b/package/build_pkg.sh @@ -165,7 +165,7 @@ build_rpm() { rpmbuild -bb \ --define "_topdir ${topdir}" \ --define "xrpld_version ${rpm_version}" \ - --define "xrpld_release ${PKG_RELEASE}" \ + --define "pkg_release ${PKG_RELEASE}" \ "${topdir}/SPECS/xrpld.spec" } diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec index 4778db7cc49..90e9d956a4f 100644 --- a/package/rpm/xrpld.spec +++ b/package/rpm/xrpld.spec @@ -1,6 +1,6 @@ Name: xrpld Version: %{xrpld_version} -Release: %{xrpld_release}%{?dist} +Release: %{pkg_release}%{?dist} Summary: XRP Ledger daemon License: ISC From be39621d6cd7dc053bbe0b05a5298e50dd63590b Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Wed, 17 Jun 2026 11:19:52 -0700 Subject: [PATCH 03/13] refactor: clarify package version naming --- .github/workflows/reusable-package.yml | 2 +- cmake/XrplPackaging.cmake | 2 +- package/README.md | 145 ++++++++++++++----------- package/build_pkg.sh | 94 +++++++--------- 4 files changed, 125 insertions(+), 118 deletions(-) diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 0e3f6570066..87bfa5d864f 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -82,7 +82,7 @@ jobs: - name: Build package env: - PKG_VERSION: ${{ needs.generate-version.outputs.version }} + XRPLD_VERSION: ${{ needs.generate-version.outputs.version }} PKG_RELEASE: ${{ inputs.pkg_release }} run: ./package/build_pkg.sh diff --git a/cmake/XrplPackaging.cmake b/cmake/XrplPackaging.cmake index fe885c200c8..b7746b2365e 100644 --- a/cmake/XrplPackaging.cmake +++ b/cmake/XrplPackaging.cmake @@ -28,7 +28,7 @@ endif() set(package_env SRC_DIR=${CMAKE_SOURCE_DIR} BUILD_DIR=${CMAKE_BINARY_DIR} - PKG_VERSION=${xrpld_version} + XRPLD_VERSION=${xrpld_version} PKG_RELEASE=${pkg_release} ) diff --git a/package/README.md b/package/README.md index 8e7b549d405..a3bf19395e8 100644 --- a/package/README.md +++ b/package/README.md @@ -6,36 +6,35 @@ This directory contains all files needed to build RPM and Debian packages for `x ``` package/ - build_pkg.sh Staging and build script (called by CMake targets and CI) + build_pkg.sh Staging and build script (called by the CMake `package` target and CI) rpm/ - xrpld.spec RPM spec (xrpld_version/pkg_release passed via rpmbuild --define) - debian/ Debian control files (control, rules, install, links, conffiles, ...) + xrpld.spec RPM spec + debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, source/format) shared/ xrpld.service systemd unit file (used by both RPM and DEB) xrpld.sysusers sysusers.d config (used by both RPM and DEB) xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB) xrpld.logrotate logrotate config (installed to /etc/logrotate.d/xrpld) + 50-xrpld.preset systemd preset (RPM only; DEB enables the unit via dh_installsystemd) ``` ## Prerequisites Packaging targets and their container images are declared in [`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json) -inside `package_configs` configurations. Today only -`linux/amd64` is emitted. The package format -(deb or rpm) is inferred at build time from the container's package manager -(`apt-get` -> deb, `dnf`/`yum` -> rpm). The image tag is composed as -`ghcr.io/xrplf/xrpld/packaging-:sha-` — -the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in -`linux.json` and both CI and local builds pick up the new image with no -workflow edits. - -| Package type | Image (derived from `linux.json`) | Tool required | -| ------------ | ---------------------------------------------------- | --------------------------------------------------------------- | -| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-` | `rpmbuild` | -| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` | - -To print the exact image tags for the current `linux.json`: +under `package_configs`, one entry per distro. Today only `linux/amd64` is +emitted. Each entry pins its full container image in an `image` field; to move +to a new image, edit that field and both CI and local builds pick it up. The +package format (deb or rpm) is inferred at build time from the container's +package manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). + +| Package type | Image (`package_configs.[].image` in `linux.json`) | Tools required | +| ------------ | ---------------------------------------------------------- | ---------------------------------------------- | +| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-` | `rpmbuild` | +| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-` | `dpkg-buildpackage`, `debhelper-compat (= 13)` | + +To print the full packaging matrix (artifact names and images) for the current +`linux.json`: ```bash ./.github/scripts/strategy-matrix/generate.py --packaging @@ -46,12 +45,13 @@ To print the exact image tags for the current `linux.json`: ### Via CI Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call -`reusable-strategy-matrix.yml` with `mode: packaging` to generate the matrix of -`{artifact_name, os}` entries, then fan out to -`reusable-package.yml` per entry. That workflow downloads the pre-built `xrpld` -binary artifact, detects the package format from the container, and calls -`build_pkg.sh` directly — no CMake configure or build step is needed inside -the packaging job. +`reusable-package.yml`. That workflow generates its own packaging matrix from +`package_configs` in `linux.json` (via `generate.py --packaging`) and fans out +one job per distro. Each job downloads the pre-built `xrpld` binary artifact, +runs in that distro's container (so the package format follows from the +container's package manager), and calls `build_pkg.sh` with `XRPLD_VERSION` and +`PKG_RELEASE` supplied via env — no CMake configure or build step is needed +inside the packaging job. ### Locally (mirrors CI) @@ -60,22 +60,20 @@ inside the same container CI uses. The image tag is derived from `linux.json` so you don't need to hardcode a SHA. ```bash -# From the repo root. Pick any image flagged with `"package": true` in -# linux.json; the package format is inferred from the container's package -# manager. Example for the rpm-producing image: -IMAGE=$(jq -r ' - .os | map(select(.package == true))[0] | - "ghcr.io/xrplf/ci/\(.distro_name)-\(.distro_version):\(.compiler_name)-\(.compiler_version)-sha-\(.image_sha)" -' .github/scripts/strategy-matrix/linux.json) - -VERSION=2.4.0-local +# From the repo root. Each distro's container image is the `image` field of its +# package_configs entry in linux.json; the package format is inferred from the +# container's package manager. Example for the rpm-producing image (use +# .package_configs.debian[0].image for the deb image): +IMAGE=$(jq -r '.package_configs.rhel[0].image' .github/scripts/strategy-matrix/linux.json) + +XRPLD_VERSION=2.4.0-local PKG_RELEASE=1 docker run --rm \ -v "$(pwd):/src" \ -w /src \ "$IMAGE" \ - ./package/build_pkg.sh --pkg-version "$VERSION" --pkg-release "$PKG_RELEASE" + ./package/build_pkg.sh --xrpld-version "$XRPLD_VERSION" --pkg-release "$PKG_RELEASE" # Output: # build/debbuild/*.deb (DEB + dbgsym .ddeb) @@ -91,19 +89,27 @@ needed, but the host toolchain replaces the pinned CI image: ```bash cmake \ -Dxrpld=ON \ - -Dxrpld_version=2.4.0-local \ + -Dpkg_release=1 \ -Dtests=OFF \ .. cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL ``` -The `cmake/XrplPackaging.cmake` module defines the target only if at least one -of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then infers the -package format from the host's package manager. The packaging script installs -to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of +The `cmake/XrplPackaging.cmake` module defines the `package` target only if at +least one of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then +infers the package format from the host's package manager. The packaging script +installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of `CMAKE_INSTALL_PREFIX`. +The version is not a CMake input on this path: `cmake/XrplVersion.cmake` reads +it from `src/libxrpl/protocol/BuildInfo.cpp` into `xrpld_version`, and +`XrplPackaging.cmake` exports that to `build_pkg.sh` as `XRPLD_VERSION` (a +`-Dxrpld_version=...` on the command line is overwritten and has no effect). The +release defaults to 1 and is overridable with `-Dpkg_release=N`. To package a +custom version string, use the script or container path above with +`--xrpld-version`. + ## How `build_pkg.sh` works `build_pkg.sh` accepts long-form flags, each of which can also be set via an @@ -114,26 +120,37 @@ defaults. Run `./package/build_pkg.sh --help` for the same table: | -------------------------- | ------------------- | ----------------------------- | ----------------------------------- | | `--src-dir DIR` | `SRC_DIR` | `$PWD` | repo root | | `--build-dir DIR` | `BUILD_DIR` | `$PWD/build` | directory holding pre-built `xrpld` | -| `--pkg-version STR` | `PKG_VERSION` | parsed from `xrpld --version` | version string, e.g. `3.2.0-b1` | -| `--pkg-release N` | `PKG_RELEASE` | `1` | package release number | +| `--xrpld-version STR` | `XRPLD_VERSION` | parsed from `xrpld --version` | `xrpld` version, e.g. `3.2.0-b1` | +| `--pkg-release N` | `PKG_RELEASE` | `1` | package release iteration | | `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime | reproducibility timestamp | -`PKG_VERSION` is the canonical input version for both package formats. -`build_pkg.sh` splits pre-release versions such as `3.2.0-b1` or -`3.2.0-rc1` into a base version and suffix, then converts the suffix separator -from `-` to `~` for package metadata so pre-releases sort before the final -release. `PKG_RELEASE` remains the package release number. For RPM builds, -those two inputs are passed to `xrpld.spec` as `xrpld_version` and -`pkg_release`; `pkg_release` is derived directly from `PKG_RELEASE` and is not -a separate user-facing input. +`XRPLD_VERSION` is the `build_pkg.sh` input for the `xrpld` software version in +both package formats. + +`build_pkg.sh` converts pre-release versions such as `3.2.0-b1` or +`3.2.0-rc1` from `-` to `~` for package metadata so pre-releases sort before +the final release. Internally, the script keeps the raw software version as +`XRPLD_VERSION` and the package-metadata form as `pkg_version`. Versions with +more than one `-` are rejected so the pre-release suffix remains a single +token. + +`PKG_RELEASE` is a different value: the package release iteration for that +`xrpld` version. RPM receives `pkg_version` and `PKG_RELEASE` as separate +`Version` and `Release` values because the spec format requires that; DEB +combines them as `deb_version` in `debian/changelog`. With `PKG_RELEASE=1`, the package metadata becomes: -| Input version | RPM version/release | Debian version | -| ------------- | --------------------- | -------------- | -| `3.2.0` | `3.2.0-1%{?dist}` | `3.2.0-1` | -| `3.2.0-b1` | `3.2.0~b1-1%{?dist}` | `3.2.0~b1-1` | -| `3.2.0-rc1` | `3.2.0~rc1-1%{?dist}` | `3.2.0~rc1-1` | +| Input version | RPM version/release | Debian version | +| ------------------ | ---------------------------- | -------------------- | +| `3.2.0` | `3.2.0-1%{?dist}` | `3.2.0-1` | +| `3.2.0-b0+abc1234` | `3.2.0~b0+abc1234-1%{?dist}` | `3.2.0~b0+abc1234-1` | +| `3.2.0-b1` | `3.2.0~b1-1%{?dist}` | `3.2.0~b1-1` | +| `3.2.0-rc1` | `3.2.0~rc1-1%{?dist}` | `3.2.0~rc1-1` | + +For Debian changelogs, final releases use the `stable` distribution, +`b0` builds, including `b0+metadata`, use `develop`, and other pre-releases use +`unstable`. The RPM path intentionally uses `~` in `Version`, matching the Debian pre-release ordering convention, so RPM filenames/NVRs begin with forms like @@ -145,7 +162,7 @@ manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). Hosts without one of those fail early. Flags are for explicit invocation; environment variables are intended for -CMake/systemd/CI integration. The CI workflow and the CMake `package` target +CMake/CI integration. The CI workflow and the CMake `package` target both invoke `build_pkg.sh` with no flags, configuring it entirely via env (see `cmake/XrplPackaging.cmake`). @@ -157,9 +174,8 @@ into the staging area, and invokes the platform build tool. 1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory. 2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`. -3. Converts `PKG_VERSION` to the RPM `xrpld_version` define, passes - `PKG_RELEASE` through as the `pkg_release` define, then runs - `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. +3. Runs `rpmbuild -bb`, passing `pkg_version` as `xrpld_version` and + `PKG_RELEASE` as `pkg_release`. The spec uses manual `install` commands to place files, disables `dwz`, and writes uncompressed RPM payloads while generating debuginfo packages. 4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm` @@ -170,8 +186,9 @@ into the staging area, and invokes the platform build tool. 2. Stages the binary, configs, `README.md`, and `LICENSE.md`. 3. Copies `package/debian/` control files into `debbuild/source/debian/`. 4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically. -5. Generates a minimal `debian/changelog` (pre-release versions use `~` instead of `-`). -6. Runs `dpkg-buildpackage -b --no-sign`. `debian/rules` uses manual `install` commands. +5. Generates a minimal `debian/changelog` using + `deb_version`. +6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands. 7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package) ## Post-build verification @@ -187,11 +204,13 @@ rpm -qlp rpmbuild/RPMS/x86_64/*.rpm ## Reproducibility -The following environment variables improve build reproducibility. They are not -set automatically by `build_pkg.sh`; set them manually if needed: +`build_pkg.sh` already defaults `SOURCE_DATE_EPOCH` to the latest git commit +time and exports it (override with `--source-date-epoch` / `SOURCE_DATE_EPOCH`); +the RPM spec clamps file modification times to it via `%build_mtime_policy`. The remaining +variables below further improve reproducibility but are _not_ set by the +script — export them yourself if needed: ```bash -export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) export TZ=UTC export LC_ALL=C.UTF-8 export GZIP=-n diff --git a/package/build_pkg.sh b/package/build_pkg.sh index ea505fe70dd..64bce76c126 100755 --- a/package/build_pkg.sh +++ b/package/build_pkg.sh @@ -4,18 +4,18 @@ set -euo pipefail # Build an RPM or Debian package from a pre-built xrpld binary. # # Flags override env vars; env vars override defaults. Env vars are intended -# for CMake/systemd/CI integration; flags are for explicit invocation. +# for CMake/CI integration; flags are for explicit invocation. usage() { cat <<'EOF' Usage: build_pkg.sh [options] Options (each can also be set via the env var shown): - --src-dir DIR repo root [SRC_DIR; default: $PWD] - --build-dir DIR directory holding xrpld [BUILD_DIR; default: $PWD/build] - --pkg-version STR version, e.g. 3.2.0-b1 [PKG_VERSION; default: parsed from xrpld --version] - --pkg-release N package release number [PKG_RELEASE; default: 1] - --source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; default: latest git commit ctime] + --src-dir DIR repo root [SRC_DIR; default: $PWD] + --build-dir DIR directory holding xrpld [BUILD_DIR; default: $PWD/build] + --xrpld-version STR xrpld version, e.g. 3.2.0-b1 [XRPLD_VERSION; default: parsed from xrpld --version] + --pkg-release N package release iteration [PKG_RELEASE; default: 1] + --source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; default: latest git commit ctime] -h, --help show this help and exit EOF } @@ -30,8 +30,8 @@ need_arg() { # Seed from env. CLI parsing below overrides these directly. SRC_DIR="${SRC_DIR:-}" BUILD_DIR="${BUILD_DIR:-}" -PKG_VERSION="${PKG_VERSION:-}" -PKG_RELEASE="${PKG_RELEASE:-}" +XRPLD_VERSION="${XRPLD_VERSION:-}" +PKG_RELEASE="${PKG_RELEASE:-1}" SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}" while [[ $# -gt 0 ]]; do @@ -46,9 +46,9 @@ while [[ $# -gt 0 ]]; do BUILD_DIR="$2" shift 2 ;; - --pkg-version) + --xrpld-version) need_arg "$@" - PKG_VERSION="$2" + XRPLD_VERSION="$2" shift 2 ;; --pkg-release) @@ -75,18 +75,31 @@ done SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)" BUILD_DIR="$(cd "${BUILD_DIR:-${PWD}/build}" && pwd)" -PKG_RELEASE="${PKG_RELEASE:-1}" -if [[ -z "${PKG_VERSION}" ]]; then - PKG_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3; exit}')" +if [[ -z "${XRPLD_VERSION}" ]]; then + XRPLD_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3; exit}')" +fi + +if [[ -z "${XRPLD_VERSION}" ]]; then + echo "XRPLD_VERSION is empty (not provided and could not be derived)." >&2 + exit 1 fi -if [[ -z "${PKG_VERSION}" ]]; then - echo "PKG_VERSION is empty (not provided and could not be derived)." >&2 +# A pre-release suffix must be a single token. RPM Version cannot contain '-', +# and this script uses one '-' as the version/release separator for both package +# formats, so versions carrying a second '-' ("beta-1", "rc1-15-gabc123") do +# not fit the convention. Reject those up front rather than mangling them later. +if [[ "${XRPLD_VERSION}" == *-*-* ]]; then + echo "build_pkg.sh: multi-segment version XRPLD_VERSION='${XRPLD_VERSION}'." >&2 + echo "Use a single-token pre-release like 3.2.0-b1 or 3.2.0-rc2." >&2 exit 1 fi -VERSION="${PKG_VERSION}" +# The version as the package formats consume it: identical to XRPLD_VERSION +# except a pre-release uses '~' (3.2.0-b1 -> 3.2.0~b1), which also sorts before +# the final 3.2.0; a no-op for a final release. Lowercase = derived internally, +# not an input (cf. pkg_type). +pkg_version="${XRPLD_VERSION/-/~}" if command -v apt-get >/dev/null 2>&1; then pkg_type=deb @@ -108,23 +121,6 @@ fi export SOURCE_DATE_EPOCH CHANGELOG_DATE="$(date -u -R -d "@$SOURCE_DATE_EPOCH")" -# Split VERSION at the first '-' into base and optional pre-release suffix. -# Examples: "3.2.0" -> ("3.2.0", ""); "3.2.0-b1" -> ("3.2.0", "b1"). -VER_BASE="${VERSION%%-*}" -VER_SUFFIX="${VERSION#*-}" -[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX="" - -# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). Neither an -# RPM Version nor a Debian upstream version may contain '-' (it's the NVR / -# version-revision separator), and the convention here is single-token -# suffixes like b1 or rc2. Fail early with a clear message rather than letting -# the package tooling blow up or silently mangle dashes. -if [[ "${VER_SUFFIX}" == *-* ]]; then - echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2 - echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2 - exit 1 -fi - SHARED="${SRC_DIR}/package/shared" DEBIAN_DIR="${SRC_DIR}/package/debian" @@ -154,17 +150,10 @@ build_rpm() { cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec" stage_common "${topdir}/SOURCES" - # Pre-releases use the modern rpm '~' convention (rpm >= 4.10): the suffix - # goes in Version (e.g. 3.2.0~b1), which rpmvercmp sorts *before* the final - # 3.2.0 — identical semantics to Debian's '~'. Release is just the package - # release number. This replaces the older "0.." Release - # hack and keeps the RPM and DEB version strings symmetric. - local rpm_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}" - set -x rpmbuild -bb \ --define "_topdir ${topdir}" \ - --define "xrpld_version ${rpm_version}" \ + --define "xrpld_version ${pkg_version}" \ --define "pkg_release ${PKG_RELEASE}" \ "${topdir}/SPECS/xrpld.spec" } @@ -182,23 +171,22 @@ build_deb() { cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles" cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate" - # Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0. - local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}" + # Full Debian version: [~
]-.
+    local deb_version="${pkg_version}-${PKG_RELEASE}"
 
-    # Derive release channel from the version suffix:
-    #   (none)      -> stable    (tagged release)
-    #   b0          -> develop   (develop-branch build)
-    #   b, rc -> unstable  (pre-release)
+    # Release channel drives the changelog distribution (RPM has no equivalent):
+    #   3.2.0 -> stable, *-b0[+metadata] -> develop,
+    #   any other pre-release -> unstable.
     local deb_distribution
-    case "${VER_SUFFIX}" in
-        "") deb_distribution="stable" ;;
-        b0) deb_distribution="develop" ;;
-        *) deb_distribution="unstable" ;;
+    case "${XRPLD_VERSION}" in
+        *-b0 | *-b0+*) deb_distribution="develop" ;;
+        *-*) deb_distribution="unstable" ;;
+        *) deb_distribution="stable" ;;
     esac
 
     cat >"${staging}/debian/changelog" <  ${CHANGELOG_DATE}
 EOF

From 828245cd28e7171a4148fced24ba4d1d2621b5af Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Wed, 17 Jun 2026 12:49:00 -0700
Subject: [PATCH 04/13] fix: avoid restarting xrpld on rpm upgrade

---
 package/rpm/xrpld.spec | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec
index 90e9d956a4f..f13ca0034e5 100644
--- a/package/rpm/xrpld.spec
+++ b/package/rpm/xrpld.spec
@@ -64,7 +64,7 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
 %systemd_preun xrpld.service
 
 %postun
-%systemd_postun_with_restart xrpld.service
+%systemd_postun xrpld.service
 
 %files
 %license %{_docdir}/%{name}/LICENSE.md

From 342d13ac607f1780daeace8479722a46312203fe Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Wed, 17 Jun 2026 16:48:20 -0700
Subject: [PATCH 05/13] fix: Avoid shell tilde expansion in package version

---
 package/build_pkg.sh | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index 64bce76c126..d97c485f8d7 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -99,7 +99,10 @@ fi
 # except a pre-release uses '~' (3.2.0-b1 -> 3.2.0~b1), which also sorts before
 # the final 3.2.0; a no-op for a final release. Lowercase = derived internally,
 # not an input (cf. pkg_type).
-pkg_version="${XRPLD_VERSION/-/~}"
+pkg_version="${XRPLD_VERSION}"
+if [[ "${XRPLD_VERSION}" == *-* ]]; then
+    pkg_version="${XRPLD_VERSION%%-*}~${XRPLD_VERSION#*-}"
+fi
 
 if command -v apt-get >/dev/null 2>&1; then
     pkg_type=deb

From 4e8266bea6203fe51377df6609985799c9edc012 Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Wed, 17 Jun 2026 16:48:50 -0700
Subject: [PATCH 06/13] fix: Clarify package version naming

---
 package/README.md      | 62 ++++++++++++++++++++++--------------------
 package/build_pkg.sh   | 28 +++++++++----------
 package/rpm/xrpld.spec |  2 +-
 3 files changed, 48 insertions(+), 44 deletions(-)

diff --git a/package/README.md b/package/README.md
index a3bf19395e8..0bc15ae8117 100644
--- a/package/README.md
+++ b/package/README.md
@@ -116,28 +116,29 @@ custom version string, use the script or container path above with
 environment variable. Flags override env vars; env vars override the built-in
 defaults. Run `./package/build_pkg.sh --help` for the same table:
 
-| Flag                       | Env var             | Default                       | Purpose                             |
-| -------------------------- | ------------------- | ----------------------------- | ----------------------------------- |
-| `--src-dir DIR`            | `SRC_DIR`           | `$PWD`                        | repo root                           |
-| `--build-dir DIR`          | `BUILD_DIR`         | `$PWD/build`                  | directory holding pre-built `xrpld` |
-| `--xrpld-version STR`      | `XRPLD_VERSION`     | parsed from `xrpld --version` | `xrpld` version, e.g. `3.2.0-b1`    |
-| `--pkg-release N`          | `PKG_RELEASE`       | `1`                           | package release iteration           |
-| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime       | reproducibility timestamp           |
+| Flag                       | Env var             | Default/source                                           | Purpose                             |
+| -------------------------- | ------------------- | -------------------------------------------------------- | ----------------------------------- |
+| `--src-dir DIR`            | `SRC_DIR`           | `$PWD`                                                   | repo root                           |
+| `--build-dir DIR`          | `BUILD_DIR`         | `$PWD/build`                                             | directory holding pre-built `xrpld` |
+| `--xrpld-version STR`      | `XRPLD_VERSION`     | set by CMake/CI; fallback: parsed from `xrpld --version` | `xrpld` version, e.g. `3.2.0-b1`    |
+| `--pkg-release N`          | `PKG_RELEASE`       | `1`                                                      | package release iteration           |
+| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime; fallback: current time          | reproducibility timestamp           |
 
 `XRPLD_VERSION` is the `build_pkg.sh` input for the `xrpld` software version in
-both package formats.
+both package formats. CMake derives it as `xrpld_version` and exports it as
+`XRPLD_VERSION`; direct script invocations may pass it explicitly or let the
+script fall back to parsing `xrpld --version`.
 
 `build_pkg.sh` converts pre-release versions such as `3.2.0-b1` or
 `3.2.0-rc1` from `-` to `~` for package metadata so pre-releases sort before
-the final release. Internally, the script keeps the raw software version as
-`XRPLD_VERSION` and the package-metadata form as `pkg_version`. Versions with
-more than one `-` are rejected so the pre-release suffix remains a single
-token.
+the final release. Versions with more than one `-` are rejected so the
+pre-release suffix remains a single token.
 
 `PKG_RELEASE` is a different value: the package release iteration for that
-`xrpld` version. RPM receives `pkg_version` and `PKG_RELEASE` as separate
-`Version` and `Release` values because the spec format requires that; DEB
-combines them as `deb_version` in `debian/changelog`.
+`xrpld` version. RPM receives the normalized version and `PKG_RELEASE` as
+separate `pkg_version` and `pkg_release` macros for its `Version` and `Release`
+values; DEB writes them as `${pkg_version}-${PKG_RELEASE}` in
+`debian/changelog`.
 
 With `PKG_RELEASE=1`, the package metadata becomes:
 
@@ -148,9 +149,10 @@ With `PKG_RELEASE=1`, the package metadata becomes:
 | `3.2.0-b1`         | `3.2.0~b1-1%{?dist}`         | `3.2.0~b1-1`         |
 | `3.2.0-rc1`        | `3.2.0~rc1-1%{?dist}`        | `3.2.0~rc1-1`        |
 
-For Debian changelogs, final releases use the `stable` distribution,
-`b0` builds, including `b0+metadata`, use `develop`, and other pre-releases use
-`unstable`.
+The Debian changelog's distribution field carries our apt repo _component_ (the
+suite/codename is assigned by the publishing infra at ingest, not here): final
+releases use `stable`, `b0` builds, including `b0+metadata`, use `develop`, and
+other pre-releases use `unstable`.
 
 The RPM path intentionally uses `~` in `Version`, matching the Debian
 pre-release ordering convention, so RPM filenames/NVRs begin with forms like
@@ -162,9 +164,11 @@ manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). Hosts without one of those
 fail early.
 
 Flags are for explicit invocation; environment variables are intended for
-CMake/CI integration. The CI workflow and the CMake `package` target
-both invoke `build_pkg.sh` with no flags, configuring it entirely via env
-(see `cmake/XrplPackaging.cmake`).
+CMake/CI integration. The CI workflow and the CMake `package` target both invoke
+`build_pkg.sh` with no flags; CMake supplies `SRC_DIR`, `BUILD_DIR`,
+`XRPLD_VERSION`, and `PKG_RELEASE` via env, while CI supplies `BUILD_DIR`,
+`XRPLD_VERSION`, and `PKG_RELEASE` via env and lets the script use defaults for
+the rest.
 
 It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
 `stage_common()` to copy the binary, config files, and shared support files
@@ -174,8 +178,8 @@ into the staging area, and invokes the platform build tool.
 
 1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
 2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
-3. Runs `rpmbuild -bb`, passing `pkg_version` as `xrpld_version` and
-   `PKG_RELEASE` as `pkg_release`.
+3. Runs `rpmbuild -bb`, passing the normalized version and `PKG_RELEASE` as the
+   `pkg_version` and `pkg_release` RPM macros.
    The spec uses manual `install` commands to place files, disables `dwz`, and
    writes uncompressed RPM payloads while generating debuginfo packages.
 4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
@@ -186,8 +190,7 @@ into the staging area, and invokes the platform build tool.
 2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
 3. Copies `package/debian/` control files into `debbuild/source/debian/`.
 4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
-5. Generates a minimal `debian/changelog` using
-   `deb_version`.
+5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`.
 6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands.
 7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
 
@@ -205,10 +208,11 @@ rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
 ## Reproducibility
 
 `build_pkg.sh` already defaults `SOURCE_DATE_EPOCH` to the latest git commit
-time and exports it (override with `--source-date-epoch` / `SOURCE_DATE_EPOCH`);
-the RPM spec clamps file modification times to it via `%build_mtime_policy`. The remaining
-variables below further improve reproducibility but are _not_ set by the
-script — export them yourself if needed:
+time, or the current time outside a git tree, and exports it (override with
+`--source-date-epoch` / `SOURCE_DATE_EPOCH`); the RPM spec clamps file
+modification times to it via `%build_mtime_policy`. The remaining variables
+below further improve reproducibility but are _not_ set by the script — export
+them yourself if needed:
 
 ```bash
 export TZ=UTC
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index d97c485f8d7..1d259caffd9 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -13,9 +13,9 @@ Usage: build_pkg.sh [options]
 Options (each can also be set via the env var shown):
   --src-dir DIR             repo root                     [SRC_DIR;           default: $PWD]
   --build-dir DIR           directory holding xrpld       [BUILD_DIR;         default: $PWD/build]
-  --xrpld-version STR       xrpld version, e.g. 3.2.0-b1  [XRPLD_VERSION;     default: parsed from xrpld --version]
+  --xrpld-version STR       xrpld version, e.g. 3.2.0-b1  [XRPLD_VERSION;     set by CMake/CI; fallback: xrpld --version]
   --pkg-release N           package release iteration     [PKG_RELEASE;       default: 1]
-  --source-date-epoch SECS  reproducibility timestamp     [SOURCE_DATE_EPOCH; default: latest git commit ctime]
+  --source-date-epoch SECS  reproducibility timestamp     [SOURCE_DATE_EPOCH; latest git ctime; fallback: current time]
   -h, --help                show this help and exit
 EOF
 }
@@ -156,7 +156,7 @@ build_rpm() {
     set -x
     rpmbuild -bb \
         --define "_topdir ${topdir}" \
-        --define "xrpld_version ${pkg_version}" \
+        --define "pkg_version ${pkg_version}" \
         --define "pkg_release ${PKG_RELEASE}" \
         "${topdir}/SPECS/xrpld.spec"
 }
@@ -174,21 +174,21 @@ build_deb() {
     cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
     cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
 
-    # Full Debian version: [~
]-.
-    local deb_version="${pkg_version}-${PKG_RELEASE}"
-
-    # Release channel drives the changelog distribution (RPM has no equivalent):
-    #   3.2.0 -> stable, *-b0[+metadata] -> develop,
-    #   any other pre-release -> unstable.
-    local deb_distribution
+    # The release channel selects our apt repo *component*. dpkg names the
+    # changelog's third field "distribution", but the suite/codename (noble,
+    # bookworm, ...) is assigned by the publishing infra at ingest; this
+    # suite-agnostic build only sets the component. RPM has no equivalent.
+    #   3.2.0 -> stable, *-b0[+metadata] -> develop, other pre-release -> unstable.
+    local deb_component
     case "${XRPLD_VERSION}" in
-        *-b0 | *-b0+*) deb_distribution="develop" ;;
-        *-*) deb_distribution="unstable" ;;
-        *) deb_distribution="stable" ;;
+        *-b0 | *-b0+*) deb_component="develop" ;;
+        *-*) deb_component="unstable" ;;
+        *) deb_component="stable" ;;
     esac
 
+    # Debian version is [~
]-.
     cat >"${staging}/debian/changelog" <  ${CHANGELOG_DATE}
diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec
index f13ca0034e5..ef27f293c32 100644
--- a/package/rpm/xrpld.spec
+++ b/package/rpm/xrpld.spec
@@ -1,5 +1,5 @@
 Name:     xrpld
-Version:  %{xrpld_version}
+Version:  %{pkg_version}
 Release:  %{pkg_release}%{?dist}
 Summary:  XRP Ledger daemon
 

From be0af3c10cbc9c56c8f5500840c429fdee98f4fc Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Thu, 18 Jun 2026 11:54:33 -0700
Subject: [PATCH 07/13] Address RPM packaging review feedback

---
 package/README.md      | 31 +++++++++++++++++++++++--------
 package/build_pkg.sh   |  8 +++++++-
 package/rpm/xrpld.spec |  9 +++++++++
 3 files changed, 39 insertions(+), 9 deletions(-)

diff --git a/package/README.md b/package/README.md
index 0bc15ae8117..6e4cd76317b 100644
--- a/package/README.md
+++ b/package/README.md
@@ -66,14 +66,14 @@ so you don't need to hardcode a SHA.
 # .package_configs.debian[0].image for the deb image):
 IMAGE=$(jq -r '.package_configs.rhel[0].image' .github/scripts/strategy-matrix/linux.json)
 
-XRPLD_VERSION=2.4.0-local
+XRPLD_VERSION=0.0.0-local
 PKG_RELEASE=1
 
 docker run --rm \
     -v "$(pwd):/src" \
     -w /src \
-    "$IMAGE" \
-    ./package/build_pkg.sh --xrpld-version "$XRPLD_VERSION" --pkg-release "$PKG_RELEASE"
+    "${IMAGE}" \
+    ./package/build_pkg.sh --xrpld-version "${XRPLD_VERSION}" --pkg-release "${PKG_RELEASE}"
 
 # Output:
 #   build/debbuild/*.deb         (DEB + dbgsym .ddeb)
@@ -134,9 +134,13 @@ script fall back to parsing `xrpld --version`.
 the final release. Versions with more than one `-` are rejected so the
 pre-release suffix remains a single token.
 
+`pkg_version` is the normalized package metadata version derived inside
+`build_pkg.sh` from `XRPLD_VERSION` (`-` pre-release separator converted to
+`~`). It is not a separate user input.
+
 `PKG_RELEASE` is a different value: the package release iteration for that
-`xrpld` version. RPM receives the normalized version and `PKG_RELEASE` as
-separate `pkg_version` and `pkg_release` macros for its `Version` and `Release`
+`xrpld` version. RPM receives the normalized `pkg_version` and `PKG_RELEASE` as
+the `pkg_version` and `pkg_release` macros for its `Version` and `Release`
 values; DEB writes them as `${pkg_version}-${PKG_RELEASE}` in
 `debian/changelog`.
 
@@ -178,19 +182,30 @@ into the staging area, and invokes the platform build tool.
 
 1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
 2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
-3. Runs `rpmbuild -bb`, passing the normalized version and `PKG_RELEASE` as the
-   `pkg_version` and `pkg_release` RPM macros.
+3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the
+   `pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro.
    The spec uses manual `install` commands to place files, disables `dwz`, and
    writes uncompressed RPM payloads while generating debuginfo packages.
 4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
 
+The uncompressed RPM payload setting is intentionally unconditional for
+generated RPMs. It trades larger RPM artifacts for much shorter package
+build/validation time, which keeps RPM package validation in the same rough time
+class as Debian package validation.
+
+RPM upgrades intentionally do not restart a running `xrpld` service. The spec
+uses `%systemd_postun`, matching Debian's `dh_installsystemd
+--no-stop-on-upgrade` behavior; operators pick up the new binary on the next
+service restart.
+
 ### DEB
 
 1. Creates a staging source tree at `debbuild/source/` inside the build directory.
 2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
 3. Copies `package/debian/` control files into `debbuild/source/debian/`.
 4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
-5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`.
+5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,
+   where `pkg_version` is derived from `XRPLD_VERSION`.
 6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands.
 7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
 
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index 1d259caffd9..210bc186305 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -77,7 +77,7 @@ SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
 BUILD_DIR="$(cd "${BUILD_DIR:-${PWD}/build}" && pwd)"
 
 if [[ -z "${XRPLD_VERSION}" ]]; then
-    XRPLD_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3; exit}')"
+    XRPLD_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR == 1 { print $3 }')"
 fi
 
 if [[ -z "${XRPLD_VERSION}" ]]; then
@@ -85,6 +85,12 @@ if [[ -z "${XRPLD_VERSION}" ]]; then
     exit 1
 fi
 
+if [[ "${XRPLD_VERSION}" == -* || "${XRPLD_VERSION}" == *- ]]; then
+    echo "build_pkg.sh: invalid XRPLD_VERSION='${XRPLD_VERSION}'." >&2
+    echo "Version cannot start or end with '-'." >&2
+    exit 1
+fi
+
 # A pre-release suffix must be a single token. RPM Version cannot contain '-',
 # and this script uses one '-' as the version/release separator for both package
 # formats, so versions carrying a second '-' ("beta-1", "rc1-15-gabc123") do
diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec
index ef27f293c32..a7741bb7e28 100644
--- a/package/rpm/xrpld.spec
+++ b/package/rpm/xrpld.spec
@@ -1,3 +1,11 @@
+%if "%{?pkg_version}" == ""
+%{error:pkg_version must be defined}
+%endif
+
+%if "%{?pkg_release}" == ""
+%{error:pkg_release must be defined}
+%endif
+
 Name:     xrpld
 Version:  %{pkg_version}
 Release:  %{pkg_release}%{?dist}
@@ -11,6 +19,7 @@ BuildRequires: systemd-rpm-macros
 
 %undefine _debugsource_packages
 %debug_package
+# Intentionally trade larger RPM artifacts for faster package validation.
 %global _binary_payload w.ufdio
 %global _find_debuginfo_dwz_opts %{nil}
 

From 45f98cc0c2c25902e287cfe4ad8808d1eaca7b6c Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Thu, 18 Jun 2026 13:25:24 -0700
Subject: [PATCH 08/13] Address packaging review feedback

---
 .github/workflows/reusable-package.yml | 20 +-----
 cmake/XrplPackaging.cmake              |  1 -
 package/README.md                      | 80 ++++++++++------------
 package/build_pkg.sh                   | 95 +++++++++++++-------------
 package/rpm/xrpld.spec                 |  6 +-
 package/shared/50-xrpld.preset         |  2 -
 6 files changed, 89 insertions(+), 115 deletions(-)
 delete mode 100644 package/shared/50-xrpld.preset

diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml
index 87bfa5d864f..608f537bff4 100644
--- a/.github/workflows/reusable-package.yml
+++ b/.github/workflows/reusable-package.yml
@@ -39,23 +39,8 @@ jobs:
         working-directory: .github/scripts/strategy-matrix
         run: ./generate.py --packaging >>"${GITHUB_OUTPUT}"
 
-  generate-version:
-    runs-on: ubuntu-latest
-    outputs:
-      version: ${{ steps.version.outputs.version }}
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
-        with:
-          sparse-checkout: |
-            .github/actions/generate-version
-            src/libxrpl/protocol/BuildInfo.cpp
-      - name: Generate version
-        id: version
-        uses: ./.github/actions/generate-version
-
   package:
-    needs: [generate-matrix, generate-version]
+    needs: [generate-matrix]
     if: ${{ github.event.repository.visibility == 'public' }}
     strategy:
       fail-fast: false
@@ -82,14 +67,13 @@ jobs:
 
       - name: Build package
         env:
-          XRPLD_VERSION: ${{ needs.generate-version.outputs.version }}
           PKG_RELEASE: ${{ inputs.pkg_release }}
         run: ./package/build_pkg.sh
 
       - name: Upload package artifact
         uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
         with:
-          name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
+          name: ${{ matrix.artifact_name }}-pkg
           path: |
             ${{ env.BUILD_DIR }}/debbuild/*.deb
             ${{ env.BUILD_DIR }}/debbuild/*.ddeb
diff --git a/cmake/XrplPackaging.cmake b/cmake/XrplPackaging.cmake
index b7746b2365e..8e3861925db 100644
--- a/cmake/XrplPackaging.cmake
+++ b/cmake/XrplPackaging.cmake
@@ -28,7 +28,6 @@ endif()
 set(package_env
     SRC_DIR=${CMAKE_SOURCE_DIR}
     BUILD_DIR=${CMAKE_BINARY_DIR}
-    XRPLD_VERSION=${xrpld_version}
     PKG_RELEASE=${pkg_release}
 )
 
diff --git a/package/README.md b/package/README.md
index 6e4cd76317b..6228262736e 100644
--- a/package/README.md
+++ b/package/README.md
@@ -15,7 +15,6 @@ package/
     xrpld.sysusers      sysusers.d config (used by both RPM and DEB)
     xrpld.tmpfiles      tmpfiles.d config (used by both RPM and DEB)
     xrpld.logrotate     logrotate config (installed to /etc/logrotate.d/xrpld)
-    50-xrpld.preset     systemd preset (RPM only; DEB enables the unit via dh_installsystemd)
 ```
 
 ## Prerequisites
@@ -28,10 +27,10 @@ to a new image, edit that field and both CI and local builds pick it up. The
 package format (deb or rpm) is inferred at build time from the container's
 package manager (`apt-get` -> deb, `dnf`/`yum` -> rpm).
 
-| Package type | Image (`package_configs.[].image` in `linux.json`) | Tools required                                 |
-| ------------ | ---------------------------------------------------------- | ---------------------------------------------- |
-| RPM          | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-`             | `rpmbuild`                                     |
-| DEB          | `ghcr.io/xrplf/xrpld/packaging-debian:sha-`           | `dpkg-buildpackage`, `debhelper-compat (= 13)` |
+| Package type | Image (`package_configs.[].image` in `linux.json`) | Tools required                                      |
+| ------------ | ---------------------------------------------------------- | --------------------------------------------------- |
+| RPM          | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-`             | `rpmbuild`                                          |
+| DEB          | `ghcr.io/xrplf/xrpld/packaging-debian:sha-`           | `dpkg-buildpackage`, debhelper with compat level 13 |
 
 To print the full packaging matrix (artifact names and images) for the current
 `linux.json`:
@@ -47,11 +46,11 @@ To print the full packaging matrix (artifact names and images) for the current
 Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
 `reusable-package.yml`. That workflow generates its own packaging matrix from
 `package_configs` in `linux.json` (via `generate.py --packaging`) and fans out
-one job per distro. Each job downloads the pre-built `xrpld` binary artifact,
-runs in that distro's container (so the package format follows from the
-container's package manager), and calls `build_pkg.sh` with `XRPLD_VERSION` and
-`PKG_RELEASE` supplied via env — no CMake configure or build step is needed
-inside the packaging job.
+one job per distro. Each job downloads the pre-built `xrpld` binary artifact and
+runs in that distro's container, so the package format follows from the
+container's package manager. The packaging script derives the package version
+from the downloaded binary's `xrpld --version` output; no CMake configure or
+build step is needed inside the packaging job.
 
 ### Locally (mirrors CI)
 
@@ -66,14 +65,13 @@ so you don't need to hardcode a SHA.
 # .package_configs.debian[0].image for the deb image):
 IMAGE=$(jq -r '.package_configs.rhel[0].image' .github/scripts/strategy-matrix/linux.json)
 
-XRPLD_VERSION=0.0.0-local
 PKG_RELEASE=1
 
 docker run --rm \
     -v "$(pwd):/src" \
     -w /src \
     "${IMAGE}" \
-    ./package/build_pkg.sh --xrpld-version "${XRPLD_VERSION}" --pkg-release "${PKG_RELEASE}"
+    ./package/build_pkg.sh --pkg-release "${PKG_RELEASE}"
 
 # Output:
 #   build/debbuild/*.deb         (DEB + dbgsym .ddeb)
@@ -102,13 +100,9 @@ infers the package format from the host's package manager. The packaging script
 installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
 `CMAKE_INSTALL_PREFIX`.
 
-The version is not a CMake input on this path: `cmake/XrplVersion.cmake` reads
-it from `src/libxrpl/protocol/BuildInfo.cpp` into `xrpld_version`, and
-`XrplPackaging.cmake` exports that to `build_pkg.sh` as `XRPLD_VERSION` (a
-`-Dxrpld_version=...` on the command line is overwritten and has no effect). The
-release defaults to 1 and is overridable with `-Dpkg_release=N`. To package a
-custom version string, use the script or container path above with
-`--xrpld-version`.
+The package version is not a CMake input on this path: `build_pkg.sh` derives it
+from the just-built `xrpld` binary's `xrpld --version` output. The package
+release defaults to 1 and is overridable with `-Dpkg_release=N`.
 
 ## How `build_pkg.sh` works
 
@@ -116,27 +110,26 @@ custom version string, use the script or container path above with
 environment variable. Flags override env vars; env vars override the built-in
 defaults. Run `./package/build_pkg.sh --help` for the same table:
 
-| Flag                       | Env var             | Default/source                                           | Purpose                             |
-| -------------------------- | ------------------- | -------------------------------------------------------- | ----------------------------------- |
-| `--src-dir DIR`            | `SRC_DIR`           | `$PWD`                                                   | repo root                           |
-| `--build-dir DIR`          | `BUILD_DIR`         | `$PWD/build`                                             | directory holding pre-built `xrpld` |
-| `--xrpld-version STR`      | `XRPLD_VERSION`     | set by CMake/CI; fallback: parsed from `xrpld --version` | `xrpld` version, e.g. `3.2.0-b1`    |
-| `--pkg-release N`          | `PKG_RELEASE`       | `1`                                                      | package release iteration           |
-| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime; fallback: current time          | reproducibility timestamp           |
+| Flag                       | Env var             | Default/source                                  | Purpose                             |
+| -------------------------- | ------------------- | ----------------------------------------------- | ----------------------------------- |
+| `--src-dir DIR`            | `SRC_DIR`           | `${PWD}`                                        | repo root                           |
+| `--build-dir DIR`          | `BUILD_DIR`         | `${PWD}/build`                                  | directory holding pre-built `xrpld` |
+| `--pkg-release N`          | `PKG_RELEASE`       | `1`                                             | package release iteration           |
+| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime; fallback: current time | reproducibility timestamp           |
 
-`XRPLD_VERSION` is the `build_pkg.sh` input for the `xrpld` software version in
-both package formats. CMake derives it as `xrpld_version` and exports it as
-`XRPLD_VERSION`; direct script invocations may pass it explicitly or let the
-script fall back to parsing `xrpld --version`.
+`build_pkg.sh` derives the `xrpld` software version from
+`${BUILD_DIR}/xrpld --version` in both package formats.
 
+The binary's version is already SemVer-validated by `BuildInfo`.
 `build_pkg.sh` converts pre-release versions such as `3.2.0-b1` or
 `3.2.0-rc1` from `-` to `~` for package metadata so pre-releases sort before
-the final release. Versions with more than one `-` are rejected so the
-pre-release suffix remains a single token.
+the final release. If that normalized package version still contains `-`,
+packaging fails because RPM forbids `-` in `Version`, and Debian uses `-` as
+the upstream/revision separator.
 
 `pkg_version` is the normalized package metadata version derived inside
-`build_pkg.sh` from `XRPLD_VERSION` (`-` pre-release separator converted to
-`~`). It is not a separate user input.
+`build_pkg.sh` from the binary-reported `xrpld` version (`-` pre-release
+separator converted to `~`). It is not a separate user input.
 
 `PKG_RELEASE` is a different value: the package release iteration for that
 `xrpld` version. RPM receives the normalized `pkg_version` and `PKG_RELEASE` as
@@ -153,10 +146,10 @@ With `PKG_RELEASE=1`, the package metadata becomes:
 | `3.2.0-b1`         | `3.2.0~b1-1%{?dist}`         | `3.2.0~b1-1`         |
 | `3.2.0-rc1`        | `3.2.0~rc1-1%{?dist}`        | `3.2.0~rc1-1`        |
 
-The Debian changelog's distribution field carries our apt repo _component_ (the
-suite/codename is assigned by the publishing infra at ingest, not here): final
-releases use `stable`, `b0` builds, including `b0+metadata`, use `develop`, and
-other pre-releases use `unstable`.
+The Debian changelog's distribution field carries the XRPLF release channel used
+by publishing, not the build host's Debian/Ubuntu codename: final releases use
+`stable`, `b0` builds, including `b0+metadata`, use `develop`, and other
+pre-releases such as beta and RC builds use `unstable`.
 
 The RPM path intentionally uses `~` in `Version`, matching the Debian
 pre-release ordering convention, so RPM filenames/NVRs begin with forms like
@@ -169,10 +162,9 @@ fail early.
 
 Flags are for explicit invocation; environment variables are intended for
 CMake/CI integration. The CI workflow and the CMake `package` target both invoke
-`build_pkg.sh` with no flags; CMake supplies `SRC_DIR`, `BUILD_DIR`,
-`XRPLD_VERSION`, and `PKG_RELEASE` via env, while CI supplies `BUILD_DIR`,
-`XRPLD_VERSION`, and `PKG_RELEASE` via env and lets the script use defaults for
-the rest.
+`build_pkg.sh` with no flags; CMake supplies `SRC_DIR`, `BUILD_DIR`, and
+`PKG_RELEASE` via env, while CI supplies `BUILD_DIR` and `PKG_RELEASE` via env
+and lets the script use defaults for the rest.
 
 It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
 `stage_common()` to copy the binary, config files, and shared support files
@@ -181,7 +173,7 @@ into the staging area, and invokes the platform build tool.
 ### RPM
 
 1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
-2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
+2. Copies `xrpld.spec` and all shared source files (binary, configs, service files) into `SOURCES/`.
 3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the
    `pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro.
    The spec uses manual `install` commands to place files, disables `dwz`, and
@@ -205,7 +197,7 @@ service restart.
 3. Copies `package/debian/` control files into `debbuild/source/debian/`.
 4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
 5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,
-   where `pkg_version` is derived from `XRPLD_VERSION`.
+   where `pkg_version` is derived from the binary-reported `xrpld` version.
 6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands.
 7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
 
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index 210bc186305..4c448684d9b 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -3,17 +3,15 @@ set -euo pipefail
 
 # Build an RPM or Debian package from a pre-built xrpld binary.
 #
-# Flags override env vars; env vars override defaults. Env vars are intended
-# for CMake/CI integration; flags are for explicit invocation.
+# Flags override env vars; env vars override defaults.
 
 usage() {
     cat <<'EOF'
 Usage: build_pkg.sh [options]
 
 Options (each can also be set via the env var shown):
-  --src-dir DIR             repo root                     [SRC_DIR;           default: $PWD]
-  --build-dir DIR           directory holding xrpld       [BUILD_DIR;         default: $PWD/build]
-  --xrpld-version STR       xrpld version, e.g. 3.2.0-b1  [XRPLD_VERSION;     set by CMake/CI; fallback: xrpld --version]
+  --src-dir DIR             repo root                     [SRC_DIR;           default: ${PWD}]
+  --build-dir DIR           directory holding xrpld       [BUILD_DIR;         default: ${PWD}/build]
   --pkg-release N           package release iteration     [PKG_RELEASE;       default: 1]
   --source-date-epoch SECS  reproducibility timestamp     [SOURCE_DATE_EPOCH; latest git ctime; fallback: current time]
   -h, --help                show this help and exit
@@ -30,7 +28,6 @@ need_arg() {
 # Seed from env. CLI parsing below overrides these directly.
 SRC_DIR="${SRC_DIR:-}"
 BUILD_DIR="${BUILD_DIR:-}"
-XRPLD_VERSION="${XRPLD_VERSION:-}"
 PKG_RELEASE="${PKG_RELEASE:-1}"
 SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}"
 
@@ -46,11 +43,6 @@ while [[ $# -gt 0 ]]; do
             BUILD_DIR="$2"
             shift 2
             ;;
-        --xrpld-version)
-            need_arg "$@"
-            XRPLD_VERSION="$2"
-            shift 2
-            ;;
         --pkg-release)
             need_arg "$@"
             PKG_RELEASE="$2"
@@ -74,40 +66,46 @@ while [[ $# -gt 0 ]]; do
 done
 
 SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
-BUILD_DIR="$(cd "${BUILD_DIR:-${PWD}/build}" && pwd)"
-
-if [[ -z "${XRPLD_VERSION}" ]]; then
-    XRPLD_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR == 1 { print $3 }')"
-fi
-
-if [[ -z "${XRPLD_VERSION}" ]]; then
-    echo "XRPLD_VERSION is empty (not provided and could not be derived)." >&2
+BUILD_DIR="${BUILD_DIR:-${PWD}/build}"
+if [[ ! -d "${BUILD_DIR}" ]]; then
+    echo "build_pkg.sh: build directory not found: ${BUILD_DIR}" >&2
+    echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
     exit 1
 fi
+BUILD_DIR="$(cd "${BUILD_DIR}" && pwd)"
 
-if [[ "${XRPLD_VERSION}" == -* || "${XRPLD_VERSION}" == *- ]]; then
-    echo "build_pkg.sh: invalid XRPLD_VERSION='${XRPLD_VERSION}'." >&2
-    echo "Version cannot start or end with '-'." >&2
+xrpld_binary="${BUILD_DIR}/xrpld"
+if [[ ! -x "${xrpld_binary}" ]]; then
+    echo "build_pkg.sh: expected executable xrpld binary at ${xrpld_binary}." >&2
+    echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
     exit 1
 fi
 
-# A pre-release suffix must be a single token. RPM Version cannot contain '-',
-# and this script uses one '-' as the version/release separator for both package
-# formats, so versions carrying a second '-' ("beta-1", "rc1-15-gabc123") do
-# not fit the convention. Reject those up front rather than mangling them later.
-if [[ "${XRPLD_VERSION}" == *-*-* ]]; then
-    echo "build_pkg.sh: multi-segment version XRPLD_VERSION='${XRPLD_VERSION}'." >&2
-    echo "Use a single-token pre-release like 3.2.0-b1 or 3.2.0-rc2." >&2
+xrpld_version="$("${xrpld_binary}" --version | awk 'NR == 1 { print $3 }')"
+
+if [[ -z "${xrpld_version}" ]]; then
+    echo "Unable to derive xrpld version from ${BUILD_DIR}/xrpld --version." >&2
     exit 1
 fi
 
-# The version as the package formats consume it: identical to XRPLD_VERSION
+# The version as the package formats consume it: identical to xrpld_version
 # except a pre-release uses '~' (3.2.0-b1 -> 3.2.0~b1), which also sorts before
 # the final 3.2.0; a no-op for a final release. Lowercase = derived internally,
 # not an input (cf. pkg_type).
-pkg_version="${XRPLD_VERSION}"
-if [[ "${XRPLD_VERSION}" == *-* ]]; then
-    pkg_version="${XRPLD_VERSION%%-*}~${XRPLD_VERSION#*-}"
+pkg_version="${xrpld_version}"
+if [[ "${xrpld_version}" == *-* ]]; then
+    pkg_version="${xrpld_version%%-*}~${xrpld_version#*-}"
+fi
+
+# BuildInfo already SemVer-validates the binary's version. Packaging adds one
+# narrower constraint: after pre-release normalization, the package version must
+# not contain '-' because RPM forbids it in Version and Debian uses it as the
+# upstream/revision separator.
+if [[ "${pkg_version}" == *-* ]]; then
+    echo "build_pkg.sh: unsupported xrpld version '${xrpld_version}'." >&2
+    echo "Package version '${pkg_version}' cannot contain '-'." >&2
+    echo "Use a single-token pre-release like 3.2.0-b1 or 3.2.0-rc2." >&2
+    exit 1
 fi
 
 if command -v apt-get >/dev/null 2>&1; then
@@ -120,15 +118,15 @@ else
 fi
 
 if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then
-    if git -C "$SRC_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
-        SOURCE_DATE_EPOCH="$(git -C "$SRC_DIR" log -1 --format=%ct)"
+    if git -C "${SRC_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
+        SOURCE_DATE_EPOCH="$(git -C "${SRC_DIR}" log -1 --format=%ct)"
     else
         SOURCE_DATE_EPOCH="$(date +%s)"
     fi
 fi
 
 export SOURCE_DATE_EPOCH
-CHANGELOG_DATE="$(date -u -R -d "@$SOURCE_DATE_EPOCH")"
+CHANGELOG_DATE="$(date -u -R -d "@${SOURCE_DATE_EPOCH}")"
 
 SHARED="${SRC_DIR}/package/shared"
 DEBIAN_DIR="${SRC_DIR}/package/debian"
@@ -148,7 +146,6 @@ stage_common() {
     cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
     cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
     cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
-    cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
 }
 
 build_rpm() {
@@ -180,22 +177,22 @@ build_deb() {
     cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
     cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
 
-    # The release channel selects our apt repo *component*. dpkg names the
-    # changelog's third field "distribution", but the suite/codename (noble,
-    # bookworm, ...) is assigned by the publishing infra at ingest; this
-    # suite-agnostic build only sets the component. RPM has no equivalent.
-    #   3.2.0 -> stable, *-b0[+metadata] -> develop, other pre-release -> unstable.
-    local deb_component
-    case "${XRPLD_VERSION}" in
-        *-b0 | *-b0+*) deb_component="develop" ;;
-        *-*) deb_component="unstable" ;;
-        *) deb_component="stable" ;;
+    # The Debian changelog distribution carries the XRPLF release channel used
+    # by publishing, not the build host's Debian/Ubuntu codename. Local package
+    # builds are not restricted by codename.
+    #   3.2.0 -> stable, *-b0[+metadata] -> develop,
+    #   other pre-release such as bN/rcN -> unstable.
+    local deb_distribution
+    case "${xrpld_version}" in
+        *-b0 | *-b0+*) deb_distribution="develop" ;;
+        *-*) deb_distribution="unstable" ;;
+        *) deb_distribution="stable" ;;
     esac
 
     # Debian version is [~
]-.
     cat >"${staging}/debian/changelog" <  ${CHANGELOG_DATE}
 EOF
diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec
index a7741bb7e28..767c6d3f248 100644
--- a/package/rpm/xrpld.spec
+++ b/package/rpm/xrpld.spec
@@ -48,7 +48,11 @@ install -Dm0644 %{_sourcedir}/validators.txt       %{buildroot}%{_sysconfdir}/%{
 install -Dm0644 %{_sourcedir}/xrpld.service        %{buildroot}%{_unitdir}/xrpld.service
 install -Dm0644 %{_sourcedir}/xrpld.sysusers       %{buildroot}%{_sysusersdir}/xrpld.conf
 install -Dm0644 %{_sourcedir}/xrpld.tmpfiles       %{buildroot}%{_tmpfilesdir}/xrpld.conf
-install -Dm0644 %{_sourcedir}/50-xrpld.preset      %{buildroot}%{_presetdir}/50-xrpld.preset
+install -Dm0644 /dev/null %{buildroot}%{_presetdir}/50-xrpld.preset
+cat >%{buildroot}%{_presetdir}/50-xrpld.preset <<'EOF'
+# /usr/lib/systemd/system-preset/50-xrpld.preset
+enable xrpld.service
+EOF
 
 # Logrotate config
 install -Dm0644 %{_sourcedir}/xrpld.logrotate      %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
diff --git a/package/shared/50-xrpld.preset b/package/shared/50-xrpld.preset
deleted file mode 100644
index bfbcd56577e..00000000000
--- a/package/shared/50-xrpld.preset
+++ /dev/null
@@ -1,2 +0,0 @@
-# /usr/lib/systemd/system-preset/50-xrpld.preset
-enable xrpld.service

From a40abb80c319f45c6ae8bb478c396d4d186dc87f Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Thu, 18 Jun 2026 13:54:45 -0700
Subject: [PATCH 09/13] Clarify Debian package channel wording

---
 package/README.md    | 7 +++----
 package/build_pkg.sh | 4 +---
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/package/README.md b/package/README.md
index 6228262736e..cc269b339d9 100644
--- a/package/README.md
+++ b/package/README.md
@@ -146,10 +146,9 @@ With `PKG_RELEASE=1`, the package metadata becomes:
 | `3.2.0-b1`         | `3.2.0~b1-1%{?dist}`         | `3.2.0~b1-1`         |
 | `3.2.0-rc1`        | `3.2.0~rc1-1%{?dist}`        | `3.2.0~rc1-1`        |
 
-The Debian changelog's distribution field carries the XRPLF release channel used
-by publishing, not the build host's Debian/Ubuntu codename: final releases use
-`stable`, `b0` builds, including `b0+metadata`, use `develop`, and other
-pre-releases such as beta and RC builds use `unstable`.
+The Debian changelog's distribution field carries the package channel: final
+releases use `stable`, `b0` builds, including `b0+metadata`, use `develop`, and
+other pre-releases such as beta and RC builds use `unstable`.
 
 The RPM path intentionally uses `~` in `Version`, matching the Debian
 pre-release ordering convention, so RPM filenames/NVRs begin with forms like
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index 4c448684d9b..3a184197b64 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -177,9 +177,7 @@ build_deb() {
     cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
     cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
 
-    # The Debian changelog distribution carries the XRPLF release channel used
-    # by publishing, not the build host's Debian/Ubuntu codename. Local package
-    # builds are not restricted by codename.
+    # Choose the Debian changelog distribution value used by our package channels.
     #   3.2.0 -> stable, *-b0[+metadata] -> develop,
     #   other pre-release such as bN/rcN -> unstable.
     local deb_distribution

From e3bb424b87d57e8a09043276dd32521f057afb05 Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Thu, 18 Jun 2026 13:57:08 -0700
Subject: [PATCH 10/13] Use Debian component terminology

---
 package/README.md    |  6 +++---
 package/build_pkg.sh | 12 ++++++------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/package/README.md b/package/README.md
index cc269b339d9..4a5ae3e7386 100644
--- a/package/README.md
+++ b/package/README.md
@@ -146,9 +146,9 @@ With `PKG_RELEASE=1`, the package metadata becomes:
 | `3.2.0-b1`         | `3.2.0~b1-1%{?dist}`         | `3.2.0~b1-1`         |
 | `3.2.0-rc1`        | `3.2.0~rc1-1%{?dist}`        | `3.2.0~rc1-1`        |
 
-The Debian changelog's distribution field carries the package channel: final
-releases use `stable`, `b0` builds, including `b0+metadata`, use `develop`, and
-other pre-releases such as beta and RC builds use `unstable`.
+The Debian changelog entry carries the repository component: final releases use
+`stable`, `b0` builds, including `b0+metadata`, use `develop`, and other
+pre-releases such as beta and RC builds use `unstable`.
 
 The RPM path intentionally uses `~` in `Version`, matching the Debian
 pre-release ordering convention, so RPM filenames/NVRs begin with forms like
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index 3a184197b64..b9fc2615f47 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -177,19 +177,19 @@ build_deb() {
     cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
     cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
 
-    # Choose the Debian changelog distribution value used by our package channels.
+    # Choose the Debian repository component for this package.
     #   3.2.0 -> stable, *-b0[+metadata] -> develop,
     #   other pre-release such as bN/rcN -> unstable.
-    local deb_distribution
+    local deb_component
     case "${xrpld_version}" in
-        *-b0 | *-b0+*) deb_distribution="develop" ;;
-        *-*) deb_distribution="unstable" ;;
-        *) deb_distribution="stable" ;;
+        *-b0 | *-b0+*) deb_component="develop" ;;
+        *-*) deb_component="unstable" ;;
+        *) deb_component="stable" ;;
     esac
 
     # Debian version is [~
]-.
     cat >"${staging}/debian/changelog" <  ${CHANGELOG_DATE}

From ff44fcd1910bf76c759f0ddb6451af1615e190b7 Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Thu, 18 Jun 2026 14:02:11 -0700
Subject: [PATCH 11/13] Restrict Debian component prerelease mapping

---
 package/README.md    |  4 ++--
 package/build_pkg.sh | 28 +++++++++++++++++++++-------
 2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/package/README.md b/package/README.md
index 4a5ae3e7386..bc8b5803f6e 100644
--- a/package/README.md
+++ b/package/README.md
@@ -147,8 +147,8 @@ With `PKG_RELEASE=1`, the package metadata becomes:
 | `3.2.0-rc1`        | `3.2.0~rc1-1%{?dist}`        | `3.2.0~rc1-1`        |
 
 The Debian changelog entry carries the repository component: final releases use
-`stable`, `b0` builds, including `b0+metadata`, use `develop`, and other
-pre-releases such as beta and RC builds use `unstable`.
+`stable`, `b0` builds, including `b0+metadata`, use `develop`, and `bN`/`rcN`
+pre-releases use `unstable`.
 
 The RPM path intentionally uses `~` in `Version`, matching the Debian
 pre-release ordering convention, so RPM filenames/NVRs begin with forms like
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index b9fc2615f47..c26c49e8ec2 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -93,8 +93,10 @@ fi
 # the final 3.2.0; a no-op for a final release. Lowercase = derived internally,
 # not an input (cf. pkg_type).
 pkg_version="${xrpld_version}"
+pre_release=""
 if [[ "${xrpld_version}" == *-* ]]; then
-    pkg_version="${xrpld_version%%-*}~${xrpld_version#*-}"
+    pre_release="${xrpld_version#*-}"
+    pkg_version="${xrpld_version%%-*}~${pre_release}"
 fi
 
 # BuildInfo already SemVer-validates the binary's version. Packaging adds one
@@ -108,6 +110,12 @@ if [[ "${pkg_version}" == *-* ]]; then
     exit 1
 fi
 
+if [[ -n "${pre_release}" && ! "${pre_release}" =~ ^(b0|b[1-9][0-9]*|rc[0-9]+)(\+.*)?$ ]]; then
+    echo "build_pkg.sh: unsupported xrpld pre-release '${pre_release}'." >&2
+    echo "Use bN or rcN, e.g. 3.2.0-b1 or 3.2.0-rc2." >&2
+    exit 1
+fi
+
 if command -v apt-get >/dev/null 2>&1; then
     pkg_type=deb
 elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
@@ -179,13 +187,19 @@ build_deb() {
 
     # Choose the Debian repository component for this package.
     #   3.2.0 -> stable, *-b0[+metadata] -> develop,
-    #   other pre-release such as bN/rcN -> unstable.
+    #   bN/rcN pre-releases -> unstable.
     local deb_component
-    case "${xrpld_version}" in
-        *-b0 | *-b0+*) deb_component="develop" ;;
-        *-*) deb_component="unstable" ;;
-        *) deb_component="stable" ;;
-    esac
+    if [[ -z "${pre_release}" ]]; then
+        deb_component="stable"
+    elif [[ "${pre_release}" =~ ^b0(\+.*)?$ ]]; then
+        deb_component="develop"
+    elif [[ "${pre_release}" =~ ^(b[1-9][0-9]*|rc[0-9]+)(\+.*)?$ ]]; then
+        deb_component="unstable"
+    else
+        echo "build_pkg.sh: unsupported xrpld pre-release '${pre_release}'." >&2
+        echo "Use bN or rcN, e.g. 3.2.0-b1 or 3.2.0-rc2." >&2
+        exit 1
+    fi
 
     # Debian version is [~
]-.
     cat >"${staging}/debian/changelog" <
Date: Thu, 18 Jun 2026 14:08:02 -0700
Subject: [PATCH 12/13] Reject metadata-only package versions

---
 package/README.md    | 1 +
 package/build_pkg.sh | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/package/README.md b/package/README.md
index bc8b5803f6e..c8ccc305c10 100644
--- a/package/README.md
+++ b/package/README.md
@@ -149,6 +149,7 @@ With `PKG_RELEASE=1`, the package metadata becomes:
 The Debian changelog entry carries the repository component: final releases use
 `stable`, `b0` builds, including `b0+metadata`, use `develop`, and `bN`/`rcN`
 pre-releases use `unstable`.
+Build metadata on a final release, such as `3.2.0+abc123`, is rejected.
 
 The RPM path intentionally uses `~` in `Version`, matching the Debian
 pre-release ordering convention, so RPM filenames/NVRs begin with forms like
diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index c26c49e8ec2..6853b729485 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -110,6 +110,12 @@ if [[ "${pkg_version}" == *-* ]]; then
     exit 1
 fi
 
+if [[ -z "${pre_release}" && "${xrpld_version}" == *+* ]]; then
+    echo "build_pkg.sh: unsupported xrpld version '${xrpld_version}'." >&2
+    echo "Build metadata is only supported on bN/rcN pre-releases." >&2
+    exit 1
+fi
+
 if [[ -n "${pre_release}" && ! "${pre_release}" =~ ^(b0|b[1-9][0-9]*|rc[0-9]+)(\+.*)?$ ]]; then
     echo "build_pkg.sh: unsupported xrpld pre-release '${pre_release}'." >&2
     echo "Use bN or rcN, e.g. 3.2.0-b1 or 3.2.0-rc2." >&2

From 034e46550347193c5b85a7c8fb596315719a9025 Mon Sep 17 00:00:00 2001
From: Michael Legleux 
Date: Thu, 18 Jun 2026 14:19:56 -0700
Subject: [PATCH 13/13] Standardize package version error

---
 package/build_pkg.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package/build_pkg.sh b/package/build_pkg.sh
index 6853b729485..1dca15fc42d 100755
--- a/package/build_pkg.sh
+++ b/package/build_pkg.sh
@@ -84,7 +84,7 @@ fi
 xrpld_version="$("${xrpld_binary}" --version | awk 'NR == 1 { print $3 }')"
 
 if [[ -z "${xrpld_version}" ]]; then
-    echo "Unable to derive xrpld version from ${BUILD_DIR}/xrpld --version." >&2
+    echo "build_pkg.sh: unable to derive xrpld version from ${xrpld_binary} --version." >&2
     exit 1
 fi