diff options
| -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 + ])) + ]; + }; + }; +} |
