Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f0f4276
feat: add config file support to configure docker images
KatyaRyazantseva Jan 16, 2026
698568d
Merge branch 'main' into add-config-file
KatyaRyazantseva Jan 19, 2026
39dc7a6
fix: move image to the validator-config.yml
KatyaRyazantseva Jan 21, 2026
b4c1b32
Merge remote-tracking branch 'upstream/main' into add-config-file
KatyaRyazantseva Jan 26, 2026
c347b73
Merge branch 'main' into add-config-file
KatyaRyazantseva Jan 29, 2026
dead69c
fix: add deploy-validator-config.yaml, delete annotated-validators.yaml
KatyaRyazantseva Feb 3, 2026
d069ba0
fix: change node to validator in user-config.yml
KatyaRyazantseva Feb 6, 2026
220b054
Merge remote-tracking branch 'upstream/main' into add-config-file
KatyaRyazantseva Feb 6, 2026
95b5242
fix: grandine flags
KatyaRyazantseva Feb 6, 2026
7584e76
fix: update comments
KatyaRyazantseva Feb 9, 2026
bb1e9a2
fix: revert deleting annotated_validators.yaml for the separate pr
KatyaRyazantseva Feb 9, 2026
7e94c95
fix: restore README for annotated validators
KatyaRyazantseva Feb 9, 2026
c6b5cbf
fix: fix deployed nodes summary, add scripts folder
KatyaRyazantseva Feb 9, 2026
6f606b1
fix: delete binary check from run-ansible
KatyaRyazantseva Feb 9, 2026
24af2e5
feat: replace validator-config.yaml with deploy-validator-config.yaml
KatyaRyazantseva Feb 9, 2026
91b9c46
Merge remote-tracking branch 'upstream/main' into add-config-file
KatyaRyazantseva Feb 9, 2026
90ca5fa
fix: use deploy_validator_config_file for prometheus
KatyaRyazantseva Feb 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -12,10 +15,13 @@ genesis.json
genesis.ssz
annotated_validators.yaml

# Deploy config (created by merge-config.sh, merges validator-config + user overrides)
deploy-validator-config.yaml

# Hash-sig validator keys (contains sensitive secret keys)
hash-sig-keys/

# Auto-generated Ansible inventory (generated from validator-config.yaml)
# Auto-generated Ansible inventory (generated from deploy-validator-config.yaml)
hosts.yml

# Temporary cache files
Expand Down
85 changes: 72 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,44 @@ Grafana is started with the two pre-provisioned dashboards from [leanMetrics](ht

> **Note:** The `--metrics` flag only affects local deployments. When using Ansible deployment mode, this flag is ignored. Metrics ports are always exposed by clients regardless of this flag.

### Using custom Docker images

You can override default Docker images using the `--configFile` 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 validator-config.yaml (merged into deploy-validator-config.yaml)
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis
```

**With custom config file:**
```sh
# Override specific node images
NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --configFile user-config.yml
```

**Example config file (user-config.yml):**
```yaml
validators:
- name: "zeam_0"
image: blockblaz/zeam:feature-branch
- name: "ream_0"
image: ghcr.io/reamlabs/ream:v2.0
```

**Testing a specific client build:**
```sh
# Create custom config file for zeam <your-PATH>/my-zeam-config.yml
validators:
- name: "zeam_0"
image: blockblaz/zeam:custom-tag

# Run with custom zeam image
NETWORK_DIR=local-devnet ./spin-node.sh --node zeam_0 --configFile <your-PATH>/my-zeam-config.yml
```

Only specify validators you want to override - others will use their defaults from `validator-config.yaml`. Overrides are merged into `deploy-validator-config.yaml` which is used during deployment.

## 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.
Expand Down Expand Up @@ -132,13 +170,17 @@ Grafana is started with the two pre-provisioned dashboards from [leanMetrics](ht
- The script will automatically pull the specified Docker images before running containers
- Example: `--tag devnet0` or `--tag devnet1`
11. `--metrics` starts a Prometheus + Grafana metrics stack alongside the devnet (local deployments only). When specified:
- Generates `metrics/prometheus/prometheus.yml` from `validator-config.yaml` with scrape targets for all configured nodes
- Generates `metrics/prometheus/prometheus.yml` from `deploy-validator-config.yaml` with scrape targets for all configured nodes
- Starts Prometheus (http://localhost:9090) and Grafana (http://localhost:3000) via Docker Compose
- Grafana is pre-provisioned with Lean Ethereum dashboards (no login required)
- On `--stop --metrics`, the metrics stack is also torn down
- On Ctrl+C cleanup, the metrics stack is stopped automatically

Note: Client metrics endpoints are always enabled regardless of this flag.
12. `--configFile` specifies a custom configuration file to override default Docker images for specific nodes.
- Path to a YAML file containing node image overrides (e.g., `user-config.yml` or `/path/to/my-config.yml`)
- Only nodes specified in the config file are overridden; others use defaults from `validator-config.yaml`
- See [Using custom Docker images](#using-custom-docker-images) scenario for usage examples
- Example: `--configFile user-config.yml` or `--configFile /path/to/custom-config.yml`

### Clients supported

Expand All @@ -150,6 +192,7 @@ Current following clients are supported:
4. Lantern
5. Lighthouse
6. Grandine
7. EthLambda

However adding a lean client to this setup is very easy. Feel free to do the PR or reach out to the maintainers.

Expand All @@ -169,22 +212,38 @@ The quickstart uses separate directories for local and Ansible deployments:

```
lean-quickstart/
├── local-devnet/ # Local development
├── client-cmds/ # Client command scripts
│ ├── 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)
│ │ ├── validator-config.yaml # Source config - Local IPs (127.0.0.1)
│ │ └── deploy-validator-config.yaml # Generated - merged config for deployment (gitignored)
│ └── data/ # Node data directories
└── ansible-devnet/ # Ansible/remote deployment
└── ansible-devnet/ # Ansible/remote deployment
├── genesis/
│ └── validator-config.yaml # Remote IPs (your server IPs)
│ ├── validator-config.yaml # Source config - Remote IPs (your server IPs)
│ └── deploy-validator-config.yaml # Generated - merged config for deployment (gitignored)
└── data/ # Node data directories
```

**Automatic Directory Selection:**

- When `deployment_mode: ansible` is set (in config or via `--deploymentMode ansible`), the script automatically uses `ansible-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

