Skip to content

Commit

Permalink
Merge pull request #3402 from dusk-network/persistent-docker-build
Browse files Browse the repository at this point in the history
docker: add support for persistent state builds with docker
  • Loading branch information
d-sonuga authored Feb 11, 2025
2 parents 8ea5111 + 7ddf654 commit d4e52a2
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 2 deletions.
File renamed without changes.
59 changes: 59 additions & 0 deletions Dockerfile.persistent
Original file line number Diff line number Diff line change
@@ -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" ]
67 changes: 65 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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=<mainnet|testnet|devnet> \
-e DUSK_CONSENSUS_KEYS_PASS=<consensus-keys-password> \
-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=<mainnet|testnet|devnet> \
-e DUSK_CONSENSUS_KEYS_PASS=<consensus-keys-password> \
-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
Expand Down
56 changes: 56 additions & 0 deletions scripts/persistent-docker-setup/detect_ips.sh
Original file line number Diff line number Diff line change
@@ -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

113 changes: 113 additions & 0 deletions scripts/persistent-docker-setup/setup.sh
Original file line number Diff line number Diff line change
@@ -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" <<EOF
base_state = "$base_state"
EOF

cp "$RUSK_TEMPLATE_CONFIG_PATH" "$RUSK_CONFIG_PATH"
sed -i "s/^kadcast_id =.*/kadcast_id = $kadcast_id/" "$RUSK_CONFIG_PATH"
sed -i "s/^bootstrapping_nodes =.*/bootstrapping_nodes = $bootstrapping_nodes/" "$RUSK_CONFIG_PATH"
sed -i "s/^genesis_timestamp =.*/genesis_timestamp = $genesis_timestamp/" "$RUSK_CONFIG_PATH"
toml_set "$RUSK_CONFIG_PATH" kadcast.public_address "$PUBLIC_IP:9000"
toml_set "$RUSK_CONFIG_PATH" kadcast.listen_address "$LISTEN_IP:9000"
if toml-cli get "$RUSK_CONFIG_PATH" http &> /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"

0 comments on commit d4e52a2

Please sign in to comment.