infra #
AWS infrastructure for lance.blue, as Terraform. Separate from the code repos on purpose: it is the only place that knows account ids, region and DNS, so nothing else has to.
The AWS account is 179302349187. It contains only lance.blue. Everything
written here is applied.
| repo | what it is |
|---|---|
arena |
the match container — MegaMek plus Suramadu, one game per image. Publishes to the ECR repository this repo creates. |
headquarters |
control plane: launches matches, holds OAuth sessions against players' own PDSes, serves the AppView |
infra |
this repo |
helm |
a queryable database of MegaMek's unit library, indexed in SQLite |
lexicons |
ATProto lexicon definitions for lance.blue |
permadeath_games_lexicons |
ATProto lexicons for games.permadeath.*, an identity and social layer for games |
sds |
artificial intelligence for playing BattleTech matches via MegaMek |
How it is run #
By hand, from a laptop. There is no CI, no OIDC role and no credential in
the repo. Applies run as lance-blue-apply, a role created by envs/bootstrap/
and assumed through the lance-blue profile. Anything sensitive is passed as
-var on the command line.
export AWS_PROFILE=lance-blue AWS_REGION=us-east-1
tofu -chdir=envs/lance.blue plan -out=lance.blue.tfplan
tofu -chdir=envs/lance.blue apply lance.blue.tfplan
Applying the saved plan means what you read is what runs.
scripts/deploy.sh is those two commands plus one confirmation, and it posts
to lance.blue's Bluesky account when a release id moved — a public record of
every deploy, built from the outputs the apply produced:
scripts/deploy.sh
The post carries a link card for the site and the commits behind the ids that moved, read out of the sibling checkouts, as a thread when they do not fit in one post.
The app password it posts with comes from the login keyring, not from this repo and not from the AWS account — see the runbook.
See docs/runbook.md for first-time setup, the bootstrap step and what to do when something is wrong.
Pre-commit hooks #
Hooks are managed by prek and configured in
prek.toml. One-time setup:
uv tool install prek
prek install --prepare-hooks
They then run on every commit; prek run --all-files runs them by hand.
Formatting and validation live here rather than in scripts. Both come from
pre-commit-terraform:
terraform_fmt rewrites unformatted files, and terraform_validate
init-and-validates the roots holding the files that changed, without touching
AWS. A local tofu-validate-callers hook adds back the one thing per-root
validation misses — a module edit that breaks the env roots composing it.
Layout #
envs/lance.blue/ |
the AWS account. Composes modules; holds the backend config and all real values. |
modules/network/ |
VPC, public subnets, no NAT gateway |
modules/ecr/ |
image repositories, one per name in a list, with a lifecycle policy |
modules/artifacts/ |
S3 for scenarios, camo and match results |
modules/match-cluster/ |
ECS cluster and the arena task definition |
modules/static-site/ |
S3 + CloudFront for the headquarters frontend |
modules/dns/ |
the lance.blue hosted zone |
modules/api-host/ |
the EC2 host running headquarters-api behind Caddy |
envs/bootstrap/ |
one-shot: the state bucket and the apply role |
scripts/ |
deploy, logs, a shell on the api host, match operations, per-match cost, health checks |
docs/ |
architecture, runbook |
Modules take inputs and return outputs; they never read remote state, and the
one place a module calls aws itself is the static-site cache invalidation. Each directory under envs/ is one AWS account. Dev and
prod resources share the account and the root, told apart by name suffix;
some resources (the hosted zone, ECR) are shared between environments. A
second AWS account would be a directory copy.
Conventions #
- Names say what the thing is and end with the environment:
lance-blue-match-task-sg-prod,lance-blue-web-bucket-prod-<account>. Shared resources — the hosted zone, the ECR repositories — carry no environment. Only prod exists today. - Tags come from
default_tagson the provider:Project=lance.blue,ManagedBy=terraform,Repo=infra, andCommitwhen the sha is passed at plan time (-var git_commit=$(git rev-parse --short=12 HEAD); omitted, the tag is simply absent). The env root passesComponent— and, for per-environment resources,Environment— into each module'stags. - Regions: one, from
var.region. Anything that must live inus-east-1regardless (CloudFront-facing certificates) uses an aliased provider and says so. - No secrets in this repo. Not in variables, not in
.tfvars, not in state if it can be avoided. A secret is passed as-varorTF_VAR_*and never written to disk. - Variable files are committed, split by who edits them:
terraform.tfvarsis hand-edited constants,releases.auto.tfvars.jsonis what is deployed and is rewritten by a deploy.
Licensing #
See discussion in the headquarters repo.