From a4569abab202154f236f69937da5657b5c8c9052 Mon Sep 17 00:00:00 2001 From: "0xThresh.eth" <0xthresh@protonmail.com> Date: Mon, 23 Jun 2025 23:21:32 -0600 Subject: [PATCH 1/2] enh: add startup script cmds and updated compose file --- docker-compose.yml | 21 ++++++++++++++++++++ infra/bootstrap/userdata.sh | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 infra/bootstrap/userdata.sh diff --git a/docker-compose.yml b/docker-compose.yml index aa1b7cf..c6c250c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,5 +14,26 @@ services: extra_hosts: - "host.docker.internal:host-gateway" + ethereum: + image: ethereum/client-go:stable + ports: + - "8545:8545" + - "30303:30303" + command: --http --http.addr 0.0.0.0 --http.port 8545 --http.api eth,net,web3 --syncmode snap + + dd_rpc: + image: ghcr.io/developer-dao/rpc:latest + ports: + - "8080:80" + environment: + - DATABASE_URL=postgresql://ddrpcdev:ddrpc123@postgres:5432/ddrpc + - ETHEREUM_ENDPOINT=http://ethereum:8545 + - SMTP_USERNAME=fake@test.com + - SMTP_PASSWORD=TEST + - JWT_KEY=0cd8a9ca80c521ce59f4663bfc5379b7a4acc11c34d8852dfc9a71b6dba00985 + depends_on: + - postgres + - ethereum + volumes: postgres_data: diff --git a/infra/bootstrap/userdata.sh b/infra/bootstrap/userdata.sh new file mode 100644 index 0000000..9beaf91 --- /dev/null +++ b/infra/bootstrap/userdata.sh @@ -0,0 +1,39 @@ +# Runs on an Ubuntu 24.04 instance + +# Add Docker's official GPG key: +sudo apt-get update +sudo apt-get install ca-certificates curl build-essential -y +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc + +# Add the repository to Apt sources: +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt-get update + +# Install Docker components +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y + +# Install Rust - this approach is interactive and will lock user data +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env + +# Get Github PAT to download image from private repo +export CR_PAT="" +echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin + +# Pull RPC image +docker pull ghcr.io/developer-dao/rpc:latest + +# Clone repo +git clone git@github.com:Developer-DAO/rpc.git /opt/rpc +cd /opt/rpc + +# Set up project +#cargo build +#cargo install openssl sqlx-cli + +docker compose up -d From 1e5ca6bfc1b389b83e1e31f3ab8dc89cfe138100 Mon Sep 17 00:00:00 2001 From: "0xThresh.eth" <0xthresh@protonmail.com> Date: Mon, 30 Jun 2025 18:06:50 -0600 Subject: [PATCH 2/2] feat: initial Tofu code for IaC --- .gitignore | 17 ++ infra/opentofu/README.md | 13 ++ infra/opentofu/ecs/backend.tf | 22 +++ infra/opentofu/ecs/locals.tf | 8 + infra/opentofu/ecs/main.tf | 292 ++++++++++++++++++++++++++++++++ infra/opentofu/ecs/variables.tf | 5 + infra/opentofu/vpc/backend.tf | 22 +++ infra/opentofu/vpc/main.tf | 22 +++ infra/opentofu/vpc/outputs.tf | 24 +++ infra/opentofu/vpc/variables.tf | 5 + 10 files changed, 430 insertions(+) create mode 100644 infra/opentofu/README.md create mode 100644 infra/opentofu/ecs/backend.tf create mode 100644 infra/opentofu/ecs/locals.tf create mode 100644 infra/opentofu/ecs/main.tf create mode 100644 infra/opentofu/ecs/variables.tf create mode 100644 infra/opentofu/vpc/backend.tf create mode 100644 infra/opentofu/vpc/main.tf create mode 100644 infra/opentofu/vpc/outputs.tf create mode 100644 infra/opentofu/vpc/variables.tf diff --git a/.gitignore b/.gitignore index f9a3295..fead513 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,20 @@ /target .env ~/.config/nvim-data/undodir/ + +# Terraform state +*.tfstate +*.tfstate.* +crash.log +*.tfvars +*.tfvars.json +drift.log +.terraform/ +.terraform.lock.hcl + +# Local execution plan files +plan.out + +# Sensitive files +*.pem +*.key diff --git a/infra/opentofu/README.md b/infra/opentofu/README.md new file mode 100644 index 0000000..190364d --- /dev/null +++ b/infra/opentofu/README.md @@ -0,0 +1,13 @@ +# OpenTofu +This folder contains OpenTofu code that represents the AWS infrastructure as code (IaC). + +## Download OpenTofu + +## Basic Operations + +## Apply Order +In order to build these resources in a new AWS account, run applies in each folder following the order below: +1. `vpc` +2. `rds` +3. `ecs` +4. `github-runners` \ No newline at end of file diff --git a/infra/opentofu/ecs/backend.tf b/infra/opentofu/ecs/backend.tf new file mode 100644 index 0000000..c98f272 --- /dev/null +++ b/infra/opentofu/ecs/backend.tf @@ -0,0 +1,22 @@ +terraform { + # backend "s3" { # TODO: Migrate to S3 when AWS account and S3 bucket is set up + # bucket = "dd-rpc-terraform-state" + # key = "ecs/terraform.tfstate" + # region = var.region + # encrypt = true + # } + backend "local" {} + + required_version = ">= 1.0.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.region +} + diff --git a/infra/opentofu/ecs/locals.tf b/infra/opentofu/ecs/locals.tf new file mode 100644 index 0000000..7b0760a --- /dev/null +++ b/infra/opentofu/ecs/locals.tf @@ -0,0 +1,8 @@ +locals { + name = "rpc" + tags = { + TofuManaged = "true" + TofuState = "ecs" + Environment = "dev" + } +} \ No newline at end of file diff --git a/infra/opentofu/ecs/main.tf b/infra/opentofu/ecs/main.tf new file mode 100644 index 0000000..975d710 --- /dev/null +++ b/infra/opentofu/ecs/main.tf @@ -0,0 +1,292 @@ +# Reference VPC outputs from the vpc folder's state +# TODO: Migrate to S3 when AWS account and S3 bucket is set up +data "terraform_remote_state" "vpc" { + backend = "local" + config = { + path = "../vpc/terraform.tfstate" + } +} + +data "aws_ami" "ecs_ami" { + most_recent = true + owners = ["amazon"] + filter { + name = "name" + values = ["amzn2-ami-ecs-hvm-*-x86_64-ebs"] + } +} + +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + cluster_name = "rpc-ecs-cluster" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + # Capacity provider - autoscaling groups + default_capacity_provider_use_fargate = false + autoscaling_capacity_providers = { + # On-demand instances + rpc_ec2 = { + auto_scaling_group_arn = module.autoscaling["rpc_ec2"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + } + + services = { + dd-rpc = { + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + grove-path = { + cpu = 512 + memory = 1024 + essential = true + image = "ghcr.io/buildwithgrove/path:main" + memory_reservation = 50 + port_mappings = [ + { + name = "grove-path" + containerPort = 3069 + protocol = "tcp" + } + ] + health_check = { + command = ["CMD-SHELL", "curl -s http://localhost:3069/healthz || exit 1"] + interval = 30 + timeout = 5 + retries = 3 + start_period = 10 + } + } + + rpc = { + cpu = 512 + memory = 1024 + essential = true + image = "ghcr.io/developer-dao/rpc:latest" + port_mappings = [ + { + name = "dd-rpc" + containerPort = 3000 + protocol = "tcp" + } + ] + + dependencies = [{ + containerName = "grove-path" + condition = "HEALTHY" + }] + memory_reservation = 100 + } + } + + # load_balancer = { + # service = { + # target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + # container_name = "ecs-sample" + # container_port = 80 + # } + # } + + subnet_ids = data.terraform_remote_state.vpc.outputs.private_subnets + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 3000 + to_port = 3000 + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + } + } + + tags = local.tags +} + +# Supporting resources +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 9.0" + + name = "${local.name}-alb" + + load_balancer_type = "application" + + vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id + subnets = data.terraform_remote_state.vpc.outputs.public_subnets + + # For example only - remove on prod + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = data.terraform_remote_state.vpc.outputs.vpc_cidr_block + } + } + + listeners = { + rpc_http = { + port = 3000 + protocol = "HTTP" + + forward = { + target_group_key = "rpc" + } + } + } + + target_groups = { + rpc = { + backend_protocol = "HTTP" + backend_port = 3000 + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } + + tags = local.tags +} + +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + for_each = { + # On-demand instances + rpc_ec2 = { + instance_type = "t3.large" + use_mixed_instances_policy = false + mixed_instances_policy = {} + user_data = <<-EOT + #!/bin/bash + + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + EOF + EOT + } + } + + name = "${local.name}-${each.key}" + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = each.value.instance_type + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(each.value.user_data) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + vpc_zone_identifier = data.terraform_remote_state.vpc.outputs.private_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 5 + desired_capacity = 2 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + # Required for managed_termination_protection = "ENABLED" + protect_from_scale_in = true + + # Spot instances + use_mixed_instances_policy = each.value.use_mixed_instances_policy + mixed_instances_policy = each.value.mixed_instances_policy + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id + + computed_ingress_with_source_security_group_id = [ + { + rule = "http-80-tcp" + source_security_group_id = module.alb.security_group_id + } + ] + number_of_computed_ingress_with_source_security_group_id = 1 + + egress_rules = ["all-all"] + + tags = local.tags +} \ No newline at end of file diff --git a/infra/opentofu/ecs/variables.tf b/infra/opentofu/ecs/variables.tf new file mode 100644 index 0000000..deece27 --- /dev/null +++ b/infra/opentofu/ecs/variables.tf @@ -0,0 +1,5 @@ +variable "region" { + description = "The AWS region to deploy the VPC in." + default = "us-east-2" + type = string +} \ No newline at end of file diff --git a/infra/opentofu/vpc/backend.tf b/infra/opentofu/vpc/backend.tf new file mode 100644 index 0000000..ab90e58 --- /dev/null +++ b/infra/opentofu/vpc/backend.tf @@ -0,0 +1,22 @@ +terraform { + # backend "s3" { # TODO: Migrate to S3 when AWS account and S3 bucket is set up + # bucket = "dd-rpc-terraform-state" + # key = "vpc/terraform.tfstate" + # region = var.region + # encrypt = true + # } + backend "local" {} + + required_version = ">= 1.0.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.region +} + diff --git a/infra/opentofu/vpc/main.tf b/infra/opentofu/vpc/main.tf new file mode 100644 index 0000000..a4547b2 --- /dev/null +++ b/infra/opentofu/vpc/main.tf @@ -0,0 +1,22 @@ +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "5.1.0" + + name = "rpc-vpc" + cidr = "10.0.0.0/16" + + azs = ["${var.region}a", "${var.region}b", "${var.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + TofuManaged = "true" + TofuState = "vpc" + Environment = "dev" + } +} \ No newline at end of file diff --git a/infra/opentofu/vpc/outputs.tf b/infra/opentofu/vpc/outputs.tf new file mode 100644 index 0000000..fc7abe9 --- /dev/null +++ b/infra/opentofu/vpc/outputs.tf @@ -0,0 +1,24 @@ +output "vpc_id" { + description = "The ID of the VPC" + value = module.vpc.vpc_id +} + +output "vpc_cidr_block" { + description = "The CIDR block of the VPC" + value = module.vpc.vpc_cidr_block +} + +output "private_subnets" { + description = "List of private subnet IDs" + value = module.vpc.private_subnets +} + +output "public_subnets" { + description = "List of public subnet IDs" + value = module.vpc.public_subnets +} + +output "nat_gateway_ids" { + description = "List of NAT Gateway IDs" + value = module.vpc.natgw_ids +} diff --git a/infra/opentofu/vpc/variables.tf b/infra/opentofu/vpc/variables.tf new file mode 100644 index 0000000..deece27 --- /dev/null +++ b/infra/opentofu/vpc/variables.tf @@ -0,0 +1,5 @@ +variable "region" { + description = "The AWS region to deploy the VPC in." + default = "us-east-2" + type = string +} \ No newline at end of file