From 86efc437f4cc1fb277950f8e7a1448dfa5388f17 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Tue, 9 Jun 2026 07:35:07 -0400 Subject: [PATCH] Manage openletter.pub and openletter.vote DNS with Terraform Linode DNS for both domains, organized by domain (openletter.pub/domain.tf and openletter.vote/domain.tf) with a small root module so a single terraform plan from the repo root covers both. State lives in a Linode Object Storage bucket. openletter.pub carries the _atproto handle attestation and the _lexicon resolution record, both pointing at the dedicated openletter.pub account (did:plc:vrxdl7yd3vz2hzdosqaywvb3). Secrets come from a gitignored .envrc.private; see .envrc.private.example. Co-Authored-By: Claude Opus 4.8 (1M context) --- .envrc | 1 + .envrc.private.example | 4 ++++ .gitignore | 10 ++++++++++ .terraform.lock.hcl | 23 +++++++++++++++++++++++ main.tf | 33 +++++++++++++++++++++++++++++++++ openletter.pub/domain.tf | 37 +++++++++++++++++++++++++++++++++++++ openletter.vote/domain.tf | 17 +++++++++++++++++ 7 files changed, 125 insertions(+) create mode 100644 .envrc create mode 100644 .envrc.private.example create mode 100644 .terraform.lock.hcl create mode 100644 main.tf create mode 100644 openletter.pub/domain.tf create mode 100644 openletter.vote/domain.tf diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4fa4a24 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +source_env_if_exists .envrc.private diff --git a/.envrc.private.example b/.envrc.private.example new file mode 100644 index 0000000..62fcf4b --- /dev/null +++ b/.envrc.private.example @@ -0,0 +1,4 @@ +# Copy to .envrc.private (gitignored) and fill in the values. +export LINODE_TOKEN="" # Linode API token (DNS provider) +export AWS_ACCESS_KEY_ID="" # Linode Object Storage key (Terraform state) +export AWS_SECRET_ACCESS_KEY="" # Linode Object Storage secret (Terraform state) diff --git a/.gitignore b/.gitignore index 797fec0..9e356f2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,13 @@ Thumbs.db .idea/ .vscode/ *~ + +# direnv +.envrc.private + +# Terraform +.terraform/ +*.tfstate +*.tfstate.* +crash.log +*.tfvars diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..896f06e --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,23 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/linode/linode" { + version = "3.14.1" + constraints = "~> 3.0" + hashes = [ + "h1:XNfCTg85vEWjIvcM2RL8q4/JEarrs3BWffo99PozYfE=", + "zh:300fe0d5791527485e7e1dfda3a81283ecdabef4b48e5ba29f29d6cd1936eab7", + "zh:3d7c36946b4be99bf4c2c341f7124118803af3d09b5e36fc3f2ba450b0a4bfab", + "zh:425768f3b409d868db05e79100c8032d20db7d7aa316ace20d76d5996e2a9f36", + "zh:4332ebbc4ebe871b735fa5117abb77dcfae68152f82571e279141027d0b5d0ab", + "zh:73fece9bce0100f136b7dd7286f578fadb97395d9fb25c0121e39d8801497db2", + "zh:763ac2cd1bba91210228ba98ba21abd74ab62877e38ec2793bbeb1f1d28327ad", + "zh:8dd2b605d762243343bec0d5c402d0940c6f84f0168c7a1c69dbda7a4096b64c", + "zh:918085d3ebcbd4bc173a65431a684fb10179c9279c3c8426b49a7fa2af410042", + "zh:9b362a78ab55724305a8d87c7e50311f63dd6267ebf5e4ca935fcdffb02833d5", + "zh:9f856cb32278b20bfd49c4c9c50bfabc83990f611fe32469708bef735069970a", + "zh:a0ed3d8552a2fc5d49d0daa0c1da23c354f4a37317532ff8219ded8ef248446e", + "zh:af984f79fe1379aa19fdbcfa0a5453cbb41de80b9d46d477bfafdbee8224db3e", + "zh:ff5e98cb470d36eacbac293dbcb7ae327e64d66a1d02d7453389d8fdaf5e7f03", + ] +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..5aed04c --- /dev/null +++ b/main.tf @@ -0,0 +1,33 @@ +terraform { + required_providers { + linode = { + source = "linode/linode" + version = "~> 3.0" + } + } + + backend "s3" { + bucket = "openletter-terraform-state" + key = "openletter.tfstate" + region = "us-east-1" + + endpoints = { + s3 = "https://us-east-1.linodeobjects.com" + } + + skip_credentials_validation = true + skip_region_validation = true + skip_requesting_account_id = true + skip_metadata_api_check = true + } +} + +provider "linode" {} + +module "openletter_pub" { + source = "./openletter.pub" +} + +module "openletter_vote" { + source = "./openletter.vote" +} diff --git a/openletter.pub/domain.tf b/openletter.pub/domain.tf new file mode 100644 index 0000000..35aea8a --- /dev/null +++ b/openletter.pub/domain.tf @@ -0,0 +1,37 @@ +terraform { + required_providers { + linode = { + source = "linode/linode" + } + } +} + +variable "openletter_did" { + type = string + description = "DID of the dedicated openletter.pub account (hosted on Bluesky). Used for both the handle attestation and lexicon resolution." + default = "did:plc:vrxdl7yd3vz2hzdosqaywvb3" +} + +resource "linode_domain" "openletter_pub" { + domain = "openletter.pub" + type = "master" + soa_email = "c@guid.foo" + ttl_sec = 300 +} + +# Handle attestation: lets the account claim openletter.pub as its handle. +resource "linode_domain_record" "atproto_handle" { + domain_id = linode_domain.openletter_pub.id + name = "_atproto" + record_type = "TXT" + target = "did=${var.openletter_did}" +} + +# Lexicon resolution: points the pub.openletter NSID authority at the repo that +# holds the published com.atproto.lexicon.schema records. +resource "linode_domain_record" "lexicon" { + domain_id = linode_domain.openletter_pub.id + name = "_lexicon" + record_type = "TXT" + target = "did=${var.openletter_did}" +} diff --git a/openletter.vote/domain.tf b/openletter.vote/domain.tf new file mode 100644 index 0000000..a7356a0 --- /dev/null +++ b/openletter.vote/domain.tf @@ -0,0 +1,17 @@ +terraform { + required_providers { + linode = { + source = "linode/linode" + } + } +} + +resource "linode_domain" "openletter_vote" { + domain = "openletter.vote" + type = "master" + soa_email = "c@guid.foo" + ttl_sec = 300 +} + +# Records for the openletter.vote web app will be added here once there is +# something to point them at. -- 2.51.2