# 🤖 The Dublin (ie) node — a Graviton box that reads the dev-mesh bucket via an # INSTANCE ROLE (no AWS keys on the box, the clean path). Ubuntu 26.04 arm64 to # mirror prod's OS; arm64 pairs with the amd64 Helsinki node to prove the mesh # works cross-arch (the release feed builds both every publish). data "aws_ami" "ubuntu_2604_arm" { most_recent = true owners = ["099720109477"] # Canonical filter { name = "name" values = ["ubuntu/images/hvm-ssd*/ubuntu-*-26.04-arm64-server-*"] } filter { name = "architecture" values = ["arm64"] } } resource "aws_key_pair" "ie_node" { key_name = "eleven-dev-mesh-ie" public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILReNBCz+nwYvJ7x/RfgW/kbvSFe3pzPhOBM1SOqbjSg eleven-dev-box" } # One /32 per operator. Add yourself via PR + `ops dev apply` (an in-place # security-group update — no instance touch); removing an entry revokes that # operator, so only ever prune with their say-so. Keep literal IPs out of the # description text — doctor/ops extract every a.b.c.d/nn inside this block. variable "ssh_ingress_cidr" { description = "Operator IPs allowed to SSH (a /32 each)" type = list(string) default = ["136.206.228.80/32", "109.255.91.23/32", "51.155.205.89/32", "109.255.206.133/32"] # paul (two networks), james, docs-t1 } resource "aws_security_group" "ie_node" { name = "eleven-dev-mesh-ie" description = "dev-mesh Dublin node: ssh from the operator, web open" ingress { description = "ssh (operators only)" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = var.ssh_ingress_cidr } ingress { description = "http (autocert HTTP-01 + serving)" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "https" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_iam_role" "ie_node" { name = "eleven-dev-mesh-ie" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } }] }) } resource "aws_iam_role_policy" "ie_node" { name = "eleven-dev-mesh-bucket" role = aws_iam_role.ie_node.id policy = data.aws_iam_policy_document.mesh_rw.json # shared with the fi user (storage.tf) } resource "aws_iam_instance_profile" "ie_node" { name = "eleven-dev-mesh-ie" role = aws_iam_role.ie_node.name } resource "aws_instance" "ie_node" { ami = data.aws_ami.ubuntu_2604_arm.id instance_type = "t4g.small" # 2 vCPU / 2 GB — the sizing floor for a node that runs a sidecar key_name = aws_key_pair.ie_node.key_name vpc_security_group_ids = [aws_security_group.ie_node.id] iam_instance_profile = aws_iam_instance_profile.ie_node.name # IMDSv2 required: an SSRF on the box can't read the role credentials with a # bare GET. Hop limit 1 (nothing runs in containers here). metadata_options { http_endpoint = "enabled" http_tokens = "required" } root_block_device { volume_size = 20 volume_type = "gp3" encrypted = true } # A newer Canonical AMI must never replace this box via a routine apply — # that destroys /data (it did, 2026-07-23). Deliberate rebuilds still work: # `ops dev rebuild` uses -replace, which overrides ignore_changes. lifecycle { ignore_changes = [ami] } tags = { Name = "eleven-dev-mesh-ie" } } # A stable address the DNS wildcard points at, so a stop/start (or a -replace of # the box) doesn't move the record. resource "aws_eip" "ie_node" { instance = aws_instance.ie_node.id domain = "vpc" tags = { Name = "eleven-dev-mesh-ie" } }