| 1 | # π€ The Dublin (ie) node β a Graviton box that reads the dev-mesh bucket via an |
| 2 | # INSTANCE ROLE (no AWS keys on the box, the clean path). Ubuntu 26.04 arm64 to |
| 3 | # mirror prod's OS; arm64 pairs with the amd64 Helsinki node to prove the mesh |
| 4 | # works cross-arch (the release feed builds both every publish). |
| 5 | |
| 6 | data "aws_ami" "ubuntu_2604_arm" { |
| 7 | most_recent = true |
| 8 | owners = ["099720109477"] # Canonical |
| 9 | |
| 10 | filter { |
| 11 | name = "name" |
| 12 | values = ["ubuntu/images/hvm-ssd*/ubuntu-*-26.04-arm64-server-*"] |
| 13 | } |
| 14 | filter { |
| 15 | name = "architecture" |
| 16 | values = ["arm64"] |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | resource "aws_key_pair" "ie_node" { |
| 21 | key_name = "eleven-dev-mesh-ie" |
| 22 | public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILReNBCz+nwYvJ7x/RfgW/kbvSFe3pzPhOBM1SOqbjSg eleven-dev-box" |
| 23 | } |
| 24 | |
| 25 | # One /32 per operator. Add yourself via PR + `ops dev apply` (an in-place |
| 26 | # security-group update β no instance touch); removing an entry revokes that |
| 27 | # operator, so only ever prune with their say-so. Keep literal IPs out of the |
| 28 | # description text β doctor/ops extract every a.b.c.d/nn inside this block. |
| 29 | variable "ssh_ingress_cidr" { |
| 30 | description = "Operator IPs allowed to SSH (a /32 each)" |
| 31 | type = list(string) |
| 32 | 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 |
| 33 | } |
| 34 | |
| 35 | resource "aws_security_group" "ie_node" { |
| 36 | name = "eleven-dev-mesh-ie" |
| 37 | description = "dev-mesh Dublin node: ssh from the operator, web open" |
| 38 | |
| 39 | ingress { |
| 40 | description = "ssh (operators only)" |
| 41 | from_port = 22 |
| 42 | to_port = 22 |
| 43 | protocol = "tcp" |
| 44 | cidr_blocks = var.ssh_ingress_cidr |
| 45 | } |
| 46 | ingress { |
| 47 | description = "http (autocert HTTP-01 + serving)" |
| 48 | from_port = 80 |
| 49 | to_port = 80 |
| 50 | protocol = "tcp" |
| 51 | cidr_blocks = ["0.0.0.0/0"] |
| 52 | } |
| 53 | ingress { |
| 54 | description = "https" |
| 55 | from_port = 443 |
| 56 | to_port = 443 |
| 57 | protocol = "tcp" |
| 58 | cidr_blocks = ["0.0.0.0/0"] |
| 59 | } |
| 60 | egress { |
| 61 | from_port = 0 |
| 62 | to_port = 0 |
| 63 | protocol = "-1" |
| 64 | cidr_blocks = ["0.0.0.0/0"] |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | resource "aws_iam_role" "ie_node" { |
| 69 | name = "eleven-dev-mesh-ie" |
| 70 | assume_role_policy = jsonencode({ |
| 71 | Version = "2012-10-17" |
| 72 | Statement = [{ |
| 73 | Action = "sts:AssumeRole" |
| 74 | Effect = "Allow" |
| 75 | Principal = { Service = "ec2.amazonaws.com" } |
| 76 | }] |
| 77 | }) |
| 78 | } |
| 79 | |
| 80 | resource "aws_iam_role_policy" "ie_node" { |
| 81 | name = "eleven-dev-mesh-bucket" |
| 82 | role = aws_iam_role.ie_node.id |
| 83 | policy = data.aws_iam_policy_document.mesh_rw.json # shared with the fi user (storage.tf) |
| 84 | } |
| 85 | |
| 86 | resource "aws_iam_instance_profile" "ie_node" { |
| 87 | name = "eleven-dev-mesh-ie" |
| 88 | role = aws_iam_role.ie_node.name |
| 89 | } |
| 90 | |
| 91 | resource "aws_instance" "ie_node" { |
| 92 | ami = data.aws_ami.ubuntu_2604_arm.id |
| 93 | instance_type = "t4g.small" # 2 vCPU / 2 GB β the sizing floor for a node that runs a sidecar |
| 94 | key_name = aws_key_pair.ie_node.key_name |
| 95 | vpc_security_group_ids = [aws_security_group.ie_node.id] |
| 96 | iam_instance_profile = aws_iam_instance_profile.ie_node.name |
| 97 | |
| 98 | # IMDSv2 required: an SSRF on the box can't read the role credentials with a |
| 99 | # bare GET. Hop limit 1 (nothing runs in containers here). |
| 100 | metadata_options { |
| 101 | http_endpoint = "enabled" |
| 102 | http_tokens = "required" |
| 103 | } |
| 104 | |
| 105 | root_block_device { |
| 106 | volume_size = 20 |
| 107 | volume_type = "gp3" |
| 108 | encrypted = true |
| 109 | } |
| 110 | |
| 111 | # A newer Canonical AMI must never replace this box via a routine apply β |
| 112 | # that destroys /data (it did, 2026-07-23). Deliberate rebuilds still work: |
| 113 | # `ops dev rebuild` uses -replace, which overrides ignore_changes. |
| 114 | lifecycle { |
| 115 | ignore_changes = [ami] |
| 116 | } |
| 117 | |
| 118 | tags = { Name = "eleven-dev-mesh-ie" } |
| 119 | } |
| 120 | |
| 121 | # A stable address the DNS wildcard points at, so a stop/start (or a -replace of |
| 122 | # the box) doesn't move the record. |
| 123 | resource "aws_eip" "ie_node" { |
| 124 | instance = aws_instance.ie_node.id |
| 125 | domain = "vpc" |
| 126 | tags = { Name = "eleven-dev-mesh-ie" } |
| 127 | } |
| 128 | |