From 38a22e3cd2dd7facc4abd8db7e07a4469c4bb5f9 Mon Sep 17 00:00:00 2001 From: Enrico Rubboli Date: Tue, 15 Sep 2026 22:59:32 +0400 Subject: [PATCH 1/6] Add native Linux packages (deb/rpm) with systemd, udev and man pages Replace the bare-binaries deb/rpm packaging with proper distribution packages: - deb: computed shared-library Depends (dpkg-shlibdeps), maintscripts using deb-systemd-helper, conffiles under /etc/mintlayer, changelog, copyright, lintian gate - rpm: systemd scriptlets, sysusers, %config(noreplace), %license, rpmlint gate; aarch64 rpms now ship alongside x86_64 - both: hardened template units per daemon (mainnet node enabled via preset, everything else opt-in), mintlayer system user, Ledger/Trezor udev rules, man pages generated from --help, hicolor icons and a validated desktop entry for the GUI release_linux.yml now runs the same packaging/ scripts inside debian:12 / fedora:latest containers, smoke-tests every artifact in a fresh container, and can be dispatched manually without a tag. packaging/test-local.sh replicates the whole pipeline locally. --- .github/workflows/release_linux.yml | 211 +++++--------- .gitignore | 20 +- packaging/README.md | 111 ++++++++ packaging/checks/smoke-deb.sh | 68 +++++ packaging/checks/smoke-rpm.sh | 67 +++++ packaging/checks/verify-artifacts.sh | 34 +++ .../applications/mintlayer-node-gui.desktop | 9 + .../common/env/api-blockchain-scanner.env | 6 + packaging/common/env/api-web-server.env | 6 + packaging/common/env/dns-server.env | 5 + packaging/common/env/node.env | 10 + packaging/common/env/wallet-rpc.env | 8 + packaging/common/preset/90-mintlayer.preset | 4 + .../mintlayer-api-blockchain-scanner@.service | 42 +++ .../systemd/mintlayer-api-web-server@.service | 41 +++ .../systemd/mintlayer-dns-server@.service | 46 ++++ .../common/systemd/mintlayer-node@.service | 46 ++++ .../systemd/mintlayer-wallet-rpc@.service | 45 +++ packaging/common/sysusers/mintlayer.conf | 3 + packaging/common/udev/51-mintlayer.rules | 15 + packaging/deb/build.sh | 259 ++++++++++++++++++ packaging/deb/changelog.in | 5 + packaging/deb/control.in | 33 +++ packaging/deb/copyright | 26 ++ packaging/deb/postinst-gui | 16 ++ packaging/deb/postinst-node | 34 +++ packaging/deb/postrm-node | 18 ++ packaging/deb/prerm-node | 13 + packaging/make-icons.sh | 25 ++ packaging/rpm/build.sh | 208 ++++++++++++++ packaging/rpm/mintlayer-node-gui.spec.in | 27 ++ packaging/rpm/mintlayer-node.spec.in | 64 +++++ packaging/test-local.sh | 249 +++++++++++++++++ 33 files changed, 1623 insertions(+), 151 deletions(-) create mode 100644 packaging/README.md create mode 100755 packaging/checks/smoke-deb.sh create mode 100755 packaging/checks/smoke-rpm.sh create mode 100755 packaging/checks/verify-artifacts.sh create mode 100644 packaging/common/applications/mintlayer-node-gui.desktop create mode 100644 packaging/common/env/api-blockchain-scanner.env create mode 100644 packaging/common/env/api-web-server.env create mode 100644 packaging/common/env/dns-server.env create mode 100644 packaging/common/env/node.env create mode 100644 packaging/common/env/wallet-rpc.env create mode 100644 packaging/common/preset/90-mintlayer.preset create mode 100644 packaging/common/systemd/mintlayer-api-blockchain-scanner@.service create mode 100644 packaging/common/systemd/mintlayer-api-web-server@.service create mode 100644 packaging/common/systemd/mintlayer-dns-server@.service create mode 100644 packaging/common/systemd/mintlayer-node@.service create mode 100644 packaging/common/systemd/mintlayer-wallet-rpc@.service create mode 100644 packaging/common/sysusers/mintlayer.conf create mode 100644 packaging/common/udev/51-mintlayer.rules create mode 100755 packaging/deb/build.sh create mode 100644 packaging/deb/changelog.in create mode 100644 packaging/deb/control.in create mode 100644 packaging/deb/copyright create mode 100644 packaging/deb/postinst-gui create mode 100644 packaging/deb/postinst-node create mode 100644 packaging/deb/postrm-node create mode 100644 packaging/deb/prerm-node create mode 100755 packaging/make-icons.sh create mode 100755 packaging/rpm/build.sh create mode 100644 packaging/rpm/mintlayer-node-gui.spec.in create mode 100644 packaging/rpm/mintlayer-node.spec.in create mode 100755 packaging/test-local.sh diff --git a/.github/workflows/release_linux.yml b/.github/workflows/release_linux.yml index 0aff632bc..60e53f490 100644 --- a/.github/workflows/release_linux.yml +++ b/.github/workflows/release_linux.yml @@ -6,6 +6,13 @@ on: binary_list: required: true type: string + workflow_dispatch: + inputs: + binary_list: + description: 'Comma-separated list of binaries (unused by packaging scripts, kept for parity)' + required: false + type: string + default: 'api-blockchain-scanner-daemon,api-web-server,dns-server,node-daemon,wallet-address-generator,wallet-cli,wallet-rpc-daemon' jobs: build: @@ -23,6 +30,8 @@ jobs: run: | VERSION=${GITHUB_REF#refs/tags/} VERSION=${VERSION#v} + [ "$VERSION" = "$GITHUB_REF" ] && VERSION="$(git describe --tags --abbrev=0 | sed -e 's/^v//')" + [ -n "$VERSION" ] || VERSION="0.0.0-ci" echo "VERSION=$VERSION" >> $GITHUB_OUTPUT echo "Version extracted: $VERSION" @@ -62,13 +71,12 @@ jobs: sudo apt-get update sudo apt-get install -y \ - rpm \ libdbus-1-dev \ libusb-1.0-0-dev \ libudev-dev fi - sudo apt-get install -y debhelper zip imagemagick pkg-config + sudo apt-get install -y zip pkg-config - name: Build env: @@ -77,129 +85,60 @@ jobs: run: | cargo build --release --locked --target ${{ matrix.arch }}-unknown-linux-gnu --features trezor,ledger - - name: Create Debian package for GUI + # GH-hosted ubuntu runners ship qemu-user-static + binfmt, so + # `docker run --platform linux/arm64` works out of the box: the arm64 leg + # runs the deb builder inside an arm64 debian:12 container. The same + # scripts run locally via packaging/test-local.sh. + - name: Create Debian packages run: | - mkdir -p debian-gui/DEBIAN - mkdir -p debian-gui/usr/bin - mkdir -p debian-gui/usr/share/applications - mkdir -p debian-gui/usr/share/icons/hicolor/512x512/apps - mkdir -p debian-gui/usr/share/icons/hicolor/256x256/apps - mkdir -p debian-gui/usr/share/icons/hicolor/128x128/apps - mkdir -p debian-gui/usr/share/icons/hicolor/64x64/apps - cp target/${{ matrix.arch }}-unknown-linux-gnu/release/node-gui debian-gui/usr/bin/mintlayer-node-gui - - # Copy and convert icon files - cp build-tools/assets/node-gui-icon_512.png debian-gui/usr/share/icons/hicolor/512x512/apps/mintlayer-node-gui.png - convert build-tools/assets/node-gui-icon_512.png -resize 256x256 debian-gui/usr/share/icons/hicolor/256x256/apps/mintlayer-node-gui.png - convert build-tools/assets/node-gui-icon_512.png -resize 128x128 debian-gui/usr/share/icons/hicolor/128x128/apps/mintlayer-node-gui.png - convert build-tools/assets/node-gui-icon_512.png -resize 64x64 debian-gui/usr/share/icons/hicolor/64x64/apps/mintlayer-node-gui.png - - # Create .desktop file - cat << EOF > debian-gui/usr/share/applications/mintlayer-node-gui.desktop - [Desktop Entry] - Name=Mintlayer Node GUI - Exec=/usr/bin/mintlayer-node-gui - Icon=mintlayer-node-gui - Type=Application - Categories=Utility;Network; - EOF - - cat << EOF > debian-gui/DEBIAN/control - Package: mintlayer-node-gui - Version: ${{ steps.get_version.outputs.VERSION }} - Section: utils - Priority: optional - Architecture: ${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }} - Maintainer: Mintlayer - Description: Mintlayer Node GUI - A graphical user interface for the Mintlayer node. - EOF - dpkg-deb --build debian-gui - mv debian-gui.deb Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb - - - name: Create Debian package for Node (without GUI) + ARCH=${{ matrix.arch }} + if [ "$ARCH" = "x86_64" ]; then DEBARCH=amd64; else DEBARCH=arm64; fi + docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work debian:12 \ + packaging/deb/build.sh \ + --package node --version "${{ steps.get_version.outputs.VERSION }}" \ + --debarch $DEBARCH \ + --binaries-dir /work/target/$ARCH-unknown-linux-gnu/release \ + --out /work/dist + docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work debian:12 \ + packaging/deb/build.sh \ + --package gui --version "${{ steps.get_version.outputs.VERSION }}" \ + --debarch $DEBARCH \ + --gui-binary /work/target/$ARCH-unknown-linux-gnu/release/node-gui \ + --repo-root /work --out /work/dist + + - name: Create RPM packages run: | - mkdir -p debian-node/DEBIAN - mkdir -p debian-node/usr/bin - IFS=',' read -ra BINARIES <<< "${{ inputs.binary_list }}" - for binary in "${BINARIES[@]}"; do - cp target/${{ matrix.arch }}-unknown-linux-gnu/release/$binary debian-node/usr/bin/mintlayer-$binary - done - cat << EOF > debian-node/DEBIAN/control - Package: mintlayer-node - Version: ${{ steps.get_version.outputs.VERSION }} - Section: utils - Priority: optional - Architecture: ${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }} - Maintainer: Mintlayer - Description: Mintlayer Node - Mintlayer node and associated tools. - EOF - dpkg-deb --build debian-node - mv debian-node.deb Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb - - - name: Create RPM package for GUI - if: matrix.arch == 'x86_64' + ARCH=${{ matrix.arch }} + docker run --rm -v "$PWD":/work -w /work fedora:latest \ + packaging/rpm/build.sh \ + --package node --rpmarch $ARCH \ + --version "${{ steps.get_version.outputs.VERSION }}" \ + --binaries-dir /work/target/$ARCH-unknown-linux-gnu/release \ + --out /work/dist + docker run --rm -v "$PWD":/work -w /work fedora:latest \ + packaging/rpm/build.sh \ + --package gui --rpmarch $ARCH \ + --version "${{ steps.get_version.outputs.VERSION }}" \ + --gui-binary /work/target/$ARCH-unknown-linux-gnu/release/node-gui \ + --repo-root /work --out /work/dist + + # Install smoke tests in fresh containers (same images as production use) + - name: Smoke test Debian packages run: | - mkdir -p rpm-gui/{BUILD,RPMS,SOURCES,SPECS,SRPMS} - mkdir -p rpm-gui/BUILDROOT/mintlayer-node-gui-${{ steps.get_version.outputs.VERSION }}-1.x86_64 - cp -r debian-gui/usr rpm-gui/BUILDROOT/mintlayer-node-gui-${{ steps.get_version.outputs.VERSION }}-1.x86_64/ - - cat << EOF > rpm-gui/SPECS/mintlayer-node-gui.spec - Name: mintlayer-node-gui - Version: ${{ steps.get_version.outputs.VERSION }} - Release: 1 - Summary: Mintlayer Node GUI - License: MIT - BuildArch: x86_64 - - %description - A graphical user interface for the Mintlayer node. - - %files - /usr/bin/mintlayer-node-gui - /usr/share/applications/mintlayer-node-gui.desktop - /usr/share/icons/hicolor/512x512/apps/mintlayer-node-gui.png - /usr/share/icons/hicolor/256x256/apps/mintlayer-node-gui.png - /usr/share/icons/hicolor/128x128/apps/mintlayer-node-gui.png - /usr/share/icons/hicolor/64x64/apps/mintlayer-node-gui.png - - %changelog - * $(date "+%a %b %d %Y") Mintlayer - ${{ steps.get_version.outputs.VERSION }}-1 - - Initial RPM release - EOF - - rpmbuild -bb --define "_topdir $(pwd)/rpm-gui" --buildroot $(pwd)/rpm-gui/BUILDROOT/mintlayer-node-gui-${{ steps.get_version.outputs.VERSION }}-1.x86_64 rpm-gui/SPECS/mintlayer-node-gui.spec - mv rpm-gui/RPMS/x86_64/mintlayer-node-gui-${{ steps.get_version.outputs.VERSION }}-1.x86_64.rpm Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_x86_64.rpm - - - name: Create RPM package for Node (without GUI) - if: matrix.arch == 'x86_64' + ARCH=${{ matrix.arch }} + if [ "$ARCH" = "x86_64" ]; then DEBARCH=amd64; else DEBARCH=arm64; fi + docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work debian:12 \ + packaging/checks/smoke-deb.sh dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${DEBARCH}.deb mintlayer-node node + docker run --rm --platform linux/$ARCH -v "$PWD":/work -w /work debian:12 \ + packaging/checks/smoke-deb.sh dist/Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${DEBARCH}.deb mintlayer-node-gui gui + + - name: Smoke test RPM packages run: | - mkdir -p rpm-node/{BUILD,RPMS,SOURCES,SPECS,SRPMS} - mkdir -p rpm-node/BUILDROOT/mintlayer-node-${{ steps.get_version.outputs.VERSION }}-1.x86_64 - cp -r debian-node/usr rpm-node/BUILDROOT/mintlayer-node-${{ steps.get_version.outputs.VERSION }}-1.x86_64/ - - cat << EOF > rpm-node/SPECS/mintlayer-node.spec - Name: mintlayer-node - Version: ${{ steps.get_version.outputs.VERSION }} - Release: 1 - Summary: Mintlayer Node - License: MIT - BuildArch: x86_64 - - %description - Mintlayer node and associated tools. - - %files - /usr/bin/* - - %changelog - * $(date "+%a %b %d %Y") Mintlayer - ${{ steps.get_version.outputs.VERSION }}-1 - - Initial RPM release - EOF - - rpmbuild -bb --define "_topdir $(pwd)/rpm-node" --buildroot $(pwd)/rpm-node/BUILDROOT/mintlayer-node-${{ steps.get_version.outputs.VERSION }}-1.x86_64 rpm-node/SPECS/mintlayer-node.spec - mv rpm-node/RPMS/x86_64/mintlayer-node-${{ steps.get_version.outputs.VERSION }}-1.x86_64.rpm Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_x86_64.rpm + ARCH=${{ matrix.arch }} + docker run --rm -v "$PWD":/work -w /work fedora:latest \ + packaging/checks/smoke-rpm.sh dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${ARCH}.rpm mintlayer-node node + docker run --rm -v "$PWD":/work -w /work fedora:latest \ + packaging/checks/smoke-rpm.sh dist/Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${ARCH}.rpm mintlayer-node-gui gui - name: Package Mintlayer Node (without GUI) as tar.gz run: | @@ -213,34 +152,32 @@ jobs: - name: Package Mintlayer Node GUI as tar.gz run: | mkdir -p Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }} - cp target/${{ matrix.arch }}-unknown-linux-gnu/release/node-gui Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}/mintlayer-node-gui - tar -czvf Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}.tar.gz Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }} + cp target/${{ matrix.arch }}-unknown-linux-gnu/release/node-gui Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}/mintlayer-node-gui + tar -czvf Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}.tar.gz Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }} - - name: Upload GUI DEB Artifact + - name: Upload Node DEB Artifact (without GUI) uses: actions/upload-artifact@v4 with: - name: Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_deb - path: Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb + name: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_deb + path: dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb - - name: Upload Node DEB Artifact (without GUI) + - name: Upload GUI DEB Artifact uses: actions/upload-artifact@v4 with: - name: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_deb - path: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb + name: Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_deb + path: dist/Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb - - name: Upload GUI RPM Artifact - if: matrix.arch == 'x86_64' + - name: Upload Node RPM Artifact uses: actions/upload-artifact@v4 with: - name: Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_x86_64_rpm - path: Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_x86_64.rpm + name: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_rpm + path: dist/Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}.rpm - - name: Upload Node RPM Artifact (without GUI) - if: matrix.arch == 'x86_64' + - name: Upload GUI RPM Artifact uses: actions/upload-artifact@v4 with: - name: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_x86_64_rpm - path: Mintlayer_Node_linux_${{ steps.get_version.outputs.VERSION }}_x86_64.rpm + name: Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}_rpm + path: dist/Mintlayer_Node_GUI_linux_${{ steps.get_version.outputs.VERSION }}_${{ matrix.arch }}.rpm - name: List tar.gz files run: | diff --git a/.gitignore b/.gitignore index 80a85164b..46e0f1fa0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,53 +3,45 @@ **/target/ # These are backup files generated by rustfmt **/*.rs.bk - .DS_Store - # Intellij IDEA .idea/ *.iml /customSpec.json - # VSCode .vscode/ - #exclude python env env/ - +# ...but not the package environment-file templates +!packaging/common/env/ # Python cache **/__pycache__ - # Files generated for the testing system test/config.ini - # The cache for docker container dependency .cargo/* - # But do not ignore the cargo.toml file !.cargo/config.toml - # The cache for chain data in container .local - # direnv cache .direnv - # Python compiled files *.pyc - # wasm wasm-wrappers/pkg/ wasm-wrappers/js-bindings-test/dist/ - # 'mintlayer-data' will be mapped to home directories of docker containers, so everything # inside it will be generated by the containers. build-tools/docker/example-mainnet/mintlayer-data/* # Same for example-mainnet-dns-server. build-tools/docker/example-mainnet-dns-server/mintlayer-data/* - # This directory will contain some generated files. build-tools/block-data-plots/output # Cloudflare wrangler local cache (created by local wrangler tooling) .wrangler/ + +packaging/dist/ +/dist/ +target-debian/ diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 000000000..b6748d123 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,111 @@ +# Native Linux packages + +Proper Debian and Fedora packages for the Mintlayer node software: systemd +integration, hardware-wallet udev rules, man pages, conffiles, declared +library dependencies, and lint/install validation — for both amd64/x86_64 and +arm64/aarch64. + +The same scripts run in CI (`.github/workflows/release_linux.yml`) and +locally, inside the same container images, so a green local run means the +tagged release build will behave identically. + +## Packages + +| Package | Contents | +|---|---| +| `mintlayer-node` | node-daemon, wallet-rpc-daemon, api-web-server, api-blockchain-scanner-daemon, dns-server, wallet-cli, wallet-address-generator + systemd units, sysusers, preset, udev rules, man pages, `/etc/mintlayer` conffiles | +| `mintlayer-node-gui` | node-gui + hicolor icons + desktop entry + man page | + +### What the packages set up + +- **systemd template units**, one instance per chain: + - `mintlayer-node@{mainnet,testnet}.service` — explicit unit: runs the node + as the `mintlayer` system user with `--datadir /var/lib/mintlayer/` + - `mintlayer-wallet-rpc@.service`, `mintlayer-api-web-server@.service`, + `mintlayer-api-blockchain-scanner@.service`, `mintlayer-dns-server@.service` + — driven by `ARGS` from `/etc/mintlayer//.env` (see the + shipped templates and each tool's `--help`) + - all units ship hardening (`ProtectSystem=strict`, `NoNewPrivileges`, …) + and log to the journal +- **Preset policy**: only `mintlayer-node@mainnet.service` is enabled on + install; everything else is opt-in. On Debian it is applied by postinst + (`systemctl preset`), on Fedora via `90-mintlayer.preset` + + `%systemd_post`. +- **System user** `mintlayer` via `sysusers.d` (+ fallback useradd in the deb + postinst). The home directory `/var/lib/mintlayer` holds chain state. +- **udev rules** (`51-mintlayer.rules`): Ledger and Trezor hidraw access via + `uaccess`, shipped with `mintlayer-node` (used by wallet-cli and, when the + recommended `mintlayer-node` package is installed, by the GUI). +- **Dependencies**: computed from the binaries with `dpkg-shlibdeps` (deb) / + RPM soname autorequires; the deb additionally gets an `ldd` gate that fails + the build if any library is unresolved. + +## Local testing (before tagging a release) + +The release workflow only triggers on tags, so validate locally first: + +```sh +packaging/test-local.sh # full matrix: both arches, deb+rpm, smoke tests +packaging/test-local.sh --quick # amd64/x86_64 only, no qemu +packaging/test-local.sh --skip-build # reuse existing target/*/release binaries +packaging/test-local.sh --check # preflight only (docker, binfmt, images) +``` + +Requirements: docker, and (for the arm64 legs on an x86_64 host) qemu binfmt: + +```sh +docker run --privileged --rm multiarch/qemu-user-static --reset -p yes +``` + +The script builds binaries with the exact CI flags +(`cargo build --release --locked --features trezor,ledger`), then: + +1. assembles debs inside `debian:12` (arm64 via `--platform linux/arm64`) +2. assembles rpms inside `fedora:latest` (`--target x86_64` and `aarch64`; + repackaging only, no emulation needed for rpm) +3. runs lintian / rpmlint inside the same containers +4. installs each package in a **fresh** container and verifies: system user, + binaries run (`--help`), `systemd-analyze verify` on all units, preset, + conffiles, icons, desktop file +5. checks the artifact names against the globs `release.yml` uploads + +A final summary matrix prints; exit code 0 = safe to tag. + +Note: building the aarch64 *binaries* locally requires the arm64 +cross-toolchain and arm64 system libraries (as CI sets up). Without them, run +`--quick` locally and let the `workflow_dispatch` dry-run of +`release_linux.yml` (Actions tab) exercise the arm64 legs in CI before +tagging. + +## CI integration + +`release_linux.yml` calls the same builders per matrix arch: + +- deb: `docker run --platform linux/$ARCH debian:12 packaging/deb/build.sh …` +- rpm: `docker run fedora:latest packaging/rpm/build.sh …` +- smoke: fresh-container installs via `packaging/checks/smoke-*.sh` + +Artifact names are unchanged (`Mintlayer_Node_linux__.deb/rpm`, +`Mintlayer_Node_GUI_linux__.deb/rpm`) so `release.yml` +attaches them exactly as before. New: GUI + node rpms are produced for both +arches (previously rpm was x86_64-only), and `workflow_dispatch` allows a +no-tag dry run. + +## Layout + +``` +packaging/ + common/ assets shared by both package formats + systemd/ template units (one per daemon) + sysusers/ mintlayer system user definition + udev/ Ledger/Trezor hidraw rules + preset/ default-enable policy (mainnet node only) + env/ per-service environment templates (/etc/mintlayer//) + applications/ .desktop entry for the GUI + deb/ control template, maintscripts, changelog, build.sh + rpm/ spec templates, build.sh + checks/ smoke-deb.sh, smoke-rpm.sh, verify-artifacts.sh + make-icons.sh hicolor icon set generator + test-local.sh full local replica of the release pipeline + dist/ build output (gitignored) +``` diff --git a/packaging/checks/smoke-deb.sh b/packaging/checks/smoke-deb.sh new file mode 100755 index 000000000..392b292d7 --- /dev/null +++ b/packaging/checks/smoke-deb.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Install-smoke test for a built .deb. Runs INSIDE a debian:12 container with +# the repository (or at least packaging/) mounted at /work. +# Usage: smoke-deb.sh node|gui +set -euo pipefail + +DEB_FILE="$1" +PKG_NAME="$2" +KIND="$3" + +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq + +apt-get install -y -qq systemd desktop-file-utils >/dev/null 2>&1 || true +apt-get install -y -qq "./$DEB_FILE" >/dev/null + +echo "== dpkg status ==" +dpkg -s "$PKG_NAME" | grep -E "^(Status|Version|Depends):" + +if [ "$KIND" = node ]; then + echo "== system user ==" + id mintlayer + + echo "== binaries ==" + for bin in node-daemon wallet-rpc-daemon api-web-server \ + api-blockchain-scanner-daemon dns-server wallet-cli \ + wallet-address-generator; do + test -x "/usr/bin/mintlayer-$bin" + "/usr/bin/mintlayer-$bin" --help >/dev/null 2>&1 + echo " ok: mintlayer-$bin --help" + done + + echo "== systemd units ==" + for unit in /usr/lib/systemd/system/mintlayer-*.service; do + systemd-analyze verify "$unit" + echo " ok: $(basename "$unit")" + done + test -f /usr/lib/systemd/system-preset/90-mintlayer.preset + grep -q "enable mintlayer-node@mainnet.service" \ + /usr/lib/systemd/system-preset/90-mintlayer.preset + test -f /usr/lib/sysusers.d/mintlayer.conf + test -f /usr/lib/udev/rules.d/51-mintlayer.rules + test -f /etc/mintlayer/mainnet/node.env + test -f /etc/mintlayer/testnet/node.env + echo " ok: preset/sysusers/udev/conffiles present" + + echo "== man pages ==" + ls /usr/share/man/man1/mintlayer-*.1.gz >/dev/null + echo " ok" +else + echo "== gui files ==" + test -x /usr/bin/mintlayer-node-gui + /usr/bin/mintlayer-node-gui --help >/dev/null 2>&1 + echo " ok: mintlayer-node-gui --help" + + desktop-file-validate /usr/share/applications/mintlayer-node-gui.desktop + echo " ok: desktop file valid" + + for size in 64 128 256 512; do + test -f "/usr/share/icons/hicolor/${size}x${size}/apps/mintlayer-node-gui.png" + done + echo " ok: icons present" + + ls /usr/share/man/man1/mintlayer-node-gui.1.gz >/dev/null + echo " ok: man page" +fi + +echo "SMOKE OK: $PKG_NAME ($KIND)" diff --git a/packaging/checks/smoke-rpm.sh b/packaging/checks/smoke-rpm.sh new file mode 100755 index 000000000..4269d07f6 --- /dev/null +++ b/packaging/checks/smoke-rpm.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Install-smoke test for a built .rpm. Runs INSIDE a fedora:latest container +# with the repository mounted at /work. +# Usage: smoke-rpm.sh node|gui +set -euo pipefail + +RPM_FILE="$1" +PKG_NAME="$2" +KIND="$3" + +dnf install -y -q systemd desktop-file-utils >/dev/null 2>&1 || true +dnf install -y -q "./$RPM_FILE" >/dev/null + +echo "== rpm query ==" +rpm -q "$PKG_NAME" +rpm -q --requires "$PKG_NAME" | head -n -2 + +if [ "$KIND" = node ]; then + echo "== system user ==" + id mintlayer + + echo "== binaries ==" + for bin in node-daemon wallet-rpc-daemon api-web-server \ + api-blockchain-scanner-daemon dns-server wallet-cli \ + wallet-address-generator; do + test -x "/usr/bin/mintlayer-$bin" + "/usr/bin/mintlayer-$bin" --help >/dev/null 2>&1 + echo " ok: mintlayer-$bin --help" + done + + echo "== systemd units ==" + for unit in /usr/lib/systemd/system/mintlayer-*.service; do + systemd-analyze verify "$unit" + echo " ok: $(basename "$unit")" + done + test -f /usr/lib/systemd/system-preset/90-mintlayer.preset + grep -q "enable mintlayer-node@mainnet.service" \ + /usr/lib/systemd/system-preset/90-mintlayer.preset + test -f /usr/lib/sysusers.d/mintlayer.conf + test -f /usr/lib/udev/rules.d/51-mintlayer.rules + test -f /etc/mintlayer/mainnet/node.env + echo " ok: preset/sysusers/udev/conffiles present" + + echo "== man pages ==" + # NOTE: the fedora container image installs with tsflags=nodocs, so check + # the package payload instead of the filesystem (real systems install docs). + rpm -ql "$PKG_NAME" | grep -q "^/usr/share/man/man1/mintlayer-.*\.1\.gz$" + echo " ok" +else + echo "== gui files ==" + test -x /usr/bin/mintlayer-node-gui + /usr/bin/mintlayer-node-gui --help >/dev/null 2>&1 + echo " ok: mintlayer-node-gui --help" + + desktop-file-validate /usr/share/applications/mintlayer-node-gui.desktop + echo " ok: desktop file valid" + + for size in 64 128 256 512; do + test -f "/usr/share/icons/hicolor/${size}x${size}/apps/mintlayer-node-gui.png" + done + echo " ok: icons present" + + rpm -ql "$PKG_NAME" | grep -q "^/usr/share/man/man1/mintlayer-node-gui\.1\.gz$" + echo " ok: man page" +fi + +echo "SMOKE OK: $PKG_NAME ($KIND)" diff --git a/packaging/checks/verify-artifacts.sh b/packaging/checks/verify-artifacts.sh new file mode 100755 index 000000000..d6c45fe86 --- /dev/null +++ b/packaging/checks/verify-artifacts.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Verify that the produced artifacts match the exact names the release +# workflow globs (release.yml: artifacts: "Mintlayer*/*"). +# Usage: verify-artifacts.sh +# arch-pairs: list of "debarch:rpmarch" pairs, e.g. amd64:x86_64 arm64:aarch64 +set -euo pipefail + +DIST_DIR="$1" +VERSION="$2" +shift 2 + +MISSING=0 +for pair in "$@"; do + DEBARCH="${pair%%:*}" + RPMARCH="${pair##*:}" + + for f in "Mintlayer_Node_linux_${VERSION}_${DEBARCH}.deb" \ + "Mintlayer_Node_GUI_linux_${VERSION}_${DEBARCH}.deb" \ + "Mintlayer_Node_linux_${VERSION}_${RPMARCH}.rpm" \ + "Mintlayer_Node_GUI_linux_${VERSION}_${RPMARCH}.rpm"; do + if [ -f "$DIST_DIR/$f" ]; then + echo "ok: $f" + else + echo "MISSING: $f" + MISSING=$((MISSING + 1)) + fi + done +done + +[ "$MISSING" -eq 0 ] || { + echo "$MISSING artifact(s) missing or misnamed" >&2 + exit 1 +} +echo "ALL ARTIFACTS OK" diff --git a/packaging/common/applications/mintlayer-node-gui.desktop b/packaging/common/applications/mintlayer-node-gui.desktop new file mode 100644 index 000000000..e02ea91fc --- /dev/null +++ b/packaging/common/applications/mintlayer-node-gui.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=Mintlayer Node GUI +Comment=Graphical interface for the Mintlayer node +Exec=mintlayer-node-gui +Icon=mintlayer-node-gui +Terminal=false +Categories=Network; +Keywords=mintlayer;node;wallet;crypto; diff --git a/packaging/common/env/api-blockchain-scanner.env b/packaging/common/env/api-blockchain-scanner.env new file mode 100644 index 000000000..a6f51d7d5 --- /dev/null +++ b/packaging/common/env/api-blockchain-scanner.env @@ -0,0 +1,6 @@ +# Environment file for mintlayer-api-blockchain-scanner@.service +# The scanner requires Postgres connection settings and node RPC access in ARGS. +# See `mintlayer-api-blockchain-scanner-daemon --help` for all options. + +# ARGS= +# RUST_LOG=info diff --git a/packaging/common/env/api-web-server.env b/packaging/common/env/api-web-server.env new file mode 100644 index 000000000..7df94fa55 --- /dev/null +++ b/packaging/common/env/api-web-server.env @@ -0,0 +1,6 @@ +# Environment file for mintlayer-api-web-server@.service +# The web server requires Postgres connection settings and node RPC access in ARGS. +# See `mintlayer-api-web-server --help` for all options. + +# ARGS= +# RUST_LOG=info diff --git a/packaging/common/env/dns-server.env b/packaging/common/env/dns-server.env new file mode 100644 index 000000000..2f59bbf1c --- /dev/null +++ b/packaging/common/env/dns-server.env @@ -0,0 +1,5 @@ +# Environment file for mintlayer-dns-server@.service +# See `mintlayer-dns-server --help` for all options. + +# ARGS=--network-address 0.0.0.0:53 +# RUST_LOG=info diff --git a/packaging/common/env/node.env b/packaging/common/env/node.env new file mode 100644 index 000000000..2fdeac0e7 --- /dev/null +++ b/packaging/common/env/node.env @@ -0,0 +1,10 @@ +# Environment file for mintlayer-node@.service +# Copy or edit as needed; the unit loads it if present (optional). +# See `mintlayer-node-daemon --help` for the full set of options; every option +# can also be set via the MAINNET_NODE / TESTNET_NODE environment variables. + +RUST_LOG=info +# RUST_BACKTRACE=full + +# Example: custom p2p port +# ARGS=--p2p-port 30313 diff --git a/packaging/common/env/wallet-rpc.env b/packaging/common/env/wallet-rpc.env new file mode 100644 index 000000000..683e2cf9e --- /dev/null +++ b/packaging/common/env/wallet-rpc.env @@ -0,0 +1,8 @@ +# Environment file for mintlayer-wallet-rpc@.service +# The daemon requires at least the chain and the wallet file path in ARGS. +# Example: +# ARGS=--wallet-file /var/lib/mintlayer/my-wallet.mlt +# See `mintlayer-wallet-rpc-daemon --help` for all options. + +# ARGS= +# RUST_LOG=info diff --git a/packaging/common/preset/90-mintlayer.preset b/packaging/common/preset/90-mintlayer.preset new file mode 100644 index 000000000..fd8d92461 --- /dev/null +++ b/packaging/common/preset/90-mintlayer.preset @@ -0,0 +1,4 @@ +# Preset policy for Mintlayer units. +# Only the mainnet node daemon is enabled by default; every other unit is +# opt-in and must be enabled explicitly by the administrator. +enable mintlayer-node@mainnet.service diff --git a/packaging/common/systemd/mintlayer-api-blockchain-scanner@.service b/packaging/common/systemd/mintlayer-api-blockchain-scanner@.service new file mode 100644 index 000000000..668277a33 --- /dev/null +++ b/packaging/common/systemd/mintlayer-api-blockchain-scanner@.service @@ -0,0 +1,42 @@ +# Template unit: one instance per chain. +# 1. Edit /etc/mintlayer//api-blockchain-scanner.env and set ARGS +# (the scanner needs access to the same Postgres database as the api web +# server and a running node). +# 2. systemctl enable --now mintlayer-api-blockchain-scanner@.service +[Unit] +Description=Mintlayer API Blockchain Scanner (%i) +Documentation=https://github.com/mintlayer/mintlayer-core +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=mintlayer +Group=mintlayer +EnvironmentFile=-/etc/mintlayer/%i/api-blockchain-scanner.env +ExecStart=/usr/bin/mintlayer-api-blockchain-scanner-daemon ${ARGS} + +Restart=on-failure +RestartSec=10s +TimeoutStopSec=120s +LimitNOFILE=65535 + +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/packaging/common/systemd/mintlayer-api-web-server@.service b/packaging/common/systemd/mintlayer-api-web-server@.service new file mode 100644 index 000000000..1fa041889 --- /dev/null +++ b/packaging/common/systemd/mintlayer-api-web-server@.service @@ -0,0 +1,41 @@ +# Template unit: one instance per chain. +# 1. Edit /etc/mintlayer//api-web-server.env and set ARGS +# (the api web server needs access to a Postgres database and a running node). +# 2. systemctl enable --now mintlayer-api-web-server@.service +[Unit] +Description=Mintlayer API Web Server (%i) +Documentation=https://github.com/mintlayer/mintlayer-core +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=mintlayer +Group=mintlayer +EnvironmentFile=-/etc/mintlayer/%i/api-web-server.env +ExecStart=/usr/bin/mintlayer-api-web-server ${ARGS} + +Restart=on-failure +RestartSec=10s +TimeoutStopSec=120s +LimitNOFILE=65535 + +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/packaging/common/systemd/mintlayer-dns-server@.service b/packaging/common/systemd/mintlayer-dns-server@.service new file mode 100644 index 000000000..d615df99b --- /dev/null +++ b/packaging/common/systemd/mintlayer-dns-server@.service @@ -0,0 +1,46 @@ +# Template unit: one instance per chain. +# 1. Edit /etc/mintlayer//dns-server.env and set ARGS if needed. +# 2. systemctl enable --now mintlayer-dns-server@.service +[Unit] +Description=Mintlayer DNS Server (%i) +Documentation=https://github.com/mintlayer/mintlayer-core +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=mintlayer +Group=mintlayer +EnvironmentFile=-/etc/mintlayer/%i/dns-server.env +ExecStart=/usr/bin/mintlayer-dns-server ${ARGS} + +StateDirectory=mintlayer + +Restart=on-failure +RestartSec=10s +TimeoutStopSec=120s +LimitNOFILE=65535 + +# Binds to port 53: grant the unprivileged user the capability to bind low ports. +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE + +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/packaging/common/systemd/mintlayer-node@.service b/packaging/common/systemd/mintlayer-node@.service new file mode 100644 index 000000000..da25d3d91 --- /dev/null +++ b/packaging/common/systemd/mintlayer-node@.service @@ -0,0 +1,46 @@ +# Template unit: one instance per chain. +# systemctl enable --now mintlayer-node@mainnet.service +# systemctl enable --now mintlayer-node@testnet.service +[Unit] +Description=Mintlayer Node Daemon (%i) +Documentation=https://github.com/mintlayer/mintlayer-core +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=mintlayer +Group=mintlayer +EnvironmentFile=-/etc/mintlayer/%i/node.env +ExecStart=/usr/bin/mintlayer-node-daemon --datadir /var/lib/mintlayer/%i %i + +# Node state (blockchain storage, p2p state) lives under /var/lib/mintlayer/%i +StateDirectory=mintlayer + +Restart=on-failure +RestartSec=10s + +# Initial sync can take a long time; give the node room to shut down cleanly. +TimeoutStopSec=300s +LimitNOFILE=65535 + +# Hardening +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/packaging/common/systemd/mintlayer-wallet-rpc@.service b/packaging/common/systemd/mintlayer-wallet-rpc@.service new file mode 100644 index 000000000..bdcf7246a --- /dev/null +++ b/packaging/common/systemd/mintlayer-wallet-rpc@.service @@ -0,0 +1,45 @@ +# Template unit: one instance per chain. +# 1. Edit /etc/mintlayer//wallet-rpc.env and set ARGS +# (the wallet rpc daemon needs at least the path to the wallet file). +# 2. systemctl enable --now mintlayer-wallet-rpc@.service +[Unit] +Description=Mintlayer Wallet RPC Daemon (%i) +Documentation=https://github.com/mintlayer/mintlayer-core +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +User=mintlayer +Group=mintlayer +EnvironmentFile=-/etc/mintlayer/%i/wallet-rpc.env +ExecStart=/usr/bin/mintlayer-wallet-rpc-daemon ${ARGS} + +StateDirectory=mintlayer + +Restart=on-failure +RestartSec=10s +TimeoutStopSec=120s +LimitNOFILE=65535 + +# The wallet database and any files referenced by ARGS must be readable by the +# 'mintlayer' user. /var/lib/mintlayer is the recommended location. +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/packaging/common/sysusers/mintlayer.conf b/packaging/common/sysusers/mintlayer.conf new file mode 100644 index 000000000..f80c6fb3d --- /dev/null +++ b/packaging/common/sysusers/mintlayer.conf @@ -0,0 +1,3 @@ +# Type=Name:Group=Gecos:Home directory:Shell +# System user for running Mintlayer daemons as packaged services. +u mintlayer - "Mintlayer daemon user" /var/lib/mintlayer /usr/sbin/nologin diff --git a/packaging/common/udev/51-mintlayer.rules b/packaging/common/udev/51-mintlayer.rules new file mode 100644 index 000000000..523bd9e9d --- /dev/null +++ b/packaging/common/udev/51-mintlayer.rules @@ -0,0 +1,15 @@ +# Mintlayer hardware wallet access rules (Ledger, Trezor). +# Grants the active local session access to the device via systemd 'uaccess', +# so node-gui / wallet-cli can talk to hardware wallets without root. + +# Ledger (all devices, vendor id 2c97) +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", MODE="0660", TAG+="uaccess" + +# Trezor One (SatoshiLabs) +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", TAG+="uaccess" + +# Trezor Model T bootloader +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c0", MODE="0660", TAG+="uaccess" + +# Trezor Model T firmware +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", TAG+="uaccess" diff --git a/packaging/deb/build.sh b/packaging/deb/build.sh new file mode 100755 index 000000000..f82a44cd6 --- /dev/null +++ b/packaging/deb/build.sh @@ -0,0 +1,259 @@ +#!/usr/bin/env bash +# Build the mintlayer-node / mintlayer-node-gui Debian packages. +# +# Intended to run inside a debian:12 container (any host arch: use +# `docker run --platform linux/arm64` for arm64), but works on a native +# Debian/Ubuntu host too. Self-provisions its build dependencies. +# +# Usage: +# build.sh --package node --version X.Y.Z --debarch amd64 \ +# --binaries-dir DIR --out DIR +# build.sh --package gui --version X.Y.Z --debarch amd64 \ +# --gui-binary PATH --repo-root DIR --out DIR +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +PACKAGE="" +VERSION="" +DEBARCH="" +BINARIES_DIR="" +GUI_BINARY="" +OUT_DIR="" +REPO_ROOT="" + +while [ $# -gt 0 ]; do + case "$1" in + --package) PACKAGE="$2"; shift 2 ;; + --version) VERSION="$2"; shift 2 ;; + --debarch) DEBARCH="$2"; shift 2 ;; + --binaries-dir) BINARIES_DIR="$2"; shift 2 ;; + --gui-binary) GUI_BINARY="$2"; shift 2 ;; + --repo-root) REPO_ROOT="$2"; shift 2 ;; + --out) OUT_DIR="$2"; shift 2 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +[ -n "$PACKAGE" ] && [ -n "$VERSION" ] && [ -n "$DEBARCH" ] && [ -n "$OUT_DIR" ] || + { echo "missing required args (package/version/debarch/out)" >&2; exit 2; } +[ -n "$REPO_ROOT" ] || REPO_ROOT="$PKG_ROOT/.." + +case "$PACKAGE" in + node) PKG_NAME="mintlayer-node" ;; + gui) PKG_NAME="mintlayer-node-gui" ;; + *) echo "invalid --package: $PACKAGE" >&2; exit 2 ;; +esac + +# --------------------------------------------------------------------------- +# Self-provision build dependencies (no-ops when already present) +# --------------------------------------------------------------------------- +NEED_INSTALL=(file help2man) +command -v dpkg-deb >/dev/null || NEED_INSTALL+=(dpkg-dev) +command -v lintian >/dev/null || NEED_INSTALL+=(lintian) +command -v convert >/dev/null || NEED_INSTALL+=(imagemagick) +# Runtime shared libraries referenced by the binaries: dpkg-shlibdeps needs to +# resolve them inside this environment to compute the Depends field. +NEED_INSTALL+=(libdbus-1-3 libusb-1.0-0 libudev1) +if [ "$PACKAGE" = gui ]; then + NEED_INSTALL+=(libwayland-client0 libxkbcommon0) +fi +if [ ${#NEED_INSTALL[@]} -gt 0 ]; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq "${NEED_INSTALL[@]}" >/dev/null +fi + +mkdir -p "$OUT_DIR" + +# --------------------------------------------------------------------------- +# Assemble the package tree +# --------------------------------------------------------------------------- +PKGDIR="$OUT_DIR/build-${PACKAGE}" +rm -rf "$PKGDIR" +mkdir -p "$PKGDIR/DEBIAN" "$PKGDIR/usr/bin" + +if [ "$PACKAGE" = node ]; then + [ -n "$BINARIES_DIR" ] || { echo "--binaries-dir required for node" >&2; exit 2; } + # All daemons + CLI tools, renamed with the mintlayer- prefix + for bin in node-daemon wallet-rpc-daemon api-web-server \ + api-blockchain-scanner-daemon dns-server wallet-cli \ + wallet-address-generator; do + cp "$BINARIES_DIR/$bin" "$PKGDIR/usr/bin/mintlayer-$bin" + chmod 0755 "$PKGDIR/usr/bin/mintlayer-$bin" + done + + # systemd integration + mkdir -p "$PKGDIR/usr/lib/systemd/system" + for unit in "$PKG_ROOT/common/systemd/"*.service; do + install -m 0644 "$unit" "$PKGDIR/usr/lib/systemd/system/" + done + install -D -m 0644 "$PKG_ROOT/common/sysusers/mintlayer.conf" \ + "$PKGDIR/usr/lib/sysusers.d/mintlayer.conf" + install -D -m 0644 "$PKG_ROOT/common/preset/90-mintlayer.preset" \ + "$PKGDIR/usr/lib/systemd/system-preset/90-mintlayer.preset" + + # udev rules for hardware wallets + install -D -m 0644 "$PKG_ROOT/common/udev/51-mintlayer.rules" \ + "$PKGDIR/usr/lib/udev/rules.d/51-mintlayer.rules" + + # per-chain environment files (conffiles: anything under /etc is flagged + # automatically by dpkg-deb) + for chain in mainnet testnet; do + for env in node.env wallet-rpc.env api-web-server.env \ + api-blockchain-scanner.env dns-server.env; do + install -D -m 0644 "$PKG_ROOT/common/env/$env" \ + "$PKGDIR/etc/mintlayer/$chain/$env" + done + done + + BINARY_NAME_PATTERN='mintlayer-*' + ARTIFACT="Mintlayer_Node_linux_${VERSION}_${DEBARCH}.deb" +else + [ -n "$GUI_BINARY" ] || { echo "--gui-binary required for gui" >&2; exit 2; } + cp "$GUI_BINARY" "$PKGDIR/usr/bin/mintlayer-node-gui" + chmod 0755 "$PKGDIR/usr/bin/mintlayer-node-gui" + + # icons + desktop entry + if [ -d "$OUT_DIR/assets/icons/usr" ]; then + # Pre-generated hicolor icon set (shared with the rpm builder) + cp -r "$OUT_DIR/assets/icons/usr" "$PKGDIR/" + else + "$PKG_ROOT/make-icons.sh" "$REPO_ROOT/build-tools/assets/node-gui-icon_512.png" "$PKGDIR" + fi + install -D -m 0644 "$PKG_ROOT/common/applications/mintlayer-node-gui.desktop" \ + "$PKGDIR/usr/share/applications/mintlayer-node-gui.desktop" + + ARTIFACT="Mintlayer_Node_GUI_linux_${VERSION}_${DEBARCH}.deb" +fi + +# --------------------------------------------------------------------------- +# Documentation: copyright, changelog, man pages +# --------------------------------------------------------------------------- +DOC_DIR="$PKGDIR/usr/share/doc/$PKG_NAME" +mkdir -p "$DOC_DIR" +install -m 0644 "$PKG_ROOT/deb/copyright" "$DOC_DIR/copyright" +install -m 0644 "$REPO_ROOT/LICENSE" "$DOC_DIR/LICENSE" + +CHANGELOG="$OUT_DIR/changelog.tmp" +sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$(date -R)/" \ + "$PKG_ROOT/deb/changelog.in" > "$CHANGELOG" +gzip -n -9 -c "$CHANGELOG" > "$DOC_DIR/changelog.gz" +rm -f "$CHANGELOG" + +# Man pages from --help output (binaries are executable on this host arch) +export LC_ALL=C.UTF-8 +MAN_DIR="$PKGDIR/usr/share/man/man1" +mkdir -p "$MAN_DIR" +for binpath in "$PKGDIR"/usr/bin/*; do + binname="$(basename "$binpath")" + if "$binpath" --help >/dev/null 2>&1; then + help2man --no-info --version-string="$VERSION" \ + --name="Part of the Mintlayer node software" \ + "$binpath" > "$MAN_DIR/$binname.1" 2>/dev/null || + { echo "warning: help2man failed for $binname, shipping stub" >&2 + printf '.TH %s 1\n.SH NAME\n%s \\- Mintlayer tool\n' "$binname" "$binname" \ + > "$MAN_DIR/$binname.1"; } + else + echo "warning: $binname --help not runnable, shipping stub man page" >&2 + printf '.TH %s 1\n.SH NAME\n%s \\- Mintlayer tool\n' "$binname" "$binname" \ + > "$MAN_DIR/$binname.1" + fi + gzip -n -9 "$MAN_DIR/$binname.1" +done + +# --------------------------------------------------------------------------- +# Verify every binary's shared libraries resolve in this environment. +# Anything "not found" here would also be unresolved at install time, and +# dpkg-shlibdeps would silently miss it from Depends. +# --------------------------------------------------------------------------- +MISSING_LIBS=0 +for binpath in "$PKGDIR"/usr/bin/*; do + if ldd "$binpath" 2>/dev/null | grep -q "not found"; then + echo "ERROR: unresolved shared libraries in $(basename "$binpath"):" >&2 + ldd "$binpath" | grep "not found" >&2 + MISSING_LIBS=1 + fi +done +[ "$MISSING_LIBS" -eq 0 ] || { + echo "install the missing libraries in the build environment and retry" >&2 + exit 1 +} + +# Strip binaries as distro packages do (the unstripped binaries remain +# available in the tar.gz release artifact). +for binpath in "$PKGDIR"/usr/bin/*; do + file "$binpath" | grep -q "not stripped" && strip --strip-unneeded "$binpath" +done + +# --------------------------------------------------------------------------- +# control: compute shared library dependencies with dpkg-shlibdeps +# --------------------------------------------------------------------------- +export DEBIAN_FRONTEND=noninteractive +SCRATCH="$OUT_DIR/dpkg-shlibdeps-scratch" +mkdir -p "$SCRATCH/debian" +cat > "$SCRATCH/debian/control" < +Standards-Version: 4.6.2 + +Package: $PKG_NAME +Architecture: any +Depends: \${shlibs:Depends}, \${misc:Depends} +Description: scratch control for dpkg-shlibdeps + Placeholder used only to satisfy dpkg-shlibdeps. +EOF + +SHLIBS="" +( cd "$SCRATCH" && dpkg-shlibdeps -O "$PKGDIR"/usr/bin/* 2>/dev/null ) | \ + grep -o 'shlibs:Depends=.*' | cut -d= -f2- > "$OUT_DIR/shlibs.tmp" || true +SHLIBS="$(cat "$OUT_DIR/shlibs.tmp")" +rm -rf "$SCRATCH" "$OUT_DIR/shlibs.tmp" + +INSTALLED_SIZE="$(du -sk --exclude=DEBIAN "$PKGDIR" | cut -f1)" + +sed -e "s/@VERSION@/$VERSION/g" \ + -e "s/@DEBARCH@/$DEBARCH/g" \ + -e "s/@INSTALLED_SIZE@/$INSTALLED_SIZE/g" \ + -e "s/@SHLIBS@/$SHLIBS/g" \ + "$PKG_ROOT/deb/control.in" > "$OUT_DIR/control.full" + +# Keep only the stanza for the package being built +awk -v want="$PKG_NAME" ' + /^Package:/ {keep = ($0 ~ "^Package: " want "$")} + {if (keep) print} +' "$OUT_DIR/control.full" > "$PKGDIR/DEBIAN/control" + +# An empty Depends line is invalid: drop it when no shared libs were found +sed -i '/^Depends: *$/d' "$PKGDIR/DEBIAN/control" +rm -f "$OUT_DIR/control.full" + +# Maintainer scripts + conffiles list +if [ "$PACKAGE" = node ]; then + install -m 0755 "$PKG_ROOT/deb/postinst-node" "$PKGDIR/DEBIAN/postinst" + install -m 0755 "$PKG_ROOT/deb/prerm-node" "$PKGDIR/DEBIAN/prerm" + install -m 0755 "$PKG_ROOT/deb/postrm-node" "$PKGDIR/DEBIAN/postrm" + ( cd "$PKGDIR" && find etc -type f | sed 's|^|/|' | sort ) > "$PKGDIR/DEBIAN/conffiles" +else + install -m 0755 "$PKG_ROOT/deb/postinst-gui" "$PKGDIR/DEBIAN/postinst" +fi + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- +dpkg-deb --build --root-owner-group "$PKGDIR" "$OUT_DIR/$ARTIFACT" +rm -rf "$PKGDIR" + +echo "built $OUT_DIR/$ARTIFACT" + +# --------------------------------------------------------------------------- +# Lint (errors fail; warnings are reported but allowed) +# --------------------------------------------------------------------------- +if ! lintian --fail-on error --info --show-overrides "$OUT_DIR/$ARTIFACT"; then + echo "lintian FAILED for $ARTIFACT" >&2 + exit 1 +fi +echo "lintian passed for $ARTIFACT" diff --git a/packaging/deb/changelog.in b/packaging/deb/changelog.in new file mode 100644 index 000000000..1a188ecee --- /dev/null +++ b/packaging/deb/changelog.in @@ -0,0 +1,5 @@ +mintlayer (@VERSION@) unstable; urgency=medium + + * Mintlayer release @VERSION@. + + -- Mintlayer @DATE@ diff --git a/packaging/deb/control.in b/packaging/deb/control.in new file mode 100644 index 000000000..e26fb317b --- /dev/null +++ b/packaging/deb/control.in @@ -0,0 +1,33 @@ +Package: mintlayer-node +Version: @VERSION@ +Section: utils +Priority: optional +Architecture: @DEBARCH@ +Maintainer: Mintlayer +Installed-Size: @INSTALLED_SIZE@ +Depends: @SHLIBS@ +Recommends: systemd | systemd-tmpfiles +Suggests: postgresql +Description: Mintlayer node and associated tools + Mintlayer full node daemon, wallet RPC daemon, API server components, + DNS seed server and command line wallet tools. + . + This package includes systemd units for running the node daemon as a + system service (the mainnet instance is enabled by default; all other + services are opt-in) and udev rules for Ledger and Trezor hardware + wallets. + +Package: mintlayer-node-gui +Version: @VERSION@ +Section: utils +Priority: optional +Architecture: @DEBARCH@ +Maintainer: Mintlayer +Installed-Size: @INSTALLED_SIZE@ +Depends: @SHLIBS@ +Recommends: mintlayer-node, hicolor-icon-theme +Description: Graphical interface for the Mintlayer node + A graphical user interface for running and interacting with a Mintlayer + node. + . + Recommends the mintlayer-node package for Ledger/Trezor udev rules. diff --git a/packaging/deb/copyright b/packaging/deb/copyright new file mode 100644 index 000000000..713c888fd --- /dev/null +++ b/packaging/deb/copyright @@ -0,0 +1,26 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: mintlayer +Source: https://github.com/mintlayer/mintlayer-core + +Files: * +Copyright: 2023 RBB S.r.l +License: MIT + Mintlayer is distributed under the MIT license. + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + . + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packaging/deb/postinst-gui b/packaging/deb/postinst-gui new file mode 100644 index 000000000..38bd01ec6 --- /dev/null +++ b/packaging/deb/postinst-gui @@ -0,0 +1,16 @@ +#!/bin/sh +# postinst for mintlayer-node-gui +set -e + +case "$1" in + configure) + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database -q /usr/share/applications >/dev/null 2>&1 || true + fi + if command -v gtk-update-icon-cache >/dev/null 2>&1; then + gtk-update-icon-cache -q -f /usr/share/icons/hicolor >/dev/null 2>&1 || true + fi + ;; +esac + +exit 0 diff --git a/packaging/deb/postinst-node b/packaging/deb/postinst-node new file mode 100644 index 000000000..870cc4851 --- /dev/null +++ b/packaging/deb/postinst-node @@ -0,0 +1,34 @@ +#!/bin/sh +# postinst for mintlayer-node +set -e + +case "$1" in + configure) + # Create the system user if systemd-sysusers has not done it already. + if ! getent passwd mintlayer >/dev/null 2>&1; then + adduser --system --group --home /var/lib/mintlayer \ + --no-create-home --quiet mintlayer || true + fi + if command -v systemd-sysusers >/dev/null 2>&1; then + systemd-sysusers mintlayer.conf || true + fi + + # Load the hardware wallet udev rules. + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules >/dev/null 2>&1 || true + udevadm trigger --subsystem-match=hidraw >/dev/null 2>&1 || true + fi + + # Enable the mainnet node (matching the shipped preset policy); all + # other units are opt-in. deb-systemd-helper works with or without + # systemd running. + if command -v deb-systemd-helper >/dev/null 2>&1; then + deb-systemd-helper enable mintlayer-node@mainnet.service >/dev/null || true + fi + if [ -d /run/systemd/system ] && command -v deb-systemd-invoke >/dev/null 2>&1; then + deb-systemd-invoke start mintlayer-node@mainnet.service >/dev/null || true + fi + ;; +esac + +exit 0 diff --git a/packaging/deb/postrm-node b/packaging/deb/postrm-node new file mode 100644 index 000000000..e661299c2 --- /dev/null +++ b/packaging/deb/postrm-node @@ -0,0 +1,18 @@ +#!/bin/sh +# postrm for mintlayer-node +set -e + +if [ "$1" = purge ]; then + if command -v deb-systemd-helper >/dev/null 2>&1; then + deb-systemd-helper purge mintlayer-node@mainnet.service >/dev/null 2>&1 || true + deb-systemd-helper unmask mintlayer-node@mainnet.service >/dev/null 2>&1 || true + fi + # Drop the system user; keep /var/lib/mintlayer unless the admin removed it + # explicitly after backing up (chain data can be large, deleting it on purge + # would be destructive beyond the package's own files). + if getent passwd mintlayer >/dev/null 2>&1; then + deluser --system mintlayer >/dev/null 2>&1 || true + fi +fi + +exit 0 diff --git a/packaging/deb/prerm-node b/packaging/deb/prerm-node new file mode 100644 index 000000000..35ea68a54 --- /dev/null +++ b/packaging/deb/prerm-node @@ -0,0 +1,13 @@ +#!/bin/sh +# prerm for mintlayer-node +set -e + +if [ "$1" = remove ] && [ -d /run/systemd/system ]; then + if command -v deb-systemd-invoke >/dev/null 2>&1; then + deb-systemd-invoke stop 'mintlayer-node@*.service' 'mintlayer-wallet-rpc@*.service' \ + 'mintlayer-api-web-server@*.service' 'mintlayer-api-blockchain-scanner@*.service' \ + 'mintlayer-dns-server@*.service' >/dev/null || true + fi +fi + +exit 0 diff --git a/packaging/make-icons.sh b/packaging/make-icons.sh new file mode 100755 index 000000000..d1af7cd31 --- /dev/null +++ b/packaging/make-icons.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Generate the hicolor icon set for mintlayer-node-gui from the 512px source. +# Usage: make-icons.sh +# Produces /usr/share/icons/hicolor/{64,128,256,512}x{...}/apps/mintlayer-node-gui.png +set -euo pipefail + +SRC="$1" +OUT="$2" + +command -v convert >/dev/null || { + echo "error: ImageMagick 'convert' not found (install imagemagick)" >&2 + exit 1 +} + +for size in 512 256 128 64; do + dir="$OUT/usr/share/icons/hicolor/${size}x${size}/apps" + mkdir -p "$dir" + if [ "$size" = 512 ]; then + cp "$SRC" "$dir/mintlayer-node-gui.png" + else + convert "$SRC" -resize "${size}x${size}" "$dir/mintlayer-node-gui.png" + fi +done + +echo "icons generated in $OUT" diff --git a/packaging/rpm/build.sh b/packaging/rpm/build.sh new file mode 100755 index 000000000..4578ae6a9 --- /dev/null +++ b/packaging/rpm/build.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +# Build the mintlayer-node / mintlayer-node-gui RPM packages. +# +# Intended to run inside a fedora:latest container (any host arch for x86_64; +# for the aarch64 target no emulation is needed: rpmbuild only repackages the +# prebuilt binaries). Self-provisions its build dependencies. +# +# Usage: +# build.sh --package node --rpmarch x86_64|aarch64 --version X.Y.Z \ +# --binaries-dir DIR --out DIR +# build.sh --package gui --rpmarch x86_64|aarch64 --version X.Y.Z \ +# --gui-binary PATH --repo-root DIR --out DIR +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +PACKAGE="" +RPMARCH="" +VERSION="" +BINARIES_DIR="" +GUI_BINARY="" +OUT_DIR="" +REPO_ROOT="" + +while [ $# -gt 0 ]; do + case "$1" in + --package) PACKAGE="$2"; shift 2 ;; + --rpmarch) RPMARCH="$2"; shift 2 ;; + --version) VERSION="$2"; shift 2 ;; + --binaries-dir) BINARIES_DIR="$2"; shift 2 ;; + --gui-binary) GUI_BINARY="$2"; shift 2 ;; + --repo-root) REPO_ROOT="$2"; shift 2 ;; + --out) OUT_DIR="$2"; shift 2 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +[ -n "$PACKAGE" ] && [ -n "$RPMARCH" ] && [ -n "$VERSION" ] && [ -n "$OUT_DIR" ] || + { echo "missing required args (package/rpmarch/version/out)" >&2; exit 2; } +[ -n "$REPO_ROOT" ] || REPO_ROOT="$PKG_ROOT/.." + +case "$RPMARCH" in + x86_64) TARGET="x86_64-redhat-linux" ;; + aarch64) TARGET="aarch64-redhat-linux" ;; + *) echo "invalid --rpmarch: $RPMARCH" >&2; exit 2 ;; +esac + +# Sanitize version for RPM (no dashes; 1.4.0-rc1 -> 1.4.0~rc1) +RPM_VERSION="${VERSION//-/~}" + +# --------------------------------------------------------------------------- +# Self-provision build dependencies (no-ops when already present) +# --------------------------------------------------------------------------- +dnf install -y -q rpm-build systemd-rpm-macros help2man file binutils >/dev/null + +mkdir -p "$OUT_DIR" +TOPDIR="$OUT_DIR/rpmbuild-$PACKAGE" +rm -rf "$TOPDIR" +mkdir -p "$TOPDIR"/{BUILD,RPMS,SOURCES,SPECS,SRPMS,BUILDROOT} +mkdir -p "$TOPDIR/BUILDROOT/mintlayer-node-gui" + +# --------------------------------------------------------------------------- +# Assemble the payload (buildroot layout) +# --------------------------------------------------------------------------- +BR="$TOPDIR/BUILDROOT/mintlayer-node-gui" +mkdir -p "$BR/usr/bin" + +if [ "$PACKAGE" = node ]; then + [ -n "$BINARIES_DIR" ] || { echo "--binaries-dir required for node" >&2; exit 2; } + for bin in node-daemon wallet-rpc-daemon api-web-server \ + api-blockchain-scanner-daemon dns-server wallet-cli \ + wallet-address-generator; do + install -m 0755 "$BINARIES_DIR/$bin" "$BR/usr/bin/mintlayer-$bin" + done + + mkdir -p "$BR/usr/lib/systemd/system" + for unit in "$PKG_ROOT/common/systemd/"*.service; do + install -m 0644 "$unit" "$BR/usr/lib/systemd/system/" + done + install -D -m 0644 "$PKG_ROOT/common/sysusers/mintlayer.conf" \ + "$BR/usr/lib/sysusers.d/mintlayer.conf" + install -D -m 0644 "$PKG_ROOT/common/preset/90-mintlayer.preset" \ + "$BR/usr/lib/systemd/system-preset/90-mintlayer.preset" + install -D -m 0644 "$PKG_ROOT/common/udev/51-mintlayer.rules" \ + "$BR/usr/lib/udev/rules.d/51-mintlayer.rules" + + for chain in mainnet testnet; do + for env in node.env wallet-rpc.env api-web-server.env \ + api-blockchain-scanner.env dns-server.env; do + install -D -m 0644 "$PKG_ROOT/common/env/$env" \ + "$BR/etc/mintlayer/$chain/$env" + done + done + + ARTIFACT="Mintlayer_Node_linux_${VERSION}_${RPMARCH}.rpm" + SPEC_IN="mintlayer-node.spec.in" +else + [ -n "$GUI_BINARY" ] || { echo "--gui-binary required for gui" >&2; exit 2; } + install -m 0755 "$GUI_BINARY" "$BR/usr/bin/mintlayer-node-gui" + if [ -d "$OUT_DIR/assets/icons/usr" ]; then + # Pre-generated hicolor icon set (shared with the deb builder) + cp -r "$OUT_DIR/assets/icons/usr" "$BR/" + else + "$PKG_ROOT/make-icons.sh" "$REPO_ROOT/build-tools/assets/node-gui-icon_512.png" "$BR" + fi + install -D -m 0644 "$PKG_ROOT/common/applications/mintlayer-node-gui.desktop" \ + "$BR/usr/share/applications/mintlayer-node-gui.desktop" + + ARTIFACT="Mintlayer_Node_GUI_linux_${VERSION}_${RPMARCH}.rpm" + SPEC_IN="mintlayer-node-gui.spec.in" +fi +SPEC_NAME="${SPEC_IN%.spec.in}.spec" + +# --------------------------------------------------------------------------- +# Strip binaries as distro packages do (before man pages are generated, so +# help2man still works — stripping does not affect --help). +# --------------------------------------------------------------------------- +dnf install -y -q binutils >/dev/null +for binpath in "$BR"/usr/bin/*; do + file "$binpath" | grep -q "not stripped" && strip --strip-unneeded "$binpath" +done + +# --------------------------------------------------------------------------- +# Man pages from --help output +# --------------------------------------------------------------------------- +export LC_ALL=C.UTF-8 +MAN_DIR="$BR/usr/share/man/man1" +mkdir -p "$MAN_DIR" +for binpath in "$BR"/usr/bin/*; do + binname="$(basename "$binpath")" + if "$binpath" --help >/dev/null 2>&1; then + help2man --no-info --version-string="$VERSION" \ + --name="Part of the Mintlayer node software" \ + "$binpath" > "$MAN_DIR/$binname.1" 2>/dev/null || + { echo "warning: help2man failed for $binname, shipping stub" >&2 + printf '.TH %s 1\n.SH NAME\n%s \\- Mintlayer tool\n' "$binname" "$binname" \ + > "$MAN_DIR/$binname.1"; } + else + echo "warning: $binname --help not runnable, shipping stub man page" >&2 + printf '.TH %s 1\n.SH NAME\n%s \\- Mintlayer tool\n' "$binname" "$binname" \ + > "$MAN_DIR/$binname.1" + fi + gzip -n -9 "$MAN_DIR/$binname.1" +done + +# License file consumed by the %files %license entry +if [ "$PACKAGE" = node ]; then + install -D -m 0644 "$REPO_ROOT/LICENSE" "$BR/usr/share/licenses/mintlayer-node/LICENSE" +else + install -D -m 0644 "$REPO_ROOT/LICENSE" "$BR/usr/share/licenses/mintlayer-node-gui/LICENSE" +fi + +# --------------------------------------------------------------------------- +# Spec + sources into the rpmbuild layout +# --------------------------------------------------------------------------- +sed -e "s/@VERSION@/$RPM_VERSION/g" \ + -e "s/@RPM_DATE@/$(date '+%a %b %e %Y')/g" \ + "$PKG_ROOT/rpm/$SPEC_IN" > "$TOPDIR/SPECS/${SPEC_IN%.spec.in}.spec" + +install -m 0644 "$PKG_ROOT/common/sysusers/mintlayer.conf" "$TOPDIR/SOURCES/" +install -m 0644 "$PKG_ROOT/common/preset/90-mintlayer.preset" "$TOPDIR/SOURCES/" +install -m 0644 "$REPO_ROOT/LICENSE" "$TOPDIR/SOURCES/" + +# --------------------------------------------------------------------------- +# Build. Repackaging of prebuilt binaries only: +# - no debuginfo (binaries are not stripped here, none exists in buildroot) +# - strip/objdump disabled so brp scripts never touch foreign-arch ELFs +# --------------------------------------------------------------------------- +rpmbuild -bb \ + --define "_topdir $TOPDIR" \ + --define "debug_package %{nil}" \ + --define "__strip /bin/true" \ + --define "__objdump /bin/true" \ + --define "_buildrootdir $TOPDIR/BUILDROOT.dir" \ + --buildroot "$BR" \ + --target "$TARGET" \ + "$TOPDIR/SPECS/$SPEC_NAME" 2>&1 | sed -n '/^Processing files/,$p' || { + echo "rpmbuild FAILED; rerun without output filter for details" >&2 + rpmbuild -bb \ + --define "_topdir $TOPDIR" \ + --define "debug_package %{nil}" \ + --define "__strip /bin/true" \ + --define "__objdump /bin/true" \ + --define "_buildrootdir $TOPDIR/BUILDROOT.dir" \ + --buildroot "$BR" \ + --target "$TARGET" \ + "$TOPDIR/SPECS/$SPEC_NAME" + } + +BUILT_RPM="$TOPDIR/RPMS/$RPMARCH/mintlayer-*.${RPMARCH}.rpm" +[ -e $BUILT_RPM ] || { echo "expected rpm not found: $BUILT_RPM" >&2; exit 1; } +mv $BUILT_RPM "$OUT_DIR/$ARTIFACT" +rm -rf "$TOPDIR" + +echo "built $OUT_DIR/$ARTIFACT" + +# --------------------------------------------------------------------------- +# Lint (errors fail; warnings are reported but allowed) +# --------------------------------------------------------------------------- +dnf install -y -q rpmlint >/dev/null +rpmlint "$OUT_DIR/$ARTIFACT" | tee "$OUT_DIR/rpmlint.log" +RPM_BASE="$(basename "$ARTIFACT" | sed 's/\.rpm$//')" +if grep -qE "^${RPM_BASE}\.[a-z0-9_]+: E:" "$OUT_DIR/rpmlint.log"; then + echo "rpmlint found errors in $ARTIFACT" >&2 + exit 1 +fi +echo "rpmlint passed for $ARTIFACT (warnings allowed)" diff --git a/packaging/rpm/mintlayer-node-gui.spec.in b/packaging/rpm/mintlayer-node-gui.spec.in new file mode 100644 index 000000000..eb0fd07c8 --- /dev/null +++ b/packaging/rpm/mintlayer-node-gui.spec.in @@ -0,0 +1,27 @@ +Name: mintlayer-node-gui +Version: @VERSION@ +Release: 1%{?dist} +Summary: Mintlayer Node GUI + +License: MIT +URL: https://github.com/mintlayer/mintlayer-core +Source0: LICENSE + +Requires: hicolor-icon-theme +Recommends: mintlayer-node + +%description +A graphical user interface for the Mintlayer node. + +Recommends the mintlayer-node package for Ledger/Trezor udev rules. + +%files +%license /usr/share/licenses/mintlayer-node-gui/LICENSE +%{_bindir}/mintlayer-node-gui +%{_datadir}/applications/mintlayer-node-gui.desktop +%{_datadir}/icons/hicolor/*/apps/mintlayer-node-gui.png +%{_mandir}/man1/mintlayer-node-gui.1.gz + +%changelog +* @RPM_DATE@ Mintlayer - @VERSION@-1 +- Mintlayer release @VERSION@. diff --git a/packaging/rpm/mintlayer-node.spec.in b/packaging/rpm/mintlayer-node.spec.in new file mode 100644 index 000000000..33f546aef --- /dev/null +++ b/packaging/rpm/mintlayer-node.spec.in @@ -0,0 +1,64 @@ +Name: mintlayer-node +Version: @VERSION@ +Release: 1%{?dist} +Summary: Mintlayer node and associated tools + +License: MIT +URL: https://github.com/mintlayer/mintlayer-core +Source0: LICENSE +Source1: mintlayer.conf +Source2: 90-mintlayer.preset + +Requires: systemd +Suggests: postgresql-server + +%description +Mintlayer full node daemon, wallet RPC daemon, API server components, +DNS seed server and command line wallet tools. + +This package includes systemd units for running the node daemon as a +system service (the mainnet instance is enabled by default; all other +services are opt-in) and udev rules for Ledger and Trezor hardware +wallets. + +%pre +%sysusers_create_package mintlayer-node %{SOURCE1} + +%post +%systemd_post mintlayer-node@mainnet.service +udevadm control --reload-rules >/dev/null 2>&1 || : +udevadm trigger --subsystem-match=hidraw >/dev/null 2>&1 || : + +%preun +%systemd_preun mintlayer-node@mainnet.service + +%postun +%systemd_postun mintlayer-node@mainnet.service + +%files +%license /usr/share/licenses/mintlayer-node/LICENSE +%{_bindir}/mintlayer-node-daemon +%{_bindir}/mintlayer-wallet-rpc-daemon +%{_bindir}/mintlayer-api-web-server +%{_bindir}/mintlayer-api-blockchain-scanner-daemon +%{_bindir}/mintlayer-dns-server +%{_bindir}/mintlayer-wallet-cli +%{_bindir}/mintlayer-wallet-address-generator +%{_unitdir}/mintlayer-node@.service +%{_unitdir}/mintlayer-wallet-rpc@.service +%{_unitdir}/mintlayer-api-web-server@.service +%{_unitdir}/mintlayer-api-blockchain-scanner@.service +%{_unitdir}/mintlayer-dns-server@.service +%{_prefix}/lib/sysusers.d/mintlayer.conf +%{_prefix}/lib/systemd/system-preset/90-mintlayer.preset +%{_udevrulesdir}/51-mintlayer.rules +%{_mandir}/man1/mintlayer-*.1.gz +%dir %attr(0755,root,root) %{_sysconfdir}/mintlayer +%dir %attr(0755,root,root) %{_sysconfdir}/mintlayer/mainnet +%dir %attr(0755,root,root) %{_sysconfdir}/mintlayer/testnet +%config(noreplace) %{_sysconfdir}/mintlayer/mainnet/*.env +%config(noreplace) %{_sysconfdir}/mintlayer/testnet/*.env + +%changelog +* @RPM_DATE@ Mintlayer - @VERSION@-1 +- Mintlayer release @VERSION@. diff --git a/packaging/test-local.sh b/packaging/test-local.sh new file mode 100755 index 000000000..beffd5ac6 --- /dev/null +++ b/packaging/test-local.sh @@ -0,0 +1,249 @@ +#!/usr/bin/env bash +# Local replica of the release packaging pipeline (.github/workflows/release_linux.yml). +# +# Builds (or reuses) the release binaries, then produces and validates all four +# native packages exactly the way CI does: same container images, same scripts, +# same gates. Run this before tagging a release — the tag build should be a +# formality. +# +# Usage: +# test-local.sh [--quick] [--version X.Y.Z] [--skip-build] +# [--binaries-dir x86_64=DIR,aarch64=DIR] [--skip-smoke] +# +# --quick amd64/x86_64 only (native containers, no qemu) +# --skip-build reuse existing binaries (see --binaries-dir) +# --skip-smoke skip the fresh-container install smoke tests +# +# By default binaries are built INSIDE a debian:12 container so their glibc +# matches the package floor — a host build on a newer distro (e.g. Arch, +# glibc 2.38+) would fail the deb builder's ldd gate by design. Use +# --binaries-dir to reuse CI-produced binaries instead. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PKG_ROOT="$REPO_ROOT/packaging" +DIST="$PKG_ROOT/dist" +mkdir -p "$DIST" + +QUICK=0 +SKIP_BUILD=0 +SKIP_SMOKE=0 +VERSION="" +BINARIES_OVERRIDE="" + +while [ $# -gt 0 ]; do + case "$1" in + --quick) QUICK=1; shift ;; + --skip-build) SKIP_BUILD=1; shift ;; + --skip-smoke) SKIP_SMOKE=1; shift ;; + --version) VERSION="$2"; shift 2 ;; + --binaries-dir) BINARIES_OVERRIDE="$2"; shift 2 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +# --------------------------------------------------------------------------- +# Preflight +# --------------------------------------------------------------------------- +echo "=== preflight ===" +command -v docker >/dev/null || { echo "docker not found" >&2; exit 1; } +docker info >/dev/null 2>&1 || { echo "docker daemon not reachable" >&2; exit 1; } + +# Version: --version wins; else derive from git tags like the workflow does. +if [ -z "$VERSION" ]; then + VERSION="$(git -C "$REPO_ROOT" describe --tags --abbrev=0 2>/dev/null | sed -e 's/^v//' || true)" + VERSION="${VERSION:-0.0.0-local}" +fi +echo "version: $VERSION" + +# arm64 emulation check (only needed when not --quick) +QEMU_OK=1 +if [ "$QUICK" -eq 0 ]; then + if ! docker run --rm --platform linux/arm64 debian:12 true >/dev/null 2>&1; then + QEMU_OK=0 + echo "WARNING: arm64 containers unavailable." >&2 + echo " register qemu binfmt with:" >&2 + echo " docker run --privileged --rm multiarch/qemu-user-static --reset -p yes" >&2 + echo " continuing with x86_64/amd64 only." >&2 + fi +fi + +echo "pulling container images..." +docker pull -q debian:12 >/dev/null +docker pull -q fedora:latest >/dev/null + +# --------------------------------------------------------------------------- +# Build (or locate) release binaries +# --------------------------------------------------------------------------- +ARCHES=(x86_64) +[ "$QUICK" -eq 0 ] && [ "$QEMU_OK" -eq 1 ] && ARCHES+=(aarch64) + +declare -A BIN_DIR +for arch in "${ARCHES[@]}"; do + BIN_DIR[$arch]="$REPO_ROOT/target/$arch-unknown-linux-gnu/release" + [ "$arch" = x86_64 ] && BIN_DIR[$arch]="$REPO_ROOT/target/release" +done +if [ "$SKIP_BUILD" -eq 1 ]; then + if [ -n "$BINARIES_OVERRIDE" ]; then + for pair in ${BINARIES_OVERRIDE//,/ }; do + [ -n "${pair%%=*}" ] && BIN_DIR[${pair%%=*}]="${pair##*=}" + done + fi +RESULTS=() +run_step() { # run_step