From eb590e1c1529e21901348dbab75dd7bacc525a7a Mon Sep 17 00:00:00 2001
From: Benjamin Gallois <business@gallois.cc>
Date: Thu, 20 Jun 2024 18:55:31 +0200
Subject: [PATCH] Fix #200 (nodes/rust/duniter-v2s!267)

* remove /ws from listen address

* fix error when user already exist

* change binary to duniter2

* add rpc-cors

* add config documentation

* add reference in service files

* use embedded distance oracle

* optimize ci

* use docker cache

* add systemd timer

* add documentation

* fix base_path default

* add duniter user

* add services

* update docs

* add deb package to ci

* add deb docker building
---
 .gitlab-ci.yml                            | 38 ++++++++++++++------
 Cargo.toml                                |  1 +
 README.md                                 |  5 ++-
 docker/build-deb.Dockerfile               | 37 +++++++++++++++++++
 docs/packaging/build-deb.md               | 29 +++++++++++++++
 docs/{user => packaging}/build-for-arm.md |  0
 docs/user/installation_debian.md          | 26 ++++++++++++++
 node/Cargo.toml                           |  7 ++++
 resources/debian/distance-oracle.service  | 10 ++++++
 resources/debian/distance-oracle.timer    | 10 ++++++
 resources/debian/duniter-mirror.service   | 37 +++++++++++++++++++
 resources/debian/duniter-smith.service    | 37 +++++++++++++++++++
 resources/debian/env_file                 | 43 +++++++++++++++++++++++
 resources/debian/postinst                 | 23 ++++++++++++
 scripts/build-deb.sh                      |  6 ++++
 15 files changed, 297 insertions(+), 12 deletions(-)
 create mode 100644 docker/build-deb.Dockerfile
 create mode 100644 docs/packaging/build-deb.md
 rename docs/{user => packaging}/build-for-arm.md (100%)
 create mode 100644 docs/user/installation_debian.md
 create mode 100644 resources/debian/distance-oracle.service
 create mode 100644 resources/debian/distance-oracle.timer
 create mode 100644 resources/debian/duniter-mirror.service
 create mode 100644 resources/debian/duniter-smith.service
 create mode 100644 resources/debian/env_file
 create mode 100644 resources/debian/postinst
 create mode 100755 scripts/build-deb.sh

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 419e7dbbf..6e33b6b46 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -89,10 +89,10 @@ run_benchmarks:
     - target/release/duniter benchmark storage --chain=dev --mul=2 --state-version=1
     - target/release/duniter benchmark overhead --chain=dev --wasm-execution=compiled --warmup=1 --repeat=100
     - target/release/duniter benchmark pallet --chain=dev --steps=5 --repeat=2 --pallet="*" --extrinsic="*" --wasm-execution=compiled
-# FIXME: "gtest_build"
-gdev_build:
+
+gtest_build:
   stage: build
-  image: rust:1-bullseye
+  extends: .env
   rules:
     - if: $CI_COMMIT_REF_NAME =~ /^wip*$/
       when: manual
@@ -105,10 +105,30 @@ gdev_build:
   variables:
     DEBIAN_FRONTEND: noninteractive
   script:
-    - apt-get update
-    - apt-get install -y clang cmake protobuf-compiler
     - cargo build -Zgit=shallow-deps --no-default-features --features gtest
 
+build_deb:
+  stage: deploy
+  extends: .env
+  rules:
+    - if: $CI_COMMIT_REF_NAME =~ /^wip*$/
+      when: manual
+    - if: $CI_COMMIT_TAG
+      when: never
+    - if: $CI_COMMIT_BRANCH =~ /^(release\/runtime-)[0-9].*/
+      when: never
+    - if: '$CI_MERGE_REQUEST_ID || $CI_COMMIT_BRANCH == "master"'
+    - when: manual
+  variables:
+    DEBIAN_FRONTEND: noninteractive
+  script:
+    - cargo install cargo-deb
+    - cargo build -Zgit=shallow-deps --release
+    - cargo deb --no-build -p duniter
+  artifacts:
+    paths:
+      - target/debian/duniter*.deb
+
 gdev_srtool_build:
   stage: build
   rules:
