A single command line quickstart to spin up lean node(s)
- β
Single source of truth -
validator-config.yaml- defines everything
- Generates full genesis state (JSON + SSZ) plus config files
- add/remove nodes, modify validator count, assign IPs, ports, enr keys
- Uses PK's
eth-beacon-genesisdocker tool (not custom tooling) - Generates PQ keys based on specified configuration in
validator-config.yaml- Force regen with flag
--forceKeyGenwhen supplied withgenerateGenesis
- Force regen with flag
- β Integrates zeam, ream, qlean, lantern (and more incoming...)
- β Configure to run clients in docker or binary mode for easy development
- β Linux & Mac compatible & tested
- β
Option to operate on single or multiple nodes or
all
- Shell terminal: Preferably linux especially if you want to pop out separate new terminals for node
- Docker: Required to run PK's eth-beacon-genesis tool and hash-sig-cli for post-quantum keys
- Install from: Docker Desktop
- yq: YAML processor for automated configuration parsing
- Install on macOS:
brew install yq - Install on Linux: See yq installation guide
- Install on macOS:
# 1. Clone the repository
git clone <repo-url>
cd lean-quickstartUsing spin-node.sh (unified entry point):
# Local deployment (default)
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --popupTerminal
# Ansible deployment (set deployment_mode: ansible in validator-config.yaml or use --deploymentMode ansible)
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --deploymentMode ansibleπ Note: When deployment mode is
ansible, the script automatically usesansible-devnet/genesis/validator-config.yamland generates genesis files inansible-devnet/genesis/. This keeps local and remote deployment configurations separate. See Ansible Deployment section or ansible/README.md for details
# Run only zeam_0 and ream_0 nodes (comma-separated)
NETWORK_DIR=local-devnet ./spin-node.sh --node zeam_0,ream_0 --generateGenesis --popupTerminal
# Run only zeam_0 and qlean_0 nodes (space-separated)
NETWORK_DIR=local-devnet ./spin-node.sh --node "zeam_0 qlean_0" --generateGenesis --popupTerminal
# Run only a single node
NETWORK_DIR=local-devnet ./spin-node.sh --node zeam_0 --generateGenesis --popupTerminalπ‘ Note: The same
spin-node.shcommand works for both local and Ansible deployments. The deployment mode is determined by thedeployment_modefield invalidator-config.yamlor the--deploymentModeparameter. When using Ansible deployment mode, the script automatically usesansible-devnet/genesis/validator-config.yamlto keep configurations separate.
# Start all nodes with metrics enabled
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --metrics-
NETWORK_DIRis an env to specify the network directory. Should have agenesisdirectory with genesis config. Adatafolder will be created inside thisNETWORK_DIRif not already there.- For local deployments: Use
local-devnet(or any custom directory) - For Ansible deployments: When
deployment_mode: ansibleis set, the script automatically usesansible-devnet/directory instead, keeping configurations separategenesisdirectory should have the following files
a.
validator-config.yamlwhich has node setup information for all the bootnodes b.validators.yamlwhich assigns validator indices c.nodes.yamlwhich has the enrs generated for each of the respective nodes. d.config.yamlthe actual network config - For local deployments: Use
-
--generateGenesisregenerate all genesis files with fresh genesis time and clean data directories -
--forceKeyGenforce regeneration of hash-sig validator keys even if they already exist.- Must be used together with
--generateGenesisflag - This will overwrite existing keys in
genesis/hash-sig-keys/ - Use this when you need to regenerate keys (e.g., after key exhaustion, configuration changes, or testing)
- Example:
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --forceKeyGen
- Must be used together with
-
--popupTerminalif you want to pop out new terminals to run the nodes, opens gnome terminals -
--nodespecify which node(s) you want to run:- Use
allto run all the nodes in a single go - Specify a single node name (e.g.,
zeam_0) to run just that node - Use comma-separated node names (e.g.,
zeam_0,qlean_0) to run multiple specific nodes - Use whitespace-separated node names (e.g.,
"zeam_0 ream_0") to run multiple specific nodes
The client is provided this input so as to parse the correct node configuration to startup the node.
- Use
-
--validatorConfigis the path to specify your nodesvalidator-config.yaml,validators.yaml(for which--nodeis still the node key to index) if your node is not a bootnode. If unspecified it assumes value ofgenesis_bootnodewhich is to say that your node config is to be picked fromgenesisfolder with--nodeas the node key index. This value is further provided to the client so that they can parse the correct config information. -
--deploymentModespecifies the deployment mode:localoransible.- If provided, this overrides the
deployment_modefield invalidator-config.yaml - If not provided, the value from
validator-config.yamlis used (defaults tolocalif not specified) - When set to
ansible: The script automatically usesansible-devnet/genesis/validator-config.yamland generates genesis files inansible-devnet/genesis/(unless--validatorConfigis explicitly provided) - Examples:
--deploymentMode localor--deploymentMode ansible
- If provided, this overrides the
-
--sshKeyor--private-keyspecifies the SSH private key file to use for remote Ansible deployments.- Only used when
deployment_mode: ansibleis set - Path to SSH private key file (e.g.,
~/.ssh/id_rsaor/path/to/custom_key) - If not provided, Ansible will use the default SSH key (
~/.ssh/id_rsa) or keys configured inansible.cfg - Example:
--sshKey ~/.ssh/custom_keyor--private-key /path/to/key.pem
- Only used when
-
--useRootflag specifies to userootuser for remote Ansible deployments.- Only used when
deployment_mode: ansibleis set - If not specified, uses the current user (whoami) for SSH connections
- If specified, uses
rootuser for SSH connections - Example:
--useRootto connect as root user
- Only used when
-
--tagspecifies the Docker image tag to use for zeam, ream, qlean, and lantern containers.
- If provided, all clients will use this tag (e.g.,
blockblaz/zeam:${tag},ghcr.io/reamlabs/ream:${tag},qdrvm/qlean-mini:${tag},piertwo/lantern:${tag}) - If not provided, defaults to
latestfor zeam, ream, and lantern, anddd67521for qlean - The script will automatically pull the specified Docker images before running containers
- Example:
--tag devnet0or--tag devnet1
--metricsenables metrics collection on all nodes. When specified, each client will activate its metrics endpoint according to its implementation. Metrics ports are configured per node invalidator-config.yaml.
Current following clients are supported:
- Zeam
- Ream
- Qlean
- Lantern
However adding a lean client to this setup is very easy. Feel free to do the PR or reach out to the maintainers.
The quickstart includes an automated genesis generator that eliminates the need for hardcoded files and uses validator-config.yaml as the source of truth.
Configuration File Location:
- Local deployments: The
validator-config.yamlfile is contained in thegenesisfolder of the providedNETWORK_DIRfolder (e.g.,local-devnet/genesis/validator-config.yaml) - Ansible deployments: When
deployment_mode: ansibleis set (either in the config file or via--deploymentMode ansible), the script automatically usesansible-devnet/genesis/validator-config.yamlinstead. This keeps local and remote deployment configurations separate.
Then post genesis generation, the quickstart spins the nodes as per their respective client cmds.
The quickstart uses separate directories for local and Ansible deployments:
lean-quickstart/
βββ local-devnet/ # Local development
β βββ genesis/
β β βββ validator-config.yaml # Local IPs (127.0.0.1)
β βββ data/ # Node data directories
β
βββ ansible-devnet/ # Ansible/remote deployment
βββ genesis/
β βββ validator-config.yaml # Remote IPs (your server IPs)
βββ data/ # Node data directories
Automatic Directory Selection:
- When
deployment_mode: ansibleis set (in config or via--deploymentMode ansible), the script automatically usesansible-devnet/genesis/validator-config.yaml - This keeps local and remote configurations completely separate
- Genesis files are generated in the appropriate directory based on deployment mode
The validator-config.yaml file defines the shuffle algorithm, active epoch configuration, and validator nodes specifications:
shuffle: roundrobin
config:
activeEpoch: 18 # Required: Exponent for active epochs (2^18 = 262,144 signatures)
keyType: "hash-sig" # Required: Network-wide signature scheme (hash-sig for post-quantum security)
validators: # validator nodes specification
- name: "zeam_0" # a 0rth zeam node
privkey: "bdf953adc161873ba026330c56450453f582e3c4ee6cb713644794bcfdd85fe5"
enrFields:
ip: "127.0.0.1"
quic: 9000
metricsPort: 8080
count: 1 # validator keys to be assigned to this nodeRequired Top-Level Fields:
shuffle: Validator assignment (to nodes) shuffle algorithm (e.g.,roundrobin)config.activeEpoch: Exponent for active epochs used in hash-sig key generation (2^activeEpoch signatures per active period)config.keyType: Network-wide signature scheme - must be"hash-sig"for post-quantum security
The spin-node.sh triggers genesis generator (generate-genesis.sh) which generates the following files based on validator-config.yaml:
- post-quantum secure validator keypairs in
genesis/hash-sig-keysunless already generated or forced with--forceKeyGen - config.yaml - With the updated genesis time in short future and pubkeys of the generated keypairs
- validators.yaml - Validator index assignments using round-robin distribution
- nodes.yaml - ENR (Ethereum Node Records) for peer discovery
- genesis.json - Genesis state in JSON format
- genesis.ssz - Genesis state in SSZ format
The genesis generator runs automatically when:
validators.yamlornodes.yamldon't exist, OR- You use the
--generateGenesisflag
# Regenerate genesis files with fresh genesis time
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesisYou can also run the generator standalone:
./generate-genesis.sh local-devnet/genesisTool's Docker Image: HASH_SIG_CLI_IMAGE="blockblaz/hash-sig-cli:latest"
Source: https://github.com/blockblaz/hash-sig-cli
Using the above docker tool the following files are generated (unless already generated or forced via --forceKeyGen flag):
Generated files:
local-devnet/genesis/hash-sig-keys/
βββ validator-keys-manifest.yaml # Metadata for all keys
βββ validator_0_pk.json # Public key for validator 0
βββ validator_0_sk.json # Secret key for validator 0
βββ validator_1_pk.json # Public key for validator 1
βββ validator_1_sk.json # Secret key for validator 1
βββ ... # Keys for additional validators
Signature Scheme: The system uses the SIGTopLevelTargetSumLifetime32Dim64Base8 hash-based signature scheme, which provides:
- Post-quantum security: Resistant to attacks from quantum computers
- Active epochs: as per
config.activeEpochfor e.g. 2^18 (262,144 signatures) - Total lifetime: 2^32 (4,294,967,296 signatures)
- Stateful signatures: Uses hierarchical signature tree structure
Validator Fields:
Hash-sig key files are automatically indexed based on the validator index (first validator uses validator_0_*.json, second uses validator_1_*.json, etc.)
Tool's Docker Image: PK_DOCKER_IMAGE="ethpandaops/eth-beacon-genesis:pk910-leanchain"
Source: ethpandaops/eth-beacon-genesis#36
config.yaml is generated with the appropriate genesis time (in short future) along with the list pubkeys of the validators in the correct sequence. For e.g:
# Genesis Settings
GENESIS_TIME: 1763712794
# Key Settings
ACTIVE_EPOCH: 10
# Validator Settings
VALIDATOR_COUNT: 2
GENESIS_VALIDATORS:
- "4b3c31094bcc9b45446b2028eae5ad192b2df16778837b10230af102255c9c5f72d7ba43eae30b2c6a779f47367ebf5a42f6c959"
- "8df32a54d2fbdf3a88035b2fe3931320cb900d364d6e7c56b19c0f3c6006ce5b3ebe802a65fe1b420183f62e830a953cb33b7804"This config.yaml is consumed by the clients to directly generate the genesis in-client. Note that clients are supposed to ignore genesis.ssz and genesis.json as their formats have not been updated.
validators.yaml is generated for validator index assignments to the nodes:
zeam_0:
- 0
- 3
ream_0:
- 1
- 4
qlean_0:
- 2Recommended: annotated_validators.yaml is also generated and should be preferred by client software as it includes public keys and private key file references directly, eliminating the need for clients to derive key filenames from validator indices:
zeam_0:
- index: 0
pubkey_hex: 4b3c31094bcc9b45446b2028eae5ad192b2df16778837b10230af102255c9c5f72d7ba43eae30b2c6a779f47367ebf5a42f6c959
privkey_file: validator_0_sk.json
- index: 3
pubkey_hex: 8df32a54d2fbdf3a88035b2fe3931320cb900d364d6e7c56b19c0f3c6006ce5b3ebe802a65fe1b420183f62e830a953cb33b7804
privkey_file: validator_3_sk.json
ream_0:
- index: 1
pubkey_hex: 5b15f72f90bd655b039f9839c36951454b89c605f8c334581cfa832bdd0c994a1350094f7e22617d77607b067b0aa2439e0ead7d
privkey_file: validator_1_sk.json
- index: 4
pubkey_hex: 71bf8f73980591574de34a0db471da74f5cfd84d4731d53f47bf3023b26c2638ac5bd24993ea71492fedbd6c4afe5c299213b76b
privkey_file: validator_4_sk.json
qlean_0:
- index: 2
pubkey_hex: b87e69568a347d1aa811cc158634fb1f4e247c5509ad2b1652a8d758ec0ab0796954e307b97dd6284fbb30088c2e595546fdf663
privkey_file: validator_2_sk.jsonnodes.yaml provide enrs of all the nodes so that clients don't have to run a discovery protocol:
- enr:-IW4QMn2QUYENcnsEpITZLph3YZee8Y3B92INUje_riQUOFQQ5Zm5kASi7E_IuQoGCWgcmCYrH920Q52kH7tQcWcPhEBgmlkgnY0gmlwhH8AAAGEcXVpY4IjKIlzZWNwMjU2azGhAhMMnGF1rmIPQ9tWgqfkNmvsG-aIyc9EJU5JFo3Tegys
- enr:-IW4QDc1Hkslu0Bw11YH4APkXvSWukp5_3VdIrtwhWomvTVVAS-EQNB-rYesXDxhHA613gG9OGR_AiIyE0VeMltTd2cBgmlkgnY0gmlwhH8AAAGEcXVpY4IjKYlzZWNwMjU2azGhA5_HplOwUZ8wpF4O3g4CBsjRMI6kQYT7ph5LkeKzLgTSPost genesis generation, the quickstarts loads and calls the appropriate node's client cmd from client-cmds folder where either docker or binary cmd is picked as per the node_setup mode. (Generally binary mode is handy for local interop debugging for a client).
Client Integration: Your client implementation should read these environment variables and use the hash-sig keys for validator operations.
$item- the node name for which this cmd is being executed, index intovalidator-config.yamlfor its configuration$configDir- the abs folder housinggenesisconfiguration (same asNETWORK_DIRenv variable provided while executing shell command), already mapped to/configin the docker mode- A generic data folder is created inside config folder accessible as
$dataDirwith$dataDir/$itemto be used as the data dir for a particular node to be used for binary format, already mapped to/datain the docker mode - Variables read and available from
validator-config.yaml(use them or directly read configuration from thevalidator-config.yamlusing$itemas the index intovalidatorssection)$metricsPort$quicPort$item.keyfilename of the p2pprivkeyread and dumped into file fromvalidator-config.yamlinside config dir (so$configDir/$item.keyor/config/$item.key)
Here is an example client cmd:
#!/bin/bash
#-----------------------qlean setup----------------------
# expects "qlean" submodule or symlink inside "lean-quickstart" root directory
# https://github.com/qdrvm/qlean-mini
node_binary="$scriptDir/qlean/build/src/executable/qlean \
--modules-dir $scriptDir/qlean/build/src/modules \
--genesis $configDir/config.yaml \
--validator-registry-path $configDir/validators.yaml \
--bootnodes $configDir/nodes.yaml \
--data-dir $dataDir/$item \
--node-id $item --node-key $configDir/$privKeyPath \
--listen-addr /ip4/0.0.0.0/udp/$quicPort/quic-v1 \
--metrics-port $metricsPort"
node_docker="--platform linux/amd64 qdrvm/qlean-mini:dd67521 \
--genesis /config/config.yaml \
--validator-registry-path /config/validators.yaml \
--bootnodes /config/nodes.yaml \
--data-dir /data \
--node-id $item --node-key /config/$privKeyPath \
--listen-addr /ip4/0.0.0.0/udp/$quicPort/quic-v1 \
--metrics-port $metricsPort"
# choose either binary or docker
node_setup="docker"Each hash-sig key has a finite lifetime of 2^32 signatures. The keys are structured as:
- Active epochs: 2^18 epochs before requiring key rotation
- Total lifetime: 2^32 total signatures possible
Hash-based signatures are stateful - each signature uses a unique one-time key from the tree. Once exhausted, keys must be rotated.
Regenerating Keys:
You can regenerate hash-sig keys using either method:
- Using
spin-node.shwith--forceKeyGenflag (recommended):
# Regenerate all hash-sig keys and genesis files
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --forceKeyGen- Using
generate-genesis.shdirectly:
# Regenerate all hash-sig keys
./generate-genesis.sh local-devnet/genesis --forceKeyGenNote: The --forceKeyGen flag is required to overwrite existing keys. Without it, the generator will skip key generation if keys already exist.
Warning:
β οΈ Regenerating keys will overwrite existing keys ingenesis/hash-sig-keys/β οΈ Keep track of signature counts to avoid key exhaustionβ οΈ Ensure you have backups of important keys before regenerating
Secret keys are highly sensitive:
β οΈ Never commitvalidator_*_sk.jsonfiles to version controlβ οΈ Never share secret keys- β Backup secret keys in secure, encrypted storage
- β
Restrict permissions on key files (e.g.,
chmod 600)
The .gitignore should already exclude hash-sig keys:
local-devnet/genesis/hash-sig-keys/
The manifest file (validator-keys-manifest.yaml) contains metadata about all generated keys:
# Hash-Signature Validator Keys Manifest
# Generated by hash-sig-cli
key_scheme: SIGTopLevelTargetSumLifetime32Dim64Base8
hash_function: Poseidon2
encoding: TargetSum
lifetime: 4294967296
log_num_active_epochs: 10
num_active_epochs: 1024
num_validators: 2
validators:
- index: 0
pubkey_hex: 0x4b3c31094bcc9b45446b2028eae5ad192b2df16778837b10230af102255c9c5f72d7ba43eae30b2c6a779f47367ebf5a42f6c959
privkey_file: validator_0_sk.json
- index: 1
pubkey_hex: 0x8df32a54d2fbdf3a88035b2fe3931320cb900d364d6e7c56b19c0f3c6006ce5b3ebe802a65fe1b420183f62e830a953cb33b7804
privkey_file: validator_1_sk.json
Problem: Hash-sig keys not loading during node startup
Warning: Hash-sig public key not found at genesis/hash-sig-keys/validator_0_pk.json
Solution: Run the genesis generator to create keys:
# Using spin-node.sh (recommended)
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis
# Or using generate-genesis.sh directly
./generate-genesis.sh local-devnet/genesisProblem: Hash-sig key file not found
Warning: Hash-sig secret key not found at genesis/hash-sig-keys/validator_5_sk.json
Solution: This usually means you have more validators configured than hash-sig keys generated. Regenerate genesis files:
# Using spin-node.sh (recommended)
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis
# Or using generate-genesis.sh directly
./generate-genesis.sh local-devnet/genesisProblem: Need to regenerate keys (e.g., after key exhaustion or configuration changes)
Solution: Use the --forceKeyGen flag to force regeneration:
# Regenerate keys and all genesis files
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --forceKeyGen
# Or using generate-genesis.sh directly
./generate-genesis.sh local-devnet/genesis --forceKeyGenThis quickstart includes automated configuration parsing:
- Official Genesis Generation: Uses PK's
eth-beacon-genesisdocker tool from PR #36 - Complete File Set: Generates
validators.yaml,nodes.yaml,genesis.json,genesis.ssz, and.keyfiles - QUIC Port Detection: Automatically extracts QUIC ports from
validator-config.yamlusingyq - Node Detection: Dynamically discovers available nodes from the validator configuration
- Private Key Management: Automatically extracts and creates
.keyfiles for each node - Error Handling: Provides clear error messages when nodes or ports are not found
The system reads all configuration from YAML files, making it easy to add new nodes or modify existing ones without changing any scripts.
The repository now includes Ansible-based deployment for enhanced automation, remote deployment capabilities, and better infrastructure management. Ansible provides idempotency, declarative configuration, and support for deploying to multiple remote hosts.
π For detailed Ansible documentation, see ansible/README.md
Recommended: Use spin-node.sh (Unified Entry Point)
spin-node.sh is the primary entry point for all deployments, including Ansible. Simply set deployment_mode: ansible in your validator-config.yaml:
# Set deployment_mode: ansible in validator-config.yaml, then:
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesisThis automatically calls run-ansible.sh internally, which reads the default deployment mode from ansible/inventory/group_vars/all.yml.
Advanced: Direct Ansible Control with ansible-deploy.sh
For advanced Ansible workflows requiring direct control (e.g., --playbook, --tags, --check, --diff), you can use ansible-deploy.sh directly:
# First generate genesis files locally
./generate-genesis.sh local-devnet/genesis
# Then deploy nodes (genesis files are copied to remote hosts automatically)
./ansible-deploy.sh --node zeam_0,ream_0 --network-dir local-devnetHowever, for most use cases, spin-node.sh is recommended as it provides a consistent interface for both local and Ansible deployments.
- β Remote Deployment: Deploy nodes to remote servers
- β Idempotency: Safe to run multiple times
- β Infrastructure as Code: Version-controlled deployment configuration
- β Multi-Host Support: Deploy to multiple hosts in parallel
- β Better State Management: Track and manage node lifecycle
- β Extensible: Easy to add new roles and playbooks
macOS:
brew install ansibleUbuntu/Debian:
sudo apt-get update
sudo apt-get install ansibleUsing pip:
pip install ansibleInstall required Ansible collections:
cd ansible
ansible-galaxy install -r requirements.ymlRecommended: Using spin-node.sh (set deployment_mode: ansible in validator-config.yaml):
# Deploy all nodes with genesis generation
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis
# Deploy specific nodes
NETWORK_DIR=local-devnet ./spin-node.sh --node zeam_0,ream_0 --generateGenesis
# Deploy with clean data directories
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --cleanDataAlternative: Using ansible-deploy.sh directly (for advanced Ansible options):
# First generate genesis files locally
./generate-genesis.sh local-devnet/genesis
# Deploy specific nodes (genesis files are copied to remote hosts automatically)
./ansible-deploy.sh --node zeam_0,ream_0 --network-dir local-devnet
# Copy genesis files to remote hosts only
./ansible-deploy.sh --playbook copy-genesis.yml --network-dir local-devnet
# Dry run (check mode)
./ansible-deploy.sh --node zeam_0,ream_0 --network-dir local-devnet --checkThe following options are available when using ansible-deploy.sh directly for advanced Ansible workflows. For most use cases, use spin-node.sh instead (see Quick Start with Ansible above).
The ansible-deploy.sh wrapper script provides the following options:
| Option | Description | Example |
|---|---|---|
--node NODES |
Nodes to deploy (single or comma/space-separated) | --node zeam_0,ream_0 |
--network-dir DIR |
Network directory | --network-dir local-devnet |
--clean-data |
Clean data directories before deployment | --clean-data |
--validator-config PATH |
Path to validator-config.yaml | --validator-config custom/path.yaml |
--deployment-mode MODE |
Deployment mode: docker or binary | --deployment-mode binary |
--playbook PLAYBOOK |
Ansible playbook to run | --playbook copy-genesis.yml |
--tags TAGS |
Run only tasks with specific tags | --tags zeam,genesis |
--check |
Dry run (check mode) | --check |
--diff |
Show file changes | --diff |
--verbose |
Verbose output | --verbose |
ansible/
βββ ansible.cfg # Ansible configuration
βββ requirements.yml # Ansible Galaxy dependencies
βββ inventory/
β βββ hosts.yml # Host inventory (localhost or remote hosts)
β βββ group_vars/ # Group variables
β βββ all.yml # Global variables
βββ playbooks/
β βββ site.yml # Main playbook (copy genesis + deploy)
β βββ copy-genesis.yml # Copy genesis files to remote hosts
β βββ deploy-nodes.yml # Node deployment playbook
β βββ deploy-single-node.yml # Helper for single node deployment
βββ roles/
βββ common/ # Common setup (Docker, yq, directories)
βββ genesis/ # Genesis file generation
βββ zeam/ # Zeam node role
βββ ream/ # Ream node role
βββ qlean/ # Qlean node role
The Ansible inventory is automatically generated from validator-config.yaml.
Configuration Setup:
For Ansible deployments, create or update ansible-devnet/genesis/validator-config.yaml with your remote server IP addresses:
deployment_mode: ansible
config:
activeEpoch: 18
keyType: "hash-sig"
validators:
- name: "zeam_0"
privkey: "..."
enrFields:
ip: "192.168.1.10" # Remote IP address
quic: 9000
metricsPort: 8081
count: 1
- name: "ream_0"
privkey: "..."
enrFields:
ip: "192.168.1.11" # Remote IP address
quic: 9001
metricsPort: 8082
count: 1Deployment:
Then use spin-node.sh with --deploymentMode ansible (or set deployment_mode: ansible in the config file):
# If using default SSH key (~/.ssh/id_rsa)
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --deploymentMode ansible
# If using a custom SSH key and root user
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --deploymentMode ansible --sshKey ~/.ssh/custom_key --useRootπ‘ Note: When
deployment_mode: ansibleis set, the script automatically usesansible-devnet/genesis/validator-config.yamland generates all genesis files inansible-devnet/genesis/. This keeps your local development (local-devnet/) and remote deployment (ansible-devnet/) configurations completely separate.
The inventory generator will automatically:
- Detect remote IPs (non-localhost) and configure remote connections
- Group nodes by client type (zeam_nodes, ream_nodes, qlean_nodes, lantern_nodes)
- Set appropriate connection parameters
- Apply SSH key file if provided via
--sshKeyparameter
Note: For remote deployment, ensure:
- SSH key-based authentication is configured
- Use
--sshKeyparameter to specify custom SSH key:--sshKey ~/.ssh/custom_key - Use
--useRootflag to connect as root user (defaults to current user) - Or manually add
ansible_userandansible_ssh_private_key_fileto the generated inventory - Or configure in
ansible/ansible.cfg(seeprivate_key_fileoption)
- Use
- Docker is installed on remote hosts (or use
deployment_mode: binaryin group_vars) - Required ports are open (QUIC ports, metrics ports)
- Genesis files are accessible (copied or mounted)
You can also run Ansible playbooks directly (after setting deployment_mode: ansible and running spin-node.sh once to generate the inventory):
cd ansible
# Run main playbook
ansible-playbook -i inventory/hosts.yml playbooks/site.yml \
-e "network_dir=$(pwd)/../local-devnet" \
-e "node_names=zeam_0,ream_0"
# Copy genesis files to remote hosts only
ansible-playbook -i inventory/hosts.yml playbooks/copy-genesis.yml \
-e "network_dir=$(pwd)/../local-devnet"
# Run with specific tags
ansible-playbook -i inventory/hosts.yml playbooks/deploy-nodes.yml \
-e "network_dir=$(pwd)/../local-devnet" \
-e "node_names=zeam_0" \
--tags zeamKey variables can be set via command-line or in ansible/inventory/group_vars/all.yml:
network_dir: Network directory path (required)genesis_dir: Genesis directory path (derived from network_dir)data_dir: Data directory path (derived from network_dir)node_names: Nodes to deploy (required, comma or space separated)clean_data: Clean data directories (default: false)deployment_mode: docker or binary (default: docker, defined inansible/inventory/group_vars/all.yml)validator_config: Validator config path (default: 'genesis_bootnode')
Note: The default deployment_mode value is read from ansible/inventory/group_vars/all.yml. When using spin-node.sh with deployment_mode: ansible, it internally calls run-ansible.sh which reads this default value. You can override it by setting deployment_mode in your validator-config.yaml or via command-line arguments.
Both deployment modes use the same spin-node.sh entry point, controlled by deployment_mode in validator-config.yaml:
| Feature | Local (deployment_mode: local) |
Ansible (deployment_mode: ansible) |
|---|---|---|
| Use Case | Local development, quick setup | Production, remote deployment |
| Complexity | Simple, direct | More structured |
| Remote Deployment | No | Yes |
| Idempotency | No | Yes |
| State Management | Manual | Declarative |
| Multi-Host | No | Yes |
| Rollback | Manual | Built-in capabilities |
| Entry Point | spin-node.sh |
spin-node.sh (same command) |
| Inventory | N/A | Auto-generated from validator-config.yaml |
Recommendation:
- Use
deployment_mode: localfor local development and quick testing - Use
deployment_mode: ansiblefor production deployments and remote hosts - Both modes use the same
spin-node.shcommand - just change thedeployment_modeinvalidator-config.yaml
The quickstart supports two deployment modes:
| Mode | Use Case | Command |
|---|---|---|
| Local | Local development, quick testing | deployment_mode: local (default) |
| Ansible | Production, remote deployment, infrastructure automation | deployment_mode: ansible |
Local deployment uses shell scripts to directly run Docker containers or binaries on the local machine. This is the default mode and is ideal for:
- Quick local development
- Testing and experimentation
- Single-machine setups
Ansible deployment provides infrastructure automation and supports two sub-modes:
| Sub-Mode | Use Case | Command |
|---|---|---|
| Docker | Deploy containers directly on hosts | --deployment-mode docker (default for Ansible) |
| Binary | Deploy binaries as systemd services | --deployment-mode binary |
Ansible mode is ideal for:
- Production deployments
- Remote server management
- Multi-host deployments
- Infrastructure as Code workflows
Clients can maintain their own branches to integrated and use binay with their repos as the static targets (check git diff main zeam_repo, it has two nodes, both specified to run zeam for sim testing in zeam using the quickstart generated genesis).
And those branches can be rebased as per client convinience whenever the main code is updated.