**Config File Flow:**

- `validator-config.yaml` - Default source config file (node IPs, ports, validator counts)
- `user-config.yml` - Optional overrides (currently custom Docker images)
- `deploy-validator-config.yaml` - Generated by merging the above two files, recreated on each deployment
- All deployment operations (local and ansible) read from `deploy-validator-config.yaml`

### Configuration

The `validator-config.yaml` file defines the shuffle algorithm, active epoch configuration, and validator nodes specifications:
Expand Down Expand Up @@ -337,13 +396,13 @@ Post genesis generation, the quickstarts loads and calls the appropriate node's
**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 into `validator-config.yaml` for its configuration
- `$item` - the node name for which this cmd is being executed, index into `deploy-validator-config.yaml` for its configuration
- `$configDir` - the abs folder housing `genesis` configuration (same as `NETWORK_DIR` env variable provided while executing shell command), already mapped to `/config` in the docker mode
- A generic data folder is created inside config folder accessible as `$dataDir` with `$dataDir/$item` to be used as the data dir for a particular node to be used for binary format, already mapped to `/data` in the docker mode
- Variables read and available from `validator-config.yaml` (use them or directly read configuration from the `validator-config.yaml` using `$item` as the index into `validators` section)
- Variables read and available from `deploy-validator-config.yaml` (use them or directly read configuration from the `deploy-validator-config.yaml` using `$item` as the index into `validators` section)
- `$metricsPort`
- `$quicPort`
- `$item.key` filename of the p2p `privkey` read and dumped into file from `validator-config.yaml` inside config dir (so `$configDir/$item.key` or `/config/$item.key`)
- `$quicPort`
- `$item.key` filename of the p2p `privkey` read and dumped into file from `deploy-validator-config.yaml` inside config dir (so `$configDir/$item.key` or `/config/$item.key`)

