eleven / ocho Public

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>
Paul Campbell 3190e899346883d297bafb7fef8c799920cad7ce parent c3bb167
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
+}