diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 4784142b7b..9b2315af60 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -99,14 +99,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: continue # RHEL: - # - 9 using GCC 12: Debug on linux/amd64. + # - 9 using GCC 12: Debug and Release on linux/amd64 + # (Release is required for RPM packaging). # - 10 using Clang: Release on linux/amd64. if os["distro_name"] == "rhel": skip = True if os["distro_version"] == "9": if ( f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12" - and build_type == "Debug" + and build_type in ["Debug", "Release"] and architecture["platform"] == "linux/amd64" ): skip = False @@ -121,7 +122,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: continue # Ubuntu: - # - Jammy using GCC 12: Debug on linux/arm64. + # - Jammy using GCC 12: Debug on linux/arm64, Release on + # linux/amd64 (Release is required for DEB packaging). # - Noble using GCC 14: Release on linux/amd64. # - Noble using Clang 18: Debug on linux/amd64. # - Noble using Clang 19: Release on linux/arm64. @@ -134,6 +136,12 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: and architecture["platform"] == "linux/arm64" ): skip = False + if ( + f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12" + and build_type == "Release" + and architecture["platform"] == "linux/amd64" + ): + skip = False elif os["distro_version"] == "noble": if ( f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14" diff --git a/.github/workflows/manual-package.yml b/.github/workflows/manual-package.yml new file mode 100644 index 0000000000..cdedacc1ad --- /dev/null +++ b/.github/workflows/manual-package.yml @@ -0,0 +1,66 @@ +name: Manual Package Build + +on: + workflow_dispatch: + inputs: + pkg_type: + description: "Package type" + required: true + type: choice + options: + - deb + - rpm + - both + artifact_run_id: + description: "Run ID to download binary artifact from (leave empty for latest on this branch)" + required: false + type: string + version: + description: "Version override (leave empty to auto-detect)" + required: false + type: string + pkg_release: + description: "Package release number (default: 1)" + required: false + type: string + default: "1" + +defaults: + run: + shell: bash + +jobs: + generate-version: + runs-on: ubuntu-latest + outputs: + version: ${{ inputs.version || steps.version.outputs.version }} + steps: + - name: Checkout repository + if: ${{ !inputs.version }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Generate version + if: ${{ !inputs.version }} + id: version + uses: ./.github/actions/generate-version + + package-deb: + if: ${{ inputs.pkg_type == 'deb' || inputs.pkg_type == 'both' }} + needs: generate-version + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: deb + artifact_name: xrpld-ubuntu-jammy-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + pkg_release: ${{ inputs.pkg_release }} + container_image: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 + + package-rpm: + if: ${{ inputs.pkg_type == 'rpm' || inputs.pkg_type == 'both' }} + needs: generate-version + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: rpm + artifact_name: xrpld-rhel-9-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + pkg_release: ${{ inputs.pkg_release }} + container_image: ghcr.io/xrplf/ci/rhel-9:gcc-12 diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 28299a1264..b741a0a732 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -67,6 +67,7 @@ jobs: .github/workflows/reusable-build-test.yml .github/workflows/reusable-clang-tidy.yml .github/workflows/reusable-clang-tidy-files.yml + .github/workflows/reusable-package.yml .github/workflows/reusable-strategy-matrix.yml .github/workflows/reusable-test.yml .github/workflows/reusable-upload-recipe.yml @@ -81,6 +82,8 @@ jobs: CMakeLists.txt conanfile.py conan.lock + package/** + - name: Check whether to run # This step determines whether the rest of the workflow should # run. The rest of the workflow will run if this job runs AND at @@ -137,6 +140,39 @@ jobs: secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + generate-version: + needs: should-run + if: ${{ needs.should-run.outputs.go == 'true' }} + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Generate version + id: version + uses: ./.github/actions/generate-version + + package-deb: + needs: [should-run, build-test, generate-version] + if: ${{ needs.should-run.outputs.go == 'true' }} + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: deb + artifact_name: xrpld-ubuntu-jammy-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + container_image: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 + + package-rpm: + needs: [should-run, build-test, generate-version] + if: ${{ needs.should-run.outputs.go == 'true' }} + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: rpm + artifact_name: xrpld-rhel-9-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + container_image: ghcr.io/xrplf/ci/rhel-9:gcc-12 + upload-recipe: needs: - should-run diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index e570a0e119..1b99d60542 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -1,5 +1,5 @@ -# This workflow uploads the libxrpl recipe to the Conan remote when a versioned -# tag is pushed. +# This workflow uploads the libxrpl recipe to the Conan remote and builds +# release packages when a versioned tag is pushed. name: Tag on: @@ -22,3 +22,49 @@ jobs: secrets: remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }} + + build-test: + if: ${{ github.repository == 'XRPLF/rippled' }} + uses: ./.github/workflows/reusable-build-test.yml + strategy: + fail-fast: true + matrix: + os: [linux] + with: + ccache_enabled: false + os: ${{ matrix.os }} + strategy_matrix: minimal + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + generate-version: + if: ${{ github.repository == 'XRPLF/rippled' }} + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Generate version + id: version + uses: ./.github/actions/generate-version + + package-deb: + needs: [build-test, generate-version] + if: ${{ github.repository == 'XRPLF/rippled' }} + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: deb + artifact_name: xrpld-ubuntu-jammy-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + container_image: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 + + package-rpm: + needs: [build-test, generate-version] + if: ${{ github.repository == 'XRPLF/rippled' }} + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: rpm + artifact_name: xrpld-rhel-9-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + container_image: ghcr.io/xrplf/ci/rhel-9:gcc-12 diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 5856c67bd3..958d093958 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -38,6 +38,8 @@ on: - "CMakeLists.txt" - "conanfile.py" - "conan.lock" + - "package/**" + - ".github/workflows/reusable-package.yml" # Run at 06:32 UTC on every day of the week from Monday through Friday. This # will force all dependencies to be rebuilt, which is useful to verify that @@ -77,7 +79,7 @@ jobs: strategy: fail-fast: ${{ github.event_name == 'merge_group' }} matrix: - os: [linux, macos, windows] + os: [linux] with: # Enable ccache only for events targeting the XRPLF repository, since # other accounts will not have access to our remote cache storage. @@ -98,3 +100,32 @@ jobs: secrets: remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }} + + generate-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Generate version + id: version + uses: ./.github/actions/generate-version + + package-deb: + needs: [build-test, generate-version] + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: deb + artifact_name: xrpld-ubuntu-jammy-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + container_image: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 + + package-rpm: + needs: [build-test, generate-version] + uses: ./.github/workflows/reusable-package.yml + with: + pkg_type: rpm + artifact_name: xrpld-rhel-9-gcc-12-amd64-release + version: ${{ needs.generate-version.outputs.version }} + container_image: ghcr.io/xrplf/ci/rhel-9:gcc-12 diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml new file mode 100644 index 0000000000..3bb3687221 --- /dev/null +++ b/.github/workflows/reusable-package.yml @@ -0,0 +1,76 @@ +# Build a Linux package (DEB or RPM) from a pre-built binary artifact. +name: Package + +on: + workflow_call: + inputs: + pkg_type: + description: "Package type to build: deb or rpm." + required: true + type: string + artifact_name: + description: "Name of the pre-built binary artifact to download." + required: true + type: string + version: + description: "Version string used for naming the output artifact." + required: true + type: string + pkg_release: + description: "Package release number. Increment when repackaging the same executable." + required: false + type: string + default: "1" + container_image: + description: "Container image to use for packaging." + required: true + type: string + +defaults: + run: + shell: bash + +env: + BUILD_DIR: build + +jobs: + package: + name: ${{ inputs.pkg_type }} (${{ inputs.version }}) + runs-on: ["self-hosted", "Linux", "X64", "heavy"] + container: ${{ inputs.container_image }} + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download pre-built binary + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact_name }} + path: ${{ env.BUILD_DIR }} + + - name: Make binary executable + run: chmod +x ${{ env.BUILD_DIR }}/xrpld + + - name: Generate RPM spec from template + if: ${{ inputs.pkg_type == 'rpm' }} + run: | + mkdir -p ${{ env.BUILD_DIR }}/package/rpm + sed -e "s/@xrpld_version@/${{ inputs.version }}/" \ + -e "s/@pkg_release@/${{ inputs.pkg_release }}/" \ + package/rpm/xrpld.spec.in > ${{ env.BUILD_DIR }}/package/rpm/xrpld.spec + + - name: Build package + run: | + ./package/build_pkg.sh ${{ inputs.pkg_type }} . ${{ env.BUILD_DIR }} "${{ inputs.version }}" "${{ inputs.pkg_release }}" + + - name: Upload package artifact + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: xrpld-${{ inputs.pkg_type }}-${{ inputs.version }} + path: | + ${{ env.BUILD_DIR }}/debbuild/*.deb + ${{ env.BUILD_DIR }}/debbuild/*.ddeb + ${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm + if-no-files-found: error diff --git a/CMakeLists.txt b/CMakeLists.txt index 80ff8fec13..d315a5dcec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ endif() include(XrplCore) include(XrplProtocolAutogen) include(XrplInstall) +include(XrplPackaging) include(XrplValidatorKeys) if(tests) diff --git a/cmake/XrplInstall.cmake b/cmake/XrplInstall.cmake index 339cdb51ec..c51151aee7 100644 --- a/cmake/XrplInstall.cmake +++ b/cmake/XrplInstall.cmake @@ -12,14 +12,14 @@ if(is_root_project AND TARGET xrpld) install( FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg" - DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld" + DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}" RENAME xrpld.cfg COMPONENT runtime ) install( FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt" - DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld" + DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}" RENAME validators.txt COMPONENT runtime ) diff --git a/cmake/XrplPackaging.cmake b/cmake/XrplPackaging.cmake new file mode 100644 index 0000000000..2947618b78 --- /dev/null +++ b/cmake/XrplPackaging.cmake @@ -0,0 +1,147 @@ +#[===================================================================[ + Linux packaging support: RPM and Debian targets + install tests +#]===================================================================] + +if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/opt/xrpld") + message( + STATUS + "Packaging targets require -DCMAKE_INSTALL_PREFIX=/opt/xrpld " + "(current: '${CMAKE_INSTALL_PREFIX}'); skipping." + ) + return() +endif() + +# Generate the RPM spec from template (substitutes @xrpld_version@, @pkg_release@). +if(NOT DEFINED pkg_release) + set(pkg_release 1) +endif() +configure_file( + ${CMAKE_SOURCE_DIR}/package/rpm/xrpld.spec.in + ${CMAKE_BINARY_DIR}/package/rpm/xrpld.spec + @ONLY +) + +find_program(RPMBUILD_EXECUTABLE rpmbuild) +if(RPMBUILD_EXECUTABLE) + add_custom_target( + package-rpm + COMMAND + ${CMAKE_SOURCE_DIR}/package/build_pkg.sh rpm ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Building RPM package" + VERBATIM + ) +else() + message(STATUS "rpmbuild not found; 'package-rpm' target not available") +endif() + +find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage) +if(DPKG_BUILDPACKAGE_EXECUTABLE) + add_custom_target( + package-deb + COMMAND + ${CMAKE_SOURCE_DIR}/package/build_pkg.sh deb ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} ${xrpld_version} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Building Debian package" + VERBATIM + ) +else() + message( + STATUS + "dpkg-buildpackage not found; 'package-deb' target not available" + ) +endif() + +#[===================================================================[ + CTest fixtures for package install verification (requires docker) +#]===================================================================] + +find_program(DOCKER_EXECUTABLE docker) +if(NOT DOCKER_EXECUTABLE) + message(STATUS "docker not found; package install tests not available") + return() +endif() + +set(DEB_TEST_IMAGE "geerlingguy/docker-ubuntu2204-ansible:latest") +set(RPM_TEST_IMAGE "geerlingguy/docker-rockylinux9-ansible:latest") + +foreach(PKG deb rpm) + if(PKG STREQUAL "deb") + set(IMAGE ${DEB_TEST_IMAGE}) + else() + set(IMAGE ${RPM_TEST_IMAGE}) + endif() + + # Fixture: start container + add_test( + NAME ${PKG}_container_start + COMMAND + sh -c + "docker rm -f xrpld_${PKG}_install_test 2>/dev/null || true && \ + docker run --rm -d \ + --name xrpld_${PKG}_install_test \ + --memory=45g --memory-swap=45g \ + --privileged \ + --cgroupns host \ + --volume '${CMAKE_SOURCE_DIR}:/root:ro' \ + --volume /sys/fs/cgroup:/sys/fs/cgroup:rw \ + --tmpfs /tmp --tmpfs /run --tmpfs /run/lock \ + ${IMAGE} \ + /usr/sbin/init" + ) + set_tests_properties( + ${PKG}_container_start + PROPERTIES FIXTURES_SETUP ${PKG}_container LABELS packaging + ) + + # Fixture: stop container + # On CI: always stop. Locally: leave running on failure for diagnosis. + add_test( + NAME ${PKG}_container_stop + COMMAND + sh -c + "if [ -n \"$CI\" ] || ! docker exec xrpld_${PKG}_install_test test -f /tmp/test_failed 2>/dev/null; then \ + docker rm -f xrpld_${PKG}_install_test; \ + else \ + echo 'Tests failed — leaving xrpld_${PKG}_install_test running for diagnosis'; \ + echo 'Clean up with: docker rm -f xrpld_${PKG}_install_test'; \ + fi" + ) + set_tests_properties( + ${PKG}_container_stop + PROPERTIES FIXTURES_CLEANUP ${PKG}_container LABELS packaging + ) + + # Install package and run smoke test + add_test( + NAME ${PKG}_install + COMMAND + docker exec -w /root xrpld_${PKG}_install_test bash + /root/package/test/smoketest.sh local + ) + set_tests_properties( + ${PKG}_install + PROPERTIES + FIXTURES_REQUIRED ${PKG}_container + FIXTURES_SETUP ${PKG}_installed + LABELS packaging + TIMEOUT 600 + ) + + # Validate install paths and compat symlinks + add_test( + NAME ${PKG}_install_paths + COMMAND + docker exec -w /root xrpld_${PKG}_install_test sh + /root/package/test/check_install_paths.sh + ) + set_tests_properties( + ${PKG}_install_paths + PROPERTIES + FIXTURES_REQUIRED "${PKG}_container;${PKG}_installed" + LABELS packaging + TIMEOUT 60 + ) +endforeach() diff --git a/cspell.config.yaml b/cspell.config.yaml index 028f02191e..128b42364a 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -97,12 +97,15 @@ words: - desync - desynced - determ + - disablerepo - distro - doxyfile - dxrpl - enabled + - enablerepo - endmacro - exceptioned + - EXPECT_STREQ - Falco - fcontext - finalizers @@ -158,6 +161,7 @@ words: - Merkle - Metafuncton - misprediction + - missingok - mptbalance - MPTDEX - mptflags @@ -189,7 +193,9 @@ words: - NOLINT - NOLINTNEXTLINE - nonxrp + - noreplace - noripple + - notifempty - nudb - nullptr - nunl @@ -209,6 +215,7 @@ words: - preauthorize - preauthorizes - preclaim + - preun - protobuf - protos - ptrs @@ -243,12 +250,14 @@ words: - sfields - shamap - shamapitem + - shlibs - sidechain - SIGGOOD - sle - sles - soci - socidb + - SRPMS - sslws - statsd - STATSDCOLLECTOR @@ -276,8 +285,8 @@ words: - txn - txns - txs - - UBSAN - ubsan + - UBSAN - umant - unacquired - unambiguity @@ -314,7 +323,6 @@ words: - xbridge - xchain - ximinez - - EXPECT_STREQ - XMACRO - xrpkuwait - xrpl diff --git a/package/README.md b/package/README.md new file mode 100644 index 0000000000..11d1e12121 --- /dev/null +++ b/package/README.md @@ -0,0 +1,118 @@ +# Linux Packaging + +This directory contains all files needed to build RPM and Debian packages for `xrpld`. + +## Directory layout + +``` +package/ + build_pkg.sh Staging and build script (called by CMake targets and CI) + rpm/ + xrpld.spec.in RPM spec template (substitutes @xrpld_version@, @pkg_release@) + deb/ + debian/ Debian control files (control, rules, install, links, conffiles, ...) + 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 /opt/xrpld/bin/, user activates) + update-xrpld.sh auto-update script (installed to /opt/xrpld/bin/) + update-xrpld-cron cron entry for auto-update (installed to /opt/xrpld/bin/) + test/ + smoketest.sh Package install smoke test + check_install_paths.sh Verify install paths and compat symlinks +``` + +## Prerequisites + +| Package type | Container | Tool required | +| ------------ | -------------------------------------- | --------------------------------------------------------------- | +| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12` | `rpmbuild` | +| DEB | `ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` | + +## Building packages + +### Via CI (recommended) + +The `reusable-package.yml` workflow downloads a pre-built `xrpld` binary artifact +and calls `build_pkg.sh` directly. No CMake configure or build step is needed in +the packaging job. + +### Via CMake (local development) + +Configure with the required install prefix, then invoke the target: + +```bash +cmake \ + -DCMAKE_INSTALL_PREFIX=/opt/xrpld \ + -Dxrpld=ON \ + -Dtests=OFF \ + .. + +# RPM (in RHEL container): +cmake --build . --target package-rpm + +# DEB (in Debian/Ubuntu container): +cmake --build . --target package-deb +``` + +The `cmake/XrplPackaging.cmake` module gates each target on whether the required +tool (`rpmbuild` / `dpkg-buildpackage`) is present at configure time, so +configuring on a host that lacks one simply omits the corresponding target. + +`CMAKE_INSTALL_PREFIX` must be `/opt/xrpld`; if it is not, both targets are +skipped with a `STATUS` message. + +## How `build_pkg.sh` works + +`build_pkg.sh [version] [pkg_release]` stages +all files and invokes the platform build tool. It resolves `src_dir` and +`build_dir` to absolute paths, then calls `stage_common()` to copy the binary, +config files, and shared support files into the staging area. + +### RPM + +1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory. +2. Copies the generated `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`. +3. Runs `rpmbuild -bb`. The spec uses manual `install` commands to place files. +4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm` + +### 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/deb/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. +7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package) + +## Post-build verification + +```bash +# DEB +dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles' +lintian -I debbuild/*.deb + +# RPM +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: + +```bash +export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) +export TZ=UTC +export LC_ALL=C.UTF-8 +export GZIP=-n +export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath" +``` + +## TODO + +- Port debsigs signing instructions and integrate into CI. +- Port RPM GPG signing setup (key import + `%{?_gpg_sign}` in spec). +- Introduce a virtual package for key rotation. diff --git a/package/build_pkg.sh b/package/build_pkg.sh new file mode 100755 index 0000000000..045e08a334 --- /dev/null +++ b/package/build_pkg.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Build an RPM or Debian package from a pre-built xrpld binary. +# +# Usage: build_pkg.sh [version] [pkg_release] +# pkg_type : rpm | deb +# src_dir : path to repository root +# build_dir : directory containing the pre-built xrpld binary +# version : package version string (e.g. 2.4.0-b1) +# pkg_release : package release number (default: 1) + +set -euo pipefail + +PKG_TYPE="${1:?pkg_type required}" +SRC_DIR="$(cd "${2:?src_dir required}" && pwd)" +BUILD_DIR="$(cd "${3:?build_dir required}" && pwd)" +VERSION="${4:-1.0.0}" +PKG_RELEASE="${5:-1}" + +SHARED="${SRC_DIR}/package/shared" + +# Stage files common to both package types into a target directory. +stage_common() { + local dest="$1" + cp "${BUILD_DIR}/xrpld" "${dest}/xrpld" + cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg" + cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt" + cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate" + cp "${SHARED}/update-xrpld.sh" "${dest}/update-xrpld.sh" + cp "${SHARED}/update-xrpld-cron" "${dest}/update-xrpld-cron" +} + +build_rpm() { + local topdir="${BUILD_DIR}/rpmbuild" + mkdir -p "${topdir}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} + + cp "${BUILD_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec" + + stage_common "${topdir}/SOURCES" + cp "${SHARED}/xrpld.service" "${topdir}/SOURCES/xrpld.service" + cp "${SHARED}/xrpld.sysusers" "${topdir}/SOURCES/xrpld.sysusers" + cp "${SHARED}/xrpld.tmpfiles" "${topdir}/SOURCES/xrpld.tmpfiles" + + set -x + rpmbuild -bb \ + --define "_topdir ${topdir}" \ + "${topdir}/SPECS/xrpld.spec" +} + +build_deb() { + local staging="${BUILD_DIR}/debbuild/source" + rm -rf "${staging}" + mkdir -p "${staging}" + + stage_common "${staging}" + cp "${SRC_DIR}/README.md" "${staging}/" + cp "${SRC_DIR}/LICENSE.md" "${staging}/" + + # debian/ control files + cp -r "${SRC_DIR}/package/deb/debian" "${staging}/debian" + + # Shared support files for dh_installsystemd / sysusers / tmpfiles + cp "${SHARED}/xrpld.service" "${staging}/debian/xrpld.service" + cp "${SHARED}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers" + cp "${SHARED}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles" + + # Generate debian/changelog (pre-release versions use ~ instead of -). + local deb_version="${VERSION//-/\~}" + # TODO: Add facility for generating the changelog + cat > "${staging}/debian/changelog" < $(LC_ALL=C date -u -R) +EOF + + chmod +x "${staging}/debian/rules" + + set -x + cd "${staging}" + dpkg-buildpackage -b --no-sign -d +} + +case "${PKG_TYPE}" in + rpm) build_rpm ;; + deb) build_deb ;; + *) + echo "Unknown package type: ${PKG_TYPE}" >&2 + exit 1 + ;; +esac diff --git a/package/deb/debian/control b/package/deb/debian/control new file mode 100644 index 0000000000..30063c68fa --- /dev/null +++ b/package/deb/debian/control @@ -0,0 +1,33 @@ +Source: xrpld +Section: net +Priority: optional +Maintainer: XRPL Foundation +Rules-Requires-Root: no +Build-Depends: + debhelper-compat (= 13), +Standards-Version: 4.7.0 +Homepage: https://github.com/XRPLF/rippled +Vcs-Git: https://github.com/XRPLF/rippled.git +Vcs-Browser: https://github.com/XRPLF/rippled + +Package: xrpld +Section: net +Priority: optional +Architecture: any +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: XRP Ledger daemon + xrpld is the reference implementation of the XRP Ledger protocol. + It participates in the peer-to-peer XRP Ledger network, processes + transactions, and maintains the ledger database. + +Package: rippled +Architecture: all +Section: oldlibs +Priority: optional +Depends: xrpld, ${misc:Depends} +Description: transitional package - use xrpld + The rippled package has been renamed to xrpld. This transitional + package ensures a smooth upgrade and can be safely removed after + xrpld is installed. diff --git a/package/deb/debian/copyright b/package/deb/debian/copyright new file mode 100644 index 0000000000..9ae66166c5 --- /dev/null +++ b/package/deb/debian/copyright @@ -0,0 +1,20 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: rippled +Source: https://github.com/XRPLF/rippled + +Files: * +Copyright: 2012-2025 Ripple Labs Inc. +License: ISC + +License: ISC + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + . + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/package/deb/debian/rules b/package/deb/debian/rules new file mode 100644 index 0000000000..4ab4111951 --- /dev/null +++ b/package/deb/debian/rules @@ -0,0 +1,37 @@ +#!/usr/bin/make -f + +export DH_VERBOSE = 1 +export DH_OPTIONS = -v + +%: + dh $@ + +override_dh_auto_configure override_dh_auto_build override_dh_auto_test: + @: + +override_dh_auto_install: + install -Dm0755 xrpld debian/tmp/opt/xrpld/bin/xrpld + install -Dm0644 xrpld.cfg debian/tmp/opt/xrpld/etc/xrpld.cfg + install -Dm0644 validators.txt debian/tmp/opt/xrpld/etc/validators.txt + install -Dm0644 xrpld.logrotate debian/tmp/opt/xrpld/bin/xrpld.logrotate + install -Dm0755 update-xrpld.sh debian/tmp/opt/xrpld/bin/update-xrpld.sh + install -Dm0644 update-xrpld-cron debian/tmp/opt/xrpld/bin/update-xrpld-cron + install -Dm0644 README.md debian/tmp/usr/share/doc/xrpld/README.md + install -Dm0644 LICENSE.md debian/tmp/usr/share/doc/xrpld/LICENSE.md + +override_dh_installsystemd: + dh_installsystemd +# see if this still works +# dh_installsystemd --no-start + +override_dh_installsysusers: + dh_installsysusers + +override_dh_installtmpfiles: + dh_installtmpfiles + +override_dh_install: + dh_install + +override_dh_dwz: + @: diff --git a/package/deb/debian/source/format b/package/deb/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/package/deb/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/package/deb/debian/xrpld.conffiles b/package/deb/debian/xrpld.conffiles new file mode 100644 index 0000000000..d9a8a1c0c5 --- /dev/null +++ b/package/deb/debian/xrpld.conffiles @@ -0,0 +1,2 @@ +/opt/xrpld/etc/xrpld.cfg +/opt/xrpld/etc/validators.txt diff --git a/package/deb/debian/xrpld.install b/package/deb/debian/xrpld.install new file mode 100644 index 0000000000..e28588f9db --- /dev/null +++ b/package/deb/debian/xrpld.install @@ -0,0 +1,10 @@ +opt/xrpld/bin/xrpld +opt/xrpld/bin/xrpld.logrotate +opt/xrpld/bin/update-xrpld.sh +opt/xrpld/bin/update-xrpld-cron + +opt/xrpld/etc/xrpld.cfg +opt/xrpld/etc/validators.txt + +usr/share/doc/xrpld/README.md +usr/share/doc/xrpld/LICENSE.md diff --git a/package/deb/debian/xrpld.links b/package/deb/debian/xrpld.links new file mode 100644 index 0000000000..888943fec3 --- /dev/null +++ b/package/deb/debian/xrpld.links @@ -0,0 +1,13 @@ +opt/xrpld/etc etc/opt/xrpld + +opt/xrpld/bin/xrpld usr/bin/xrpld + +## remove when "rippled" deprecated +opt/xrpld/bin/xrpld opt/xrpld/bin/rippled +opt/xrpld/bin/xrpld usr/bin/rippled +opt/xrpld/bin/xrpld usr/local/bin/rippled +opt/xrpld/etc/xrpld.cfg opt/xrpld/etc/rippled.cfg +var/log/xrpld var/log/rippled +var/lib/xrpld var/lib/rippled +opt/xrpld opt/ripple +etc/opt/xrpld etc/opt/ripple diff --git a/package/rpm/xrpld.spec.in b/package/rpm/xrpld.spec.in new file mode 100644 index 0000000000..d145a9fc95 --- /dev/null +++ b/package/rpm/xrpld.spec.in @@ -0,0 +1,113 @@ +%global xrpld_version @xrpld_version@ +%global pkg_release @pkg_release@ +%global _opt_prefix /opt/xrpld +%global ver_base %(v=%{xrpld_version}; echo ${v%%-*}) +%global _has_dash %(v=%{xrpld_version}; [ "${v#*-}" != "$v" ] && echo 1 || echo 0) +%if 0%{?_has_dash} + %global ver_suffix %(v=%{xrpld_version}; printf %s "${v#*-}") +%endif +Name: xrpld +Version: %{ver_base} +Release: %{?ver_suffix:0.%{ver_suffix}.}%{pkg_release}%{?dist} +Summary: XRP Ledger daemon + +License: ISC +URL: https://github.com/XRPLF/rippled + +Source0: xrpld +Source1: xrpld.cfg +Source2: validators.txt +Source3: xrpld.service +Source4: xrpld.sysusers +Source5: xrpld.tmpfiles +Source6: xrpld.logrotate +Source7: update-xrpld.sh +Source8: update-xrpld-cron + +BuildArch: x86_64 +BuildRequires: systemd-rpm-macros + +%undefine _debugsource_packages +%debug_package + +%{?systemd_requires} +%{?sysusers_requires_compat} + +%description +xrpld is the reference implementation of the XRP Ledger protocol. It +participates in the peer-to-peer XRP Ledger network, processes +transactions, and maintains the ledger database. + +%install +rm -rf %{buildroot} + +# Suppress debugsource subpackage — no source files in the build tree. +touch %{_builddir}/debugsourcefiles.list + +# Install binary and config files. +install -Dm0755 %{SOURCE0} %{buildroot}%{_opt_prefix}/bin/xrpld +install -Dm0644 %{SOURCE1} %{buildroot}%{_opt_prefix}/etc/xrpld.cfg +install -Dm0644 %{SOURCE2} %{buildroot}%{_opt_prefix}/etc/validators.txt + +# Compatibility symlinks (matches debian/xrpld.links). +mkdir -p %{buildroot}/etc/opt %{buildroot}/usr/bin %{buildroot}/usr/local/bin \ + %{buildroot}/var/log %{buildroot}/var/lib +ln -s %{_opt_prefix}/etc %{buildroot}/etc/opt/xrpld +ln -s %{_opt_prefix}/bin/xrpld %{buildroot}/usr/bin/xrpld + +## remove when "rippled" deprecated +ln -s xrpld %{buildroot}%{_opt_prefix}/bin/rippled +ln -s %{_opt_prefix}/bin/xrpld %{buildroot}/usr/bin/rippled +ln -s %{_opt_prefix}/bin/xrpld %{buildroot}/usr/local/bin/rippled +ln -s xrpld.cfg %{buildroot}%{_opt_prefix}/etc/rippled.cfg +ln -s %{_opt_prefix} %{buildroot}/opt/ripple +ln -s /etc/opt/xrpld %{buildroot}/etc/opt/ripple +ln -s xrpld %{buildroot}/var/log/rippled +ln -s xrpld %{buildroot}/var/lib/rippled + +# Install systemd/sysusers/tmpfiles support files. +install -Dm0644 %{SOURCE3} %{buildroot}%{_unitdir}/xrpld.service +install -Dm0644 %{SOURCE4} %{buildroot}%{_sysusersdir}/xrpld.conf +install -Dm0644 %{SOURCE5} %{buildroot}%{_tmpfilesdir}/xrpld.conf +install -Dm0644 %{SOURCE6} %{buildroot}%{_opt_prefix}/bin/xrpld.logrotate +install -Dm0755 %{SOURCE7} %{buildroot}%{_opt_prefix}/bin/update-xrpld.sh +install -Dm0644 %{SOURCE8} %{buildroot}%{_opt_prefix}/bin/update-xrpld-cron + +%pre +%sysusers_create_compat %{SOURCE4} + +%post +%systemd_post xrpld.service + +%preun +%systemd_preun xrpld.service + +%postun +%systemd_postun_with_restart xrpld.service + +%files +%dir %{_opt_prefix} +%dir %{_opt_prefix}/bin +%{_opt_prefix}/bin/xrpld +%{_opt_prefix}/bin/xrpld.logrotate +%{_opt_prefix}/bin/update-xrpld.sh +%{_opt_prefix}/bin/update-xrpld-cron +%{_opt_prefix}/bin/rippled +/usr/bin/xrpld +/usr/bin/rippled +/usr/local/bin/rippled +%dir %{_opt_prefix}/etc +%config(noreplace) %{_opt_prefix}/etc/xrpld.cfg +%config(noreplace) %{_opt_prefix}/etc/validators.txt +%{_opt_prefix}/etc/rippled.cfg +/etc/opt/xrpld +/etc/opt/ripple +/opt/ripple +%{_unitdir}/xrpld.service +%{_sysusersdir}/xrpld.conf +%{_tmpfilesdir}/xrpld.conf +/var/log/rippled +/var/lib/rippled +%ghost %dir /var/opt/ripple +%ghost %dir /var/opt/ripple/lib +%ghost %dir /var/opt/ripple/log diff --git a/package/shared/update-xrpld-cron b/package/shared/update-xrpld-cron new file mode 100644 index 0000000000..cccc397386 --- /dev/null +++ b/package/shared/update-xrpld-cron @@ -0,0 +1,9 @@ +# For automatic updates, symlink this file to /etc/cron.d/ +# Do not remove the newline at the end of this cron script + +# bash required for use of RANDOM below. +SHELL=/bin/bash +PATH=/sbin;/bin;/usr/sbin;/usr/bin + +# invoke check/update script with random delay up to 59 mins +0 * * * * root sleep $((RANDOM*3540/32768)) && /opt/xrpld/bin/update-xrpld.sh diff --git a/package/shared/update-xrpld.sh b/package/shared/update-xrpld.sh new file mode 100755 index 0000000000..5c5c5fcce8 --- /dev/null +++ b/package/shared/update-xrpld.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# auto-update script for xrpld daemon + +# Check for sudo/root permissions +if [[ $(id -u) -ne 0 ]] ; then + echo "This update script must be run as root or sudo" + exit 1 +fi + +LOCKDIR=/tmp/xrpld-update.lock +UPDATELOG=/var/log/xrpld/update.log + +function cleanup { + # If this directory isn't removed, future updates will fail. + rmdir $LOCKDIR +} + +# Use mkdir to check if process is already running. mkdir is atomic, as against file create. +if ! mkdir $LOCKDIR 2>/dev/null; then + echo $(date -u) "lockdir exists - won't proceed." >> $UPDATELOG + exit 1 +fi +trap cleanup EXIT + +source /etc/os-release +can_update=false + +if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]] ; then + # Silent update + apt-get update -qq + + # The next line is an "awk"ward way to check if the package needs to be updated. + XRPLD=$(apt-get install -s --only-upgrade xrpld | awk '/^Inst/ { print $2 }') + test "$XRPLD" == "xrpld" && can_update=true + + function apply_update { + apt-get install xrpld -qq + } +elif [[ "$ID" == "fedora" || "$ID" == "centos" || "$ID" == "rhel" || "$ID" == "scientific" ]] ; then + RIPPLE_REPO=${RIPPLE_REPO-stable} + yum --disablerepo=* --enablerepo=ripple-$RIPPLE_REPO clean expire-cache + + yum check-update -q --enablerepo=ripple-$RIPPLE_REPO xrpld || can_update=true + + function apply_update { + yum update -y --enablerepo=ripple-$RIPPLE_REPO xrpld + } +else + echo "unrecognized distro!" + exit 1 +fi + +# Do the actual update and restart the service after reloading systemctl daemon. +if [ "$can_update" = true ] ; then + exec 3>&1 1>>${UPDATELOG} 2>&1 + set -e + apply_update + systemctl daemon-reload + systemctl restart xrpld.service + echo $(date -u) "xrpld daemon updated." +else + echo $(date -u) "no updates available" >> $UPDATELOG +fi diff --git a/package/shared/xrpld.logrotate b/package/shared/xrpld.logrotate new file mode 100644 index 0000000000..f27d493bc8 --- /dev/null +++ b/package/shared/xrpld.logrotate @@ -0,0 +1,15 @@ +/var/log/xrpld/*.log { + daily + minsize 200M + rotate 7 + nocreate + missingok + notifempty + compress + compresscmd /usr/bin/nice + compressoptions -n19 ionice -c3 gzip + compressext .gz + postrotate + /opt/xrpld/bin/xrpld --conf /etc/opt/xrpld/xrpld.cfg logrotate + endscript +} diff --git a/package/shared/xrpld.service b/package/shared/xrpld.service new file mode 100644 index 0000000000..30457d4227 --- /dev/null +++ b/package/shared/xrpld.service @@ -0,0 +1,15 @@ +[Unit] +Description=XRP Ledger Daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/opt/xrpld/bin/xrpld --net --silent --conf /etc/opt/xrpld/xrpld.cfg +Restart=on-failure +User=xrpld +Group=xrpld +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target diff --git a/package/shared/xrpld.sysusers b/package/shared/xrpld.sysusers new file mode 100644 index 0000000000..4547ac6f3d --- /dev/null +++ b/package/shared/xrpld.sysusers @@ -0,0 +1 @@ +u xrpld - "XRP Ledger daemon" /var/lib/xrpld /sbin/nologin diff --git a/package/shared/xrpld.tmpfiles b/package/shared/xrpld.tmpfiles new file mode 100644 index 0000000000..df4a33cda0 --- /dev/null +++ b/package/shared/xrpld.tmpfiles @@ -0,0 +1,2 @@ +d /var/opt/ripple/lib 0750 xrpld xrpld - +d /var/opt/ripple/log 0750 xrpld xrpld - diff --git a/package/test/check_install_paths.sh b/package/test/check_install_paths.sh new file mode 100755 index 0000000000..72f7fa9ad7 --- /dev/null +++ b/package/test/check_install_paths.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh +# Validate installed paths and compat symlinks for xrpld packages. + +set -e +set -x +trap 'test $? -ne 0 && touch /tmp/test_failed' EXIT + +check() { test $1 "$2" || { echo "FAIL: $1 $2"; exit 1; }; } +check_resolves_to() { + actual=$(readlink -f "$1") + [ "$actual" = "$2" ] || { echo "FAIL: $1 resolves to $actual, expected $2"; exit 1; } +} + +# var dirs (compat symlinks) +check -L /var/log/rippled +check -L /var/lib/rippled + +# compat directory symlinks — existence and resolved target +check -L /opt/ripple +check_resolves_to /opt/ripple /opt/xrpld + +check -L /etc/opt/xrpld +check_resolves_to /etc/opt/xrpld /opt/xrpld/etc + +check -L /etc/opt/ripple +check_resolves_to /etc/opt/ripple /opt/xrpld/etc + +# config accessible via all expected paths +check -f /opt/xrpld/etc/xrpld.cfg +check -f /opt/xrpld/etc/rippled.cfg +check -f /etc/opt/xrpld/xrpld.cfg +check -f /etc/opt/xrpld/rippled.cfg +check -f /etc/opt/ripple/xrpld.cfg +check -f /etc/opt/ripple/rippled.cfg + +if systemctl is-system-running >/dev/null 2>&1; then + # service file sanity check + SERVICE=$(systemctl cat xrpld) + echo "$SERVICE" | grep -q 'ExecStart=/opt/xrpld/bin/xrpld' || { echo "FAIL: ExecStart wrong"; echo "$SERVICE"; exit 1; } + echo "$SERVICE" | grep -q 'User=xrpld' || { echo "FAIL: User not xrpld"; echo "$SERVICE"; exit 1; } +fi + +# binary accessible via all expected paths +/opt/xrpld/bin/xrpld --version +/opt/xrpld/bin/rippled --version +/opt/ripple/bin/xrpld --version +/opt/ripple/bin/rippled --version +/usr/bin/xrpld --version +/usr/bin/rippled --version +/usr/local/bin/rippled --version diff --git a/package/test/smoketest.sh b/package/test/smoketest.sh new file mode 100755 index 0000000000..70355ff3c1 --- /dev/null +++ b/package/test/smoketest.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Install a locally-built package and run basic verification. +# +# Usage: smoketest.sh local +# Expects packages in build/{dpkg,rpm}/packages/ or build/debbuild/ / build/rpmbuild/RPMS/ + +set -x +trap 'test $? -ne 0 && touch /tmp/test_failed' EXIT + +install_from=$1 + +. /etc/os-release +case ${ID} in + ubuntu|debian) + pkgtype="dpkg" + ;; + fedora|centos|rhel|rocky|almalinux) + pkgtype="rpm" + ;; + *) + echo "unrecognized distro!" + exit 1 + ;; +esac + +if [ "${install_from}" != "local" ]; then + echo "only 'local' install mode is supported" + exit 1 +fi + +# Install the package +if [ "${pkgtype}" = "dpkg" ] ; then + apt-get -y update + # Find .deb files — check both possible output locations + debs=$(find build/debbuild/ build/dpkg/packages/ -name '*.deb' ! -name '*dbgsym*' 2>/dev/null | head -5) + if [ -z "$debs" ]; then + echo "No .deb files found" + exit 1 + fi + dpkg --no-debsig -i $debs || apt-get -y install -f +elif [ "${pkgtype}" = "rpm" ] ; then + # Find .rpm files — check both possible output locations + rpms=$(find build/rpmbuild/RPMS/ build/rpm/packages/ -name '*.rpm' \ + ! -name '*debug*' ! -name '*devel*' ! -name '*.src.rpm' 2>/dev/null | head -5) + if [ -z "$rpms" ]; then + echo "No .rpm files found" + exit 1 + fi + rpm -i $rpms +fi + +# Verify installed version +VERSION_OUTPUT=$(/opt/xrpld/bin/xrpld --version) +INSTALLED=$(echo "$VERSION_OUTPUT" | head -1 | awk '{print $NF}') +echo "Installed version: ${INSTALLED}" + +# Run unit tests +if [ -n "${CI:-}" ]; then + unittest_jobs=$(nproc) +else + unittest_jobs=16 +fi + +cd /tmp +/opt/xrpld/bin/xrpld --unittest --unittest-jobs ${unittest_jobs} > /tmp/unittest_results || true +cd - + +num_failures=$(tail /tmp/unittest_results -n1 | grep -oP '\d+(?= failures)') +if [ "${num_failures:-0}" -ne 0 ]; then + echo "$num_failures unit test(s) failed:" + grep 'failed:' /tmp/unittest_results + exit 1 +fi + +# Compat path checks +"$(dirname "${BASH_SOURCE[0]}")/check_install_paths.sh"