Here is an example client cmd:
```bash
Expand Down Expand Up @@ -499,7 +558,7 @@ This quickstart includes automated configuration parsing:

- **Official Genesis Generation**: Uses PK's `eth-beacon-genesis` docker tool from [PR #36](https://github.com/ethpandaops/eth-beacon-genesis/pull/36)
- **Complete File Set**: Generates `validators.yaml`, `nodes.yaml`, `genesis.json`, `genesis.ssz`, and `.key` files
- **QUIC Port Detection**: Automatically extracts QUIC ports from `validator-config.yaml` using `yq`
- **QUIC Port Detection**: Automatically extracts QUIC ports from `deploy-validator-config.yaml` using `yq`
- **Node Detection**: Dynamically discovers available nodes from the validator configuration
- **Private Key Management**: Automatically extracts and creates `.key` files for each node
- **Error Handling**: Provides clear error messages when nodes or ports are not found
Expand Down Expand Up @@ -667,7 +726,7 @@ ansible/

### Remote Deployment

The Ansible inventory is **automatically generated** from `validator-config.yaml`.
The Ansible inventory is **automatically generated** from `deploy-validator-config.yaml`.

**Configuration Setup:**

Expand Down Expand Up @@ -776,7 +835,7 @@ Both deployment modes use the same `spin-node.sh` entry point, controlled by `de
| **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 |
| **Inventory** | N/A | Auto-generated from deploy-validator-config.yaml |

**Recommendation:**
- Use `deployment_mode: local` for local development and quick testing
Expand Down
7 changes: 7 additions & 0 deletions ansible-devnet/genesis/validator-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ validators:
- name: "zeam_0"
# node id 7d0904dc6d8d7130e0e68d5d3175d0c3cf470f8725f67bd8320882f5b9753cc0
# peer id 16Uiu2HAkvi2sxT75Bpq1c7yV2FjnSQJJ432d6jeshbmfdJss1i6f
image: blockblaz/zeam:latest
privkey: "bdf953adc161873ba026330c56450453f582e3c4ee6cb713644794bcfdd85fe5"
enrFields:
# verify /ip4/127.0.0.1/udp/9000/quic-v1/p2p/16Uiu2HAkvi2sxT75Bpq1c7yV2FjnSQJJ432d6jeshbmfdJss1i6f
Expand All @@ -19,6 +20,7 @@ validators:
- name: "ream_0"
# node id bc531fc1a99a896acb45603f28a32f81ae607480af46435009de4609370cb7bb
# peer id 16Uiu2HAmPQhkD6Zg5Co2ee8ShshkiY4tDePKFARPpCS2oKSLj1E1
image: ghcr.io/reamlabs/ream:latest
privkey: "af27950128b49cda7e7bc9fcb7b0270f7a3945aa7543326f3bfdbd57d2a97a32"
enrFields:
#verify /ip4/127.0.0.1/udp/9001/quic-v1/p2p/16Uiu2HAmPQhkD6Zg5Co2ee8ShshkiY4tDePKFARPpCS2oKSLj1E1
Expand All @@ -30,6 +32,7 @@ validators:

- name: "qlean_0"
# TODO: add a third entry in nodes.yaml corresponding to this
image: qdrvm/qlean-mini:latest
privkey: "c2bbdac5e876b3e9d4b8b6b8c2bbdac5e876b3e9d4b8b6b8c2bbdac5e876b3e9"
enrFields:
#verify /ip4/127.0.0.1/udp/9001/quic-v1/p2p/16Uiu2HAmPQhkD6Zg5Co2ee8ShshkiY4tDePKFARPpCS2oKSLj1E1
Expand All @@ -41,6 +44,7 @@ validators:
- name: "lantern_0"
# node id a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
# peer id 16Uiu2HAm7TYVs6qvDKnrovd9m4vvRikc4HPXm1WyLumKSe5fHxBv
image: piertwo/lantern:v0.0.2
privkey: "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5"
# verify /ip4/127.0.0.1/udp/9004/quic-v1/p2p/16Uiu2HAm7TYVs6qvDKnrovd9m4vvRikc4HPXm1WyLumKSe5fHxBv
enrFields:
Expand All @@ -52,6 +56,7 @@ validators:
- name: "lighthouse_0"
# node id a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
# peer id 16Uiu2HAm7TYVs6qvDKnrovd9m4vvRikc4HPXm1WyLumKSe5fHxBv
image: hopinheimer/lighthouse:latest
privkey: "4fd22cf461fbeae4947a3fdaef8d533fc7fd1ef1ce4cd98e993210c18234df3f"
# verify /ip4/127.0.0.1/udp/9004/quic-v1/p2p/16Uiu2HAm7TYVs6qvDKnrovd9m4vvRikc4HPXm1WyLumKSe5fHxBv
enrFields:
Expand All @@ -61,6 +66,7 @@ validators:
count: 1

- name: "grandine_0"
image: sifrai/lean:latest
privkey: "64a7f5ab53907966374ca23af36392910af682eec82c12e3abbb6c2ccdf39a72"
enrFields:
ip: "37.27.250.20"
Expand All @@ -69,6 +75,7 @@ validators:
count: 1

