From 295b74da1cc677972d023d669d323bf799af2893 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 8 Sep 2026 21:36:55 +0100 Subject: [PATCH] build: Add assert-enabled builds and packages --- .github/scripts/strategy-matrix/generate.py | 46 ++++- .github/scripts/strategy-matrix/linux.json | 13 ++ .github/workflows/on-pr.yml | 1 + .github/workflows/on-trigger.yml | 1 + .../reusable-package-test-install.yml | 103 ++++++++++ .github/workflows/reusable-package.yml | 178 ++++++------------ cmake/XrplPackaging.cmake | 8 +- docs/install.md | 23 ++- package/README.md | 106 +++++++++-- package/build_pkg.py | 100 +++++++++- package/debian/{control => control.in} | 5 +- package/debian/{xrpld.docs => docs} | 0 package/debian/{xrpld.links => links} | 0 package/debian/lintian-overrides.in | 6 + package/debian/rules | 41 +++- package/debian/xrpld.lintian-overrides | 6 - package/rpm/xrpld.spec | 49 +++-- 17 files changed, 504 insertions(+), 182 deletions(-) create mode 100644 .github/workflows/reusable-package-test-install.yml rename package/debian/{control => control.in} (93%) rename package/debian/{xrpld.docs => docs} (100%) rename package/debian/{xrpld.links => links} (100%) create mode 100644 package/debian/lintian-overrides.in delete mode 100644 package/debian/xrpld.lintian-overrides diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 65671dbd11..5528d6442e 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -15,6 +15,14 @@ _BASE_CMAKE_ARGS = [ "-Drust=ON", ] +# The package formats a config can be packaged as, each with its own +# install-test job in reusable-package.yml. +PACKAGE_TYPES = ("deb", "rpm") + +# The package name a variant suffixes, as build_pkg.py's BASE_NAME spells it: +# the two have to agree, or the artifact globs miss what was built. +BASE_NAME = "xrpld" + # Maps sanitizer names (as used in cmake) to short config-name suffixes. _SANITIZER_SUFFIX: dict[str, str] = { "address": "asan", @@ -62,10 +70,20 @@ def get_cmake_args(build_type: str, extra_args: str) -> str: class PackageConfig: """The 'package' map of a config whose binaries are also packaged.""" - type: str # "deb" or "rpm"; has to match what the image provides + type: str # has to match what the image provides # The packaging container image: a vanilla distro image, not the nix image # the config itself builds in. image: str + # A flavour of the package, named xrpld-, for a config whose + # binaries are not the plain release build. A variant needs no counterpart + # in the other format. + variant: str = "" + + def __post_init__(self) -> None: + assert self.type in PACKAGE_TYPES, ( + f"unsupported package type {self.type!r}: " + f"use one of {', '.join(PACKAGE_TYPES)}." + ) @dataclasses.dataclass @@ -178,6 +196,8 @@ class PackagingEntry: validator_keys_artifact_name: str image: str package_type: str # "deb" or "rpm"; drives the format-specific steps + package_variant: str # passed to build_pkg.py --variant; empty for xrpld + package_name: str # the name it builds under, which the artifact globs use # --------------------------------------------------------------------------- @@ -267,12 +287,32 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]: validator_keys_artifact_name=f"validator-keys-{name}", image=cfg.package.image, package_type=cfg.package.type, + package_variant=cfg.package.variant, + package_name=( + f"{BASE_NAME}-{cfg.package.variant}" + if cfg.package.variant + else BASE_NAME + ), ) ) return entries +def package_names_by_type(entries: list[PackagingEntry]) -> dict[str, list[str]]: + """The names of the packages in 'entries', keyed by format. + + Derived from the packaging matrix rather than listed again, so the packages + the install-test jobs look for are the packages that were built. + """ + return { + package_type: sorted( + {e.package_name for e in entries if e.package_type == package_type} + ) + for package_type in PACKAGE_TYPES + } + + def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]: """Expand a PlatformFile (macOS or Windows) into matrix entries. @@ -341,6 +381,10 @@ if __name__ == "__main__": if args.packaging: matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json")) + # One list per format, so each install-test job installs the packages its + # own format produced. + for package_type, names in package_names_by_type(matrix).items(): + print(f"{package_type}_package_names={json.dumps(names)}") else: if args.config in ("linux", None): matrix += expand_linux_matrix( diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 2229332e41..9b069f0ce3 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -76,6 +76,19 @@ "type": "deb", "image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10" } + }, + { + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["amd64"], + "minimal": false, + "suffix": "assert", + "extra_cmake_args": "-Dvalidator_keys=ON -Dassert=ON", + "package": { + "type": "deb", + "image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10", + "variant": "assert" + } } ], diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 933c7b8a54..25bbaa2cc9 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -85,6 +85,7 @@ jobs: .github/workflows/reusable-build-test.yml .github/workflows/reusable-check-autogen.yml .github/workflows/reusable-clang-tidy.yml + .github/workflows/reusable-package-test-install.yml .github/workflows/reusable-package.yml .github/workflows/reusable-rust.yml .github/workflows/reusable-strategy-matrix.yml diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 2099f5f739..2bf73332d2 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -23,6 +23,7 @@ on: - ".github/workflows/reusable-build-test.yml" - ".github/workflows/reusable-check-autogen.yml" - ".github/workflows/reusable-clang-tidy.yml" + - ".github/workflows/reusable-package-test-install.yml" - ".github/workflows/reusable-package.yml" - ".github/workflows/reusable-rust.yml" - ".github/workflows/reusable-strategy-matrix.yml" diff --git a/.github/workflows/reusable-package-test-install.yml b/.github/workflows/reusable-package-test-install.yml new file mode 100644 index 0000000000..84db0b666d --- /dev/null +++ b/.github/workflows/reusable-package-test-install.yml @@ -0,0 +1,103 @@ +# Install one package format on every distro family it targets, one job per +# package name and image, and run the binaries there. Called once per format by +# reusable-package.yml, which owns the names and the image lists. +name: Install packages + +on: + workflow_call: + inputs: + package_type: + description: 'The package format to install ("deb" or "rpm").' + required: true + type: string + package_names: + description: "JSON array of package names built for this format." + required: true + type: string + images: + description: "JSON array of container images to install in." + required: true + type: string + +defaults: + run: + shell: bash + +env: + PACKAGE_DIR: packages + +jobs: + install: + strategy: + fail-fast: false + matrix: + package_name: ${{ fromJson(inputs.package_names) }} + image: ${{ fromJson(inputs.images) }} + name: "${{ matrix.package_name }} on ${{ matrix.image }}" + permissions: + contents: read + runs-on: ubuntu-latest + container: ${{ matrix.image }} + timeout-minutes: 5 + + steps: + # Every package lands in one directory; the step below picks its own, + # which keeps this independent of the artifact names. + - name: Download package artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: "*-pkg" + merge-multiple: true + path: ${{ env.PACKAGE_DIR }} + + - name: Find the package + id: find + env: + PACKAGE_NAME: ${{ matrix.package_name }} + PACKAGE_TYPE: ${{ inputs.package_type }} + run: | + # The version follows the name, separated by '_' in a DEB and '-' in an + # RPM. Requiring a digit after it is what keeps 'xrpld' from picking up + # another package, such as 'xrpld-assert'. + pattern="${PACKAGE_NAME}[_-][0-9]*.${PACKAGE_TYPE}" + package="$(find "${PACKAGE_DIR}" -type f -name "${pattern}" -print -quit)" + test -n "${package}" || { + echo "no ${pattern} found in ${PACKAGE_DIR}" >&2 + exit 1 + } + echo "package=${package}" >>"${GITHUB_OUTPUT}" + + - name: Install the DEB + if: ${{ inputs.package_type == 'deb' }} + env: + DEBIAN_FRONTEND: noninteractive + PACKAGE: ${{ steps.find.outputs.package }} + run: | + # Stock Debian and Ubuntu images carry no package lists, so apt has + # nothing to resolve the systemd dependency from until it fetches them. + apt-get update -qq + apt-get install -y "./${PACKAGE}" + + - name: Install the RPM + if: ${{ inputs.package_type == 'rpm' }} + env: + PACKAGE: ${{ steps.find.outputs.package }} + run: dnf install -y "./${PACKAGE}" + + - name: Run xrpld + run: xrpld --version + + - name: Run validator-keys + run: validator-keys --version + + - name: Run rippled, the legacy compatibility symlink + run: rippled --version + + - name: Check the service account + run: id xrpld + + - name: Check the state directory + run: test -d /var/lib/xrpld + + - name: Check the log directory + run: test -d /var/log/xrpld diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 9dbc027176..e7dd34b064 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -3,8 +3,10 @@ # # - 'package' builds and signs one format per config that carries a "package" # map in linux.json; that map names the container image and the format -# - 'test-install' installs what was built on a range of distros and runs the -# binaries there, so a package that cannot be installed never reaches Nexus +# - 'test-install-deb' and 'test-install-rpm' call +# reusable-package-test-install.yml to install what was built on a range of +# distros and run the binaries there, so a package that cannot be installed +# never reaches Nexus # - 'publish' uploads with the image's publish_pkg.py, doing a --dry-run # unless 'publish: true' # @@ -49,6 +51,8 @@ jobs: runs-on: ubuntu-latest outputs: matrix: ${{ steps.generate.outputs.matrix }} + deb_package_names: ${{ steps.generate.outputs.deb_package_names }} + rpm_package_names: ${{ steps.generate.outputs.rpm_package_names }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -107,6 +111,7 @@ jobs: - name: Build package env: PACKAGE_TYPE: ${{ matrix.package_type }} + PACKAGE_VARIANT: ${{ matrix.package_variant }} PKG_RELEASE: ${{ steps.release_info.outputs.pkg_release }} CHANNEL: ${{ steps.release_info.outputs.channel }} run: | @@ -114,6 +119,7 @@ jobs: --package-type "${PACKAGE_TYPE}" \ --build-dir "${BUILD_DIR}" \ --pkg-release "${PKG_RELEASE}" \ + --variant "${PACKAGE_VARIANT}" \ --channel "${CHANNEL}" # Before the upload, so the artifact, the tested package and the published @@ -125,14 +131,17 @@ jobs: run: ./package/sign_rpm.py --package-dir "${BUILD_DIR}" # Split from the debug symbols, which are an order of magnitude larger, so - # that test-install downloads only what it installs. + # that test-install downloads only what it installs. In the globs below the + # version follows the name, separated by '_' in a DEB and '-' in an RPM. A + # version starts with a digit and a longer name does not, so that one digit + # is what tells 'xrpld-3.4.1-...' from 'xrpld-assert-3.4.1-...'. - name: Upload package artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.xrpld_artifact_name }}-pkg path: | - ${{ env.BUILD_DIR }}/debbuild/xrpld_*.deb - ${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-[0-9]*.rpm + ${{ env.BUILD_DIR }}/debbuild/${{ matrix.package_name }}_[0-9]*.deb + ${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/${{ matrix.package_name }}-[0-9]*.rpm if-no-files-found: error - name: Upload debug symbol artifact @@ -140,129 +149,52 @@ jobs: with: name: ${{ matrix.xrpld_artifact_name }}-pkg-debug path: | - ${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.deb - ${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.ddeb - ${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-debuginfo-*.rpm + ${{ env.BUILD_DIR }}/debbuild/${{ matrix.package_name }}-dbgsym_[0-9]*.deb + ${{ env.BUILD_DIR }}/debbuild/${{ matrix.package_name }}-dbgsym_[0-9]*.ddeb + ${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/${{ matrix.package_name }}-debuginfo-[0-9]*.rpm if-no-files-found: error - # Every distro family the packages target, oldest release first, so both ends - # of the dependency range they declare are exercised. - test-install: - needs: [package] - strategy: - fail-fast: false - matrix: - include: - - package_type: deb - image: debian:11 - - package_type: deb - image: debian:12 - - package_type: deb - image: debian:13 - - package_type: deb - image: ubuntu:20.04 - - package_type: deb - image: ubuntu:22.04 - - package_type: deb - image: ubuntu:24.04 - - package_type: deb - image: ubuntu:26.04 + # One call per format, so a variant packaged for one format is installed for + # that format alone. The images are every distro family that format targets, + # oldest release first, so both ends of the dependency range the packages + # declare are exercised. + test-install-deb: + needs: [generate-matrix, package] + name: install deb + uses: ./.github/workflows/reusable-package-test-install.yml + with: + package_type: deb + package_names: ${{ needs.generate-matrix.outputs.deb_package_names }} + images: | + [ + "debian:11", + "debian:12", + "debian:13", + "ubuntu:20.04", + "ubuntu:22.04", + "ubuntu:24.04", + "ubuntu:26.04" + ] - - package_type: rpm - image: almalinux:9 - - package_type: rpm - image: almalinux:10 - - package_type: rpm - image: rockylinux/rockylinux:9 - - package_type: rpm - image: rockylinux/rockylinux:10 - - package_type: rpm - image: registry.access.redhat.com/ubi9/ubi - - package_type: rpm - image: registry.access.redhat.com/ubi10/ubi - name: "install ${{ matrix.package_type }} on ${{ matrix.image }}" - permissions: - contents: read - runs-on: ubuntu-latest - container: ${{ matrix.image }} - timeout-minutes: 5 - - steps: - # Both formats land in one directory; the step below picks its own by - # extension, so this stays independent of the artifact names. - - name: Download package artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: "*-pkg" - merge-multiple: true - path: ${{ env.PACKAGE_DIR }} - - - name: Find the package - id: find - env: - PACKAGE_TYPE: ${{ matrix.package_type }} - run: | - package="$(find "${PACKAGE_DIR}" -type f -name "*.${PACKAGE_TYPE}" -print -quit)" - test -n "${package}" || { - echo "no .${PACKAGE_TYPE} found in ${PACKAGE_DIR}" >&2 - exit 1 - } - echo "package=${package}" >>"${GITHUB_OUTPUT}" - - # Debian 11 went end-of-life on 2026-08-31 - # (https://www.debian.org/News/2026/20260831) and its packages are - # already partly gone from deb.debian.org, so switch to the - # snapshot.debian.org entries the image ships commented out in its - # sources.list: they are pinned to the snapshot the image was built - # from, so they serve every version it needs and never go away. - # Snapshots keep their original, long-passed Valid-Until, hence the - # disabled check; the retries absorb snapshot.debian.org's throttling. - - name: Switch Debian 11 to snapshot.debian.org - if: ${{ matrix.image == 'debian:11' }} - run: | - sed -i 's|^deb |# deb |; s|^# deb http://snapshot|deb http://snapshot|' /etc/apt/sources.list - printf '%s\n' \ - 'Acquire::Check-Valid-Until "false";' \ - 'Acquire::Retries "3";' \ - >/etc/apt/apt.conf.d/99snapshot - - - name: Install the DEB - if: ${{ matrix.package_type == 'deb' }} - env: - DEBIAN_FRONTEND: noninteractive - PACKAGE: ${{ steps.find.outputs.package }} - run: | - # Stock Debian and Ubuntu images carry no package lists, so apt has - # nothing to resolve the systemd dependency from until it fetches them. - apt-get update -qq - apt-get install -y "./${PACKAGE}" - - - name: Install the RPM - if: ${{ matrix.package_type == 'rpm' }} - env: - PACKAGE: ${{ steps.find.outputs.package }} - run: dnf install -y "./${PACKAGE}" - - - name: Run xrpld - run: xrpld --version - - - name: Run validator-keys - run: validator-keys --version - - - name: Run rippled, the legacy compatibility symlink - run: rippled --version - - - name: Check the service account - run: id xrpld - - - name: Check the state directory - run: test -d /var/lib/xrpld - - - name: Check the log directory - run: test -d /var/log/xrpld + test-install-rpm: + needs: [generate-matrix, package] + name: install rpm + uses: ./.github/workflows/reusable-package-test-install.yml + with: + package_type: rpm + package_names: ${{ needs.generate-matrix.outputs.rpm_package_names }} + images: | + [ + "almalinux:9", + "almalinux:10", + "rockylinux/rockylinux:9", + "rockylinux/rockylinux:10", + "registry.access.redhat.com/ubi9/ubi", + "registry.access.redhat.com/ubi10/ubi" + ] publish: - needs: [generate-matrix, package, test-install] + needs: [generate-matrix, package, test-install-deb, test-install-rpm] strategy: fail-fast: false matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} diff --git a/cmake/XrplPackaging.cmake b/cmake/XrplPackaging.cmake index e2f7029ad2..0fdeae0d0d 100644 --- a/cmake/XrplPackaging.cmake +++ b/cmake/XrplPackaging.cmake @@ -44,12 +44,18 @@ else() set(pkg_type rpm) endif() +# Unquoted below, so an empty value adds no argument at all. +set(pkg_variant_option "") +if(assert) + set(pkg_variant_option --variant=assert) +endif() + add_custom_target( package COMMAND ${CMAKE_SOURCE_DIR}/package/build_pkg.py --package-type=${pkg_type} --build-dir=${CMAKE_BINARY_DIR} --pkg-release=${pkg_release} - --channel=UNRELEASED + ${pkg_variant_option} --channel=UNRELEASED WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS xrpld validator-keys COMMENT "Building Linux ${pkg_type} package" diff --git a/docs/install.md b/docs/install.md index 4c52b587b6..34606fbe11 100644 --- a/docs/install.md +++ b/docs/install.md @@ -6,7 +6,8 @@ `xrpld` is published as DEB and RPM packages for 64-bit x86 Linux. Use APT on Debian-based distributions such as Debian and Ubuntu, -and YUM on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux. +and DNF on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux, +where `yum` is a symlink to `dnf`. To build from source instead, see [BUILD.md](../BUILD.md). ## Release channels @@ -81,7 +82,7 @@ wherever it appears in the repository configuration. sudo apt -y install xrpld ``` -### With the YUM package manager +### With the DNF package manager 1. Add the XRPL Foundation package-signing key: @@ -109,9 +110,23 @@ wherever it appears in the repository configuration. 3. Install the `xrpld` package: ```bash - sudo yum install -y xrpld + sudo dnf install -y xrpld ``` +### Optional: the assert-enabled build + +Every channel also carries `xrpld-assert` as a DEB, the same build with assertions +enabled, for diagnosing a problem on a non-production server. +It installs the same files as `xrpld` and replaces it, so install one or the other: + +```bash +sudo apt -y install xrpld-assert # APT removes xrpld itself +``` + +Switching stops the service, since it is a removal and an installation rather than an upgrade, +and APT starts it again. +Install `xrpld` the same way to switch back. + ## The xrpld service Both package managers install a systemd unit and enable it, so `xrpld` starts on boot. @@ -121,7 +136,7 @@ Check whether it is already running: systemctl status xrpld.service ``` -The APT packages start it immediately as well; the YUM packages do not, so start it yourself: +The DEB packages start it immediately as well; the RPM packages do not, so start it yourself: ```bash sudo systemctl start xrpld.service diff --git a/package/README.md b/package/README.md index 6e88309ecd..57db577029 100644 --- a/package/README.md +++ b/package/README.md @@ -15,7 +15,8 @@ package/ publish_pkg.py Uploads built packages to the XRPLF Nexus repositories (called by CI, and shipped in that image) rpm/ xrpld.spec RPM spec - debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, xrpld.lintian-overrides, source/format) + debian/ Debian control files (control.in, lintian-overrides.in, rules, copyright, docs, links, source/format). + The `.in` files are templates rendered by `build_pkg.py`; `docs` and `links` are staged under the package name shared/ xrpld.service systemd unit file (used by both RPM and DEB) xrpld.sysusers sysusers.d config (used by both RPM and DEB) @@ -32,20 +33,74 @@ packaging job cannot drift apart. Today only `linux/amd64` is emitted. The map pins the full container image in `image` — edit that field to move to a new image and both CI and local builds pick it up — and names the format that image builds in `type`, which CI passes to `build_pkg.py` as `--package-type`; the two -have to stay in step. +have to stay in step. An optional `variant` names a flavour of the package (see +[Package variants](#package-variants)), and CI passes it as `--variant`. | Package type | Image (`configs.[].package.image` in `linux.json`) | Tools required | | ------------ | ---------------------------------------------------------- | -------------------------------------------------------------- | | RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-` | `rpmbuild`, `rpmsign` | | DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-` | `dpkg-buildpackage`, debhelper with compat level 13, `lintian` | -To print the full packaging matrix (artifact names and images) for the current -`linux.json`: +To print the full packaging matrix (artifact names, images and package names) +for the current `linux.json`: ```bash ./.github/scripts/strategy-matrix/generate.py --packaging ``` +## Package variants + +A config whose binaries are not the plain release build cannot be packaged as +`xrpld`: both would carry the same name and version, so whichever published last +would win. It is packaged as a **variant** instead — `variant: "assert"` in its +`package` map, which CI passes to `build_pkg.py` as `--variant assert`, +producing `xrpld-assert`. What the build option itself does is a build concern, +not a packaging one; see the options table in [`BUILD.md`](../BUILD.md). + +A variant ships the same paths as `xrpld` — `/usr/bin/xrpld`, `/etc/xrpld`, +`xrpld.service`, `/etc/logrotate.d/xrpld` — differing only in the per-package +documentation directory, so it declares itself a stand-in for the plain package +rather than something installable next to it: `Conflicts`, `Replaces` and a +versioned `Provides: xrpld` on Debian, `Conflicts` and `Provides` on RPM. +Neither format declares `Obsoletes`, so `apt upgrade` and `dnf upgrade` keep an +installed flavour on its own flavour, and switching is always explicit: + +```bash +apt-get install xrpld-assert # apt removes the plain package itself +dnf swap xrpld xrpld-VARIANT # 'dnf install' alone stops at the conflict +``` + +Only the DEB packages carry a variant today — `xrpld-assert` comes from the +`debian` config alone, there being no call for an assert build on RHEL-based +distributions — but the RPM side works the same way if one is added. + +A switch is a removal plus an installation rather than an upgrade, so unlike a +version upgrade it stops the service: Debian's scriptlets start it again, while +on RPM the operator runs `systemctl start xrpld`. Configuration survives either +way, being conffiles on Debian and `%config(noreplace)` on RPM. + +`dnf` installs the replacement before erasing the old flavour, whose `%preun` +would leave `xrpld.service` disabled, so `%postun` re-applies the preset when +the unit file outlives the erase — which, since rpm keeps a file another +installed package owns, happens only during a swap. The cost is that a +deliberate `systemctl disable` is not carried across an RPM switch. + +The alternative is an `xrpld-common` package owning the unit, the sysusers and +tmpfiles snippets and the configuration, required by both flavours at an exact +version: nothing is erased mid-swap, so no scriptlet has to detect one. It is +not worth it for a single variant — it moves files out of the production +package, and a sanitizer flavour would likely need its own unit anyway, putting +the lifecycle back where it is now. + +Adding a variant is the flavour in `VARIANTS` in `build_pkg.py`, which is the +list `--variant` accepts, plus a config in `linux.json` with the CMake arguments +and a `package` map naming it, for one format or for both: `generate.py +--packaging` emits the package names per format, and the `test-install-deb` and +`test-install-rpm` jobs install what their own format produced. + +Operators switch between the flavours as described in +[`docs/install.md`](../docs/install.md#optional-the-assert-enabled-build). + ## Building packages ### Via CI @@ -56,9 +111,11 @@ Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call 1. `package` fans out one job per config carrying a `package` map, building and signing in that config's container, and uploading `-pkg` alongside `-pkg-debug` for the much larger debug symbols. -2. `test-install` installs `-pkg` in the container of every distro the - packages target and runs the binaries there, so one that cannot be installed - never reaches Nexus. +2. `test-install-deb` and `test-install-rpm` call + [`reusable-package-test-install.yml`](../.github/workflows/reusable-package-test-install.yml) + with their format's package names and distro images, installing each package + in the container of every distro that format targets and running the binaries + there, so one that cannot be installed never reaches Nexus. 3. `publish` uploads both artifacts, or lists what it would upload. The packaging script derives the package version from the downloaded binary's @@ -104,6 +161,9 @@ docker run --rm \ # build/rpmbuild/RPMS/x86_64/*.rpm ``` +Add `--variant assert` to package binaries built with `-Dassert=ON`; the package +is then named `xrpld-assert`. + ### Via CMake (host-side target) If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage` @@ -133,6 +193,9 @@ The package version is not a CMake input on this path: `build_pkg.py` 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`. +`-Dassert=ON` passes `--variant assert`, so such a build packages as +`xrpld-assert` without anything else being asked for. + ## Publishing packages Packages are published to the XRPLF repositories on Sonatype Nexus at @@ -147,6 +210,9 @@ the event, and `publish_pkg.py` maps that channel to its repositories: | push to `develop` | `xrpld --version` | `develop` | `deb-develop` | `rpm-develop-hosted` | | tag, non-public codebase | _any_ | `private` | `deb-private` | `rpm-private-hosted` | +A variant is published to the same channel under its own name, so +`xrpld-assert` never overwrites `xrpld`. + Only a tag names a channel — do not extend that to `develop`, where `BuildInfo.cpp`'s `versionString` moves through `-bN`, `-rcN` and even the final version during a release cycle, which would send develop builds into `stable`. @@ -160,7 +226,7 @@ the last, and the date and hash say which commit a package on `packages.xrplf.org` came from. Both reach the packaging scripts as arguments, so neither script derives anything itself. -Publishing is its own job, gated behind `test-install`, uploading from the same +Publishing is its own job, gated behind the install tests, uploading from the same image that built the packages with the `publish_pkg.py` shipped in it — the same copy other repositories run. Without `publish: true` the job is a `--dry-run`, listing the uploads it would make without needing credentials, so @@ -175,7 +241,7 @@ Nexus owns the repository metadata; nothing here indexes anything. Worth knowing - Each apt-hosted repository needs a distribution (ours use `any`) and a PGP signing keypair configured in Nexus, which rejects one created without a keypair. Nexus signs the apt metadata with it, never the packages. -- Hosted yum repositories cannot be signed by Nexus, so each `rpm--hosted` +- yum-hosted repositories cannot be signed by Nexus, so each `rpm--hosted` repository sits behind a `rpm-` yum group repository whose metadata Nexus signs. Uploads go to the hosted repository; clients point at the group and verify the metadata with `repo_gpgcheck=1`. Nexus never signs the RPMs @@ -244,6 +310,19 @@ 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. +`--variant` is the flavour of the package, empty by default and accepting only +the flavours in `VARIANTS`; see [Package variants](#package-variants). The RPM +path passes it to the spec as the `pkg_variant` macro, which suffixes `Name` and +adds the `Conflicts`/`Provides` pair. Debian control files have no conditionals, so the DEB path renders +`debian/control.in` and `debian/lintian-overrides.in` instead, substituting +`@PKG@` with the package name and `@VARIANT_FIELDS@` with the +`Conflicts`/`Replaces`/`Provides` block, empty for the plain package; a token +with no value fails the build rather than reaching dpkg. The files debhelper +keys by package name (`docs`, `links`, and the units) are staged under that same +name. The paths inside the package are unchanged either way, so `debian/rules` +reads its package name from `dh_listpackages` and names the unit, sysusers, +tmpfiles and logrotate files with `--name xrpld`. + The package format is `--package-type`, either `deb` or `rpm`. It is required, so a job never silently builds the wrong format for the image it runs in; the matching build tool still has to be on PATH. @@ -286,8 +365,13 @@ service restart. 1. Creates a staging source tree at `debbuild/source/` inside the build directory. 2. Stages the binaries, configs, `README.md`, `LICENSE.md`, and `validator-keys-LICENSE`. -3. Copies `package/debian/` control files into `debbuild/source/debian/`. -4. Copies shared service/sysusers/tmpfiles/logrotate into `debian/` where `dh_installsystemd`, `dh_installsysusers`, `dh_installtmpfiles` and `dh_installlogrotate` pick them up automatically. +3. Stages `package/debian/` into `debbuild/source/debian/`: the `.in` templates + are rendered, and the files debhelper keys by package name (`docs`, `links`, + `lintian-overrides`) are staged under the name being built. +4. Copies shared service/sysusers/tmpfiles/logrotate into `debian/` as + `.xrpld.*`, which `dh_installsystemd`, `dh_installsysusers`, + `dh_installtmpfiles` and `dh_installlogrotate` read because `debian/rules` + passes them `--name xrpld`. 5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`, 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. diff --git a/package/build_pkg.py b/package/build_pkg.py index 1aaf53d5ff..77ef8f3120 100755 --- a/package/build_pkg.py +++ b/package/build_pkg.py @@ -21,6 +21,14 @@ SRC_DIR = Path(__file__).resolve().parents[1] PRE_RELEASE = re.compile(r"^(b|rc)(0|[1-9][0-9]*)(\+.*)?$") +# The package name a variant suffixes, and the name every variant keeps for its +# on-disk paths (/usr/bin/xrpld, /etc/xrpld, xrpld.service). +BASE_NAME = "xrpld" + +# The flavours that can be built, '' being the plain xrpld package. A variant +# needs a config in linux.json to be built by CI; see package/README.md. +VARIANTS = ("", "assert") + # Files both packaging systems consume, staged under the same names. STAGED_FROM_BUILD = ("xrpld", "validator-keys", "validator-keys-LICENSE") STAGED_FROM_SRC = { @@ -31,6 +39,18 @@ STAGED_FROM_SRC = { } STAGED_UNITS = ("xrpld.service", "xrpld.sysusers", "xrpld.tmpfiles", "xrpld.logrotate") +# debian/ files debhelper keys by package name, staged as '.'. +DEBIAN_PKG_FILES = ("docs", "links") + +# Debian control files have no conditionals, so what makes a variant replace the +# plain package is rendered into control.in rather than written there. +DEB_VARIANT_FIELDS = """\ +Conflicts: xrpld +Replaces: xrpld +Provides: xrpld (= ${binary:Version})""" + +TOKEN = re.compile(r"@[A-Z_]+@") + def run(*command: object, cwd: Path | None = None) -> None: """Echo a command and run it.""" @@ -75,6 +95,28 @@ def package_version(reported: str) -> str: return version +def render(template: Path, dest: Path, values: dict[str, str]) -> None: + """Write template to dest with its @TOKEN@ placeholders substituted. + + A token left without a value fails the build rather than reaching dpkg. + """ + text = template.read_text() + for token, value in values.items(): + text = text.replace(f"@{token}@", value) + + missing = sorted(set(TOKEN.findall(text))) + assert not missing, f"{template}: no value for {', '.join(missing)}" + + # An empty value at the end of a stanza would otherwise leave a blank line, + # which is what ends a stanza. + dest.write_text(text.rstrip("\n") + "\n") + + +def package_name(variant: str) -> str: + """The binary package name for a variant: '' -> xrpld, 'assert' -> xrpld-assert.""" + return f"{BASE_NAME}-{variant}" if variant else BASE_NAME + + def read_version(xrpld: Path) -> str: """Read the version from the binary that is about to be packaged.""" fields = capture(xrpld, "--version").partition("\n")[0].split() @@ -135,17 +177,18 @@ def stage_common(build_dir: Path, dest: Path) -> None: shutil.copy2(SRC_DIR / source, dest / name) -def stage_units(dest: Path) -> None: +def stage_units(dest: Path, *, prefix: str = "") -> None: """Copy the systemd, sysusers, tmpfiles and logrotate files into dest. - Each format wants them somewhere else: rpmbuild reads them from SOURCES, - debhelper from debian/. + Each format wants them somewhere else: rpmbuild reads them from SOURCES by + path, debhelper from debian/ by package name -- hence 'prefix', which makes + the copies 'xrpld-assert.xrpld.service' and so on. """ for name in STAGED_UNITS: - shutil.copy2(SRC_DIR / "package" / "shared" / name, dest / name) + shutil.copy2(SRC_DIR / "package" / "shared" / name, dest / f"{prefix}{name}") -def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None: +def build_rpm(build_dir: Path, *, version: str, pkg_release: str, variant: str) -> None: """Stage the spec and its sources, then build the binary RPMs.""" topdir = build_dir / "rpmbuild" for name in ("BUILD", "BUILDROOT", "RPMS", "SOURCES", "SPECS", "SRPMS"): @@ -156,6 +199,9 @@ def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None: stage_common(build_dir, topdir / "SOURCES") stage_units(topdir / "SOURCES") + # The spec defaults it to nothing, so a plain build is unchanged. + variant_defines = ["--define", f"pkg_variant {variant}"] if variant else [] + run( "rpmbuild", "-bb", @@ -168,10 +214,29 @@ def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None: # The image tracks the newest distro, but the packages target el9. "--define", "dist .el9", + *variant_defines, spec, ) +def stage_debian(dest: Path, name: str) -> None: + """Stage the debian directory for the package name being built.""" + source = SRC_DIR / "package" / "debian" + shutil.copytree( + source, dest, ignore=shutil.ignore_patterns("*.in", *DEBIAN_PKG_FILES) + ) + + values = { + "PKG": name, + "VARIANT_FIELDS": "" if name == BASE_NAME else DEB_VARIANT_FIELDS, + } + render(source / "control.in", dest / "control", values) + render(source / "lintian-overrides.in", dest / f"{name}.lintian-overrides", values) + + for suffix in DEBIAN_PKG_FILES: + shutil.copy2(source / suffix, dest / f"{name}.{suffix}") + + def build_deb( build_dir: Path, *, @@ -180,21 +245,23 @@ def build_deb( pkg_release: str, channel: str, epoch: int, + name: str, ) -> None: """Stage the debian directory and its sources, then build the binary DEBs.""" staging = build_dir / "debbuild" / "source" stage_common(build_dir, staging) - shutil.copytree(SRC_DIR / "package" / "debian", staging / "debian") + stage_debian(staging / "debian", name) - # debhelper picks these up from debian/ automatically. - stage_units(staging / "debian") + # Prefixed whether it is a variant's name or not: debian/rules names them + # explicitly either way. + stage_units(staging / "debian", prefix=f"{name}.") date = datetime.fromtimestamp(epoch, timezone.utc).strftime( "%a, %d %b %Y %H:%M:%S %z" ) # The leading spaces are significant to dpkg. changelog = textwrap.dedent(f"""\ - xrpld ({version}-{pkg_release}) {channel}; urgency=medium + {name} ({version}-{pkg_release}) {channel}; urgency=medium * Release {reported}. -- XRPL Foundation {date} @@ -223,6 +290,14 @@ def main() -> None: default="1", help="package release iteration (default: %(default)s)", ) + parser.add_argument( + "--variant", + default="", + choices=VARIANTS, + help="the flavour of the package to build: 'assert' produces " + "xrpld-assert, which ships the same paths as xrpld and replaces it " + "(default: the plain xrpld package)", + ) parser.add_argument( "--channel", required=True, @@ -234,6 +309,8 @@ def main() -> None: build_dir: Path = args.build_dir.resolve() pkg_release: str = args.pkg_release channel: str = args.channel + variant: str = args.variant + name = package_name(variant) assert build_dir.is_dir(), ( f"build directory not found: {build_dir}. Build the binaries before " @@ -253,6 +330,8 @@ def main() -> None: for tree in ("debbuild", "rpmbuild"): shutil.rmtree(build_dir / tree, ignore_errors=True) + print(f"Building {package_type} {name} {version}-{pkg_release}", flush=True) + if package_type == "deb": build_deb( build_dir, @@ -261,9 +340,10 @@ def main() -> None: pkg_release=pkg_release, channel=channel, epoch=epoch, + name=name, ) else: - build_rpm(build_dir, version=version, pkg_release=pkg_release) + build_rpm(build_dir, version=version, pkg_release=pkg_release, variant=variant) if __name__ == "__main__": diff --git a/package/debian/control b/package/debian/control.in similarity index 93% rename from package/debian/control rename to package/debian/control.in index 359f39f770..20486efc9a 100644 --- a/package/debian/control +++ b/package/debian/control.in @@ -1,4 +1,4 @@ -Source: xrpld +Source: @PKG@ Section: net Priority: optional Maintainer: XRPL Foundation @@ -11,7 +11,7 @@ Homepage: https://github.com/XRPLF/rippled Vcs-Git: https://github.com/XRPLF/rippled.git Vcs-Browser: https://github.com/XRPLF/rippled -Package: xrpld +Package: @PKG@ Architecture: any Depends: ${shlibs:Depends}, @@ -22,3 +22,4 @@ Description: XRP Ledger daemon transactions, and maintains the ledger database. This package also includes the validator-keys tool for validator key management. +@VARIANT_FIELDS@ diff --git a/package/debian/xrpld.docs b/package/debian/docs similarity index 100% rename from package/debian/xrpld.docs rename to package/debian/docs diff --git a/package/debian/xrpld.links b/package/debian/links similarity index 100% rename from package/debian/xrpld.links rename to package/debian/links diff --git a/package/debian/lintian-overrides.in b/package/debian/lintian-overrides.in new file mode 100644 index 0000000000..5e72a5ef6b --- /dev/null +++ b/package/debian/lintian-overrides.in @@ -0,0 +1,6 @@ +# The /usr/local/bin/rippled symlink is deliberate compatibility for pre-FHS +# layouts, so the Policy 9.1.2 tags it raises are expected. +# TODO: remove alongside debian/links after rippled fully deprecated. +@PKG@: dir-in-usr-local [usr/local/bin/] +@PKG@: file-in-usr-local [usr/local/bin/rippled] +@PKG@: file-in-unusual-dir [usr/local/bin/rippled] diff --git a/package/debian/rules b/package/debian/rules index dd6d1e66b9..bc12f54218 100755 --- a/package/debian/rules +++ b/package/debian/rules @@ -8,33 +8,58 @@ export DH_VERBOSE = 1 # the binaries actually run on. LIBC_MIN = 2.31 +# The binary package's name, which a variant build changes to e.g. xrpld-assert, +# and the directory debhelper expects its files staged in. +PKG := $(firstword $(shell dh_listpackages)) +PKG_DIR = debian/$(PKG) + +# The base name, which every package ships under whatever it is called itself. +BASE_NAME = xrpld + +# What build_pkg.py stages beside this directory, each installed under its own +# name. The binaries are also the ones checked against LIBC_MIN below. +BINARIES = $(BASE_NAME) validator-keys +CONFIGS = $(BASE_NAME).cfg validators.txt + %: dh $@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test: @: +# The unit, sysusers, tmpfiles and logrotate files are named after the daemon +# rather than after the package, so a variant still ships xrpld.service and +# /etc/logrotate.d/xrpld. debhelper only reads debian/$(PKG).$(BASE_NAME).* when told +# the name. override_dh_installsystemd: - dh_installsystemd --no-stop-on-upgrade xrpld.service + dh_installsystemd --no-stop-on-upgrade --name $(BASE_NAME) # The tmpfiles snippet sets ownership to the xrpld user, so the sysusers snippet # has to be emitted first: run it early and make its own sequence slot a no-op. execute_before_dh_installtmpfiles: - dh_installsysusers + dh_installsysusers --name $(BASE_NAME) override_dh_installsysusers: +override_dh_installtmpfiles: + dh_installtmpfiles --name $(BASE_NAME) + +override_dh_installlogrotate: + dh_installlogrotate --name $(BASE_NAME) + override_dh_install: - install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld - install -D -m 0755 validator-keys debian/xrpld/usr/bin/validator-keys - install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg - install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt + for binary in $(BINARIES); do \ + install -D -m 0755 "$$binary" "$(PKG_DIR)/usr/bin/$$binary"; \ + done + for config in $(CONFIGS); do \ + install -D -m 0644 "$$config" "$(PKG_DIR)/etc/$(BASE_NAME)/$$config"; \ + done override_dh_shlibdeps: dh_shlibdeps # Guards against the toolchain moving past LIBC_MIN and the packages then # claiming a floor they do not meet. - for binary in xrpld validator-keys; do \ + for binary in $(BINARIES); do \ needed=$$(readelf --dyn-syms --wide $$binary \ | grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -1); \ if [ -z "$$needed" ]; then \ @@ -46,7 +71,7 @@ override_dh_shlibdeps: exit 1; \ fi; \ done - sed -i 's/libc6 (>= [0-9.]*)/libc6 (>= $(LIBC_MIN))/' debian/xrpld.substvars + sed -i 's/libc6 (>= [0-9.]*)/libc6 (>= $(LIBC_MIN))/' debian/$(PKG).substvars override_dh_dwz: @: diff --git a/package/debian/xrpld.lintian-overrides b/package/debian/xrpld.lintian-overrides deleted file mode 100644 index a0b3f583ed..0000000000 --- a/package/debian/xrpld.lintian-overrides +++ /dev/null @@ -1,6 +0,0 @@ -# The /usr/local/bin/rippled symlink is deliberate compatibility for pre-FHS -# layouts, so the Policy 9.1.2 tags it raises are expected. -# TODO: remove alongside debian/xrpld.links after rippled fully deprecated. -xrpld: dir-in-usr-local [usr/local/bin/] -xrpld: file-in-usr-local [usr/local/bin/rippled] -xrpld: file-in-unusual-dir [usr/local/bin/rippled] diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec index 5139cd54e5..45e7a78e42 100644 --- a/package/rpm/xrpld.spec +++ b/package/rpm/xrpld.spec @@ -6,10 +6,14 @@ %{error:pkg_release must be defined} %endif -Name: xrpld +# The base name, which every package ships under. A variant build +# (build_pkg.py --variant) only suffixes the package name, e.g. xrpld-assert. +%global base_name xrpld + +Name: %{base_name}%{?pkg_variant:-%{pkg_variant}} Version: %{pkg_version} Release: %{pkg_release}%{?dist} -Summary: XRP Ledger daemon +Summary: XRP Ledger daemon%{?pkg_variant: (%{pkg_variant} build)} License: ISC URL: https://github.com/XRPLF/rippled @@ -17,6 +21,12 @@ URL: https://github.com/XRPLF/rippled ExclusiveArch: x86_64 aarch64 BuildRequires: systemd-rpm-macros +# A variant owns the same paths, so it stands in for the plain package. +%if "%{?pkg_variant}" != "" +Conflicts: %{base_name} +Provides: %{base_name} = %{version}-%{release} +%endif + # These have to precede %%debug_package: it opens the debuginfo subpackage, and # any tag after it is silently dropped from the main package. %{?systemd_requires} @@ -52,22 +62,22 @@ management. : %install -install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name} +install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{base_name} install -Dm0755 %{_sourcedir}/validator-keys %{buildroot}%{_bindir}/validator-keys -install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg -install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt +install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{base_name}/xrpld.cfg +install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{base_name}/validators.txt # systemd units, sysusers, tmpfiles, preset 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 -d %{buildroot}%{_presetdir} -cat >%{buildroot}%{_presetdir}/50-xrpld.preset <<'EOF' +cat >%{buildroot}%{_presetdir}/50-%{base_name}.preset <<'EOF' enable xrpld.service EOF # Logrotate config -install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name} +install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{base_name} # Docs install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md @@ -78,13 +88,13 @@ install -Dm0644 %{_sourcedir}/validator-keys-LICENSE %{buildroot}%{_docdir}/%{na # Legacy compatibility for pre-FHS package layouts. # TODO: remove after rippled fully deprecated. install -d %{buildroot}/usr/local/bin -ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled +ln -s %{_bindir}/%{base_name} %{buildroot}/usr/local/bin/rippled %pre -%sysusers_create_package %{name} %{_sourcedir}/xrpld.sysusers +%sysusers_create_package %{base_name} %{_sourcedir}/xrpld.sysusers %post -%tmpfiles_create_package %{name} %{_sourcedir}/xrpld.tmpfiles +%tmpfiles_create_package %{base_name} %{_sourcedir}/xrpld.tmpfiles %systemd_post xrpld.service %preun @@ -92,6 +102,13 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled %postun %systemd_postun xrpld.service +# A flavour swap installs the replacement before erasing this package, so the +# %%preun above has just disabled a unit the replacement still owns. rpm keeps a +# file that another installed package owns, so the unit outliving our own erase +# means exactly that; a plain erase takes it with us and re-presets nothing. +if [ $1 -eq 0 ] && [ -f %{_unitdir}/xrpld.service ]; then + systemctl preset xrpld.service >/dev/null 2>&1 || : +fi %files %attr(0755,root,root) %dir %{_docdir}/%{name} @@ -99,18 +116,18 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled %license %{_docdir}/%{name}/validator-keys-LICENSE %doc %{_docdir}/%{name}/README.md -%attr(0755,root,root) %dir %{_sysconfdir}/%{name} +%attr(0755,root,root) %dir %{_sysconfdir}/%{base_name} -%{_bindir}/%{name} +%{_bindir}/%{base_name} %{_bindir}/validator-keys -%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg -%config(noreplace) %{_sysconfdir}/%{name}/validators.txt -%config(noreplace) %{_sysconfdir}/logrotate.d/%{name} +%config(noreplace) %{_sysconfdir}/%{base_name}/xrpld.cfg +%config(noreplace) %{_sysconfdir}/%{base_name}/validators.txt +%config(noreplace) %{_sysconfdir}/logrotate.d/%{base_name} %{_unitdir}/xrpld.service -%attr(0644,root,root) %{_presetdir}/50-xrpld.preset +%attr(0644,root,root) %{_presetdir}/50-%{base_name}.preset %{_sysusersdir}/xrpld.conf %{_tmpfilesdir}/xrpld.conf %ghost %dir /var/lib/xrpld