@@ -139,7 +159,7 @@ gdev_srtool_build:
 
 tests:
   stage: tests
-  image: rust:1-bullseye
+  extends: .env
   rules:
     - if: $CI_COMMIT_REF_NAME =~ /^wip*$/
       when: manual
@@ -152,8 +172,6 @@ tests:
   variables:
     DEBIAN_FRONTEND: noninteractive
   script:
-    - apt-get update
-    - apt-get install -y clang cmake protobuf-compiler
     - cargo tu
     - cargo tf
     - cargo cucumber-build
@@ -303,14 +321,12 @@ create_g1_data:
   stage: build
   rules:
     - if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_BRANCH =~ /^(release\/runtime-)[0-9].*/
-  image: rust:1-bullseye
+  extends: .env
   variables:
     WASM_FILE: $CI_PROJECT_DIR/release/${RUNTIME}_runtime.compact.compressed.wasm
     DUNITER_GENESIS_DATA: $CI_PROJECT_DIR/release/genesis.json # py-g1-migrator outputs this file with `./main.py`
     DEBIAN_FRONTEND: noninteractive
   script:
-    - apt-get update
-    - apt-get install -y clang cmake protobuf-compiler
     - cargo run -Zgit=shallow-deps ${FEATURES} -- build-spec --chain=${RUNTIME}_live > release/${RUNTIME}.json
     - cargo run -Zgit=shallow-deps ${FEATURES} -- build-spec --chain=release/${RUNTIME}.json --disable-default-bootnode --raw > release/${RUNTIME}-raw.json
     - cp node/specs/${RUNTIME}_client-specs.yaml release/
diff --git a/Cargo.toml b/Cargo.toml
index 8c35f9bbe..3f19f2f60 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -276,3 +276,4 @@ zeroize = { opt-level = 3 }
 lto = "thin"
 # Substrate runtime requires unwinding.
 panic = "unwind"
+
diff --git a/README.md b/README.md
index 6d67f3a60..513381125 100644
--- a/README.md
+++ b/README.md
@@ -37,9 +37,12 @@
     - [replay-block](./docs/test/replay-block.md)
   - [user](./docs/user/)
     - [autocompletion](./docs/user/autocompletion.md)
-    - [build-for-arm](./docs/user/build-for-arm.md)
     - [mirror](./docs/user/mirror.md) deploy a permanent ǦDev mirror node
     - [smith](./docs/user/smith.md) deploy a permanent ǦDev validator node
+    - [debian installation](./docs/user/installation_debian.md)
+  - [packaging](./docs/packaging/)
+    - [build-for-arm](./docs/packaging/build-for-arm.md)
+    - [build-debian](./docs/packaging/build-deb.md) build a native Debian package
 - [end2end-tests](./end2end-tests/) automated end to end tests written with cucumber
 - [live-tests](./live-tests/) sanity checks to test the storage of a live chain
 
