From 742867f9e6820ab56f973ddf9f1196526f19f8c4 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Tue, 4 Aug 2026 13:46:26 -0700 Subject: [PATCH 1/2] chore: migrate www redirect to apex to fastly --- modules/playful-web/main.tf | 42 ++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/modules/playful-web/main.tf b/modules/playful-web/main.tf index 09bb5fb..8aab6e2 100644 --- a/modules/playful-web/main.tf +++ b/modules/playful-web/main.tf @@ -24,6 +24,10 @@ resource "fastly_service_vcl" "cdn" { name = var.domain } + domain { + name = "www.${var.domain}" + } + backend { address = var.host name = "Host 1" @@ -136,6 +140,31 @@ resource "fastly_service_vcl" "cdn" { name = "Generated by force TLS and enable HSTS" timer_support = false } + + snippet { + name = "Redirect www to apex (recv)" + type = "recv" + priority = 100 + content = <<-VCL + if (std.tolower(req.http.host) == "www.${var.domain}") { + error 618 "www-to-apex"; + } + VCL + } + + snippet { + name = "Redirect www to apex (error)" + type = "error" + priority = 100 + content = <<-VCL + if (obj.status == 618 && obj.response == "www-to-apex") { + set obj.status = 301; + set obj.response = "Moved Permanently"; + set obj.http.Location = "https://${var.domain}" + req.url; + return (deliver); + } + VCL + } } resource "fastly_tls_subscription" "main" { @@ -187,11 +216,10 @@ resource "porkbun_dns_record" "apex" { ttl = 600 } -resource "porkbun_url_forward" "redirect" { - domain = var.domain - subdomain = "www" - include_path = true - location = "https://${var.domain}" - type = "permanent" - wildcard = false +resource "porkbun_dns_record" "www" { + domain = var.domain + subdomain = "www" + type = "CNAME" + content = one([for record in data.fastly_tls_configuration.default_tls.dns_records : record.record_value if record.record_type == "CNAME"]) + ttl = 600 } From 4343bc49ba18ed1e96f14643f9d8a6d48e229a14 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Tue, 4 Aug 2026 14:27:48 -0700 Subject: [PATCH 2/2] Support more kinds of subdomain redirects --- main.tf | 8 +++ modules/playful-web/main.tf | 116 +++++++++++++++++++++++++++--------- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/main.tf b/main.tf index 2e6c529..c15f75b 100644 --- a/main.tf +++ b/main.tf @@ -12,4 +12,12 @@ module "playful-web" { domain = var.playful_web_domain host = var.playful_web_host noindex = var.env != "prod" + + subdomain_redirects = { + www = { + location = "https://${var.playful_web_domain}" + preserve_url = true + status = 301 + } + } } diff --git a/modules/playful-web/main.tf b/modules/playful-web/main.tf index 8aab6e2..0304dd6 100644 --- a/modules/playful-web/main.tf +++ b/modules/playful-web/main.tf @@ -13,6 +13,31 @@ variable "noindex" { description = "Whether a noindex header should be appended to every response" } +variable "subdomain_redirects" { + type = map(object({ + location = string + preserve_url = bool + status = number + })) + description = "Map of subdomain to its redirect configuration" + default = {} + + validation { + condition = alltrue([ + for redirect in values(var.subdomain_redirects) : + contains([301, 302], redirect.status) + ]) + error_message = "Redirect status must be either 301 or 302." + } +} + +locals { + subdomain_redirect_hosts = { + for subdomain, redirect in var.subdomain_redirects : + "${subdomain}.${var.domain}" => redirect + } +} + resource "fastly_service_vcl" "cdn" { activate = true comment = "Managed by Tofu" @@ -24,8 +49,11 @@ resource "fastly_service_vcl" "cdn" { name = var.domain } - domain { - name = "www.${var.domain}" + dynamic "domain" { + for_each = local.subdomain_redirect_hosts + content { + name = domain.key + } } backend { @@ -141,29 +169,61 @@ resource "fastly_service_vcl" "cdn" { timer_support = false } - snippet { - name = "Redirect www to apex (recv)" - type = "recv" - priority = 100 - content = <<-VCL - if (std.tolower(req.http.host) == "www.${var.domain}") { - error 618 "www-to-apex"; - } - VCL - } - - snippet { - name = "Redirect www to apex (error)" - type = "error" - priority = 100 - content = <<-VCL - if (obj.status == 618 && obj.response == "www-to-apex") { - set obj.status = 301; - set obj.response = "Moved Permanently"; - set obj.http.Location = "https://${var.domain}" + req.url; - return (deliver); - } - VCL + dynamic "snippet" { + for_each = length(local.subdomain_redirect_hosts) > 0 ? [1] : [] + content { + name = "Redirects (tables)" + type = "init" + priority = 100 + content = join("\n", concat( + ["table redirect_locations STRING {"], + [for host, redirect in local.subdomain_redirect_hosts : " ${jsonencode(host)}: ${jsonencode(redirect.location)},"], + ["}", "", "table redirect_statuses INTEGER {"], + [for host, redirect in local.subdomain_redirect_hosts : " ${jsonencode(host)}: ${redirect.status},"], + ["}", "", "table redirect_preserve_urls BOOL {"], + [for host, redirect in local.subdomain_redirect_hosts : " ${jsonencode(host)}: ${redirect.preserve_url},"], + ["}"], + )) + } + } + + dynamic "snippet" { + for_each = length(local.subdomain_redirect_hosts) > 0 ? [1] : [] + content { + name = "Redirects (recv)" + type = "recv" + priority = 100 + content = <<-VCL + if (table.contains(redirect_locations, std.tolower(req.http.host))) { + error 618 "redirect"; + } + VCL + } + } + + dynamic "snippet" { + for_each = length(local.subdomain_redirect_hosts) > 0 ? [1] : [] + content { + name = "Redirects (error)" + type = "error" + priority = 100 + content = <<-VCL + if (obj.status == 618 && obj.response == "redirect") { + set obj.status = table.lookup_integer(redirect_statuses, std.tolower(req.http.host), 302); + if (obj.status == 301) { + set obj.response = "Moved Permanently"; + } else { + set obj.response = "Found"; + } + set obj.http.Location = table.lookup(redirect_locations, std.tolower(req.http.host), ""); + if (table.lookup_bool(redirect_preserve_urls, std.tolower(req.http.host), false)) { + set obj.http.Location = obj.http.Location + req.url; + } + synthetic ""; + return (deliver); + } + VCL + } } } @@ -216,9 +276,11 @@ resource "porkbun_dns_record" "apex" { ttl = 600 } -resource "porkbun_dns_record" "www" { +resource "porkbun_dns_record" "subdomain_redirect" { + for_each = var.subdomain_redirects + domain = var.domain - subdomain = "www" + subdomain = each.key type = "CNAME" content = one([for record in data.fastly_tls_configuration.default_tls.dns_records : record.record_value if record.record_type == "CNAME"]) ttl = 600