Identities for entities did.bot
agent llm did
didbot plan aws-deploy.md
9.5 kB
Markdown
at main


id: aws-deploy title: A one-shot Terraform module an operator can consume without cloning this status: open crates: [] dependsOn: [deploy] exitCriterion: > An operator with an AWS account and nothing else pins a module version, supplies a zone name and a container image tag, applies it, and is handed the NS records to delegate — with no file from this repository on their disk. #

aws-deploy #

infra/pds/ lives inside this repository today: a checkout, a terraform apply, and knowledge of which files matter. The owner asked for a one-shot Terraform module, published as its own repository, that stands up the whole thing for an operator who has not cloned this source tree and should not need to.

This project already has the precedent for splitting a component out: vibescrobble-index, its query service and the canvas were extracted into their own repository (see ff72f82f, the build! commit that removed didbot-index, didbot-query and the canvas, and left index and canvas in plan/ naming no crates, "for now", until that repository is published). This epic is the same move applied to infra/pds/: extract it, version it, and leave a pointer here rather than the Terraform itself. Whatever conventions that split settled — how the new repository is named, how it is linked back from here — this epic should follow rather than invent a second way of doing the same thing.

The design problems, which matter more than the extraction itself #

The module and the binary must stay in step. infra/pds/templates/user_data.sh.tftpl writes a systemd unit whose ExecStart passes specific flags — --zone, --operator, --port, --data, --tls, --acme-environment, --route53-zone-id today, confirmed by reading that template. A module version and a server (container image) version are therefore coupled: a flag renamed, dropped, or given new meaning between two image versions is invisible to Terraform, and an operator who bumps the module without also picking a compatible image gets a server that will not start, or worse, one that starts and silently misreads a flag that used to mean something else. Today nothing states the compatible range in either direction.

Secrets and Terraform state. infra/pds/variables.tf's ssm_prefix comment already states the current discipline: secret values are written to SSM out of band, by a human or a separate process this configuration does not run, so terraform.tfstate never carries key material. A one-shot module that generates the attestation secret for the operator — which is the more "one-shot" reading of the request — breaks that discipline by construction: anything a Terraform resource creates is a value Terraform's state file holds, in plaintext, in whatever backend that state lives in (local disk by default, S3 with no encryption unless configured, a CI runner's workspace). State is not a secret store anyone chose for this purpose, and it is routinely committed, shared, or left in a CI cache by people who never meant it to hold credentials.

What it must output. Above everything else: the NS records that delegate the new zone at its parent. infra/pds/outputs.tf's name_servers output already says why in its own comment — Route53 assigns these when the hosted zone is created, and nothing under the new zone resolves until an NS record naming them exists at the parent. Under did.bot that record is infra/site/'s pds_zone_name_servers, so the delegation is an apply; under any other parent it is a visit to that zone's own provider. Either way the module has to hand the operator the four names: a one-shot module that applies cleanly and prints nothing actionable has shipped half a deployment.

What it must not assume. infra/pds/variables.tf today requires a vpc_id and an instance_subnet_id with no default, on purpose — this project has never assumed an existing VPC. A one-shot module aimed at an operator with "an AWS account and nothing else" raises the question of whether it should now provision a minimal VPC itself, and the answer this epic should state plainly either way rather than let default: it must not assume one exists, and it must not assume the operator wants Terraform's defaults for anything that affects blast radius — region, instance size, whether SSH access exists at all. Offering a minimal-VPC path as one option is reasonable; making it the only path is not.

Relation to fleet #

fleet is written for an operator running several servers, each "a whole personal data server in its own right — its own zone, its own accounts, its own keys, its own repositories." A module the operator instantiates once per deployment is the natural shape for that: terraform apply with a different root_zone and a different ssm_prefix, ten times, rather than a script that assumes it is the only deployment on the account. This epic does not need fleet to exist first, but the module's variables (anything that would collide across two instantiations in the same account — IAM role names, KMS alias names, the backup vault name, all of which infra/pds/'s current .tf files hardcode as didbot-pds* today) need to be parameterised with that in mind, or a second deployment in the same account will fail to apply rather than coexist.

Done #

Nothing closed yet.