diff --git a/docker/build-deb.Dockerfile b/docker/build-deb.Dockerfile
new file mode 100644
index 000000000..6fb06a3bc
--- /dev/null
+++ b/docker/build-deb.Dockerfile
@@ -0,0 +1,37 @@
+FROM paritytech/ci-linux:production
+
+# Set the working directory
+WORKDIR /app/
+
+# Copy the toolchain
+COPY rust-toolchain.toml ./
+
+# Install toolchain, substrate and cargo-deb with cargo cache
+RUN --mount=type=cache,target=/root/.cargo \
+    cargo install cargo-deb
+
+# Create a dummy project to cache dependencies
+COPY Cargo.toml .
+COPY rust-toolchain.toml ./
+RUN --mount=type=cache,target=/app/target \
+    --mount=type=cache,target=/root/.cargo/registry \
+    mkdir src && \
+    sed -i '/git = \|version = /!d' Cargo.toml && \
+    sed -i 's/false/true/' Cargo.toml && \
+    sed -i '1s/^/\[package\]\nname\=\"Dummy\"\n\[dependencies\]\n/' Cargo.toml && \
+    echo "fn main() {}" > src/main.rs && \
+    cargo build -Zgit=shallow-deps --release && \
+    rm -rf src Cargo.lock Cargo.toml
+
+# Copy the entire project
+COPY . .
+
+# Build the project and create Debian packages
+RUN --mount=type=cache,target=/app/target \
+    --mount=type=cache,target=/root/.cargo/registry \
+    cargo build -Zgit=shallow-deps --release && \
+    cargo deb --no-build -p duniter && \
+    cp -r ./target/debian/ ./
+
+# Clean up unnecessary files to reduce image size
+RUN rm -rf /app/target/release /root/.cargo/registry
diff --git a/docs/packaging/build-deb.md b/docs/packaging/build-deb.md
new file mode 100644
index 000000000..124512d15
--- /dev/null
+++ b/docs/packaging/build-deb.md
@@ -0,0 +1,29 @@
+# How to Build Duniter-V2S Debian Package
+
+Compile packages for native integration for Debian-based systems.
+
+## With Docker (on any system)
+
+1. Install Docker and Docker Buildx.
+2. Use the `scripts/build-deb.sh` script.
+3. The `.deb` packages will be located in the `target/debian` folder.
+
+## Without Docker (on a Debian-based system)
+
+1. Install the necessary dependencies:
+   ```sh
+   sudo apt-get install -y clang cmake protobuf-compiler libssl-dev
+   ```
+2. Compile the project:
+   ```sh
+   cargo build --release
+   ```
+3. Install `cargo-deb`:
+   ```sh
+   cargo install cargo-deb
+   ```
+4. Build the Duniter node `.deb` package:
+   ```sh
+   cargo deb --no-build -p duniter
+   ```
+5. The `.deb` package will be located in the `target/debian` folder.
diff --git a/docs/user/build-for-arm.md b/docs/packaging/build-for-arm.md
similarity index 100%
rename from docs/user/build-for-arm.md
rename to docs/packaging/build-for-arm.md
diff --git a/docs/user/installation_debian.md b/docs/user/installation_debian.md
new file mode 100644
index 000000000..b923b70db
--- /dev/null
+++ b/docs/user/installation_debian.md
@@ -0,0 +1,26 @@
+# Debian Setup Instructions
+
+## Mirror Node
+
+1. Download the Duniter .deb file.
+2. Install the package: `dpkg -i duniter_vx.y.z.deb`.
+3. Change the default configuration (at least the node name) by modifying `/etc/duniter/env_file`.
+4. Start the service: `sudo systemctl start duniter-mirror.service`.
+5. Enable the service at startup: `sudo systemctl enable duniter-mirror.service`.
+
+## Smith Node
+
+1. Download the Duniter .deb file.
+2. Install the package: `dpkg -i duniter_vx.y.z.deb`.
+3. Change the default configuration (at least the node name) by modifying `/etc/duniter/env_file`.
+4. Create network keys using the same base path as in the config file: `duniter key generate-node-key --base-path <YOUR_BASE_PATH> --chain <YOUR_CHAIN>`.
+5. Start the service: `sudo systemctl start duniter-validator.service`.
+6. Enable the service at startup: `sudo systemctl enable duniter-validator.service`.
+
+## Distance Oracle
+
+A Smith node needs to be installed.
+
+1. Change the default configuration by modifying `/etc/duniter/env_file`.
+2. Start the service: `sudo systemctl start distance-oracle.timer`.
+3. Enable the service at startup: `sudo systemctl enable distance-oracle.timer`.
diff --git a/node/Cargo.toml b/node/Cargo.toml
index e4125940b..3d90a37e8 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -185,3 +185,10 @@ substrate-build-script-utils = { workspace = true }
 sc-cli = { workspace = true }
 sc-service = { workspace = true }
 sp-trie = { workspace = true }
