Terraform
Terraform is used to manage the network and hardware settings.
Directoryterraform/ network/hardware
- backend.tf configures the state file
- bunny.tf configures the CDN for media files
- dns.tf configures domain stuff
- providers.tf defines the providers I’m using
- vars.tf sets what to pass in when running Terraform
- web.tf configures my web server
locals { default_ttl = 1 # auto default_proxied = false}
data "cloudflare_zones" "zones" { name = local.domain}
resource "cloudflare_dns_record" "record" { for_each = local.records
zone_id = data.cloudflare_zones.zones.result[0].id
name = each.value.name type = each.value.type content = each.value.content
ttl = try(each.value.ttl, local.default_ttl) proxied = try(each.value.proxied, local.default_proxied)
priority = try(each.value.priority, null)}Here Terraform uses Cloudflare to set all the records for my domain.
resource "mythicbeasts_pi" "web" { identifier = var.pi_identifier disk_size = 50 model = 4 memory = 4096}
resource "mythicbeasts_proxy_endpoint" "endpoint" { for_each = local.web_subdomains
domain = local.domain hostname = each.value address = mythicbeasts_pi.web.ip site = "all" proxy_protocol = true}
resource "ansible_host" "web" { name = "pi" groups = ["web"] variables = { ansible_host = "ssh.${mythicbeasts_pi.web.identifier}.hostedpi.com" ansible_port = mythicbeasts_pi.web.ssh_port ansible_user = "ops" }}Here Terraform requests the Pi and configures the Proxy endpoints using its IP address.
It also creates an ansible_host for the Pi so that Ansible can target it to configure it.
locals { media_hostname = "media.${local.domain}" media_storage_region = "DE" media_s3_region = lower(local.media_storage_region)}
resource "bunnynet_storage_zone" "media" { name = "paultibbetts-uk-media-storage" region = local.media_storage_region zone_tier = "Standard" type = "S3"}
resource "bunnynet_pullzone" "media" { name = "paultibbetts-uk-media-cdn"
origin { type = "StorageZone" storagezone = bunnynet_storage_zone.media.id }
routing { tier = "Standard" zones = ["EU", "US"] }
cache_enabled = true cache_chunked = true block_no_referer = false block_post_requests = true strip_cookies = true}
resource "bunnynet_pullzone_hostname" "media" { pullzone = bunnynet_pullzone.media.id name = local.media_hostname tls_enabled = true force_ssl = true
depends_on = [cloudflare_dns_record.record["media"]]}
output "media_store" { value = { bucket = bunnynet_storage_zone.media.name access_key = bunnynet_storage_zone.media.name endpoint = "https://${local.media_s3_region}-s3.storage.bunnycdn.com" public_base_url = "https://${local.media_hostname}" region = local.media_s3_region }}
output "media_store_secret_key" { value = bunnynet_storage_zone.media.password sensitive = true}Terraform uses this file to configure the “Storage Zone” (bucket) to store media files in and the “Pull Zone” (CDN) to serve them.
State file
Section titled “State file”The state file, that records the current state of the infrastructure that Terraform manages, is hosted on MinIO running on my NAS in my homelab.
This is configured using:
terraform { backend "s3" { bucket = "tfstate" key = "paultibbetts.uk.tfstate" region = "main" skip_credentials_validation = true skip_requesting_account_id = true skip_metadata_api_check = true skip_region_validation = true use_path_style = true }}Variables
Section titled “Variables”The variables that are my setup go in terraform/locals.tf:
locals { domain = "paultibbetts.uk"
web_subdomains = { apex = "@" www = "www" infra = "infra" dev = "dev" }
proxy_records = { for key, host in local.web_subdomains : key => { type = "CNAME" name = host content = "proxy.mythic-beasts.com" } }
records = merge( local.proxy_records, { micro = { type = "CNAME" name = "micro" content = "eu.micro.blog" }
media = { type = "CNAME" name = "media" content = bunnynet_pullzone.media.cdn_domain }
microsoft_webmaster_tools = { type = "CNAME" name = "7a1aef5690ea3c9c1ba23cb97b83da6d" content = "verify.bing.com" }
google_search_console = { type = "TXT" name = "@" content = "google-site-verification=KdhaJN7FNL4MnESeK7HbdYbnv2M6LY602PnIMWQ6BQw" }
atproto = { type = "TXT" name = "_atproto" content = "did=did:plc:adwdgyaga5q2psrjbvo4pndr" }
github_pages_challenge = { type = "TXT" name = "_github-pages-challenge-paultibbetts" content = "0d468c5988c95ab09a88f0c494dcb9" }
mythic_beasts_challenge = { type = "TXT" name = "_mythic-beasts-challenge" content = "T7eS19QKZV6VOo0og5sn2OmKDoem4dLL7IgT9tJLT7c" }
openai_domain_verification = { type = "TXT" name = "@" content = "openai-domain-verification=dv-CPJLyYLfDZ990DDlefMkxdkN" }
# Apple iCloud email # https://support.apple.com/en-gb/102374
apple_domain_verificaton = { type = "TXT" name = "@" content = "apple-domain=JTK5xOaQo9cwujIv" }
spf = { type = "TXT" name = "@" content = "v=spf1 include:icloud.com ~all" }
mx_icloud_01 = { type = "MX" name = "@" content = "mx01.mail.icloud.com" priority = 10 }
mx_icloud_02 = { type = "MX" name = "@" content = "mx02.mail.icloud.com" priority = 10 }
dkim_sig1 = { type = "CNAME" name = "sig1._domainkey" content = "sig1.dkim.paultibbetts.uk.at.icloudmailadmin.com" }
dmarc = { type = "TXT" name = "_dmarc" content = "v=DMARC1; p=none" }
} )}Secrets
Section titled “Secrets”Terraform needs a few secrets passing to it to work.
Because it manages the domain using Cloudflare it needs a Cloudflare API token passing to it.
MinIO, which is where the state file is hosted, also needs to be told the key to use.
Media files are served through the Bunny.net CDN, and that needs an API token to configure.
These secrets are exported as environment variables and read by Terraform:
source secrets.shwhere secrets.sh:
export TF_VAR_cloudflare_api_token=$(pass cloudflare_api_token_paultibbettsuk)export TF_VAR_bunnynet_api_key=$(pass bunnynet_api_key_paultibbettsuk)
export AWS_ENDPOINT_URL_S3=$(pass minio_endpoint)export AWS_ACCESS_KEY_ID=$(pass minio_access_key)export AWS_SECRET_ACCESS_KEY=$(pass minio_secret_key)
export MYTHICBEASTS_KEYID=$(pass mythicbeasts_keyid)export MYTHICBEASTS_SECRET=$(pass mythicbeasts_secret)The script grabs the secrets from a pass vault before exporting them for future commands to use.