diff --git a/Dockerfile b/Dockerfile.ephemeral similarity index 100% rename from Dockerfile rename to Dockerfile.ephemeral diff --git a/Dockerfile.persistent b/Dockerfile.persistent new file mode 100644 index 0000000000..aa71eaffa8 --- /dev/null +++ b/Dockerfile.persistent @@ -0,0 +1,59 @@ +# --- Build stage --- +FROM ubuntu:24.04 AS build-stage + +RUN apt-get update && apt-get install -y unzip curl build-essential openssl libssl-dev pkg-config && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +WORKDIR /opt/rusk +ENV RUSK_PROFILE_PATH=/opt/dusk/rusk/ +ENV PATH="$PATH:/root/.cargo/bin" + +RUN apt-get update && apt-get install -y clang && rm -rf /var/lib/apt/lists/* + +# Using this to modify rusk config file before running a node +RUN cargo install toml-cli --version 0.2.3 + +COPY . . + +ARG TARGETPLATFORM +# See also https://github.com/docker/buildx/issues/510 +ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64} + +# Generate keys and compile genesis contracts +RUN make keys +RUN make wasm + +ARG NODE_TYPE="provisioner" + +RUN case "$NODE_TYPE" in \ + "provisioner") cargo build --release -p dusk-rusk ;; \ + "archive") cargo build --release --features archive -p dusk-rusk ;; \ + "prover") cargo build --release --no-default-features --features prover -p dusk-rusk ;; \ + *) echo "Unrecognized node type: $NODE_TYPE. Expected one of 'provisioner', 'archive' and 'prover'"; exit 1 ;; \ + esac + +# --- Run stage --- +FROM ubuntu:24.04 AS run-stage + +RUN apt-get update && apt-get install -y unzip curl net-tools libssl-dev && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/dusk + +ENV RUSK_PROFILE_PATH=/opt/dusk/rusk +ENV RUSK_RECOVERY_INPUT=/opt/dusk/conf/genesis.toml +ENV RUST_BACKTRACE=full +ENV NETWORK=mainnet + +EXPOSE 9000/udp +EXPOSE 8080/tcp + +# Copy only the necessary files from the build stage +COPY --from=build-stage /opt/rusk/target/release/rusk /opt/dusk/bin/rusk +COPY --from=build-stage /opt/rusk/scripts/persistent-docker-setup/setup.sh /opt/dusk/setup.sh +COPY --from=build-stage /opt/rusk/scripts/persistent-docker-setup/detect_ips.sh /opt/dusk/detect_ips.sh +COPY --from=build-stage /root/.cargo/bin/toml /usr/bin/toml-cli + +RUN chmod +x /opt/dusk/setup.sh /opt/dusk/detect_ips.sh + +CMD [ "./setup.sh" ] diff --git a/README.md b/README.md index 8281dcb00c..4bb437ae79 100644 --- a/README.md +++ b/README.md @@ -160,18 +160,20 @@ make wasm for=transfer ## 🐳 Docker support +### Local Ephemeral Node + It's also possible to run a local ephemeral node with Docker. To build the Docker image with archive: ```bash -docker build -t rusk . +docker build -f Dockerfile.ephemeral -t rusk . ``` To build the Docker image **without** archive: ```bash -docker build -t rusk --build-arg CARGO_FEATURES="" . +docker build -t -f Dockerfile.ephemeral rusk --build-arg CARGO_FEATURES="" . ``` To run Rusk inside a Docker container: @@ -182,6 +184,67 @@ docker run -p 9000:9000/udp -p 8080:8080/tcp rusk Port 9000 is used for Kadcast, port 8080 for the HTTP and GraphQL APIs. +### Persistent Node + +To build the docker image for a provisioner +```bash +docker build -f Dockerfile.persistent -t rusk --build-arg NODE_TYPE=provisioner . +``` + +To build for an archiver or prover instead, build with NODE_TYPE=archive or NODE_TYPE=prover, +respectively. + +To run: + +```bash +docker run -it \ + -v /path/to/consensus.keys:/opt/dusk/conf/consensus.keys \ + -v /path/to/rusk/profile:/opt/dusk/rusk \ + -e NETWORK= \ + -e DUSK_CONSENSUS_KEYS_PASS= \ + -p 9000:9000/udp \ + -p 8080:8080/tcp \ + rusk +``` + +#### Customizing Configuration + +The configuration used for rusk is based on the template file at `https://raw.githubusercontent.com/dusk-network/node-installer/9cdf0be1372ca6cb52cb279bd58781a3a27bf8ae/conf/rusk.toml`. +As part of the node setup process when the container is started, the kadcast ID, bootstrapping nodes, and genesis timestamp +will be changed based on the selected network and the resulting configuration will be used for running the node. +The IP addresses used for listening in kadcast and, if configured, http will be detected and automatically configured. + +To customize the configuration, the configuration template file can be copied and modified. The custom configuration template +should be mounted on `/opt/dusk/conf/rusk.template.toml`. + +```bash +docker run -it \ + -v /path/to/consensus.keys:/opt/dusk/conf/consensus.keys + -v /path/to/rusk/profile:/opt/dusk/rusk \ + -v /path/to/rusk.modified-template.toml:/opt/dusk/conf/rusk.template.toml \ + -e NETWORK= \ + -e DUSK_CONSENSUS_KEYS_PASS= \ + -p 9000:9000/udp \ + -p 8080:8080/tcp \ + rusk +``` + +##### IP Addresses + +When using a custom configuration file, properties that use IP addresses should be set to 'N/A'. For example, if +you want HTTP to be configured: + +```toml +[http] +listen_address = 'N/A' +``` + +This entry should be present in the template configuration file. When the node is starting, the address to be used +will be detected and this configuration will be set to listen at port 8080. + +Likewise, the `kadcast.public_address` and `kadcast.listen_address` properties in the configuration file should be set +to 'N/A'. During node startup, they will be detected and set to use port 9000. + ## License The Rusk software is licensed under the diff --git a/scripts/persistent-docker-setup/detect_ips.sh b/scripts/persistent-docker-setup/detect_ips.sh new file mode 100755 index 0000000000..2420ea4a00 --- /dev/null +++ b/scripts/persistent-docker-setup/detect_ips.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Script for detecting IP addresses in the persistent state Docker container. + +# Fetch IPv4 WAN address using ifconfig.me, fallback to ipinfo.io +PUBLIC_IP=$(curl -4 -s https://ifconfig.me) +if [ -z "$PUBLIC_IP" ]; then + PUBLIC_IP=$(curl -4 -s https://ipinfo.io/ip) +fi + +# Validate IPv4 address +if [[ -z "$PUBLIC_IP" || ! "$PUBLIC_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Unable to retrieve a valid WAN IPv4 address" + exit 1 +fi + +runOnMac=false +int2ip() { printf ${2+-v} $2 "%d.%d.%d.%d" \ + $(($1>>24)) $(($1>>16&255)) $(($1>>8&255)) $(($1&255)) ;} +ip2int() { local _a=(${1//./ }) ; printf ${2+-v} $2 "%u" $(( _a<<24 | + ${_a[1]} << 16 | ${_a[2]} << 8 | ${_a[3]} )) ;} +while IFS=$' :\t\r\n' read a b c d; do + [ "$a" = "usage" ] && [ "$b" = "route" ] && runOnMac=true + if $runOnMac ;then + case $a in + gateway ) gWay=$b ;; + interface ) iFace=$b ;; + esac + else + [ "$a" = "0.0.0.0" ] && [ "$c" = "$a" ] && iFace=${d##* } gWay=$b + fi +done < <(/sbin/route -n 2>&1 || /sbin/route -n get 0.0.0.0/0) +ip2int $gWay gw +while read lhs rhs; do + [ "$lhs" ] && { + [ -z "${lhs#*:}" ] && iface=${lhs%:} + [ "$lhs" = "inet" ] && [ "$iface" = "$iFace" ] && { + mask=${rhs#*netmask } + mask=${mask%% *} + [ "$mask" ] && [ -z "${mask%0x*}" ] && + printf -v mask %u $mask || + ip2int $mask mask + ip2int ${rhs%% *} ip + (( ( ip & mask ) == ( gw & mask ) )) && + int2ip $ip myIp && int2ip $mask netMask + } + } +done < <(/sbin/ifconfig) + +echo "$PUBLIC_IP" +if [ -z "$myIp" ]; then + echo "$PUBLIC_IP" +else + echo "$myIp" +fi + diff --git a/scripts/persistent-docker-setup/setup.sh b/scripts/persistent-docker-setup/setup.sh new file mode 100644 index 0000000000..8c86b4545d --- /dev/null +++ b/scripts/persistent-docker-setup/setup.sh @@ -0,0 +1,113 @@ +#!/bin/bash +set -e + +# Script for setting up a node in the persistent state Docker container. +# +# It detects the IP addresses for the node, generates a configuration file for +# the node based on the default `rusk.toml` used in the dusk-node-installer (or +# a user-supplied configuration file which can be provided by mounting at +# `/opt/dusk/conf/rusk.template.toml`), and runs the node. + +echo "Starting node environment" + +RUSK_CONFIG_DIR=/opt/dusk/conf +RUSK_TEMPLATE_CONFIG_PATH="$RUSK_CONFIG_DIR/rusk.template.toml" +RUSK_CONFIG_PATH="$RUSK_CONFIG_DIR/rusk.toml" + +detect_ips_output=$(./detect_ips.sh) +PUBLIC_IP=$(echo "$detect_ips_output" | sed -n '1p') +LISTEN_IP=$(echo "$detect_ips_output" | sed -n '2p') + +toml_set() { + file=$1 + property=$2 + value=$3 + + echo -e "$(toml-cli set $file $property $value)" > $file +} + +configure_network() { + local network=$1 + local kadcast_id + local bootstrapping_nodes + local genesis_timestamp + local base_state + local prover_url + + case "$network" in + mainnet) + kadcast_id="0x1" + bootstrapping_nodes="['165.232.91.113:9000', '64.226.105.70:9000', '137.184.232.115:9000']" + genesis_timestamp="'2025-01-07T12:00:00Z'" + base_state="https://nodes.dusk.network/genesis-state" + prover_url="https://provers.dusk.network" + ;; + testnet) + kadcast_id="0x2" + bootstrapping_nodes="['134.122.62.88:9000','165.232.64.16:9000','137.184.118.43:9000']" + genesis_timestamp="'2024-12-23T17:00:00Z'" + base_state="https://testnet.nodes.dusk.network/genesis-state" + prover_url="https://testnet.provers.dusk.network" + ;; + devnet) + kadcast_id="0x3" + bootstrapping_nodes="['128.199.32.54', '159.223.29.22', '143.198.225.158']" + genesis_timestamp="'2024-12-23T12:00:00Z'" + base_state="https://devnet.nodes.dusk.network/genesis-state" + prover_url="https://devnet.provers.dusk.network" + ;; + *) + echo "Unknown network: $network. Defaulting to mainnet." + configure_network "mainnet" + return + ;; + esac + + echo "Generating configuration" + + cat > "$RUSK_CONFIG_DIR/genesis.toml" < /dev/null; then + toml_set "$RUSK_CONFIG_PATH" http.listen_address "$LISTEN_IP:8080" + fi +} + +download_rusk_config() { + echo "Downloading default template rusk config from the dusk node installer" + REMOTE_LOCATION=https://raw.githubusercontent.com/dusk-network/node-installer/9cdf0be1372ca6cb52cb279bd58781a3a27bf8ae/conf/rusk.toml + mkdir -p "$RUSK_CONFIG_DIR" + curl -o "$RUSK_TEMPLATE_CONFIG_PATH" "$REMOTE_LOCATION" + if [ "$(cat $RUSK_TEMPLATE_CONFIG_PATH)" = "404: Not Found" ]; then + echo "Couldn't find the default rusk template config file. This is a bug, please file an issue." + exit 1 + fi +} + +if [ ! -f "$RUSK_TEMPLATE_CONFIG_PATH" ]; then + download_rusk_config +fi + +configure_network "$NETWORK" + +if [ -z "$DUSK_CONSENSUS_KEYS_PASS" ]; then + echo "DUSK_CONSENSUS_KEYS_PASS is not set" + exit 1 +fi + +echo "Selected network: $NETWORK" + +/opt/dusk/bin/rusk recovery keys +/opt/dusk/bin/rusk recovery state + +echo "Starting rusk" +echo "Rusk config:" +cat "$RUSK_CONFIG_PATH" +/opt/dusk/bin/rusk --config "$RUSK_CONFIG_PATH"