+
+[package.metadata.deb]
+maintainer-scripts = "../resources/debian"
+systemd-units = [ { unit-name = "duniter-mirror", enable = false },
+                  { unit-name = "duniter-smith", enable = false }]
+assets = [ ["../resources/debian/env_file", "/etc/duniter/env_file", "0640"],
+           ["../target/release/duniter", "/usr/bin/duniter2", "755"]]
diff --git a/resources/debian/distance-oracle.service b/resources/debian/distance-oracle.service
new file mode 100644
index 000000000..66fe13e53
--- /dev/null
+++ b/resources/debian/distance-oracle.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Duniter distance oracle.
+Requires=duniter-smith.service
+After=duniter-smith.service
+
+[Service]
+EnvironmentFile=/etc/duniter/env_file
+ExecStart=/usr/bin/duniter2 distance-oracle --evaluation-result-dir ${BASE_PATH}/chains/${DUNITER_CHAIN_NAME}/distance --rpc-url ${ORACLE_RPC_URL} --log ${ORACLE_LOG_LEVEL}
+User=duniter
+Group=duniter
diff --git a/resources/debian/distance-oracle.timer b/resources/debian/distance-oracle.timer
new file mode 100644
index 000000000..de7009329
--- /dev/null
+++ b/resources/debian/distance-oracle.timer
@@ -0,0 +1,10 @@
+[Unit]
+Description=Duniter distance oracle timer.
+
+[Timer]
+EnvironmentFile=/etc/duniter/env_file
+OnBootSec=1min
+OnUnitActiveSec=2min
+
+[Install]
+WantedBy=multi-user.target
diff --git a/resources/debian/duniter-mirror.service b/resources/debian/duniter-mirror.service
new file mode 100644
index 000000000..f48e1f2fb
--- /dev/null
+++ b/resources/debian/duniter-mirror.service
@@ -0,0 +1,37 @@
+# Inspired from https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/scripts/packaging/polkadot.service
+[Unit]
+Description=Duniter mirror node.
+
+[Service]
+EnvironmentFile=/etc/duniter/env_file
+ExecStart=/usr/bin/duniter2 --chain ${DUNITER_CHAIN_NAME} --name ${DUNITER_NODE_NAME}_mirror --listen-addr ${DUNITER_LISTEN_ADDR} --rpc-cors ${DUNITER_RPC_CORS} --state-pruning ${DUNITER_PRUNING_PROFILE} --base-path ${BASE_PATH}
+User=duniter
+Group=duniter
+Restart=always
+RestartSec=120
+CapabilityBoundingSet=
+LockPersonality=true
+NoNewPrivileges=true
+PrivateDevices=true
+PrivateMounts=true
+PrivateTmp=true
+PrivateUsers=true
+ProtectClock=true
+ProtectControlGroups=true
+ProtectHostname=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectSystem=strict
+RemoveIPC=true
+RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
+RestrictNamespaces=false
+RestrictSUIDSGID=true
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+SystemCallFilter=landlock_add_rule landlock_create_ruleset landlock_restrict_self seccomp mount umount2
+SystemCallFilter=~@clock @module @reboot @swap @privileged
+SystemCallFilter=pivot_root
+UMask=0027
+
+[Install]
+WantedBy=multi-user.target
diff --git a/resources/debian/duniter-smith.service b/resources/debian/duniter-smith.service
new file mode 100644
index 000000000..5b6ebd9d9
--- /dev/null
+++ b/resources/debian/duniter-smith.service
@@ -0,0 +1,37 @@
+# Inspired from https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/scripts/packaging/polkadot.service
+[Unit]
+Description=Duniter smith node.
+
+[Service]
+EnvironmentFile=/etc/duniter/env_file
+ExecStart=/usr/bin/duniter2 --chain ${DUNITER_CHAIN_NAME} --name ${DUNITER_NODE_NAME}_smith --listen-addr ${DUNITER_LISTEN_ADDR} --rpc-cors ${DUNITER_RPC_CORS} --state-pruning ${DUNITER_PRUNING_PROFILE} --base-path ${BASE_PATH} --rpc-methods Unsafe --validator
+User=duniter
+Group=duniter
+Restart=always
+RestartSec=120
+CapabilityBoundingSet=
+LockPersonality=true
+NoNewPrivileges=true
+PrivateDevices=true
+PrivateMounts=true
+PrivateTmp=true
+PrivateUsers=true
+ProtectClock=true
+ProtectControlGroups=true
+ProtectHostname=true
+ProtectKernelModules=true
+ProtectKernelTunables=true
+ProtectSystem=strict
+RemoveIPC=true
+RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
+RestrictNamespaces=false
+RestrictSUIDSGID=true
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+SystemCallFilter=landlock_add_rule landlock_create_ruleset landlock_restrict_self seccomp mount umount2
+SystemCallFilter=~@clock @module @reboot @swap @privileged
+SystemCallFilter=pivot_root
+UMask=0027
+
+[Install]
+WantedBy=multi-user.target
diff --git a/resources/debian/env_file b/resources/debian/env_file
new file mode 100644
index 000000000..e896759f7
--- /dev/null
+++ b/resources/debian/env_file
@@ -0,0 +1,43 @@
+# Sets the name of the node.
+# This should be a unique identifier for your node within the network.
+DUNITER_NODE_NAME=My Node
+
+# Specifies the blockchain network to connect to.
+DUNITER_CHAIN_NAME=gdev
+
+# Defines the address and port for node communication.
+# The format is /ip4/[IP address]/tcp/[port]/[protocol].
+# If SMITH NODE: `/ip4/0.0.0.0/tcp/<port>` and `/ip6/[::]/tcp/<port>`. Otherwise: `/ip4/0.0.0.0/tcp/<port>/ws` and `/ip6/[::]/tcp/<port>/ws`.
+DUNITER_LISTEN_ADDR=/ip4/0.0.0.0/tcp/30333
+
+# Specify browser origins allowed to access the HTTP & WS RPC servers.
+# A comma-separated list with no space of origins.
+# Value of `all` will disable origin validation. Default is to allow localhost and
+#<https://polkadot.js.org> origins.
+# Default: "http://localhost:*,http://127.0.0.1:*,https://localhost:*,https://127.0.0.1:*,https://polkadot.js.org"
+DUNITER_RPC_CORS=http://localhost:*,http://127.0.0.1:*,https://localhost:*,https://127.0.0.1:*,https://polkadot.js.org
+
+# Configures the pruning profile to manage how old blockchain data is stored.
+# This setting can only be set on the first creation of the database.
+# Options:
+# - 'archive': Keep the state of all blocks.
+# - 'archive-canonical': Keep only the state of finalized blocks.
+# - [number]: Keep the state of the last specified number of finalized blocks.
+# Default: 256 for a balanced pruning strategy.
+DUNITER_PRUNING_PROFILE=256
+
+# Sets the directory for storing Duniter data.
+# This should be a writable path on your system by the duniter user where the node can store its data.
+# Default: /home/duniter/.local/share/duniter
+BASE_PATH=/home/duniter/.local/share/duniter
+
+# URL for the Oracle RPC server.
+# This should point to the RPC endpoint that the oracle will use to communicate with the blockchain.
+# Default: ws://127.0.0.1:9944 for a local WebSocket RPC server.
+ORACLE_RPC_URL=ws://127.0.0.1:9944
+
+# Determines the log level for the Oracle.
+# Options include 'error', 'warn', 'info', 'debug', 'trace'.
+# 'info' is a good default that provides useful runtime information without too much detail.
+# Default: info
+ORACLE_LOG_LEVEL=info
diff --git a/resources/debian/postinst b/resources/debian/postinst
new file mode 100644
index 000000000..d2bdfc531
--- /dev/null
+++ b/resources/debian/postinst
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -e
+
+action="$1"
+config_file="/etc/duniter/env_file"
+
+if [ "$action" = "configure" ]; then
+  # Make user and group
+  getent group duniter >/dev/null 2>&1 || addgroup --system duniter
+  getent passwd duniter >/dev/null 2>&1 ||
+    adduser --system --disabled-password \
+    --ingroup duniter duniter
+
+  # Create user home dir
+  if [ ! -d "/home/duniter/" ]; then
+      mkdir /home/duniter
+      chown -R duniter:duniter /home/duniter
+      chmod 700 /home/duniter
+  fi
+fi
+
+#DEBHELPER#
diff --git a/scripts/build-deb.sh b/scripts/build-deb.sh
new file mode 100755
index 000000000..bbe52821b
--- /dev/null
+++ b/scripts/build-deb.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+cd "$(dirname "$0")"
+docker buildx build -t build_deb --file ../docker/build-deb.Dockerfile ../
+id=$(docker create build_deb)
+docker cp $id:/app/debian/ ../target
-- 
GitLab