# Open SWE base AMI — ARM64 (Graviton) Ubuntu 24.04 LTS. # # Builds the immutable base image for the single EC2 instance per env # (open-swe-dev / open-swe-prod) in seahaven-vpc. The image bakes the runtime # (uv + Python 3.12, nginx, awscli v2, CloudWatch agent) and the service-user / # systemd / nginx TEMPLATES. It bakes NO secrets and NO env-specific values — # those are materialized at first boot by user-data + deploy/seahaven/fetch-config.sh # (Secrets Manager + SSM -> root-only tmpfs .env, fail-fast). # # Build: packer init . && packer build open-swe-base.pkr.hcl # The resulting AMI id is pinned in infra/cdk.context.json (CDK cachedInContext:true); # see README.md "AMI -> cdk.context.json pinning contract". packer { required_version = ">= 1.11.0, < 2.0.0" required_plugins { amazon = { source = "github.com/hashicorp/amazon" version = "1.3.6" } } } variable "aws_region" { type = string default = "us-east-1" } variable "instance_type" { type = string default = "t4g.medium" # ARM64 (Graviton) build host; runtime instances are ~t4g.large } variable "ami_name_prefix" { type = string default = "open-swe-base-arm64" } # Versions baked into the image. Pin and bump deliberately. variable "python_version" { type = string default = "3.12" } variable "node_major" { type = string default = "24" } variable "uv_version" { type = string default = "0.11.24" } variable "cloudwatch_agent_deb_url" { type = string default = "https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/arm64/latest/amazon-cloudwatch-agent.deb" } variable "awscli_zip_url" { type = string default = "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" } locals { timestamp = formatdate("YYYYMMDD-hhmmss", timestamp()) } # Latest Canonical Ubuntu 24.04 (Noble) arm64 server image. source "amazon-ebs" "open-swe" { region = var.aws_region instance_type = var.instance_type ssh_username = "ubuntu" ami_name = "${var.ami_name_prefix}-${local.timestamp}" # ASCII only — AWS rejects non-ASCII in the AMI Description attribute. ami_description = "Open SWE base - Ubuntu 24.04 arm64 + uv/py3.12 + nginx + CW agent (templates only, no secrets)" source_ami_filter { filters = { name = "ubuntu/images/hvm-ssd*/ubuntu-noble-24.04-arm64-server-*" architecture = "arm64" root-device-type = "ebs" virtualization-type = "hvm" } owners = ["099720109477"] # Canonical most_recent = true } # IMDSv2 required on the build host. metadata_options { http_endpoint = "enabled" http_tokens = "required" http_put_response_hop_limit = 1 } # gp3 root, encrypted. Runtime root size is set by CDK; this is just the build host. launch_block_device_mappings { device_name = "/dev/sda1" volume_size = 20 volume_type = "gp3" encrypted = true delete_on_termination = true } tags = { Name = "open-swe-base-arm64" Purpose = "open-swe-runtime-base" ManagedBy = "packer" } } build { name = "open-swe-base" sources = ["source.amazon-ebs.open-swe"] # Stage the boot-time templates into the image. The destination dir must exist # BEFORE a trailing-slash (contents-only) file upload — packer's file provisioner # does not create it, and uploading the directory itself trips scp ("Is a # directory"). So mkdir first, then upload the contents into it. provisioner "shell" { inline = ["mkdir -p /tmp/open-swe-templates"] } provisioner "file" { source = "${path.root}/templates/" destination = "/tmp/open-swe-templates" } provisioner "shell" { environment_vars = [ "PYTHON_VERSION=${var.python_version}", "NODE_MAJOR=${var.node_major}", "UV_VERSION=${var.uv_version}", "CLOUDWATCH_AGENT_DEB_URL=${var.cloudwatch_agent_deb_url}", "AWSCLI_ZIP_URL=${var.awscli_zip_url}", ] # {{ .Vars }} MUST be included or the environment_vars above never reach the # script (provision.sh runs under `set -u` and fails on the first reference). execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E bash '{{ .Path }}'" script = "${path.root}/scripts/provision.sh" } }