infra: dev box for the activator proof — t4g.small, instance role, no keys (#638)
One Graviton instance (#550 Part 7's minimum shape; the ASG comes later) with object-rw on the instances bucket via instance profile — the credential-free path every later pool box follows. SSH restricted to the operator's IP; 80/443 open for when DNS points here. Not yet applied — plan saved, awaiting approval.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+131
−0
infra/dev/compute.tf+131 −0
diff --git a/infra/dev/compute.tf b/infra/dev/compute.tf| new file mode 100644 |
| index 0000000..4a250ee |
| --- /dev/null |
| +++ b/infra/dev/compute.tf |
| @@ -0,0 +1,131 @@ |
| +# 🤖 The dev box: one Graviton instance for the hibernation-activator proof |
| +# (#550 Part 7's "minimum viable" shape — the ASG comes later; one pet |
| +# instance is enough to prove wake/sleep on real AWS metal). It reads the |
| +# instances bucket via an INSTANCE ROLE — no AWS keys ever land on the box. |
| + |
| +data "aws_ami" "al2023_arm" { |
| + most_recent = true |
| + owners = ["amazon"] |
| + |
| + filter { |
| + name = "name" |
| + values = ["al2023-ami-2023*-arm64"] |
| + } |
| + |
| + filter { |
| + name = "architecture" |
| + values = ["arm64"] |
| + } |
| +} |
| + |
| +resource "aws_key_pair" "dev_box" { |
| + key_name = "eleven-dev-box" |
| + public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILReNBCz+nwYvJ7x/RfgW/kbvSFe3pzPhOBM1SOqbjSg eleven-dev-box" |
| +} |
| + |
| +variable "ssh_ingress_cidr" { |
| + description = "Where SSH is allowed from (the operator's current IP/32)" |
| + type = string |
| + default = "109.255.91.23/32" |
| +} |
| + |
| +resource "aws_security_group" "dev_box" { |
| + name = "eleven-dev-box" |
| + description = "dev activator box: ssh from the operator, web open" |
| + |
| + ingress { |
| + description = "ssh (operator only)" |
| + from_port = 22 |
| + to_port = 22 |
| + protocol = "tcp" |
| + cidr_blocks = [var.ssh_ingress_cidr] |
| + } |
| + |
| + ingress { |
| + description = "http/https (autocert + serving, for when DNS points here)" |
| + 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"] |
| + } |
| +} |
| + |
| +# The box's identity: object-rw on the instances bucket only, via instance |
| +# profile — the credential-free path every later box in the pool follows. |
| +resource "aws_iam_role" "dev_box" { |
| + name = "eleven-dev-box" |
| + |
| + assume_role_policy = jsonencode({ |
| + Version = "2012-10-17" |
| + Statement = [{ |
| + Action = "sts:AssumeRole" |
| + Effect = "Allow" |
| + Principal = { Service = "ec2.amazonaws.com" } |
| + }] |
| + }) |
| +} |
| + |
| +data "aws_iam_policy_document" "dev_box" { |
| + statement { |
| + sid = "ListInstancesBucket" |
| + actions = ["s3:ListBucket"] |
| + resources = [aws_s3_bucket.state["instances"].arn] |
| + } |
| + |
| + statement { |
| + sid = "ReadWriteInstanceObjects" |
| + actions = [ |
| + "s3:GetObject", |
| + "s3:PutObject", |
| + "s3:DeleteObject", |
| + ] |
| + resources = ["${aws_s3_bucket.state["instances"].arn}/*"] |
| + } |
| +} |
| + |
| +resource "aws_iam_role_policy" "dev_box" { |
| + name = "eleven-dev-box-instances-bucket" |
| + role = aws_iam_role.dev_box.id |
| + policy = data.aws_iam_policy_document.dev_box.json |
| +} |
| + |
| +resource "aws_iam_instance_profile" "dev_box" { |
| + name = "eleven-dev-box" |
| + role = aws_iam_role.dev_box.name |
| +} |
| + |
| +resource "aws_instance" "dev_box" { |
| + ami = data.aws_ami.al2023_arm.id |
| + instance_type = "t4g.small" |
| + key_name = aws_key_pair.dev_box.key_name |
| + vpc_security_group_ids = [aws_security_group.dev_box.id] |
| + iam_instance_profile = aws_iam_instance_profile.dev_box.name |
| + |
| + root_block_device { |
| + volume_size = 20 |
| + volume_type = "gp3" |
| + } |
| + |
| + tags = { |
| + Name = "eleven-dev-box" |
| + } |
| +} |
| + |
| +output "dev_box_ip" { |
| + value = aws_instance.dev_box.public_ip |
| +} |