From 3516c9e5d50843252a680fdc1039ee9368a079d7 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 11 Feb 2026 14:46:54 +0100 Subject: [PATCH 1/2] feat: merge do and hc --- terraform/devnet-0/.terraform.lock.hcl | 90 --- terraform/devnet-0/ansible_inventory.tmpl | 2 +- terraform/devnet-0/cloudflare.tf | 69 ++- terraform/devnet-0/digitalocean.tf | 151 +---- terraform/devnet-0/firewall.tf | 202 ++++++- terraform/devnet-0/hetzner.tf | 152 +++++ terraform/devnet-0/hetzner/cloudflare.tf | 43 -- terraform/devnet-0/hetzner/firewall.tf | 181 ------ terraform/devnet-0/hetzner/hetzner.tf | 246 -------- terraform/devnet-0/hetzner/main.tf | 145 ----- terraform/devnet-0/hetzner/nodes.tf | 683 --------------------- terraform/devnet-0/main.tf | 112 ++-- terraform/devnet-0/nodes.tf | 704 +--------------------- terraform/devnet-0/outputs.tf | 118 ++++ 14 files changed, 606 insertions(+), 2292 deletions(-) delete mode 100644 terraform/devnet-0/.terraform.lock.hcl create mode 100644 terraform/devnet-0/hetzner.tf delete mode 100644 terraform/devnet-0/hetzner/cloudflare.tf delete mode 100644 terraform/devnet-0/hetzner/firewall.tf delete mode 100644 terraform/devnet-0/hetzner/hetzner.tf delete mode 100644 terraform/devnet-0/hetzner/main.tf delete mode 100644 terraform/devnet-0/hetzner/nodes.tf create mode 100644 terraform/devnet-0/outputs.tf diff --git a/terraform/devnet-0/.terraform.lock.hcl b/terraform/devnet-0/.terraform.lock.hcl deleted file mode 100644 index 0df7c15..0000000 --- a/terraform/devnet-0/.terraform.lock.hcl +++ /dev/null @@ -1,90 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/cloudflare/cloudflare" { - version = "3.35.0" - constraints = "~> 3.0" - hashes = [ - "h1:pn9uUSAuIE8XgqJuZ9fOs98bRN9qw4o0JHFgmwtbMyI=", - "zh:13aabc00fee823422831bcc870227650cc765fc4c9622074d24d6d62a4ac0e37", - "zh:1544405f0ea6b388dad7eb25c434427b2682417396da9186e1b33551e6b4adff", - "zh:5d58394cb8e71bd4bf6ef0135f1ca6a4ad2cec937f3731b224125eb34ee059f7", - "zh:648596ed545ed01ae757d5a0b37c20e8050cfb51d42e9a2c82fcc94d883ff11d", - "zh:68d75e14eef4f073faa975ed6baf4db7e0e1f2fc61a4e54fd95325df42793810", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:9916cc626fef57428c4c60db7897b34068c65639b68482e94f62d97d773d64bc", - "zh:9c8c9f369eb30e7360a0ebd7918e4846ca4d5bca430b861fdbde7522a3146459", - "zh:a40e244688bbcb6f1a771e6ea89fb0b0b7bb53be3fab718abc66b3593e0f8133", - "zh:cc5a6191aa8713275550ff2b6adda6e6d56e4780c9cbe3d1da1dc23ea893bfff", - "zh:d1dd435780e8c7e79bff26b46a76df0e123971849355ad17877d1e24dc5953c3", - "zh:d751fc72f2833f2bdb897fa89de2bb5b6efbad1e648896642f0e6fe5cde789c8", - "zh:dfc4c90b3605ec1bb7cc7a9f1fb1b67235578bdd6b9be78e7b3516b55d0422db", - "zh:e6101a80fe24e2df3ab60152458ff1666a4a1befc87c62e459a219cdbb53e6df", - "zh:e9bcf26c44dd231f74703b6a6717470021a3ba7e1d7531dcf7287a6441300e27", - ] -} - -provider "registry.terraform.io/digitalocean/digitalocean" { - version = "2.29.0" - constraints = "~> 2.28" - hashes = [ - "h1:mJrr4YaOsB7bWfCSJZneiXB6JMnVNnFxYRmQ8vKaOSQ=", - "zh:0af0a1a2de818c5dc8ee7ad4dc4731452848e84cfa0c1ce514af1c7aad15c53c", - "zh:27229f3162b4142be48554f56227265982f3b74e4c79fa5d2528c8a3912d1e19", - "zh:31d6e73bfe12231fa0ab3bbeef0e4aa9822a2008ae2a1a8b22557bdada4af7a3", - "zh:6e7417413e96b87a11d47e9acbc88e6d707a6ab23a7de6b584fc600d9d3cbf00", - "zh:9faf40798a698b80e8d56e502c220856d2d5f55d5137b9cf5371f2fdaeadd70a", - "zh:b9ab9caf21b3f928fdd891e749fd8d33f6d441b39a08d725edf58cf8027a9b7b", - "zh:be32b3a35474f8acbab4d0ad8676810fa05a87918cc1874b53672159005016c0", - "zh:c2e8f7c08cad44b46e2e5580183e1ef2a4f1803347de136d1a35f333973a25f0", - "zh:cf0aba5b5042c762da489050716815652f809f3ef0ededb0f981f11691dbef03", - "zh:d1c0874c0ae0aa1eae86dbd131978796303599709c35b5dee926887d375f4cc8", - "zh:d4eecb61e763950a5a0f40cddc7a58345419a522b783aae7b0703309a354bb0c", - "zh:d866df86dd78eb2a9e54ebff637301522766710bb6dc7f8e330f1146822b62ee", - "zh:da51541ef96d0a5745740dc623bff3ccfb6b098b548d78cf5e9d95a15c69963a", - "zh:ede343be1528b468feae3a1cbf781e223f63ce33446a008a42f2fb799a23b436", - "zh:f20a60e2cecd29bbcc73d59e95aca368e2c55b7648f1923df2c0f7578026b048", - "zh:fccaf963f2db1e271e9d28276172910ca6b95471b8e0dfac758daf0495ce17f5", - ] -} - -provider "registry.terraform.io/hashicorp/local" { - version = "2.4.0" - hashes = [ - "h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=", - "zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9", - "zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf", - "zh:70a6f6a852dd83768d0778ce9817d81d4b3f073fab8fa570bff92dcb0824f732", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:82a803f2f484c8b766e2e9c32343e9c89b91997b9f8d2697f9f3837f62926b35", - "zh:9708a4e40d6cc4b8afd1352e5186e6e1502f6ae599867c120967aebe9d90ed04", - "zh:973f65ce0d67c585f4ec250c1e634c9b22d9c4288b484ee2a871d7fa1e317406", - "zh:c8fa0f98f9316e4cfef082aa9b785ba16e36ff754d6aba8b456dab9500e671c6", - "zh:cfa5342a5f5188b20db246c73ac823918c189468e1382cb3c48a9c0c08fc5bf7", - "zh:e0e2b477c7e899c63b06b38cd8684a893d834d6d0b5e9b033cedc06dd7ffe9e2", - "zh:f62d7d05ea1ee566f732505200ab38d94315a4add27947a60afa29860822d3fc", - "zh:fa7ce69dde358e172bd719014ad637634bbdabc49363104f4fca759b4b73f2ce", - ] -} - -provider "registry.terraform.io/hetznercloud/hcloud" { - version = "1.42.1" - constraints = "~> 1.42.1" - hashes = [ - "h1:1ek+EAXn9sMHCFnCiDw9naAGGXW3d+qco8t0/mGEupc=", - "zh:002e2e57c1425bb4cf620c6a80732ee071726d0d82d0523c5258dde3222113df", - "zh:03213d79fc2bcd94ac812ca22c1d1d6678132ab957d26a65c84ee52853059c02", - "zh:0785429efdb084cb4e5a0d899112764c21d2260391e82897d7e67c9e5deccc31", - "zh:12a5653b7a00f458b65b89b15d4517f785322ebb65b5a689fa8766042a09184c", - "zh:2dc7464290a623eb599cfbf731d13554448a7a824c2b1db16275f482d9059670", - "zh:35a7e19868a304d77ab192871ccaa45418c13a3aac301df8d9f57c1259913051", - "zh:368202d94a1104895c1d566e3f16edd55e05a09881fd4a20cd4854ca3593fee9", - "zh:431503e5055979aabf520675bb465496d934979c7a687e1cd3c8d2ae27bfa649", - "zh:45cede3c2147cfdc76d53853e07395c05b1feff8dca16a2f8f7f1fd151e2449f", - "zh:8b57869af18982af21f6f816e65e6057ec5055481b220147fdbe0959917ae112", - "zh:be9ba4813dcf640c0df04543a3c74b0db117fbd3dcc26140e252cf5157734945", - "zh:d3fb9ca398a153dc894caa94f95ef2e989350cf2bbfa29bc93ff2608cab44c1f", - "zh:fc690be8cbada1e99063ed1c6148f9a70ab341100a97ad2886f4826a951780d3", - "zh:ffa9470e41fa04ac667d4d830987aeed2070767d57f2414692c2dd395a405fba", - ] -} diff --git a/terraform/devnet-0/ansible_inventory.tmpl b/terraform/devnet-0/ansible_inventory.tmpl index 3bfc22e..582cc02 100644 --- a/terraform/devnet-0/ansible_inventory.tmpl +++ b/terraform/devnet-0/ansible_inventory.tmpl @@ -7,7 +7,7 @@ ethereum_network_name=${ethereum_network_name} [${replace(gid, "-", "_")}] %{ for key, host in hosts ~} %{ if host.group == gid ~} -${host.hostname} ansible_host=${host.ip} ipv6=${host.ipv6} cloud=${host.cloud} cloud_region=${host.region} ethereum_node_cl_supernode_enabled=${title(host.supernode)} %{ if tonumber(host.validator_end) > 0 }validator_start=${host.validator_start} validator_end=${host.validator_end}%{ endif } +${host.hostname} ansible_host=${host.ip} ipv6=${host.ipv6} cloud=${host.cloud} cloud_region=${host.region} arch=${host.arch} ethereum_node_cl_supernode_enabled=${title(host.supernode)} %{ if tonumber(host.validator_end) > 0 }validator_start=${host.validator_start} validator_end=${host.validator_end}%{ endif } %{ endif ~} %{ endfor ~} %{ if gid == "lighthouse-reth" ~} diff --git a/terraform/devnet-0/cloudflare.tf b/terraform/devnet-0/cloudflare.tf index 06ef3bd..1e21eca 100644 --- a/terraform/devnet-0/cloudflare.tf +++ b/terraform/devnet-0/cloudflare.tf @@ -7,39 +7,52 @@ data "cloudflare_zone" "default" { name = "ethpandaops.io" } +locals { + # Combine bootnodes from both providers + bootnodes = merge( + { + for vm in local.digitalocean_vms : vm.id => { + name = vm.name + ipv4 = digitalocean_droplet.main[vm.id].ipv4_address + ipv6 = try(digitalocean_droplet.main[vm.id].ipv6_address, null) + } if can(regex("bootnode", vm.name)) + }, + { + for vm in local.hcloud_vms : vm.id => { + name = vm.name + ipv4 = hcloud_server.main[vm.id].ipv4_address + ipv6 = try(hcloud_server.main[vm.id].ipv6_address, null) + } if can(regex("bootnode", vm.name)) + } + ) +} + resource "cloudflare_record" "server_record_v4" { - for_each = { - for vm in local.digitalocean_vms : "${vm.id}" => vm if can(regex("bootnode", vm.name)) - } - zone_id = data.cloudflare_zone.default.id - name = "${each.value.name}.${var.ethereum_network}" - type = "A" - value = digitalocean_droplet.main[each.value.id].ipv4_address - proxied = false - ttl = 120 + for_each = local.bootnodes + zone_id = data.cloudflare_zone.default.id + name = "${each.value.name}.${var.ethereum_network}" + type = "A" + value = each.value.ipv4 + proxied = false + ttl = 120 } resource "cloudflare_record" "server_record_v6" { - for_each = { - for vm in local.digitalocean_vms : "${vm.id}" => vm if vm.ipv6 && can(regex("bootnode", vm.name)) - } - zone_id = data.cloudflare_zone.default.id - name = "${each.value.name}.${var.ethereum_network}" - type = "AAAA" - value = digitalocean_droplet.main[each.value.id].ipv6_address - proxied = false - ttl = 120 + for_each = { for k, v in local.bootnodes : k => v if v.ipv6 != null } + zone_id = data.cloudflare_zone.default.id + name = "${each.value.name}.${var.ethereum_network}" + type = "AAAA" + value = each.value.ipv6 + proxied = false + ttl = 120 } resource "cloudflare_record" "server_record_ns" { - for_each = { - for vm in local.digitalocean_vms : "${vm.id}" => vm if can(regex("bootnode", vm.name)) - } - zone_id = data.cloudflare_zone.default.id - name = "srv.${var.ethereum_network}" - type = "NS" - value = "${each.value.name}.${var.ethereum_network}.${data.cloudflare_zone.default.name}" - proxied = false - ttl = 120 + for_each = local.bootnodes + zone_id = data.cloudflare_zone.default.id + name = "srv.${var.ethereum_network}" + type = "NS" + value = "${each.value.name}.${var.ethereum_network}.${data.cloudflare_zone.default.name}" + proxied = false + ttl = 120 } - diff --git a/terraform/devnet-0/digitalocean.tf b/terraform/devnet-0/digitalocean.tf index f28bce5..bae5b65 100644 --- a/terraform/devnet-0/digitalocean.tf +++ b/terraform/devnet-0/digitalocean.tf @@ -40,40 +40,39 @@ variable "digitalocean_regions" { // LOCALS //////////////////////////////////////////////////////////////////////////////////////// locals { - base_cidr_block = var.base_cidr_block digitalocean_vpcs = { for region in var.digitalocean_regions : region => { name = "${var.ethereum_network}-${region}" region = region - ip_range = cidrsubnet(local.base_cidr_block, 8, index(var.digitalocean_regions, region)) + ip_range = cidrsubnet(var.base_cidr_block, 8, index(var.digitalocean_regions, region)) } } } locals { digitalocean_vm_groups = flatten([ - for vm_group in local.vm_groups : - vm_group.count > 0 ? [ - for i in range(0, vm_group.count) : { - group_name = "${vm_group.name}" - id = "${vm_group.name}-${i + 1}" + for node in local.digitalocean_nodes : [ + for i in range(0, node.count) : { + group_name = node.name + id = "${node.name}-${node.start_index + i + 1}" vms = { "${i + 1}" = { tags = join(",", compact([ - "group_name:${vm_group.name}", - "val_start:${vm_group.validator_start + (i * (vm_group.validator_end - vm_group.validator_start) / vm_group.count)}", - "val_end:${min(vm_group.validator_start + ((i + 1) * (vm_group.validator_end - vm_group.validator_start) / vm_group.count), vm_group.validator_end)}", - "supernode:${try(vm_group.supernode, can(regex("(super|bootnode|mev)", vm_group.name))) ? "True" : "False"}", - can(regex("bootnode", vm_group.name)) ? "bootnode:${var.ethereum_network}" : null, - can(regex("mev-relay", vm_group.name)) ? "mev-relay:${var.ethereum_network}" : null + "group_name:${node.name}", + "val_start:${node.validator_start + (i * (node.validator_end - node.validator_start) / node.count)}", + "val_end:${min(node.validator_start + ((i + 1) * (node.validator_end - node.validator_start) / node.count), node.validator_end)}", + "supernode:${node.supernode != null ? (node.supernode ? "True" : "False") : (can(regex("(super|bootnode|mev)", node.name)) ? "True" : "False")}", + "arch:amd64", + can(regex("bootnode", node.name)) ? "bootnode:${var.ethereum_network}" : null, + can(regex("mev-relay", node.name)) ? "mev-relay:${var.ethereum_network}" : null ])) - region = try(vm_group.region, var.digitalocean_regions[i % length(var.digitalocean_regions)]) - size = try(vm_group.size, can(regex("(super|bootnode)", vm_group.name)) ? var.digitalocean_supernode_size : var.digitalocean_fullnode_size) - ipv6 = try(vm_group.ipv6, true) + region = node.region != null ? node.region : var.digitalocean_regions[i % length(var.digitalocean_regions)] + size = node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.digitalocean_supernode_size : var.digitalocean_fullnode_size) + ipv6 = node.ipv6 } } } - ] : [] + ] ]) } @@ -86,30 +85,25 @@ locals { "EthNetwork:${var.ethereum_network}" ] - # flatten vm_groups so that we can use it with for_each() digitalocean_vms = flatten([ for group in local.digitalocean_vm_groups : [ for vm_key, vm in group.vms : { - id = "${group.id}" - group_key = "${group.group_name}" + id = group.id + group_key = group.group_name vm_key = vm_key - name = try(vm.name, "${group.id}") - ssh_keys = try(vm.ssh_keys, [data.digitalocean_ssh_key.main.fingerprint]) - region = try(vm.region, try(group.region, local.digitalocean_default_region)) - image = try(vm.image, local.digitalocean_default_image) - size = try(vm.size, local.digitalocean_default_size) - resize_disk = try(vm.resize_disk, true) - monitoring = try(vm.monitoring, true) - backups = try(vm.backups, false) - ipv6 = try(vm.ipv6, true) - ansible_vars = try(vm.ansible_vars, null) - vpc_uuid = try(vm.vpc_uuid, try( - digitalocean_vpc.main[vm.region].id, - digitalocean_vpc.main[try(group.region, local.digitalocean_default_region)].id - )) - - tags = concat(local.digitalocean_global_tags, try(split(",", group.tags), []), try(split(",", vm.tags), [])) + name = group.id + ssh_keys = [data.digitalocean_ssh_key.main.fingerprint] + region = vm.region + image = local.digitalocean_default_image + size = vm.size + resize_disk = true + monitoring = true + backups = false + ipv6 = vm.ipv6 + vpc_uuid = digitalocean_vpc.main[vm.region].id + + tags = concat(local.digitalocean_global_tags, split(",", vm.tags)) } ] ]) @@ -136,7 +130,7 @@ resource "digitalocean_vpc" "main" { resource "digitalocean_droplet" "main" { for_each = { - for vm in local.digitalocean_vms : "${vm.id}" => vm + for vm in local.digitalocean_vms : vm.id => vm } name = "${var.ethereum_network}-${each.value.name}" region = each.value.region @@ -156,86 +150,3 @@ resource "digitalocean_project_resources" "droplets" { project = data.digitalocean_project.main.id resources = [each.value.urn] } - - - -//////////////////////////////////////////////////////////////////////////////////////// -// GENERATED FILES AND OUTPUTS -//////////////////////////////////////////////////////////////////////////////////////// - -resource "local_file" "ansible_inventory" { - content = templatefile("ansible_inventory.tmpl", - { - ethereum_network_name = "${var.ethereum_network}" - groups = merge( - { for group in local.digitalocean_vm_groups : "${group.group_name}" => true... }, - ) - hosts = merge( - { - for key, server in digitalocean_droplet.main : "do.${key}" => { - ip = "${server.ipv4_address}" - ipv6 = try(server.ipv6_address, "none") - group = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^group_name:", tag))][0], "unknown") - validator_start = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^val_start:", tag))][0], 0) - validator_end = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^val_end:", tag))][0], 0) - supernode = try(title([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^supernode:", tag))][0]), "undefined") - tags = "${server.tags}" - hostname = "${split(".", key)[0]}" - cloud = "digitalocean" - region = "${server.region}" - } - } - ) - } - ) - filename = "../../ansible/inventories/devnet-0/inventory.ini" -} - -locals { - ssh_config_path = pathexpand("~/.ssh/config.d/ssh_config.${var.ethereum_network}.digitalocean") -} - -resource "local_file" "ssh_config" { - content = templatefile("${path.module}/ssh_config.tmpl", - { - ethereum_network = var.ethereum_network - hosts = merge( - { - for key, server in digitalocean_droplet.main : "${var.ethereum_network}-${key}" => { - hostname = server.ipv4_address - private_ip = server.ipv4_address_private - name = key - user = "devops" - } - } - ) - } - ) - filename = local.ssh_config_path - - depends_on = [digitalocean_droplet.main] - - lifecycle { - create_before_destroy = true - } -} - -# Ensure cleanup on destroy -resource "null_resource" "ssh_config_cleanup" { - triggers = { - ssh_config_path = local.ssh_config_path - } - - # This provisioner runs on destroy - provisioner "local-exec" { - when = destroy - command = "rm -f ${self.triggers.ssh_config_path} || true" - } - - depends_on = [local_file.ssh_config] -} - -output "ssh_config_file" { - value = "SSH config generated at: ${local.ssh_config_path}" - description = "Path to the generated SSH config file" -} diff --git a/terraform/devnet-0/firewall.tf b/terraform/devnet-0/firewall.tf index 8542eca..285b559 100644 --- a/terraform/devnet-0/firewall.tf +++ b/terraform/devnet-0/firewall.tf @@ -1,8 +1,9 @@ - +//////////////////////////////////////////////////////////////////////////////////////// +// DIGITALOCEAN FIREWALLS +//////////////////////////////////////////////////////////////////////////////////////// resource "digitalocean_firewall" "main" { - name = "${var.ethereum_network}-nodes" - // Tags are used to select which droplets should - // be assigned to this firewall. + count = length(local.digitalocean_vms) > 0 ? 1 : 0 + name = "${var.ethereum_network}-nodes" tags = [ "EthNetwork:${var.ethereum_network}" ] @@ -93,9 +94,8 @@ resource "digitalocean_firewall" "main" { } resource "digitalocean_firewall" "bootnode" { - name = "${var.ethereum_network}-nodes-bootnode" - // Tags are used to select which droplets should - // be assigned to this firewall. + count = contains(keys(digitalocean_droplet.main), "bootnode-1") ? 1 : 0 + name = "${var.ethereum_network}-nodes-bootnode" tags = [ "bootnode:${var.ethereum_network}" ] @@ -140,3 +140,191 @@ resource "digitalocean_firewall" "mev_relay" { } depends_on = [digitalocean_project_resources.droplets] } + +//////////////////////////////////////////////////////////////////////////////////////// +// HETZNER FIREWALLS +//////////////////////////////////////////////////////////////////////////////////////// +resource "hcloud_firewall" "machine_firewall" { + count = local.hetzner_has_servers ? 1 : 0 + name = "${var.ethereum_network}-firewall" + + apply_to { + label_selector = "EthNetwork=${var.ethereum_network}" + } + + // SSH + rule { + description = "Allow SSH" + direction = "in" + protocol = "tcp" + port = "22" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Allow all inbound ICMP + rule { + description = "Allow all inbound ICMP" + direction = "in" + protocol = "icmp" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Nginx / Web + rule { + description = "Allow HTTP" + direction = "in" + protocol = "tcp" + port = "80" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow HTTPS" + direction = "in" + protocol = "tcp" + port = "443" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Consensus layer p2p port + rule { + description = "Allow consensus p2p port TCP" + direction = "in" + protocol = "tcp" + port = "9000-9002" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow consensus p2p port UDP" + direction = "in" + protocol = "udp" + port = "9000-9002" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Execution layer p2p Port + rule { + description = "Allow execution p2p port TCP" + direction = "in" + protocol = "tcp" + port = "30303" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow execution p2p port UDP" + direction = "in" + protocol = "udp" + port = "30303" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow execution torrent port TCP" + direction = "in" + protocol = "tcp" + port = "42069" + source_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow execution torrent port UDP" + direction = "in" + protocol = "udp" + port = "42069" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Engine rpc-snooper api + rule { + description = "Allow engine snooper api port TCP" + direction = "in" + protocol = "tcp" + port = "8961" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Allow all outbound traffic + rule { + description = "Allow all outbound traffic TCP" + direction = "out" + protocol = "tcp" + port = "1-65535" + destination_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow all outbound traffic UDP" + direction = "out" + protocol = "udp" + port = "1-65535" + destination_ips = ["0.0.0.0/0", "::/0"] + } + + rule { + description = "Allow all outbound traffic ICMP" + direction = "out" + protocol = "icmp" + destination_ips = ["0.0.0.0/0", "::/0"] + } +} + +resource "hcloud_firewall" "bootnode_firewall" { + count = contains(keys(hcloud_server.main), "bootnode-1") ? 1 : 0 + name = "${var.ethereum_network}-bootnode-firewall" + + apply_to { + label_selector = "bootnode=${var.ethereum_network}" + } + + // DNS + rule { + description = "Allow DNS UDP" + direction = "in" + protocol = "udp" + port = "53" + source_ips = ["0.0.0.0/0", "::/0"] + } + rule { + description = "Allow DNS TCP" + direction = "in" + protocol = "tcp" + port = "53" + source_ips = ["0.0.0.0/0", "::/0"] + } + + // Bootnodoor P2P + rule { + description = "Allow Bootnodoor P2P port TCP" + direction = "in" + protocol = "tcp" + port = "9010" + source_ips = ["0.0.0.0/0", "::/0"] + } + rule { + description = "Allow Bootnodoor P2P port UDP" + direction = "in" + protocol = "udp" + port = "9010" + source_ips = ["0.0.0.0/0", "::/0"] + } +} + +resource "hcloud_firewall" "mev_relay_firewall" { + count = contains(keys(hcloud_server.main), "mev-relay-1") ? 1 : 0 + name = "${var.ethereum_network}-mev-relay-firewall" + + apply_to { + label_selector = "mev=${var.ethereum_network}" + } + + // mev-relay ports + rule { + description = "Allow MEV Relay ports" + direction = "in" + protocol = "tcp" + port = "9060-9062" + source_ips = ["0.0.0.0/0", "::/0"] + } +} diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf new file mode 100644 index 0000000..3a1058f --- /dev/null +++ b/terraform/devnet-0/hetzner.tf @@ -0,0 +1,152 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// VARIABLES +//////////////////////////////////////////////////////////////////////////////////////// +variable "hcloud_ssh_key_fingerprint" { + type = string + default = "d6:76:2d:9c:5b:33:80:ff:0f:09:a2:10:9b:58:7e:dc" +} + +variable "hetzner_supernode_size" { + type = string + default = "cax41" +} + +variable "hetzner_fullnode_size" { + type = string + default = "cax31" +} + +variable "hetzner_regions" { + default = [ + "nbg1", + "fsn1", + "hel1" + ] +} + +//////////////////////////////////////////////////////////////////////////////////////// +// LOCALS +//////////////////////////////////////////////////////////////////////////////////////// +locals { + hetzner_has_servers = length(local.hetzner_nodes) > 0 + + hetzner_network = { + for region in var.hetzner_regions : region => { + name = "${var.ethereum_network}-${region}" + ip_range = cidrsubnet(var.base_cidr_block, 8, index(var.hetzner_regions, region)) + } + } + hetzner_network_subnets = { + for region in var.hetzner_regions : region => { + zone = "eu-central" + ip_range = cidrsubnet(var.base_cidr_block, 8, index(var.hetzner_regions, region)) + } + } +} + +locals { + hetzner_vm_groups = flatten([ + for node in local.hetzner_nodes : [ + for i in range(0, node.count) : { + group_name = node.name + id = "${node.name}-${node.start_index + i + 1}" + vms = { + "${i + 1}" = { + arch = can(regex("^cax", node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size))) ? "arm64" : "amd64" + labels = join(",", compact([ + "group_name:${node.name}", + "val_start:${node.validator_start + (i * (node.validator_end - node.validator_start) / node.count)}", + "val_end:${min(node.validator_start + ((i + 1) * (node.validator_end - node.validator_start) / node.count), node.validator_end)}", + "supernode:${node.supernode != null ? (node.supernode ? "True" : "False") : (can(regex("(super|bootnode|mev)", node.name)) ? "True" : "False")}", + "arch:${can(regex("^cax", node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size))) ? "arm64" : "amd64"}", + can(regex("bootnode", node.name)) ? "bootnode:${var.ethereum_network}" : null, + can(regex("mev-relay", node.name)) ? "mev:${var.ethereum_network}" : null + ])) + location = node.location != null ? node.location : var.hetzner_regions[i % length(var.hetzner_regions)] + size = node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size) + ipv4_enabled = node.ipv4_enabled + ipv6_enabled = node.ipv6_enabled + } + } + } + ] + ]) +} + +locals { + hcloud_default_location = "nbg1" + hcloud_default_image = "debian-13" + hcloud_default_server_type = var.hetzner_fullnode_size + hcloud_global_labels = [ + "Owner:Devops", + "EthNetwork:${var.ethereum_network}" + ] + + hcloud_vms = flatten([ + for group in local.hetzner_vm_groups : [ + for vm_key, vm in group.vms : { + id = group.id + group_key = group.group_name + vm_key = vm_key + + name = group.id + ipv4_enabled = vm.ipv4_enabled + ipv6_enabled = vm.ipv6_enabled + ssh_keys = local.hetzner_has_servers ? [data.hcloud_ssh_key.main[0].id] : [] + location = vm.location + image = local.hcloud_default_image + server_type = vm.size + backups = false + + labels = concat(local.hcloud_global_labels, split(",", vm.labels)) + } + ] + ]) +} + +//////////////////////////////////////////////////////////////////////////////////////// +// HETZNER RESOURCES +//////////////////////////////////////////////////////////////////////////////////////// +resource "hcloud_network" "main" { + for_each = local.hetzner_has_servers ? local.hetzner_network : {} + name = each.value.name + ip_range = each.value.ip_range +} + +resource "hcloud_network_subnet" "main" { + for_each = local.hetzner_has_servers ? local.hetzner_network_subnets : {} + network_id = hcloud_network.main[each.key].id + type = "cloud" + network_zone = each.value.zone + ip_range = each.value.ip_range +} + +data "hcloud_ssh_key" "main" { + count = local.hetzner_has_servers ? 1 : 0 + fingerprint = var.hcloud_ssh_key_fingerprint +} + +resource "hcloud_server" "main" { + for_each = { + for vm in local.hcloud_vms : vm.id => vm + } + name = "${var.ethereum_network}-${each.value.name}" + image = each.value.image + server_type = each.value.server_type + location = each.value.location + ssh_keys = each.value.ssh_keys + backups = each.value.backups + labels = { for label in each.value.labels : split(":", label)[0] => split(":", label)[1] } + public_net { + ipv4_enabled = each.value.ipv4_enabled + ipv6_enabled = each.value.ipv6_enabled + } +} + +resource "hcloud_server_network" "main" { + for_each = { + for vm in local.hcloud_vms : vm.id => vm + } + server_id = hcloud_server.main[each.key].id + network_id = hcloud_network.main[each.value.location].id +} diff --git a/terraform/devnet-0/hetzner/cloudflare.tf b/terraform/devnet-0/hetzner/cloudflare.tf deleted file mode 100644 index f4193a4..0000000 --- a/terraform/devnet-0/hetzner/cloudflare.tf +++ /dev/null @@ -1,43 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////// -// DNS NAMES -//////////////////////////////////////////////////////////////////////////////////////// - -data "cloudflare_zone" "default" { - name = "ethpandaops.io" -} - -resource "cloudflare_record" "server_record" { - for_each = { - for vm in local.hcloud_vms : "${vm.id}" => vm if coalesce(vm.ipv4_enabled, true) == true && can(regex("bootnode", vm.name)) - } - zone_id = data.cloudflare_zone.default.id - name = "${each.value.name}.${var.ethereum_network}" - type = "A" - value = hcloud_server.main[each.value.id].ipv4_address - proxied = false - ttl = 120 -} - -resource "cloudflare_record" "server_record6" { - for_each = { - for vm in local.hcloud_vms : "${vm.id}" => vm if coalesce(vm.ipv6_enabled, true) == true && can(regex("bootnode", vm.name)) - } - zone_id = data.cloudflare_zone.default.id - name = "${each.value.name}.${var.ethereum_network}" - type = "AAAA" - value = hcloud_server.main[each.value.id].ipv6_address - proxied = false - ttl = 120 -} - -resource "cloudflare_record" "server_record_ns" { - for_each = { - for vm in local.hcloud_vms : "${vm.id}" => vm if can(regex("bootnode", vm.name)) - } - zone_id = data.cloudflare_zone.default.id - name = "srv.${var.ethereum_network}" - type = "NS" - value = "${each.value.name}.${var.ethereum_network}.${data.cloudflare_zone.default.name}" - proxied = false - ttl = 120 -} \ No newline at end of file diff --git a/terraform/devnet-0/hetzner/firewall.tf b/terraform/devnet-0/hetzner/firewall.tf deleted file mode 100644 index fb3b6c5..0000000 --- a/terraform/devnet-0/hetzner/firewall.tf +++ /dev/null @@ -1,181 +0,0 @@ -resource "hcloud_firewall" "machine_firewall" { - name = "${var.ethereum_network}-firewall" - - apply_to { - label_selector = "EthNetwork=${var.ethereum_network}" - } - - # SSH - rule { - description = "Allow SSH" - direction = "in" - protocol = "tcp" - port = "22" - source_ips = ["0.0.0.0/0", "::/0"] - } - - # Allow all inbound ICMP - rule { - description = "Allow all inbound ICMP" - direction = "in" - protocol = "icmp" - source_ips = ["0.0.0.0/0", "::/0"] - } - - # Nginx / Web - rule { - description = "Allow HTTP" - direction = "in" - protocol = "tcp" - port = "80" - source_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow HTTPS" - direction = "in" - protocol = "tcp" - port = "443" - source_ips = ["0.0.0.0/0", "::/0"] - } - - # Consensus layer p2p port - rule { - description = "Allow consensus p2p port TCP" - direction = "in" - protocol = "tcp" - port = "9000-9002" - source_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow consensus p2p port UDP" - direction = "in" - protocol = "udp" - port = "9000-9002" - source_ips = ["0.0.0.0/0", "::/0"] - } - - # Execution layer p2p Port - rule { - description = "Allow execution p2p port TCP" - direction = "in" - protocol = "tcp" - port = "30303" - source_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow execution p2p port UDP" - direction = "in" - protocol = "udp" - port = "30303" - source_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow execution torrent port TCP" - direction = "in" - protocol = "tcp" - port = "42069" - source_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow execution torrent port UDP" - direction = "in" - protocol = "udp" - port = "42069" - source_ips = ["0.0.0.0/0", "::/0"] - } - - // Engine rpc-snooper api - rule { - description = "Allow engine snooper api port TCP" - direction = "in" - protocol = "tcp" - port = "8961" - source_ips = ["0.0.0.0/0", "::/0"] - } - - # Allow all outbound traffic - rule { - description = "Allow all outbound traffic TCP" - direction = "out" - protocol = "tcp" - port = "1-65535" - destination_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow all outbound traffic UDP" - direction = "out" - protocol = "udp" - port = "1-65535" - destination_ips = ["0.0.0.0/0", "::/0"] - } - - rule { - description = "Allow all outbound traffic ICMP" - direction = "out" - protocol = "icmp" - destination_ips = ["0.0.0.0/0", "::/0"] - } -} - - -resource "hcloud_firewall" "bootnode_firewall" { - name = "${var.ethereum_network}-bootnode-firewall" - - apply_to { - label_selector = "bootnode=${var.ethereum_network}" - } - - # DNS - rule { - description = "Allow DNS UDP" - direction = "in" - protocol = "udp" - port = "53" - source_ips = ["0.0.0.0/0", "::/0"] - } - rule { - description = "Allow DNS TCP" - direction = "in" - protocol = "tcp" - port = "53" - source_ips = ["0.0.0.0/0", "::/0"] - } - - // Bootnodoor P2P - rule { - description = "Allow Bootnodoor P2P port TCP" - direction = "in" - protocol = "tcp" - port = "9010" - source_ips = ["0.0.0.0/0", "::/0"] - } - rule { - description = "Allow Bootnodoor P2P port UDP" - direction = "in" - protocol = "udp" - port = "9010" - source_ips = ["0.0.0.0/0", "::/0"] - } -} - -resource "hcloud_firewall" "mev_relay_firewall" { - name = "${var.ethereum_network}-mev-relay-firewall" - - apply_to { - label_selector = "mev=${var.ethereum_network}" - } - - rule { - description = "Allow MEV Relay ports" - direction = "in" - protocol = "tcp" - port = "9060-9062" - source_ips = ["0.0.0.0/0", "::/0"] - } -} \ No newline at end of file diff --git a/terraform/devnet-0/hetzner/hetzner.tf b/terraform/devnet-0/hetzner/hetzner.tf deleted file mode 100644 index 1ea917f..0000000 --- a/terraform/devnet-0/hetzner/hetzner.tf +++ /dev/null @@ -1,246 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////// -// TERRAFORM PROVIDERS & BACKEND -//////////////////////////////////////////////////////////////////////////////////////// -provider "hcloud" { - token = var.devnet_hcloud_token -} -//////////////////////////////////////////////////////////////////////////////////////// -// VARIABLES -//////////////////////////////////////////////////////////////////////////////////////// -variable "devnet_hcloud_token" { - type = string - description = "Hetzner Cloud API Token" - sensitive = true -} - -variable "hcloud_ssh_key_fingerprint" { - type = string - default = "d6:76:2d:9c:5b:33:80:ff:0f:09:a2:10:9b:58:7e:dc" -} - -variable "hetzner_supernode_size" { - type = string - default = "cax41" -} -variable "hetzner_fullnode_size" { - type = string - default = "cax31" -} - -variable "hetzner_regions" { - default = [ - "nbg1", - "fsn1", - "hel1" - ] -} - -//////////////////////////////////////////////////////////////////////////////////////// -// LOCALS -//////////////////////////////////////////////////////////////////////////////////////// -locals { - hetzner_network = { - for region in var.hetzner_regions : region => { - name = "${var.ethereum_network}-${region}" - ip_range = cidrsubnet(var.base_cidr_block, 8, index(var.hetzner_regions, region)) - } - } - hetzner_network_subnets = { - for region in var.hetzner_regions : region => { - zone = "eu-central" - ip_range = cidrsubnet(var.base_cidr_block, 8, index(var.hetzner_regions, region)) - } - } -} - -locals { - hetzner_vm_groups = flatten([ - for vm_group in local.vm_groups : - [ - for i in range(0, vm_group.count) : { - group_name = "${vm_group.name}" - id = "hc-${vm_group.name}-${i + 1}" - vms = { - "${i + 1}" = { - labels = join(",", compact([ - "group_name:${vm_group.name}", - "val_start:${vm_group.validator_start + (i * (vm_group.validator_end - vm_group.validator_start) / vm_group.count)}", - "val_end:${min(vm_group.validator_start + ((i + 1) * (vm_group.validator_end - vm_group.validator_start) / vm_group.count), vm_group.validator_end)}", - "supernode:${try(vm_group.supernode, can(regex("(super|bootnode|mev)", vm_group.name))) ? "True" : "False"}", - can(regex("bootnode", vm_group.name)) ? "bootnode:${var.ethereum_network}" : null, - can(regex("mev-relay", vm_group.name)) ? "mev:${var.ethereum_network}" : null - ])) - location = try(vm_group.location, var.hetzner_regions[i % length(var.hetzner_regions)]) - size = try(vm_group.size, can(regex("(super|bootnode)", vm_group.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size) - ansible_vars = try(vm_group.ansible_vars, null) - ipv4_enabled = try(vm_group.ipv4_enabled, true) - ipv6_enabled = try(vm_group.ipv6_enabled, true) - } - } - } - ] - ]) -} - -locals { - hcloud_default_location = "nbg1" - hcloud_default_image = "debian-13" - hcloud_default_server_type = var.hetzner_fullnode_size - hcloud_global_labels = [ - "Owner:Devops", - "EthNetwork:${var.ethereum_network}" - ] - # hcloud_global_labels_list = [for k, v in local.hcloud_global_labels : "${k}=${v}"] - - # flatten vm_groups so that we can use it with for_each() - hcloud_vms = flatten([ - for group in local.hetzner_vm_groups : [ - for vm_key, vm in group.vms : { - id = "${group.id}" - group_key = "${group.group_name}" - vm_key = vm_key - - name = try(vm.name, "${group.id}") - ipv4_enabled = try(vm.ipv4_enabled, true) - ipv6_enabled = try(vm.ipv6_enabled, true) - ssh_keys = try(vm.ssh_keys, [data.hcloud_ssh_key.main.id]) - location = try(vm.location, try(group.location, local.hcloud_default_location)) - image = try(vm.image, local.hcloud_default_image) - server_type = try(vm.size, local.hcloud_default_server_type) - backups = try(vm.backups, false) - ansible_vars = try(vm.ansible_vars, null) - - labels = concat(local.hcloud_global_labels, try(split(",", group.labels), []), try(split(",", vm.labels), [])) - } - ] - ]) -} -//////////////////////////////////////////////////////////////////////////////////////// -// HETZNER RESOURCES -//////////////////////////////////////////////////////////////////////////////////////// -resource "hcloud_network" "main" { - for_each = local.hetzner_network - name = try(each.value.name, "${var.ethereum_network}-${each.key}") - ip_range = each.value.ip_range -} - -resource "hcloud_network_subnet" "main" { - for_each = local.hetzner_network_subnets - network_id = hcloud_network.main[each.key].id - type = "cloud" - network_zone = each.value.zone - ip_range = each.value.ip_range -} - -data "hcloud_ssh_key" "main" { - fingerprint = var.hcloud_ssh_key_fingerprint -} - -resource "hcloud_server" "main" { - for_each = { - for vm in local.hcloud_vms : "${vm.id}" => vm - } - name = "${var.ethereum_network}-${each.value.name}" - image = each.value.image - server_type = each.value.server_type - location = each.value.location - ssh_keys = each.value.ssh_keys - backups = each.value.backups - labels = { for label in each.value.labels : split(":", label)[0] => split(":", label)[1] } - public_net { - ipv4_enabled = try(each.value.ipv4_enabled, true) - ipv6_enabled = try(each.value.ipv6_enabled, true) - } -} - -resource "hcloud_server_network" "main" { - for_each = { - for vm in local.hcloud_vms : "${vm.id}" => vm - } - server_id = hcloud_server.main[each.key].id - network_id = hcloud_network.main[each.value.location].id - -} - - -//////////////////////////////////////////////////////////////////////////////////////// -// GENERATED FILES AND OUTPUTS -//////////////////////////////////////////////////////////////////////////////////////// - -resource "local_file" "ansible_inventory" { - depends_on = [hcloud_server.main] - content = templatefile("../ansible_inventory.tmpl", - { - ethereum_network_name = "${var.ethereum_network}" - groups = merge( - { for group in local.hetzner_vm_groups : "${group.group_name}" => true... }, - ) - hosts = merge( - { - for key, server in hcloud_server.main : "${key}" => { - ip = coalesce(server.ipv4_address, (try(server.ipv6_address, ""))) - ipv6 = coalesce(server.ipv6_address, "") - group = server.labels.group_name - validator_start = server.labels.val_start - validator_end = server.labels.val_end - supernode = server.labels.supernode - tags = server.labels - hostname = split(".", key)[0] - cloud = "hetzner" - region = server.datacenter - } - } - ) - } - ) - filename = "../../../ansible/inventories/devnet-0/hetzner_inventory.ini" -} - -locals { - ssh_config_path = pathexpand("~/.ssh/config.d/ssh_config.${var.ethereum_network}.hetzner") -} - -resource "local_file" "ssh_config" { - content = templatefile("${path.module}/../ssh_config.tmpl", - { - ethereum_network = var.ethereum_network - hosts = merge( - { - for key, server in hcloud_server.main : "${var.ethereum_network}-${key}" => { - hostname = coalesce(server.ipv4_address, (try(server.ipv6_address, ""))) - private_ip = try(hcloud_server_network.main[key].ip, "") - name = key - user = "devops" - } - } - ) - } - ) - filename = local.ssh_config_path - - depends_on = [hcloud_server.main] - - lifecycle { - create_before_destroy = true - } -} - -# Ensure cleanup on destroy -resource "null_resource" "ssh_config_cleanup" { - triggers = { - ssh_config_path = local.ssh_config_path - } - - # This provisioner runs on destroy - provisioner "local-exec" { - when = destroy - command = "rm -f ${self.triggers.ssh_config_path} || true" - } - - depends_on = [local_file.ssh_config] -} - -output "ssh_config_file" { - value = "SSH config generated at: ${local.ssh_config_path}" - description = "Path to the generated SSH config file" -} diff --git a/terraform/devnet-0/hetzner/main.tf b/terraform/devnet-0/hetzner/main.tf deleted file mode 100644 index 8532ead..0000000 --- a/terraform/devnet-0/hetzner/main.tf +++ /dev/null @@ -1,145 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////// -// TERRAFORM PROVIDERS & BACKEND -//////////////////////////////////////////////////////////////////////////////////////// -terraform { - required_providers { - digitalocean = { - source = "digitalocean/digitalocean" - version = "~> 2.28" - } - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 3.0" - } - hcloud = { - source = "hetznercloud/hcloud" - version = "~> 1.42.1" - } - random = { - source = "hashicorp/random" - version = "3.5.1" - } - } -} - -terraform { - backend "s3" { - skip_credentials_validation = true - skip_metadata_api_check = true - endpoints = { s3 = "https://fra1.digitaloceanspaces.com" } - skip_requesting_account_id = true - skip_s3_checksum = true - region = "us-east-1" - bucket = "merge-testnets" - key = "infrastructure/template-devnet-0/hetzner-terraform.tfstate" - } -} - -provider "digitalocean" { - http_retry_max = 20 -} - -provider "cloudflare" { - api_token = var.cloudflare_api_token -} - -//////////////////////////////////////////////////////////////////////////////////////// -// VARIABLES -//////////////////////////////////////////////////////////////////////////////////////// -variable "cloudflare_api_token" { - type = string - sensitive = true - description = "Cloudflare API Token" -} - -variable "ethereum_network" { - type = string - default = "template-devnet-0" -} - -variable "base_cidr_block" { - default = "10.207.0.0/16" -} -//////////////////////////////////////////////////////////////////////////////////////// -// LOCALS -//////////////////////////////////////////////////////////////////////////////////////// -locals { - vm_groups = [ - var.mev_relay, - var.bootnode, - # Fullnodes - var.lighthouse_geth_full, - var.lighthouse_nethermind_full, - var.lighthouse_erigon_full, - var.lighthouse_besu_full, - var.lighthouse_reth_full, - var.lighthouse_nimbusel_full, - var.prysm_geth_full, - var.prysm_nethermind_full, - var.prysm_erigon_full, - var.prysm_besu_full, - var.prysm_reth_full, - var.prysm_nimbusel_full, - var.lodestar_geth_full, - var.lodestar_nethermind_full, - var.lodestar_erigon_full, - var.lodestar_besu_full, - var.lodestar_reth_full, - var.lodestar_nimbusel_full, - var.nimbus_geth_full, - var.nimbus_nethermind_full, - var.nimbus_erigon_full, - var.nimbus_besu_full, - var.nimbus_reth_full, - var.nimbus_nimbusel_full, - var.teku_geth_full, - var.teku_nethermind_full, - var.teku_erigon_full, - var.teku_besu_full, - var.teku_reth_full, - var.teku_nimbusel_full, - var.grandine_geth_full, - var.grandine_nethermind_full, - var.grandine_erigon_full, - var.grandine_besu_full, - var.grandine_reth_full, - var.grandine_nimbusel_full, - # Supernodes - var.lighthouse_geth_super, - var.lighthouse_nethermind_super, - var.lighthouse_erigon_super, - var.lighthouse_besu_super, - var.lighthouse_reth_super, - var.lighthouse_nimbusel_super, - var.prysm_geth_super, - var.prysm_nethermind_super, - var.prysm_erigon_super, - var.prysm_besu_super, - var.prysm_reth_super, - var.prysm_nimbusel_super, - var.lodestar_geth_super, - var.lodestar_nethermind_super, - var.lodestar_erigon_super, - var.lodestar_besu_super, - var.lodestar_reth_super, - var.lodestar_nimbusel_super, - var.nimbus_geth_super, - var.nimbus_nethermind_super, - var.nimbus_erigon_super, - var.nimbus_besu_super, - var.nimbus_reth_super, - var.nimbus_nimbusel_super, - var.teku_geth_super, - var.teku_nethermind_super, - var.teku_erigon_super, - var.teku_besu_super, - var.teku_reth_super, - var.teku_nimbusel_super, - var.grandine_geth_super, - var.grandine_nethermind_super, - var.grandine_erigon_super, - var.grandine_besu_super, - var.grandine_reth_super, - var.grandine_nimbusel_super, - ] -} diff --git a/terraform/devnet-0/hetzner/nodes.tf b/terraform/devnet-0/hetzner/nodes.tf deleted file mode 100644 index 6e3ca83..0000000 --- a/terraform/devnet-0/hetzner/nodes.tf +++ /dev/null @@ -1,683 +0,0 @@ -# Bootnode -variable "bootnode" { - default = { - name = "bootnode" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "mev_relay" { - default = { - name = "mev-relay" - count = 1 - validator_start = 0 - validator_end = 0 - size = "ccx53" - supernode = true - } -} - -# Supernodes -# Lighthouse -variable "lighthouse_geth_super" { - default = { - name = "lighthouse-geth-super" - count = 1 - validator_start = 200 - validator_end = 300 - } -} - -variable "lighthouse_besu_super" { - default = { - name = "lighthouse-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nethermind_super" { - default = { - name = "lighthouse-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_erigon_super" { - default = { - name = "lighthouse-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_reth_super" { - default = { - name = "lighthouse-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nimbusel_super" { - default = { - name = "lighthouse-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Prysm -variable "prysm_geth_super" { - default = { - name = "prysm-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_besu_super" { - default = { - name = "prysm-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nethermind_super" { - default = { - name = "prysm-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_erigon_super" { - default = { - name = "prysm-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_reth_super" { - default = { - name = "prysm-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nimbusel_super" { - default = { - name = "prysm-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Lodestar -variable "lodestar_geth_super" { - default = { - name = "lodestar-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nethermind_super" { - default = { - name = "lodestar-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_besu_super" { - default = { - name = "lodestar-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_erigon_super" { - default = { - name = "lodestar-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_reth_super" { - default = { - name = "lodestar-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nimbusel_super" { - default = { - name = "lodestar-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Nimbus -variable "nimbus_geth_super" { - default = { - name = "nimbus-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_besu_super" { - default = { - name = "nimbus-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nethermind_super" { - default = { - name = "nimbus-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_erigon_super" { - default = { - name = "nimbus-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_reth_super" { - default = { - name = "nimbus-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nimbusel_super" { - default = { - name = "nimbus-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Teku -variable "teku_geth_super" { - default = { - name = "teku-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_besu_super" { - default = { - name = "teku-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nethermind_super" { - default = { - name = "teku-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_erigon_super" { - default = { - name = "teku-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_reth_super" { - default = { - name = "teku-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nimbusel_super" { - default = { - name = "teku-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Grandine -variable "grandine_geth_super" { - default = { - name = "grandine-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_besu_super" { - default = { - name = "grandine-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nethermind_super" { - default = { - name = "grandine-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_erigon_super" { - default = { - name = "grandine-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_reth_super" { - default = { - name = "grandine-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nimbusel_super" { - default = { - name = "grandine-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - - -# Fullnodes -# Lighthouse -variable "lighthouse_geth_full" { - default = { - name = "lighthouse-geth-full" - count = 1 - validator_start = 300 - validator_end = 400 - } -} - -variable "lighthouse_besu_full" { - default = { - name = "lighthouse-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nethermind_full" { - default = { - name = "lighthouse-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_erigon_full" { - default = { - name = "lighthouse-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_reth_full" { - default = { - name = "lighthouse-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nimbusel_full" { - default = { - name = "lighthouse-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Prysm -variable "prysm_geth_full" { - default = { - name = "prysm-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_besu_full" { - default = { - name = "prysm-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nethermind_full" { - default = { - name = "prysm-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_erigon_full" { - default = { - name = "prysm-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_reth_full" { - default = { - name = "prysm-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nimbusel_full" { - default = { - name = "prysm-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Lodestar -variable "lodestar_geth_full" { - default = { - name = "lodestar-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nethermind_full" { - default = { - name = "lodestar-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_besu_full" { - default = { - name = "lodestar-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_erigon_full" { - default = { - name = "lodestar-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_reth_full" { - default = { - name = "lodestar-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nimbusel_full" { - default = { - name = "lodestar-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Nimbus -variable "nimbus_geth_full" { - default = { - name = "nimbus-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_besu_full" { - default = { - name = "nimbus-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nethermind_full" { - default = { - name = "nimbus-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_erigon_full" { - default = { - name = "nimbus-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_reth_full" { - default = { - name = "nimbus-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nimbusel_full" { - default = { - name = "nimbus-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Teku -variable "teku_geth_full" { - default = { - name = "teku-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_besu_full" { - default = { - name = "teku-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nethermind_full" { - default = { - name = "teku-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_erigon_full" { - default = { - name = "teku-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_reth_full" { - default = { - name = "teku-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nimbusel_full" { - default = { - name = "teku-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Grandine -variable "grandine_geth_full" { - default = { - name = "grandine-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_besu_full" { - default = { - name = "grandine-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nethermind_full" { - default = { - name = "grandine-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_erigon_full" { - default = { - name = "grandine-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_reth_full" { - default = { - name = "grandine-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nimbusel_full" { - default = { - name = "grandine-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} \ No newline at end of file diff --git a/terraform/devnet-0/main.tf b/terraform/devnet-0/main.tf index 50332fd..31dd7e1 100644 --- a/terraform/devnet-0/main.tf +++ b/terraform/devnet-0/main.tf @@ -39,6 +39,10 @@ provider "cloudflare" { api_token = var.cloudflare_api_token } +provider "hcloud" { + token = var.hcloud_token +} + //////////////////////////////////////////////////////////////////////////////////////// // VARIABLES //////////////////////////////////////////////////////////////////////////////////////// @@ -48,6 +52,13 @@ variable "cloudflare_api_token" { description = "Cloudflare API Token" } +variable "hcloud_token" { + type = string + sensitive = true + default = "" + description = "Hetzner Cloud API Token (optional if not using Hetzner)" +} + variable "ethereum_network" { type = string default = "template-devnet-0" @@ -56,85 +67,32 @@ variable "ethereum_network" { variable "base_cidr_block" { default = "10.2.0.0/16" } + //////////////////////////////////////////////////////////////////////////////////////// // LOCALS //////////////////////////////////////////////////////////////////////////////////////// locals { - vm_groups = [ - var.bootnode, - # Supernodes - var.lighthouse_geth_super, - var.lighthouse_nethermind_super, - var.lighthouse_besu_super, - var.lighthouse_erigon_super, - var.lighthouse_reth_super, - var.lighthouse_nimbusel_super, - var.prysm_geth_super, - var.prysm_nethermind_super, - var.prysm_besu_super, - var.prysm_erigon_super, - var.prysm_reth_super, - var.prysm_nimbusel_super, - var.teku_geth_super, - var.teku_nethermind_super, - var.teku_besu_super, - var.teku_erigon_super, - var.teku_reth_super, - var.teku_nimbusel_super, - var.nimbus_geth_super, - var.nimbus_nethermind_super, - var.nimbus_besu_super, - var.nimbus_erigon_super, - var.nimbus_reth_super, - var.nimbus_nimbusel_super, - var.lodestar_geth_super, - var.lodestar_nethermind_super, - var.lodestar_besu_super, - var.lodestar_erigon_super, - var.lodestar_reth_super, - var.lodestar_nimbusel_super, - var.grandine_geth_super, - var.grandine_nethermind_super, - var.grandine_besu_super, - var.grandine_erigon_super, - var.grandine_reth_super, - var.grandine_nimbusel_super, - # Fullnodes - var.lighthouse_geth_full, - var.lighthouse_nethermind_full, - var.lighthouse_besu_full, - var.lighthouse_erigon_full, - var.lighthouse_reth_full, - var.lighthouse_nimbusel_full, - var.prysm_geth_full, - var.prysm_nethermind_full, - var.prysm_besu_full, - var.prysm_erigon_full, - var.prysm_reth_full, - var.prysm_nimbusel_full, - var.teku_geth_full, - var.teku_nethermind_full, - var.teku_besu_full, - var.teku_erigon_full, - var.teku_reth_full, - var.teku_nimbusel_full, - var.nimbus_geth_full, - var.nimbus_nethermind_full, - var.nimbus_besu_full, - var.nimbus_erigon_full, - var.nimbus_reth_full, - var.nimbus_nimbusel_full, - var.lodestar_geth_full, - var.lodestar_nethermind_full, - var.lodestar_besu_full, - var.lodestar_erigon_full, - var.lodestar_reth_full, - var.lodestar_nimbusel_full, - var.grandine_geth_full, - var.grandine_nethermind_full, - var.grandine_besu_full, - var.grandine_erigon_full, - var.grandine_reth_full, - var.grandine_nimbusel_full, + # Normalize node entries with defaults and calculate starting index for continuous numbering + nodes_normalized = [ + for idx, node in var.nodes : { + name = node.name + count = node.count + cloud = node.cloud + validator_start = try(node.validator_start, 0) + validator_end = try(node.validator_end, 0) + size = try(node.size, null) + region = try(node.region, null) + location = try(node.location, try(node.region, null)) + supernode = try(node.supernode, null) + ipv6 = try(node.ipv6, true) + ipv4_enabled = try(node.ipv4_enabled, true) + ipv6_enabled = try(node.ipv6_enabled, true) + # Calculate starting index: sum of counts from all previous entries with same name + start_index = sum([for i, n in var.nodes : n.count if i < idx && n.name == node.name]) + } ] -} \ No newline at end of file + + # Filter by cloud provider (only nodes with count > 0) + digitalocean_nodes = [for n in local.nodes_normalized : n if n.cloud == "digitalocean" && n.count > 0] + hetzner_nodes = [for n in local.nodes_normalized : n if n.cloud == "hetzner" && n.count > 0] +} diff --git a/terraform/devnet-0/nodes.tf b/terraform/devnet-0/nodes.tf index d06c8a4..1b443b5 100644 --- a/terraform/devnet-0/nodes.tf +++ b/terraform/devnet-0/nodes.tf @@ -1,672 +1,34 @@ -# Bootnode -variable "bootnode" { - default = { - name = "bootnode" - count = 1 - validator_start = 0 - validator_end = 0 - } +######################################################################################## +# NODE DEFINITIONS +# +# Define your fleet as a list of node entries. Each entry supports: +# +# Required: +# - name : Node type (e.g., "lighthouse-geth-super", "bootnode") +# - count : Number of instances +# - cloud : "digitalocean" or "hetzner" +# +# Optional: +# - validator_start : First validator index (default: 0) +# - validator_end : Last validator index (default: 0) +# - size : Instance size override (provider-specific) +# - region : Region override (digitalocean) or location (hetzner) +# - supernode : Force supernode=true/false (auto-detected from name) +# +# Examples: +# { name = "bootnode", count = 1, cloud = "digitalocean" } +# { name = "lighthouse-geth-super", count = 2, cloud = "hetzner", validator_start = 0, validator_end = 200 } +# { name = "mev-relay", count = 1, cloud = "hetzner", size = "ccx53" } +# +######################################################################################## + +variable "nodes" { + description = "List of node definitions for the devnet" + default = [ + { name = "bootnode", count = 1, cloud = "digitalocean" }, + { name = "mev-relay", count = 1, cloud = "hetzner", size = "ccx53" }, + { name = "lighthouse-geth-super", count = 2, cloud = "digitalocean", validator_start = 0, validator_end = 200 }, + { name = "lighthouse-geth-super", count = 1, cloud = "hetzner", validator_start = 200, validator_end = 300 }, + { name = "prysm-nethermind-full", count = 1, cloud = "hetzner", validator_start = 300, validator_end = 400 }, + ] } - -# Supernodes -# Lighthouse -variable "lighthouse_geth_super" { - default = { - name = "lighthouse-geth-super" - count = 1 - validator_start = 0 - validator_end = 100 - } -} - -variable "lighthouse_besu_super" { - default = { - name = "lighthouse-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nethermind_super" { - default = { - name = "lighthouse-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_erigon_super" { - default = { - name = "lighthouse-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_reth_super" { - default = { - name = "lighthouse-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nimbusel_super" { - default = { - name = "lighthouse-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Prysm -variable "prysm_geth_super" { - default = { - name = "prysm-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_besu_super" { - default = { - name = "prysm-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nethermind_super" { - default = { - name = "prysm-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_erigon_super" { - default = { - name = "prysm-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_reth_super" { - default = { - name = "prysm-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nimbusel_super" { - default = { - name = "prysm-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Lodestar -variable "lodestar_geth_super" { - default = { - name = "lodestar-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nethermind_super" { - default = { - name = "lodestar-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_besu_super" { - default = { - name = "lodestar-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_erigon_super" { - default = { - name = "lodestar-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_reth_super" { - default = { - name = "lodestar-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nimbusel_super" { - default = { - name = "lodestar-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Nimbus -variable "nimbus_geth_super" { - default = { - name = "nimbus-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_besu_super" { - default = { - name = "nimbus-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nethermind_super" { - default = { - name = "nimbus-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_erigon_super" { - default = { - name = "nimbus-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_reth_super" { - default = { - name = "nimbus-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nimbusel_super" { - default = { - name = "nimbus-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Teku -variable "teku_geth_super" { - default = { - name = "teku-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_besu_super" { - default = { - name = "teku-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nethermind_super" { - default = { - name = "teku-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_erigon_super" { - default = { - name = "teku-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_reth_super" { - default = { - name = "teku-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nimbusel_super" { - default = { - name = "teku-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Grandine -variable "grandine_geth_super" { - default = { - name = "grandine-geth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_besu_super" { - default = { - name = "grandine-besu-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nethermind_super" { - default = { - name = "grandine-nethermind-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_erigon_super" { - default = { - name = "grandine-erigon-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_reth_super" { - default = { - name = "grandine-reth-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nimbusel_super" { - default = { - name = "grandine-nimbusel-super" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - - -# Fullnodes -# Lighthouse -variable "lighthouse_geth_full" { - default = { - name = "lighthouse-geth-full" - count = 1 - validator_start = 100 - validator_end = 200 - } -} - -variable "lighthouse_besu_full" { - default = { - name = "lighthouse-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nethermind_full" { - default = { - name = "lighthouse-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_erigon_full" { - default = { - name = "lighthouse-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_reth_full" { - default = { - name = "lighthouse-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lighthouse_nimbusel_full" { - default = { - name = "lighthouse-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Prysm -variable "prysm_geth_full" { - default = { - name = "prysm-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_besu_full" { - default = { - name = "prysm-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nethermind_full" { - default = { - name = "prysm-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_erigon_full" { - default = { - name = "prysm-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_reth_full" { - default = { - name = "prysm-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "prysm_nimbusel_full" { - default = { - name = "prysm-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Lodestar -variable "lodestar_geth_full" { - default = { - name = "lodestar-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nethermind_full" { - default = { - name = "lodestar-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_besu_full" { - default = { - name = "lodestar-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_erigon_full" { - default = { - name = "lodestar-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_reth_full" { - default = { - name = "lodestar-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "lodestar_nimbusel_full" { - default = { - name = "lodestar-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Nimbus -variable "nimbus_geth_full" { - default = { - name = "nimbus-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_besu_full" { - default = { - name = "nimbus-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nethermind_full" { - default = { - name = "nimbus-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_erigon_full" { - default = { - name = "nimbus-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_reth_full" { - default = { - name = "nimbus-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "nimbus_nimbusel_full" { - default = { - name = "nimbus-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Teku -variable "teku_geth_full" { - default = { - name = "teku-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_besu_full" { - default = { - name = "teku-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nethermind_full" { - default = { - name = "teku-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_erigon_full" { - default = { - name = "teku-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_reth_full" { - default = { - name = "teku-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "teku_nimbusel_full" { - default = { - name = "teku-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -# Grandine -variable "grandine_geth_full" { - default = { - name = "grandine-geth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_besu_full" { - default = { - name = "grandine-besu-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nethermind_full" { - default = { - name = "grandine-nethermind-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_erigon_full" { - default = { - name = "grandine-erigon-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_reth_full" { - default = { - name = "grandine-reth-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} - -variable "grandine_nimbusel_full" { - default = { - name = "grandine-nimbusel-full" - count = 0 - validator_start = 0 - validator_end = 0 - } -} \ No newline at end of file diff --git a/terraform/devnet-0/outputs.tf b/terraform/devnet-0/outputs.tf new file mode 100644 index 0000000..9f79587 --- /dev/null +++ b/terraform/devnet-0/outputs.tf @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// GENERATED FILES AND OUTPUTS +//////////////////////////////////////////////////////////////////////////////////////// + +resource "local_file" "ansible_inventory" { + content = templatefile("ansible_inventory.tmpl", + { + ethereum_network_name = "${var.ethereum_network}" + groups = merge( + { for group in local.digitalocean_vm_groups : "${group.group_name}" => true... }, + { for group in local.hetzner_vm_groups : "${group.group_name}" => true... }, + ) + hosts = merge( + { + for key, server in digitalocean_droplet.main : "do.${key}" => { + ip = "${server.ipv4_address}" + ipv6 = try(server.ipv6_address, "none") + group = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^group_name:", tag))][0], "unknown") + validator_start = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^val_start:", tag))][0], 0) + validator_end = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^val_end:", tag))][0], 0) + supernode = try(title([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^supernode:", tag))][0]), "undefined") + arch = try([for tag in tolist(server.tags) : split(":", tag)[1] if can(regex("^arch:", tag))][0], "amd64") + tags = "${server.tags}" + hostname = "${split(".", key)[0]}" + cloud = "digitalocean" + region = "${server.region}" + } + }, + { + for key, server in hcloud_server.main : "${key}" => { + ip = coalesce(server.ipv4_address, (try(server.ipv6_address, ""))) + ipv6 = coalesce(server.ipv6_address, "") + group = server.labels.group_name + validator_start = server.labels.val_start + validator_end = server.labels.val_end + supernode = server.labels.supernode + arch = server.labels.arch + tags = server.labels + hostname = split(".", key)[0] + cloud = "hetzner" + region = server.datacenter + } + } + ) + } + ) + filename = "../../ansible/inventories/devnet-0/inventory.ini" +} + +locals { + ssh_config_path = pathexpand("~/.ssh/config.d/ssh_config.${var.ethereum_network}") +} + +resource "local_file" "ssh_config" { + content = templatefile("${path.module}/ssh_config.tmpl", + { + ethereum_network = var.ethereum_network + hosts = merge( + { + for key, server in digitalocean_droplet.main : "${var.ethereum_network}-${key}" => { + hostname = server.ipv4_address + private_ip = server.ipv4_address_private + name = key + user = "devops" + } + }, + { + for key, server in hcloud_server.main : "${var.ethereum_network}-${key}" => { + hostname = coalesce(server.ipv4_address, (try(server.ipv6_address, ""))) + private_ip = try(hcloud_server_network.main[key].ip, "") + name = key + user = "devops" + } + } + ) + } + ) + filename = local.ssh_config_path + + depends_on = [digitalocean_droplet.main, hcloud_server.main] + + lifecycle { + create_before_destroy = true + } +} + +resource "null_resource" "ssh_config_cleanup" { + triggers = { + ssh_config_path = local.ssh_config_path + } + + provisioner "local-exec" { + when = destroy + command = "rm -f ${self.triggers.ssh_config_path} || true" + } + + depends_on = [local_file.ssh_config] +} + +output "ssh_config_file" { + value = "SSH config generated at: ${local.ssh_config_path}" + description = "Path to the generated SSH config file" +} + +output "digitalocean_server_count" { + value = length(digitalocean_droplet.main) + description = "Number of DigitalOcean servers created" +} + +output "hetzner_server_count" { + value = length(hcloud_server.main) + description = "Number of Hetzner servers created" +} + +output "total_server_count" { + value = length(digitalocean_droplet.main) + length(hcloud_server.main) + description = "Total number of servers created across all providers" +} From f6009bf54146edfeabd2d40ac5c18bc2f235edb7 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 11 Feb 2026 14:57:21 +0100 Subject: [PATCH 2/2] refactor(terraform): centralize validator range and supernode determination logic in locals This commit refactors the variable assignments within the `digitalocean.tf` and `hetzner.tf` files to calculate validator indices (`val_start`, `val_end`) and determine the `supernode` status based on a cleaner logic, derived from node counts and explicit flags. It extracts validator range calculation and the logic for determining if a node is a supernode (based on explicit flag, name containing 'bootnode'/'mev', or if the validator count for that node group is >= 128) into new local variables before resource creation. This improves readability and consistency in determining node characteristics across different cloud providers. It also updates `nodes.tf` to remove 'super' from 'lighthouse-geth-super' names and adjust validator ranges as discovered during the refactoring for better segmenting. The size determination in `hetzner.tf` is also refactored to use the new `supernode` local variable derived from the improved logic. Finally, it updates the tags/labels construction to use these new local variables instead of complex inline expressions. --- terraform/devnet-0/digitalocean.tf | 39 ++++++++++++++++-------- terraform/devnet-0/hetzner.tf | 49 ++++++++++++++++++++++-------- terraform/devnet-0/nodes.tf | 6 ++-- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/terraform/devnet-0/digitalocean.tf b/terraform/devnet-0/digitalocean.tf index bae5b65..f3a05f3 100644 --- a/terraform/devnet-0/digitalocean.tf +++ b/terraform/devnet-0/digitalocean.tf @@ -57,18 +57,24 @@ locals { id = "${node.name}-${node.start_index + i + 1}" vms = { "${i + 1}" = { - tags = join(",", compact([ - "group_name:${node.name}", - "val_start:${node.validator_start + (i * (node.validator_end - node.validator_start) / node.count)}", - "val_end:${min(node.validator_start + ((i + 1) * (node.validator_end - node.validator_start) / node.count), node.validator_end)}", - "supernode:${node.supernode != null ? (node.supernode ? "True" : "False") : (can(regex("(super|bootnode|mev)", node.name)) ? "True" : "False")}", - "arch:amd64", - can(regex("bootnode", node.name)) ? "bootnode:${var.ethereum_network}" : null, - can(regex("mev-relay", node.name)) ? "mev-relay:${var.ethereum_network}" : null - ])) + # Validator range for this instance + val_start = node.validator_start + (i * (node.validator_end - node.validator_start) / node.count) + val_end = min( + node.validator_start + ((i + 1) * (node.validator_end - node.validator_start) / node.count), + node.validator_end + ) + validator_count = node.count > 0 ? (node.validator_end - node.validator_start) / node.count : 0 + + # Supernode: explicit > bootnode/mev > validator_count >= 128 + supernode = ( + node.supernode != null ? node.supernode : + can(regex("(bootnode|mev)", node.name)) ? true : + (node.count > 0 ? (node.validator_end - node.validator_start) / node.count >= 128 : false) + ) + region = node.region != null ? node.region : var.digitalocean_regions[i % length(var.digitalocean_regions)] - size = node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.digitalocean_supernode_size : var.digitalocean_fullnode_size) ipv6 = node.ipv6 + arch = "amd64" } } } @@ -96,14 +102,23 @@ locals { ssh_keys = [data.digitalocean_ssh_key.main.fingerprint] region = vm.region image = local.digitalocean_default_image - size = vm.size + size = vm.supernode ? var.digitalocean_supernode_size : var.digitalocean_fullnode_size resize_disk = true monitoring = true backups = false ipv6 = vm.ipv6 vpc_uuid = digitalocean_vpc.main[vm.region].id - tags = concat(local.digitalocean_global_tags, split(",", vm.tags)) + tags = concat(local.digitalocean_global_tags, [ + "group_name:${group.group_name}", + "val_start:${vm.val_start}", + "val_end:${vm.val_end}", + "supernode:${vm.supernode ? "True" : "False"}", + "arch:${vm.arch}", + ], compact([ + can(regex("bootnode", group.group_name)) ? "bootnode:${var.ethereum_network}" : null, + can(regex("mev-relay", group.group_name)) ? "mev-relay:${var.ethereum_network}" : null + ])) } ] ]) diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf index 3a1058f..0fec9d7 100644 --- a/terraform/devnet-0/hetzner.tf +++ b/terraform/devnet-0/hetzner.tf @@ -52,18 +52,31 @@ locals { id = "${node.name}-${node.start_index + i + 1}" vms = { "${i + 1}" = { - arch = can(regex("^cax", node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size))) ? "arm64" : "amd64" - labels = join(",", compact([ - "group_name:${node.name}", - "val_start:${node.validator_start + (i * (node.validator_end - node.validator_start) / node.count)}", - "val_end:${min(node.validator_start + ((i + 1) * (node.validator_end - node.validator_start) / node.count), node.validator_end)}", - "supernode:${node.supernode != null ? (node.supernode ? "True" : "False") : (can(regex("(super|bootnode|mev)", node.name)) ? "True" : "False")}", - "arch:${can(regex("^cax", node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size))) ? "arm64" : "amd64"}", - can(regex("bootnode", node.name)) ? "bootnode:${var.ethereum_network}" : null, - can(regex("mev-relay", node.name)) ? "mev:${var.ethereum_network}" : null - ])) + # Validator range for this instance + val_start = node.validator_start + (i * (node.validator_end - node.validator_start) / node.count) + val_end = min( + node.validator_start + ((i + 1) * (node.validator_end - node.validator_start) / node.count), + node.validator_end + ) + validator_count = node.count > 0 ? (node.validator_end - node.validator_start) / node.count : 0 + + # Supernode: explicit > bootnode/mev > validator_count >= 128 + supernode = ( + node.supernode != null ? node.supernode : + can(regex("(bootnode|mev)", node.name)) ? true : + (node.count > 0 ? (node.validator_end - node.validator_start) / node.count >= 128 : false) + ) + + # Size: explicit > supernode-based default + size = ( + node.size != null ? node.size : + (node.supernode != null ? node.supernode : + can(regex("(bootnode|mev)", node.name)) ? true : + (node.count > 0 ? (node.validator_end - node.validator_start) / node.count >= 128 : false) + ) ? var.hetzner_supernode_size : var.hetzner_fullnode_size + ) + location = node.location != null ? node.location : var.hetzner_regions[i % length(var.hetzner_regions)] - size = node.size != null ? node.size : (can(regex("(super|bootnode)", node.name)) ? var.hetzner_supernode_size : var.hetzner_fullnode_size) ipv4_enabled = node.ipv4_enabled ipv6_enabled = node.ipv6_enabled } @@ -98,7 +111,19 @@ locals { server_type = vm.size backups = false - labels = concat(local.hcloud_global_labels, split(",", vm.labels)) + # Architecture: cax* = ARM64, everything else = AMD64 + arch = can(regex("^cax", vm.size)) ? "arm64" : "amd64" + + labels = concat(local.hcloud_global_labels, [ + "group_name:${group.group_name}", + "val_start:${vm.val_start}", + "val_end:${vm.val_end}", + "supernode:${vm.supernode ? "True" : "False"}", + "arch:${can(regex("^cax", vm.size)) ? "arm64" : "amd64"}", + ], compact([ + can(regex("bootnode", group.group_name)) ? "bootnode:${var.ethereum_network}" : null, + can(regex("mev-relay", group.group_name)) ? "mev:${var.ethereum_network}" : null + ])) } ] ]) diff --git a/terraform/devnet-0/nodes.tf b/terraform/devnet-0/nodes.tf index 1b443b5..123b75b 100644 --- a/terraform/devnet-0/nodes.tf +++ b/terraform/devnet-0/nodes.tf @@ -27,8 +27,8 @@ variable "nodes" { default = [ { name = "bootnode", count = 1, cloud = "digitalocean" }, { name = "mev-relay", count = 1, cloud = "hetzner", size = "ccx53" }, - { name = "lighthouse-geth-super", count = 2, cloud = "digitalocean", validator_start = 0, validator_end = 200 }, - { name = "lighthouse-geth-super", count = 1, cloud = "hetzner", validator_start = 200, validator_end = 300 }, - { name = "prysm-nethermind-full", count = 1, cloud = "hetzner", validator_start = 300, validator_end = 400 }, + { name = "lighthouse-geth", count = 2, cloud = "digitalocean", validator_start = 0, validator_end = 400 }, + { name = "lighthouse-geth", count = 1, cloud = "hetzner", validator_start = 400, validator_end = 500 }, + { name = "prysm-nethermind", count = 1, cloud = "hetzner", validator_start = 500, validator_end = 550 }, ] }