- name: "ethlambda_0"
image: ghcr.io/lambdaclass/ethlambda:latest
privkey: "299550529a79bc2dce003747c52fb0639465c893e00b0440ac66144d625e066a"
enrFields:
ip: "78.47.44.215"
Expand Down
6 changes: 3 additions & 3 deletions ansible/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,11 @@ Or use sudo (not recommended):
./ansible-deploy.sh --node zeam_0 --network-dir local-devnet --docker-with-sudo
```

#### 5. "Node not found in validator-config.yaml"
#### 5. "Node not found in deploy-validator-config.yaml"

Ensure node name matches exactly in `validator-config.yaml`:
Ensure node name matches exactly in `deploy-validator-config.yaml`:
```sh
yq eval '.validators[].name' local-devnet/genesis/validator-config.yaml
yq eval '.validators[].name' local-devnet/genesis/deploy-validator-config.yaml
```

#### 6. Container starts but immediately exits
Expand Down
2 changes: 1 addition & 1 deletion ansible/inventory/group_vars/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
network_dir: "{{ playbook_dir }}/../local-devnet"
genesis_dir: "{{ network_dir }}/genesis"
data_dir: "{{ network_dir }}/data"
validator_config_file: "{{ genesis_dir }}/validator-config.yaml"
deploy_validator_config_file: "{{ genesis_dir }}/deploy-validator-config.yaml"

# Remote paths (for remote deployments)
# Use standard paths on remote hosts
Expand Down
16 changes: 8 additions & 8 deletions ansible/playbooks/clean-node-data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
connection: local
gather_facts: no
vars:
validator_config_file: "{{ genesis_dir }}/validator-config.yaml"
deploy_validator_config_file: "{{ genesis_dir }}/deploy-validator-config.yaml"
tags:
- zeam
- ream
Expand All @@ -18,15 +18,15 @@
- deploy

tasks:
- name: Validate validator-config.yaml exists
- name: Validate deploy-validator-config.yaml exists
stat:
path: "{{ validator_config_file }}"
register: validator_config_stat
path: "{{ deploy_validator_config_file }}"
register: deploy_config_stat

- name: Fail if validator-config.yaml missing
- name: Fail if deploy-validator-config.yaml missing
fail:
msg: "validator-config.yaml not found at {{ validator_config_file }}"
when: not validator_config_stat.stat.exists
msg: "deploy-validator-config.yaml not found at {{ deploy_validator_config_file }}"
when: not deploy_config_stat.stat.exists

- name: Verify yq is available
command: yq --version
Expand All @@ -42,7 +42,7 @@

- name: Extract all node names
shell: |
yq eval '.validators[].name' {{ validator_config_file }}
yq eval '.validators[].name' {{ deploy_validator_config_file }}
register: all_node_names_raw
changed_when: false

Expand Down
10 changes: 5 additions & 5 deletions ansible/playbooks/copy-genesis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
hosts: all:!local
gather_facts: yes
vars:
validator_config_file: "{{ genesis_dir }}/validator-config.yaml"
deploy_validator_config_file: "{{ genesis_dir }}/deploy-validator-config.yaml"
# Use remote_genesis_dir from group_vars, or default to standard remote path
actual_remote_genesis_dir: "{{ remote_genesis_dir | default('/opt/lean-quickstart/genesis') }}"

Expand Down Expand Up @@ -51,7 +51,7 @@
- nodes.yaml
- genesis.json
- genesis.ssz
- validator-config.yaml
- deploy-validator-config.yaml

- name: Fail if required genesis files are missing
fail:
Expand All @@ -68,9 +68,9 @@
delegate_to: localhost
run_once: true

- name: Extract all node names from validator-config.yaml
- name: Extract all node names from deploy-validator-config.yaml
shell: |
yq eval '.validators[].name' "{{ genesis_dir }}/validator-config.yaml"
yq eval '.validators[].name' "{{ genesis_dir }}/deploy-validator-config.yaml"
register: all_node_names_raw
delegate_to: localhost
run_once: true
Expand Down Expand Up @@ -141,7 +141,7 @@
- nodes.yaml
- genesis.json
- genesis.ssz
- validator-config.yaml
- deploy-validator-config.yaml

- name: Copy node private key files to remote host
copy:
Expand Down
Loading
Loading