diff options
| author | Franck Cuny <franck@fcuny.net> | 2025-08-09 12:41:52 -0700 |
|---|---|---|
| committer | Franck Cuny <franck@fcuny.net> | 2025-08-09 12:41:52 -0700 |
| commit | 97a7718d1070b02068b7a9ccd76ab5a7d5703031 (patch) | |
| tree | 95c23d793e377ef7142cc88bec83025f53cc55dd | |
| parent | change emacs theme and simplify whitespace configuration (diff) | |
| download | infra-97a7718d1070b02068b7a9ccd76ab5a7d5703031.tar.gz | |
write the terraform configuration in HCL
This will become more tricky over time. Using HCL is not as elegant and
forces me to learn yet another DSL, but this will be easier to debug if
I'm stuck with something I don't know how to do.
| -rw-r--r-- | infra/tf/.envrc.local.template | 4 | ||||
| -rw-r--r-- | infra/tf/.gitignore | 1 | ||||
| -rw-r--r-- | infra/tf/backups-bucket/main.tf | 43 | ||||
| -rw-r--r-- | infra/tf/backups-bucket/outputs.tf | 24 | ||||
| -rw-r--r-- | infra/tf/backups-bucket/terraform.tfvars | 27 | ||||
| -rw-r--r-- | infra/tf/backups-bucket/variables.tf | 73 | ||||
| -rw-r--r-- | infra/tf/backups.nix | 42 | ||||
| -rw-r--r-- | infra/tf/cloudflare-dns/main.tf | 24 | ||||
| -rw-r--r-- | infra/tf/cloudflare-dns/records.tf | 196 | ||||
| -rw-r--r-- | infra/tf/cloudflare-dns/terraform.tfvars | 3 | ||||
| -rw-r--r-- | infra/tf/cloudflare-dns/variables.tf | 11 | ||||
| -rw-r--r-- | infra/tf/dns.nix | 138 | ||||
| -rw-r--r-- | infra/tf/flake-module.nix | 17 |
13 files changed, 423 insertions, 180 deletions
diff --git a/infra/tf/.envrc.local.template b/infra/tf/.envrc.local.template new file mode 100644 index 0000000..82ebf5f --- /dev/null +++ b/infra/tf/.envrc.local.template @@ -0,0 +1,4 @@ +#!/bin/sh + +# stored in 1password +export CLOUDFLARE_API_TOKEN=... diff --git a/infra/tf/.gitignore b/infra/tf/.gitignore new file mode 100644 index 0000000..cff6f1b --- /dev/null +++ b/infra/tf/.gitignore @@ -0,0 +1 @@ +/.envrc.local diff --git a/infra/tf/backups-bucket/main.tf b/infra/tf/backups-bucket/main.tf new file mode 100644 index 0000000..a86e582 --- /dev/null +++ b/infra/tf/backups-bucket/main.tf @@ -0,0 +1,43 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.0" + } + } + + backend "gcs" { + bucket = "fcuny-infra-tofu-state" + prefix = "backups" + } +} + +provider "google" { + project = var.project_id + region = var.region +} + +resource "google_storage_bucket" "backups" { + name = var.bucket_name + location = var.location + uniform_bucket_level_access = var.uniform_bucket_level_access + force_destroy = var.force_destroy + public_access_prevention = var.public_access_prevention + storage_class = var.storage_class + + # Optional: Add lifecycle rules for cost optimization + dynamic "lifecycle_rule" { + for_each = var.lifecycle_rules + content { + condition { + age = lifecycle_rule.value.age + } + action { + type = lifecycle_rule.value.action + storage_class = lifecycle_rule.value.storage_class + } + } + } +} diff --git a/infra/tf/backups-bucket/outputs.tf b/infra/tf/backups-bucket/outputs.tf new file mode 100644 index 0000000..3ca60c5 --- /dev/null +++ b/infra/tf/backups-bucket/outputs.tf @@ -0,0 +1,24 @@ +output "bucket_name" { + description = "Name of the created backup bucket" + value = google_storage_bucket.backups.name +} + +output "bucket_url" { + description = "URL of the backup bucket" + value = google_storage_bucket.backups.url +} + +output "bucket_self_link" { + description = "Self-link of the backup bucket" + value = google_storage_bucket.backups.self_link +} + +output "bucket_location" { + description = "Location of the backup bucket" + value = google_storage_bucket.backups.location +} + +output "bucket_storage_class" { + description = "Storage class of the backup bucket" + value = google_storage_bucket.backups.storage_class +} diff --git a/infra/tf/backups-bucket/terraform.tfvars b/infra/tf/backups-bucket/terraform.tfvars new file mode 100644 index 0000000..1d4f2c4 --- /dev/null +++ b/infra/tf/backups-bucket/terraform.tfvars @@ -0,0 +1,27 @@ +# GCP Configuration +project_id = "fcuny-infra" +region = "us-west1" + +# Bucket Configuration +bucket_name = "fcuny-infra-backups" +location = "us-west1" + +# Bucket Settings +uniform_bucket_level_access = true +force_destroy = true +public_access_prevention = "enforced" +storage_class = "NEARLINE" + +# Lifecycle Rules (optional customization) +lifecycle_rules = [ + { + age = 365 # After 1 year + action = "SetStorageClass" + storage_class = "COLDLINE" + }, + { + age = 730 # After 2 years + action = "SetStorageClass" + storage_class = "ARCHIVE" + } +] diff --git a/infra/tf/backups-bucket/variables.tf b/infra/tf/backups-bucket/variables.tf new file mode 100644 index 0000000..ffdc1a6 --- /dev/null +++ b/infra/tf/backups-bucket/variables.tf @@ -0,0 +1,73 @@ +variable "project_id" { + description = "GCP Project ID" + type = string + default = "fcuny-infra" +} + +variable "region" { + description = "GCP Region" + type = string + default = "us-west1" +} + +variable "bucket_name" { + description = "Name of the backup storage bucket" + type = string + default = "fcuny-infra-backups" +} + +variable "location" { + description = "Location for the storage bucket" + type = string + default = "us-west1" +} + +variable "uniform_bucket_level_access" { + description = "Enable uniform bucket-level access" + type = bool + default = true +} + +variable "force_destroy" { + description = "Allow destruction of bucket even if it contains objects" + type = bool + default = true +} + +variable "public_access_prevention" { + description = "Public access prevention setting" + type = string + default = "enforced" + + validation { + condition = contains(["enforced", "inherited"], var.public_access_prevention) + error_message = "Public access prevention must be either 'enforced' or 'inherited'." + } +} + +variable "storage_class" { + description = "Storage class for the bucket" + type = string + default = "NEARLINE" + + validation { + condition = contains(["STANDARD", "NEARLINE", "COLDLINE", "ARCHIVE"], var.storage_class) + error_message = "Storage class must be one of: STANDARD, NEARLINE, COLDLINE, ARCHIVE." + } +} + +variable "lifecycle_rules" { + description = "List of lifecycle rules for the bucket" + type = list(object({ + age = number + action = string + storage_class = string + })) + default = [ + { + age = 365 + action = "SetStorageClass" + storage_class = "COLDLINE" + } + ] +} diff --git a/infra/tf/backups.nix b/infra/tf/backups.nix deleted file mode 100644 index e76ed2e..0000000 --- a/infra/tf/backups.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - pkgs, -}: -pkgs.writeTextFile { - name = "backups.tf.json"; - text = builtins.toJSON ([ - { - terraform = { - backend = { - gcs = { - bucket = "fcuny-infra-tofu-state"; - prefix = "backups"; - }; - }; - }; - } - { - provider = { - google = [ - { - project = "fcuny-infra"; - region = "us-west1"; - } - ]; - }; - } - { - resource = { - google_storage_bucket = { - "backups" = { - name = "fcuny-infra-backups"; - location = "us-west1"; - uniform_bucket_level_access = true; - force_destroy = true; - public_access_prevention = "enforced"; - storage_class = "NEARLINE"; - }; - }; - }; - } - ]); -} diff --git a/infra/tf/cloudflare-dns/main.tf b/infra/tf/cloudflare-dns/main.tf new file mode 100644 index 0000000..30442e6 --- /dev/null +++ b/infra/tf/cloudflare-dns/main.tf @@ -0,0 +1,24 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 5" + } + } + + backend "gcs" { + bucket = "fcuny-infra-tofu-state" + prefix = "cloudflare-dns" + } +} + +provider "cloudflare" { + # API token will be provided via CLOUDFLARE_API_TOKEN environment variable +} + +# Use data source for existing zone instead of managing it +data "cloudflare_zone" "main" { + zone_id = var.zone_id +} diff --git a/infra/tf/cloudflare-dns/records.tf b/infra/tf/cloudflare-dns/records.tf new file mode 100644 index 0000000..b1543d1 --- /dev/null +++ b/infra/tf/cloudflare-dns/records.tf @@ -0,0 +1,196 @@ +resource "cloudflare_dns_record" "cname_root_0" { + content = "185.199.108.153" + name = "fcuny.net" + proxied = false + ttl = 1 + type = "A" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "cname_root_1" { + content = "185.199.110.153" + name = "fcuny.net" + proxied = false + ttl = 1 + type = "A" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "cname_root_2" { + content = "185.199.109.153" + name = "fcuny.net" + proxied = false + ttl = 1 + type = "A" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "cname_root_3" { + content = "185.199.111.153" + name = "fcuny.net" + proxied = false + ttl = 1 + type = "A" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "cname_dkim_0" { + content = "fm1.fcuny.net.dkim.fmhosted.com" + name = "fm1._domainkey" + proxied = false + ttl = 60 + type = "CNAME" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "cname_dkim_1" { + content = "fm2.fcuny.net.dkim.fmhosted.com" + name = "fm2._domainkey" + proxied = false + ttl = 60 + type = "CNAME" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "cname_dkim_2" { + content = "fm3.fcuny.net.dkim.fmhosted.com" + name = "fm3._domainkey" + proxied = false + ttl = 60 + type = "CNAME" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "mx_0" { + content = "in1-smtp.messagingengine.com" + name = "fcuny.net" + priority = 10 + proxied = false + ttl = 1 + type = "MX" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "mx_1" { + content = "in2-smtp.messagingengine.com" + name = "fcuny.net" + priority = 20 + proxied = false + ttl = 1 + type = "MX" + zone_id = var.zone_id +} + +resource "cloudflare_dns_record" "srv_caldavs" { + name = "_caldavs._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 443 + priority = 0 + target = "caldav.fastmail.com" + weight = 1 + } +} + +resource "cloudflare_dns_record" "srv_caldav" { + name = "_caldav._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 0 + priority = 0 + target = "." + weight = 0 + } +} + +resource "cloudflare_dns_record" "srv_carddavs" { + name = "_carddavs._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 443 + priority = 0 + target = "carddav.fastmail.com" + weight = 1 + } +} + +resource "cloudflare_dns_record" "srv_carddav" { + name = "_carddav._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 0 + priority = 0 + target = "." + weight = 0 + } +} + +resource "cloudflare_dns_record" "srv_imaps" { + name = "_imaps._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 993 + priority = 0 + target = "imap.fastmail.com" + weight = 1 + } +} + +resource "cloudflare_dns_record" "srv_imap" { + name = "_imap._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 0 + priority = 0 + target = "." + weight = 0 + } +} + +resource "cloudflare_dns_record" "srv_smtp" { + name = "_submission._tcp" + priority = 0 + proxied = false + ttl = 1 + type = "SRV" + zone_id = var.zone_id + data = { + port = 587 + priority = 0 + target = "smtp.fastmail.com" + weight = 1 + } +} + +resource "cloudflare_dns_record" "txt_spf" { + content = "\"v=spf1 include:spf.messagingengine.com ?all\"" + name = "fcuny.net" + proxied = false + ttl = 1 + type = "TXT" + zone_id = var.zone_id +} diff --git a/infra/tf/cloudflare-dns/terraform.tfvars b/infra/tf/cloudflare-dns/terraform.tfvars new file mode 100644 index 0000000..5a88de4 --- /dev/null +++ b/infra/tf/cloudflare-dns/terraform.tfvars @@ -0,0 +1,3 @@ +# Zone Configuration +zone_id = "6878e48b5cb81c7d789040632153719d" +state_bucket = "fcuny-infra-tofu-state" diff --git a/infra/tf/cloudflare-dns/variables.tf b/infra/tf/cloudflare-dns/variables.tf new file mode 100644 index 0000000..24a4a35 --- /dev/null +++ b/infra/tf/cloudflare-dns/variables.tf @@ -0,0 +1,11 @@ +variable "zone_id" { + description = "Cloudflare zone ID" + type = string + default = "6878e48b5cb81c7d789040632153719d" +} + +variable "state_bucket" { + description = "GCS bucket for Terraform state" + type = string + default = "fcuny-infra-tofu-state" +} diff --git a/infra/tf/dns.nix b/infra/tf/dns.nix deleted file mode 100644 index df0ed65..0000000 --- a/infra/tf/dns.nix +++ /dev/null @@ -1,138 +0,0 @@ -{ - pkgs, -}: -let - zoneId = "6878e48b5cb81c7d789040632153719d"; - zoneName = "fcuny.net"; - - # Helper function to create DNS records with common fields - mkRecord = - type: name: content: extra: - { - inherit name type; - zone_id = zoneId; - ttl = 1; - proxied = false; - content = content; - } - // extra; - - # Helper for A records (typically proxied) - mkARecord = name: ip: mkRecord "A" name ip { proxied = true; }; - - # Helper for CNAME records - mkCNAME = name: target: mkRecord "CNAME" name target { }; - - # Helper for MX records - mkMXRecord = - priority: target: - mkRecord "MX" zoneName target { - inherit priority; - }; - - # Helper for SRV records with data block - mkSRVRecord = name: port: target: weight: priority: { - inherit name; - type = "SRV"; - zone_id = zoneId; - ttl = 1; - proxied = false; - priority = priority; - data = { - inherit - port - target - weight - priority - ; - }; - }; - - # Helper for TXT records - mkTXTRecord = name: content: mkRecord "TXT" name content { }; - -in -pkgs.writeTextFile { - name = "cloudflare-dns.tf.json"; - text = builtins.toJSON ([ - { - terraform = { - required_providers = { - cloudflare = { - source = "cloudflare/cloudflare"; - version = "~> 4.0"; - }; - }; - backend = { - gcs = { - bucket = "fcuny-infra-tofu-state"; - prefix = "cloudflare-dns"; - }; - }; - }; - } - { - provider = { - cloudflare = [ { } ]; - }; - } - { - # Use data source for existing zone instead of managing it - data = { - cloudflare_zone = { - "main" = { - name = zoneName; - }; - }; - }; - } - { - resource = { - cloudflare_record = { - # A records for root domain - "cname_root_0" = mkARecord zoneName "185.199.108.153"; - "cname_root_1" = mkARecord zoneName "185.199.110.153"; - "cname_root_2" = mkARecord zoneName "185.199.109.153"; - "cname_root_3" = mkARecord zoneName "185.199.111.153"; - - # DKIM CNAME records - "cname_dkim_0" = mkCNAME "fm1._domainkey" "fm1.fcuny.net.dkim.fmhosted.com" // { - ttl = 60; - }; - "cname_dkim_1" = mkCNAME "fm2._domainkey" "fm2.fcuny.net.dkim.fmhosted.com" // { - ttl = 60; - }; - "cname_dkim_2" = mkCNAME "fm3._domainkey" "fm3.fcuny.net.dkim.fmhosted.com" // { - ttl = 60; - }; - - # Git subdomain via Cloudflare tunnel - "cname_git" = mkCNAME "git" "b5d5071d-3c09-4379-9d6c-0684c478f151.cfargotunnel.com" // { - proxied = true; - }; - - # MX records - "mx_0" = mkMXRecord 10 "in1-smtp.messagingengine.com"; - "mx_1" = mkMXRecord 20 "in2-smtp.messagingengine.com"; - - # SPF TXT record - "txt_spf" = mkTXTRecord zoneName "v=spf1 include:spf.messagingengine.com ?all"; - }; - }; - } - { - resource = { - cloudflare_record = { - # SRV records for email services - "srv_caldavs" = mkSRVRecord "_caldavs._tcp" 443 "caldav.fastmail.com" 1 0; - "srv_caldav" = mkSRVRecord "_caldav._tcp" 0 "." 0 0; - "srv_carddavs" = mkSRVRecord "_carddavs._tcp" 443 "carddav.fastmail.com" 1 0; - "srv_carddav" = mkSRVRecord "_carddav._tcp" 0 "." 0 0; - "srv_imaps" = mkSRVRecord "_imaps._tcp" 993 "imap.fastmail.com" 1 0; - "srv_imap" = mkSRVRecord "_imap._tcp" 0 "." 0 0; - "srv_smtp" = mkSRVRecord "_submission._tcp" 587 "smtp.fastmail.com" 1 0; - }; - }; - } - ]); -} diff --git a/infra/tf/flake-module.nix b/infra/tf/flake-module.nix new file mode 100644 index 0000000..a386177 --- /dev/null +++ b/infra/tf/flake-module.nix @@ -0,0 +1,17 @@ +{ + perSystem = + { pkgs, ... }: + { + devShells.terraform = pkgs.mkShellNoCC { + packages = with pkgs; [ + google-cloud-sdk + (pkgs.opentofu.withPlugins (p: [ + p.google + p.cloudflare + p.external + p.null + ])) + ]; + }; + }; +} |
