diff --git a/.gitignore b/.gitignore index a32ef5e..37e6f92 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ data *.old .DS_Store +# User configuration files +user-config.yml + # Generated genesis files (created by generate-genesis.sh) config.yaml validators.yaml diff --git a/README.md b/README.md index 3b2916a..70ecd54 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,44 @@ NETWORK_DIR=local-devnet ./spin-node.sh --node zeam_0 --generateGenesis --popupT NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --metrics ``` +### Using custom Docker images + +You can override default Docker images using the `--config-file` flag. This is useful for testing custom builds or using specific versions without modifying the codebase. + +**Basic usage (without custom images):** +```sh +# Uses default images from client-cmds/default-client-config.yml +NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis +``` + +**With custom config file:** +```sh +# Override specific client images +NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --config-file user-config.yml +``` + +**Example config file (user-config.yml):** +```yaml +clients: + - name: zeam + image: blockblaz/zeam:feature-branch + - name: ream + image: ghcr.io/reamlabs/ream:v2.0 +``` + +**Testing a specific client build:** +```sh +# Create custom config file for zeam /my-zeam-config.yml +clients: + - name: zeam + image: blockblaz/zeam:custom-tag + +# Run with custom zeam image +NETWORK_DIR=local-devnet ./spin-node.sh --node zeam_0 --config-file /my-zeam-config.yml +``` + +Only specify clients you want to override - others will use their defaults from `client-cmds/default-client-config.yml`. + ## Args 1. `NETWORK_DIR` is an env to specify the network directory. Should have a `genesis` directory with genesis config. A `data` folder will be created inside this `NETWORK_DIR` if not already there. @@ -119,6 +157,11 @@ NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --metrics - If not provided, defaults to `latest` for zeam, ream, and lantern, and `dd67521` for qlean - The script will automatically pull the specified Docker images before running containers - Example: `--tag devnet0` or `--tag devnet1` +11. `--config-file` specifies a custom configuration file to override default Docker images for specific clients. + - Path to a YAML file containing client image overrides (e.g., `user-config.yml` or `/path/to/my-config.yml`) + - Only clients specified in the config file are overridden; others use defaults from `client-cmds/default-client-config.yml` + - See [Using custom Docker images](#using-custom-docker-images) scenario for usage examples + - Example: `--config-file user-config.yml` or `--config-file /path/to/custom-config.yml` 12. `--metrics` enables metrics collection on all nodes. When specified, each client will activate its metrics endpoint according to its implementation. Metrics ports are configured per node in `validator-config.yaml`. ### Clients supported @@ -148,12 +191,19 @@ The quickstart uses separate directories for local and Ansible deployments: ``` lean-quickstart/ -├── local-devnet/ # Local development +├── client-cmds/ # Client command scripts +│ ├── default-client-config.yml # Default Docker images for all clients +│ ├── zeam-cmd.sh +│ ├── ream-cmd.sh +│ └── ... +├── user-config.yml.example # Example custom config (copy to user-config.yml) +├── user-config.yml # Your custom image overrides (gitignored) +├── local-devnet/ # Local development │ ├── genesis/ │ │ └── validator-config.yaml # Local IPs (127.0.0.1) │ └── data/ # Node data directories │ -└── ansible-devnet/ # Ansible/remote deployment +└── ansible-devnet/ # Ansible/remote deployment ├── genesis/ │ └── validator-config.yaml # Remote IPs (your server IPs) └── data/ # Node data directories diff --git a/client-cmds/default-client-config.yml b/client-cmds/default-client-config.yml new file mode 100644 index 0000000..cc700c0 --- /dev/null +++ b/client-cmds/default-client-config.yml @@ -0,0 +1,22 @@ +# Default Client Configuration +# This file contains the default Docker images for all supported clients +# These defaults are used when no custom config file is specified via --config-file + +clients: + - name: zeam + image: blockblaz/zeam:latest + + - name: ream + image: ghcr.io/reamlabs/ream:latest + + - name: qlean + image: qdrvm/qlean-mini:latest + + - name: lantern + image: piertwo/lantern:latest + + - name: lighthouse + image: hopinheimer/lighthouse:latest + + - name: grandine + image: sifrai/lean:latest diff --git a/client-cmds/grandine-cmd.sh b/client-cmds/grandine-cmd.sh index b8456a6..f07e6a8 100644 --- a/client-cmds/grandine-cmd.sh +++ b/client-cmds/grandine-cmd.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Docker image (set from default-client-config.yml or user config via --config-file) +# grandineImage is exported by spin-node.sh before sourcing this file + node_binary="$grandine_bin \ --genesis $configDir/config.yaml \ --validator-registry-path $configDir/validators.yaml \ @@ -10,7 +13,7 @@ node_binary="$grandine_bin \ --address 0.0.0.0 \ --hash-sig-key-dir $configDir/hash-sig-keys" -node_docker="sifrai/lean:unstable \ +node_docker="$grandineImage \ --genesis /config/config.yaml \ --validator-registry-path /config/validators.yaml \ --bootnodes /config/nodes.yaml \ diff --git a/client-cmds/lantern-cmd.sh b/client-cmds/lantern-cmd.sh index 3075a95..cd3dcc3 100755 --- a/client-cmds/lantern-cmd.sh +++ b/client-cmds/lantern-cmd.sh @@ -1,7 +1,8 @@ #!/bin/bash #-----------------------lantern setup---------------------- -LANTERN_IMAGE="piertwo/lantern:v0.0.1" +# Docker image (set from default-client-config.yml or user config via --config-file) +# lanternImage is exported by spin-node.sh before sourcing this file devnet_flag="" if [ -n "$devnet" ]; then @@ -24,7 +25,7 @@ node_binary="$scriptDir/lantern/build/lantern_cli \ --log-level debug \ --hash-sig-key-dir $configDir/hash-sig-keys" -node_docker="$LANTERN_IMAGE --data-dir /data \ +node_docker="$lanternImage --data-dir /data \ --genesis-config /config/config.yaml \ --validator-registry-path /config/validators.yaml \ --genesis-state /config/genesis.ssz \ diff --git a/client-cmds/lighthouse-cmd.sh b/client-cmds/lighthouse-cmd.sh index 1e129c2..51938c6 100644 --- a/client-cmds/lighthouse-cmd.sh +++ b/client-cmds/lighthouse-cmd.sh @@ -3,6 +3,9 @@ # Metrics enabled by default metrics_flag="--metrics" +# Docker image (set from default-client-config.yml or user config via --config-file) +# lighthouseImage is exported by spin-node.sh before sourcing this file + node_binary="$lighthouse_bin lean_node \ --datadir \"$dataDir/$item\" \ --config \"$configDir/config.yaml\" \ @@ -16,7 +19,7 @@ node_binary="$lighthouse_bin lean_node \ --metrics-address 0.0.0.0 \ --metrics-port $metricsPort" -node_docker="hopinheimer/lighthouse:latest lighthouse lean_node \ +node_docker="$lighthouseImage lighthouse lean_node \ --datadir /data \ --config /config/config.yaml \ --validators /config/validator-config.yaml \ diff --git a/client-cmds/qlean-cmd.sh b/client-cmds/qlean-cmd.sh index 178ca98..a90b775 100644 --- a/client-cmds/qlean-cmd.sh +++ b/client-cmds/qlean-cmd.sh @@ -3,6 +3,10 @@ #-----------------------qlean setup---------------------- # expects "qlean" submodule or symlink inside "lean-quickstart" root directory # https://github.com/qdrvm/qlean-mini + +# Docker image (set from default-client-config.yml or user config via --config-file) +# qleanImage is exported by spin-node.sh before sourcing this file + node_binary="$scriptDir/qlean/build/src/executable/qlean \ --modules-dir $scriptDir/qlean/build/src/modules \ --genesis $configDir/config.yaml \ @@ -16,8 +20,8 @@ node_binary="$scriptDir/qlean/build/src/executable/qlean \ --listen-addr /ip4/0.0.0.0/udp/$quicPort/quic-v1 \ --prometheus-port $metricsPort \ -ldebug" - -node_docker="qdrvm/qlean-mini:3a96a1f \ + +node_docker="$qleanImage \ --genesis /config/config.yaml \ --validator-registry-path /config/validators.yaml \ --validator-keys-manifest /config/hash-sig-keys/validator-keys-manifest.yaml \ diff --git a/client-cmds/ream-cmd.sh b/client-cmds/ream-cmd.sh index 0c5105d..ad30479 100755 --- a/client-cmds/ream-cmd.sh +++ b/client-cmds/ream-cmd.sh @@ -4,6 +4,9 @@ # Metrics enabled by default metrics_flag="--metrics" +# Docker image (set from default-client-config.yml or user config via --config-file) +# reamImage is exported by spin-node.sh before sourcing this file + # modify the path to the ream binary as per your system node_binary="$scriptDir/../ream/target/release/ream --data-dir $dataDir/$item \ lean_node \ @@ -17,7 +20,7 @@ node_binary="$scriptDir/../ream/target/release/ream --data-dir $dataDir/$item \ --metrics-port $metricsPort \ --http-address 0.0.0.0" -node_docker="ghcr.io/reamlabs/ream:latest --data-dir /data \ +node_docker="$reamImage --data-dir /data \ lean_node \ --network /config/config.yaml \ --validator-registry-path /config/validators.yaml \ diff --git a/client-cmds/zeam-cmd.sh b/client-cmds/zeam-cmd.sh index 1485465..23c2085 100644 --- a/client-cmds/zeam-cmd.sh +++ b/client-cmds/zeam-cmd.sh @@ -6,6 +6,9 @@ # Metrics enabled by default metrics_flag="--metrics_enable" +# Docker image (set from default-client-config.yml or user config via --config-file) +# zeamImage is exported by spin-node.sh before sourcing this file + node_binary="$scriptDir/../zig-out/bin/zeam node \ --custom_genesis $configDir \ --validator_config $validatorConfig \ @@ -14,7 +17,7 @@ node_binary="$scriptDir/../zig-out/bin/zeam node \ $metrics_flag \ --metrics_port $metricsPort" -node_docker="--security-opt seccomp=unconfined blockblaz/zeam:devnet1 node \ +node_docker="--security-opt seccomp=unconfined $zeamImage node \ --custom_genesis /config \ --validator_config $validatorConfig \ --data-dir /data \ diff --git a/load-client-config.sh b/load-client-config.sh new file mode 100644 index 0000000..3d014aa --- /dev/null +++ b/load-client-config.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# Load client configuration from default and optional user config files + +# Arrays to store client names and images (bash 3.2 compatible) +CLIENT_NAMES=() +CLIENT_IMAGES_LIST=() +KNOWN_CLIENTS=("zeam" "ream" "qlean" "lantern" "lighthouse" "grandine") + +# Function to load default config +load_default_config() { + local default_config="$scriptDir/client-cmds/default-client-config.yml" + + if [ ! -f "$default_config" ]; then + echo "⚠️ Warning: Default config not found at $default_config" + return 1 + fi + + # Load default images using yq + while IFS= read -r line; do + local client_name=$(echo "$line" | awk '{print $1}') + local client_image=$(echo "$line" | awk '{print $2}') + CLIENT_NAMES+=("$client_name") + CLIENT_IMAGES_LIST+=("$client_image") + done < <(yq eval '.clients[] | .name + " " + .image' "$default_config") + + echo "✓ Loaded default client images from $default_config" +} + +# Function to find index of client name +find_client_index() { + local search_name="$1" + local i=0 + for name in "${CLIENT_NAMES[@]}"; do + if [ "$name" == "$search_name" ]; then + echo "$i" + return 0 + fi + ((i++)) + done + echo "-1" +} + +# Function to load user config and override defaults +load_user_config() { + local user_config="$1" + + if [ -z "$user_config" ]; then + return 0 + fi + + if [ ! -f "$user_config" ]; then + echo "⚠️ Warning: User config file not found at $user_config - using defaults" + return 1 + fi + + echo "Loading user config from $user_config..." + + # Load user-specified images + local override_count=0 + while IFS= read -r line; do + local client_name=$(echo "$line" | awk '{print $1}') + local client_image=$(echo "$line" | awk '{print $2}') + + # Validate client name + local valid_client=false + for known in "${KNOWN_CLIENTS[@]}"; do + if [ "$client_name" == "$known" ]; then + valid_client=true + break + fi + done + + if [ "$valid_client" == "false" ]; then + echo "⚠️ Warning: Unknown client '$client_name' in config - skipping" + continue + fi + + # Validate image format (basic check) + if [[ ! "$client_image" =~ ^[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+$ ]]; then + echo "⚠️ Warning: Invalid image format '$client_image' for client '$client_name' - using default" + continue + fi + + # Find and update the client image + local idx=$(find_client_index "$client_name") + if [ "$idx" != "-1" ]; then + CLIENT_IMAGES_LIST[$idx]="$client_image" + echo " ✓ Override $client_name: $client_image" + ((override_count++)) + fi + done < <(yq eval '.clients[] | .name + " " + .image' "$user_config" 2>/dev/null) + + if [ $override_count -eq 0 ]; then + echo "⚠️ No valid overrides found in user config" + else + echo "✓ Applied $override_count custom image(s) from user config" + fi +} + +# Function to get image for a specific client +get_client_image() { + local client_name="$1" + local idx=$(find_client_index "$client_name") + if [ "$idx" != "-1" ]; then + echo "${CLIENT_IMAGES_LIST[$idx]}" + fi +} + +# Function to display loaded configuration +display_client_config() { + echo "" + echo "==================================================" + echo "Client Configuration:" + echo "==================================================" + printf "%-12s | %s\n" "Client" "Docker Image" + echo "--------------------------------------------------" + local i=0 + for client in "${CLIENT_NAMES[@]}"; do + if [ -n "${CLIENT_IMAGES_LIST[$i]}" ]; then + printf "%-12s | %s\n" "$client" "${CLIENT_IMAGES_LIST[$i]}" + fi + ((i++)) + done + echo "==================================================" + echo "" +} + +# Load default configuration +load_default_config + +# Load user configuration if provided +if [ -n "$configFile" ]; then + load_user_config "$configFile" +fi diff --git a/parse-env.sh b/parse-env.sh index 91b2a6a..da57287 100755 --- a/parse-env.sh +++ b/parse-env.sh @@ -76,6 +76,11 @@ while [[ $# -gt 0 ]]; do shift # past argument shift # past value ;; + --config-file) + configFile="$2" + shift # past argument + shift # past value + ;; --stop) stopNodes=true shift @@ -109,4 +114,5 @@ echo "generateGenesis = $generateGenesis" echo "cleanData = $cleanData" echo "popupTerminal = $popupTerminal" echo "dockerTag = ${dockerTag:-latest}" +echo "configFile = ${configFile:-none}" echo "enableMetrics = $enableMetrics" diff --git a/spin-node.sh b/spin-node.sh index 09eae2d..8af9957 100755 --- a/spin-node.sh +++ b/spin-node.sh @@ -10,6 +10,9 @@ fi # 0. parse env and args source "$(dirname $0)/parse-env.sh" +# Load client configuration (default + user config if provided) +source "$(dirname $0)/load-client-config.sh" + # Check if yq is installed (needed for deployment mode detection) if ! command -v yq &> /dev/null; then echo "Error: yq is required but not installed. Please install yq first." @@ -229,6 +232,9 @@ elif [[ "$OSTYPE" == "linux"* ]]; then done fi spinned_pids=() +declare -A node_images +declare -A node_modes + for item in "${spin_nodes[@]}"; do echo -e "\n\nspining $item: client=$client (mode=$node_setup)" printf '%*s' $(tput cols) | tr ' ' '-' @@ -248,11 +254,45 @@ for item in "${spin_nodes[@]}"; do IFS='_' read -r -a elements <<< "$item" client="${elements[0]}" + # Get docker image from config (always set - from default-client-config.yml or user override) + client_image=$(get_client_image "$client") + + # Export the image variable for this client (will be used by client-cmd.sh) + case "$client" in + zeam) + export zeamImage="$client_image" + ;; + ream) + export reamImage="$client_image" + ;; + qlean) + export qleanImage="$client_image" + ;; + lantern) + export lanternImage="$client_image" + ;; + lighthouse) + export lighthouseImage="$client_image" + ;; + grandine) + export grandineImage="$client_image" + ;; + esac + echo " ✓ Using image for $client: $client_image" + # get client specific cmd and its mode (docker, binary) sourceCmd="source client-cmds/$client-cmd.sh" echo "$sourceCmd" eval $sourceCmd + # Store the final image for display + if [ "$node_setup" == "docker" ]; then + node_images["$item"]=$(echo "$node_docker" | grep -oE '[^ ]+:[^ ]+' | head -1) + fi + + # Store node mode + node_modes["$item"]="$node_setup" + # spin nodes if [ "$node_setup" == "binary" ] then @@ -292,6 +332,25 @@ done; container_names="${spin_nodes[*]}" process_ids="${spinned_pids[*]}" +# Display summary table +echo "" +echo "==================================================" +echo "Deployed Nodes Summary:" +echo "==================================================" +printf "%-15s | %-10s | %s\n" "Node" "Mode" "Docker Image" +echo "--------------------------------------------------" +for node in "${spin_nodes[@]}"; do + mode="${node_modes[$node]}" + if [ "$mode" == "docker" ]; then + image="${node_images[$node]}" + printf "%-15s | %-10s | %s\n" "$node" "$mode" "$image" + else + printf "%-15s | %-10s | %s\n" "$node" "$mode" "N/A (binary mode)" + fi +done +echo "==================================================" +echo "" + cleanup() { echo -e "\n\ncleaning up" printf '%*s' $(tput cols) | tr ' ' '-' diff --git a/user-config.yml.example b/user-config.yml.example new file mode 100644 index 0000000..15c0611 --- /dev/null +++ b/user-config.yml.example @@ -0,0 +1,26 @@ +# User Client Configuration Example +# Copy this file to user-config.yml and customize your client images +# Usage: ./spin-node.sh --config-file user-config.yml --node all +# +# Only specify clients you want to override - others will use defaults +# from client-cmds/default-client-config.yml + +clients: + - name: zeam + image: blockblaz/zeam:custom-tag + + - name: ream + image: ghcr.io/reamlabs/ream:my-branch + + # Add more clients as needed: + # - name: qlean + # image: qdrvm/qlean-mini:custom-tag + # + # - name: lantern + # image: piertwo/lantern:custom-tag + # + # - name: lighthouse + # image: hopinheimer/lighthouse:custom-tag + # + # - name: grandine + # image: sifrai/lean:custom-tag