Infrastructure-as-code for managing DNS records across all hyperpolymath domains.
- ✅ Manage unlimited domains from single CSV/Excel file
- ✅ Consistent DNS structure across all domains
- ✅ Version controlled changes
- ✅ Preview before apply (see exactly what will change)
- ✅ Bulk updates (change all domains at once)
- ✅ Domain-specific customization (keys, tunnel IDs, etc.)
- ✅ Optional Web3/IPFS gateway hostnames for direct
https://ipfs.<domain>/access - ✅ Optional edge consent/capability prefilters when the origin already enforces the canonical policy
# macOS
brew install terraform
# Linux
wget https://releases.hashicorp.com/terraform/1.7.0/terraform_1.7.0_linux_amd64.zip
unzip terraform_1.7.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/# Copy example file
cp terraform.tfvars.example terraform.tfvars
# Edit with your API token
nano terraform.tfvarsterraform.tfvars:
cloudflare_api_token = "your-api-token-here"
cloudflare_account_id = "your-account-id-here"Edit domains.csv (open in Excel or any spreadsheet):
| Column | Description | Example |
|---|---|---|
domain |
Domain name | wokelang.org |
github_user |
GitHub username | hyperpolymath |
github_repo |
GitHub repo name | wokelang |
tunnel_id |
Cloudflare Tunnel ID | abc123-def456 |
mx_primary |
Primary mail server | mail.wokelang.org |
mx_secondary |
Secondary mail server | backup.mail.wokelang.org |
admin_email |
Admin email | j.d.a.jewell@open.ac.uk |
ssh_fp_sha256 |
SSH fingerprint (SHA256) | sha256:ABC123... |
ssh_fp_sha256_backup |
Backup SSH fingerprint | sha256:DEF456... |
dkim_selector |
DKIM selector | default |
tlsa_cert_hash |
TLSA certificate hash | d2abde240d7c... |
enable_mail |
Enable MX records | true/false |
enable_tunnel |
Enable Cloudflare Tunnel | true/false |
enable_ssh |
Enable SSHFP records | true/false |
enable_github_pages |
Enable GitHub Pages CNAME | true/false |
enable_ipfs_gateway |
Enable Cloudflare Web3 IPFS hostname | true/false |
ipfs_dnslink |
Initial DNSLink for Web3 hostname | /ipns/onboarding.ipfs.cloudflare.com |
pages_project |
Cloudflare Pages project name | wokelang |
# Initialize Terraform
terraform init
# Preview changes (DRY RUN)
terraform plan
# Apply changes
terraform applywww→ CNAME to root (proxied)static→ CNAME to root (proxied)assets→ CNAME to root (proxied)cdn→ CNAME to root (proxied)discourse→ CNAME to root (for forums)zulip→ CNAME to root (for chat)chat→ CNAME to root (service hostname used by the NUJ site repos)conference→ CNAME to rootmembers→ CNAME to root (members area)stfp→ CNAME to root (secure file transfer)office→ CNAME to root (office collaboration)ci→ CNAME to root (CI/CD status)status→ CNAME to root (status page)logs→ CNAME to root (logs)api→ CNAME to root (API gateway)auth→ CNAME to root (auth service)wasm→ CNAME to root (WASM proxy)linkedin→ CNAME to rootrss→ CNAME to root- SPF TXT record
- DMARC TXT record
- CAA records (Let's Encrypt, DigiCert, iodef)
- GitHub Pages:
gh-pagesCNAME (ifenable_github_pages=true) - Cloudflare Pages: Custom domain setup (if
pages_projectset) - Web3/IPFS:
ipfs.<domain>hostname managed by Cloudflare Web3 (ifenable_ipfs_gateway=true; requires a Web3 gateway subscription) - Mail: MX, MTA-STS, TLS-RPT (if
enable_mail=true) - SSH: SSHFP records (if
enable_ssh=true) - Tunnel:
*.internalCNAMEs (ifenable_tunnel=true)
# On your server
ssh-keygen -r yourdomain.org | grep "SSHFP 1 2"
# Output: yourdomain.org IN SSHFP 1 2 <fingerprint>
# Extract just the fingerprint
ssh-keygen -r yourdomain.org | grep "SSHFP 1 2" | awk '{print $6}'# List tunnels
cloudflared tunnel list
# Or via API
curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/YOUR_ACCOUNT_ID/cfd_tunnel" \
-H "Authorization: Bearer YOUR_API_TOKEN"# Get certificate hash for SMTP
openssl s_client -connect mail.yourdomain.org:25 -starttls smtp </dev/null 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform DER | \
openssl dgst -sha256 -binary | \
xxd -p -u -c 64Just add a new row to domains.csv and run:
terraform applyTerraform will only create records for the new domain, leaving existing ones untouched!
For IPFS hostnames, Terraform creates the Web3 hostname object and bootstraps its
initial dnslink value. The website publish scripts then update that dnslink
after each publish, so the Terraform resource intentionally ignores later
dnslink drift.
Edit the CSV, then:
terraform plan # Preview changes
terraform apply # Apply changesDelete the row from CSV, then:
terraform applyTerraform will destroy all DNS records for that domain.
# Apply changes only to wokelang.org
terraform apply -target='cloudflare_record.www["wokelang.org"]'
# Destroy only one domain's records
terraform destroy -target='data.cloudflare_zones.all["example.com"]'- Open
domains.csvin Excel - Add/edit domains as spreadsheet rows
- File → Save As → CSV (Comma delimited) (*.csv)
- Run
terraform apply
Terraform tracks what it created in terraform.tfstate. Do NOT delete this file!
To version control safely:
git add domains.csv main.tf variables.tf
git add terraform.tfvars # WARNING: Contains API token!
git commit -m "Add new domain"Security Note: Add terraform.tfvars to .gitignore if storing API tokens!
Domain isn't added to Cloudflare yet. Add it at: https://dash.cloudflare.com
API token needs these permissions:
- Zone:DNS:Edit
- Account:Cloudflare Pages:Edit
- Zone:Read
- Web3 Hostnames Write (if using IPFS Web3 hostnames)
Manually delete conflicting record in Cloudflare dashboard, then re-run terraform apply.
main.tf- Terraform configuration (DNS resource definitions)variables.tf- Variable declarationsdomains.csv- Your data (edit this in Excel)terraform.tfvars- Credentials (API token)terraform.tfstate- Terraform state (auto-generated, don't edit)
domain,github_user,github_repo,tunnel_id,mx_primary,mx_secondary,admin_email,ssh_fp_sha256,ssh_fp_sha256_backup,dkim_selector,tlsa_cert_hash,enable_mail,enable_tunnel,enable_ssh,enable_github_pages,enable_consent_gate,enable_capability_gate,enable_ipfs_gateway,ipfs_dnslink,pages_project
wokelang.org,hyperpolymath,wokelang,abc123-tunnel,mail.wokelang.org,backup.mail.wokelang.org,admin@example.org,E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855,,default,d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971,true,true,true,false,false,false,false,,wokelang- Populate domains.csv with all your domains
- Get domain-specific values (SSH fingerprints, tunnel IDs, etc.)
- Run terraform plan to preview
- Run terraform apply to deploy!
Your entire DNS infrastructure will be code! 🚀
See TOPOLOGY.md for a visual architecture map and completion dashboard.