| 1 | locals { |
| 2 | cache_staging_domain = "cache-staging.nixos.org" |
| 3 | } |
| 4 | |
| 5 | # This is the old bucket we want to archive. |
| 6 | module "cache-staging-202010" { |
| 7 | source = "./cache-bucket" |
| 8 | bucket_name = "nix-cache-staging" |
| 9 | providers = { |
| 10 | aws = aws.us |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | import { |
| 15 | to = module.cache-staging-202010.aws_s3_bucket_lifecycle_configuration.cache |
| 16 | id = "nix-cache-staging" |
| 17 | } |
| 18 | |
| 19 | import { |
| 20 | to = module.cache-staging-202010.aws_s3_bucket_cors_configuration.cache |
| 21 | id = "nix-cache-staging" |
| 22 | } |
| 23 | |
| 24 | |
| 25 | # This is the new bucket we want to use in future. |
| 26 | module "cache-staging-202410" { |
| 27 | source = "./cache-bucket" |
| 28 | bucket_name = "nix-cache-staging-202410" |
| 29 | providers = { |
| 30 | # move the new bucket to EU |
| 31 | aws = aws |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | import { |
| 36 | to = module.cache-staging-202410.aws_s3_bucket_lifecycle_configuration.cache |
| 37 | id = "nix-cache-staging-202410" |
| 38 | } |
| 39 | |
| 40 | import { |
| 41 | to = module.cache-staging-202410.aws_s3_bucket_cors_configuration.cache |
| 42 | id = "nix-cache-staging-202410" |
| 43 | } |
| 44 | |
| 45 | # The fastly configuration below will first try the new bucket and than the old bucket. |
| 46 | # As demonstation we have two files in the buckets: |
| 47 | # $ curl https://cache-staging.nixos.org/new-cache │ |
| 48 | # new |
| 49 | # $ curl https://cache-staging.nixos.org/old-cache |
| 50 | # old |
| 51 | |
| 52 | resource "aws_s3_object" "old-cache-test-file" { |
| 53 | provider = aws.us |
| 54 | depends_on = [module.cache-staging-202010] |
| 55 | |
| 56 | bucket = module.cache-staging-202010.bucket |
| 57 | content_type = "text/plain" |
| 58 | etag = filemd5("${path.module}/cache-staging/old-cache-test-file") |
| 59 | key = "old-cache" |
| 60 | source = "${path.module}/cache-staging/old-cache-test-file" |
| 61 | } |
| 62 | resource "aws_s3_object" "new-cache-test-file" { |
| 63 | provider = aws |
| 64 | depends_on = [module.cache-staging-202410] |
| 65 | |
| 66 | bucket = module.cache-staging-202410.bucket |
| 67 | content_type = "text/plain" |
| 68 | etag = filemd5("${path.module}/cache-staging/new-cache-test-file") |
| 69 | key = "new-cache" |
| 70 | source = "${path.module}/cache-staging/new-cache-test-file" |
| 71 | } |
| 72 | |
| 73 | resource "fastly_service_vcl" "cache-staging" { |
| 74 | name = local.cache_staging_domain |
| 75 | default_ttl = 86400 |
| 76 | |
| 77 | backend { |
| 78 | address = module.cache-staging-202010.bucket_regional_domain_name |
| 79 | auto_loadbalance = false |
| 80 | between_bytes_timeout = 10000 |
| 81 | connect_timeout = 5000 |
| 82 | error_threshold = 0 |
| 83 | first_byte_timeout = 15000 |
| 84 | max_conn = 200 |
| 85 | name = "old_bucket" |
| 86 | port = 443 |
| 87 | # For the old bucket we want to use Ashburn as our bucket is in us-east-1 |
| 88 | shield = "iad-va-us" |
| 89 | ssl_cert_hostname = module.cache-staging-202010.bucket_regional_domain_name |
| 90 | ssl_check_cert = true |
| 91 | use_ssl = true |
| 92 | weight = 100 |
| 93 | } |
| 94 | |
| 95 | backend { |
| 96 | address = module.cache-staging-202410.bucket_regional_domain_name |
| 97 | auto_loadbalance = false |
| 98 | between_bytes_timeout = 10000 |
| 99 | connect_timeout = 5000 |
| 100 | error_threshold = 0 |
| 101 | first_byte_timeout = 15000 |
| 102 | max_conn = 200 |
| 103 | name = "new_bucket" |
| 104 | port = 443 |
| 105 | # The new bucket is in EU (eu-west-1) |
| 106 | shield = "dub-dublin-ie" |
| 107 | ssl_cert_hostname = module.cache-staging-202410.bucket_regional_domain_name |
| 108 | ssl_check_cert = true |
| 109 | use_ssl = true |
| 110 | |
| 111 | # newer bucket has higher priority |
| 112 | weight = 200 |
| 113 | } |
| 114 | |
| 115 | # Temporarily disabled due to nix-index bugs: see https://github.com/nix-community/nix-index/issues/249 |
| 116 | #request_setting { |
| 117 | # name = "Redirect HTTP to HTTPS" |
| 118 | # force_ssl = true |
| 119 | #} |
| 120 | |
| 121 | condition { |
| 122 | name = "is-404" |
| 123 | priority = 0 |
| 124 | statement = "beresp.status == 404" |
| 125 | type = "CACHE" |
| 126 | } |
| 127 | |
| 128 | condition { |
| 129 | name = "Match /" |
| 130 | priority = 10 |
| 131 | statement = "req.url ~ \"^/$\"" |
| 132 | type = "REQUEST" |
| 133 | } |
| 134 | |
| 135 | condition { |
| 136 | name = "Restarts > 0" |
| 137 | type = "REQUEST" |
| 138 | priority = 20 |
| 139 | statement = "req.restarts > 0" |
| 140 | } |
| 141 | |
| 142 | domain { |
| 143 | name = "cache-staging.nixos.org" |
| 144 | } |
| 145 | |
| 146 | header { |
| 147 | name = "Landing page" |
| 148 | request_condition = "Match /" |
| 149 | ignore_if_set = false |
| 150 | priority = 10 |
| 151 | type = "request" |
| 152 | |
| 153 | action = "set" |
| 154 | destination = "url" |
| 155 | source = "\"/index.html\"" |
| 156 | |
| 157 | } |
| 158 | |
| 159 | header { |
| 160 | name = "Use old bucket" |
| 161 | request_condition = "Restarts > 0" |
| 162 | ignore_if_set = false |
| 163 | priority = 20 |
| 164 | type = "request" |
| 165 | |
| 166 | action = "set" |
| 167 | destination = "backend" |
| 168 | source = "F_old_bucket" |
| 169 | } |
| 170 | |
| 171 | # Clean headers for caching |
| 172 | header { |
| 173 | destination = "http.x-amz-request-id" |
| 174 | type = "cache" |
| 175 | action = "delete" |
| 176 | name = "remove x-amz-request-id" |
| 177 | } |
| 178 | header { |
| 179 | destination = "http.x-amz-version-id" |
| 180 | type = "cache" |
| 181 | action = "delete" |
| 182 | name = "remove x-amz-version-id" |
| 183 | } |
| 184 | header { |
| 185 | destination = "http.x-amz-id-2" |
| 186 | type = "cache" |
| 187 | action = "delete" |
| 188 | name = "remove x-amz-id-2" |
| 189 | } |
| 190 | |
| 191 | # Enable Streaming Miss. |
| 192 | # https://docs.fastly.com/en/guides/streaming-miss |
| 193 | # https://github.com/NixOS/infra/issues/212#issuecomment-1187568233 |
| 194 | header { |
| 195 | priority = 20 |
| 196 | destination = "do_stream" |
| 197 | type = "cache" |
| 198 | action = "set" |
| 199 | name = "Enabling Streaming Miss" |
| 200 | source = "true" |
| 201 | } |
| 202 | |
| 203 | # Allow CORS GET requests. |
| 204 | header { |
| 205 | destination = "http.access-control-allow-origin" |
| 206 | type = "response" |
| 207 | action = "set" |
| 208 | name = "CORS Allow" |
| 209 | source = "\"*\"" |
| 210 | } |
| 211 | |
| 212 | response_object { |
| 213 | name = "404-page" |
| 214 | cache_condition = "is-404" |
| 215 | content = "404" |
| 216 | content_type = "text/plain" |
| 217 | response = "Not Found" |
| 218 | status = 404 |
| 219 | } |
| 220 | |
| 221 | snippet { |
| 222 | name = "Variables for aws s3 auth" |
| 223 | type = "miss" |
| 224 | priority = 90 |
| 225 | content = <<-EOT |
| 226 | declare local var.awsAccessKey STRING; |
| 227 | declare local var.awsSecretKey STRING; |
| 228 | declare local var.awsS3Bucket STRING; |
| 229 | declare local var.awsRegion STRING; |
| 230 | declare local var.awsS3Host STRING; |
| 231 | |
| 232 | declare local var.canonicalHeaders STRING; |
| 233 | declare local var.signedHeaders STRING; |
| 234 | declare local var.canonicalRequest STRING; |
| 235 | declare local var.canonicalQuery STRING; |
| 236 | declare local var.stringToSign STRING; |
| 237 | declare local var.dateStamp STRING; |
| 238 | declare local var.signature STRING; |
| 239 | declare local var.scope STRING; |
| 240 | EOT |
| 241 | } |
| 242 | |
| 243 | # Authenticate Fastly<->S3 requests. See Fastly documentation: |
| 244 | # https://docs.fastly.com/en/guides/amazon-s3#using-an-amazon-s3-private-bucket |
| 245 | snippet { |
| 246 | name = "Authenticate S3 requests for new bucket" |
| 247 | type = "miss" |
| 248 | priority = 100 |
| 249 | content = templatefile("${path.module}/cache-staging/s3-authn.vcl", { |
| 250 | backend_name = "F_new_bucket" |
| 251 | aws_region = module.cache-staging-202410.region |
| 252 | bucket = module.cache-staging-202410.bucket |
| 253 | backend_domain = module.cache-staging-202410.bucket_domain_name |
| 254 | access_key = local.cache-iam.key |
| 255 | secret_key = local.cache-iam.secret |
| 256 | }) |
| 257 | } |
| 258 | |
| 259 | snippet { |
| 260 | name = "Authenticate S3 requests for old bucket" |
| 261 | type = "miss" |
| 262 | priority = 100 |
| 263 | content = templatefile("${path.module}/cache-staging/s3-authn.vcl", { |
| 264 | backend_name = "F_old_bucket" |
| 265 | aws_region = module.cache-staging-202010.region |
| 266 | bucket = module.cache-staging-202010.bucket |
| 267 | backend_domain = module.cache-staging-202010.bucket_domain_name |
| 268 | access_key = local.cache-iam.key |
| 269 | secret_key = local.cache-iam.secret |
| 270 | }) |
| 271 | } |
| 272 | |
| 273 | snippet { |
| 274 | content = "set req.url = querystring.remove(req.url);" |
| 275 | name = "Remove all query strings" |
| 276 | priority = 50 |
| 277 | type = "recv" |
| 278 | } |
| 279 | |
| 280 | |
| 281 | # Work around the 2GB size limit for large files |
| 282 | # |
| 283 | # See https://docs.fastly.com/en/guides/segmented-caching |
| 284 | snippet { |
| 285 | content = <<-EOT |
| 286 | if (req.url.path ~ "^/nar/") { |
| 287 | set req.enable_segmented_caching = true; |
| 288 | } |
| 289 | EOT |
| 290 | name = "Enable segment caching for NAR files" |
| 291 | priority = 60 |
| 292 | type = "recv" |
| 293 | } |
| 294 | |
| 295 | snippet { |
| 296 | name = "Fallback to old bucket on 403 or return 404" |
| 297 | type = "fetch" |
| 298 | priority = 90 |
| 299 | content = <<-EOT |
| 300 | if (beresp.status == 403) { |
| 301 | if (req.backend == F_new_bucket) { |
| 302 | restart; |
| 303 | } else { |
| 304 | set beresp.status = 404; |
| 305 | } |
| 306 | } |
| 307 | EOT |
| 308 | } |
| 309 | |
| 310 | # We will switch to this snipped once we retire the old bucket instead of the fallback above |
| 311 | #snippet { |
| 312 | # name = "Return 404 on 403" |
| 313 | # type = "fetch" |
| 314 | # priority = 90 |
| 315 | # content = <<-EOT |
| 316 | # if (beresp.status == 403) { |
| 317 | # set beresp.status = 404; |
| 318 | # } |
| 319 | # EOT |
| 320 | #} |
| 321 | |
| 322 | # Add a snippet to set a custom header based on the backend used |
| 323 | snippet { |
| 324 | name = "Set-Backend-Header" |
| 325 | type = "deliver" |
| 326 | priority = 70 |
| 327 | content = <<-EOT |
| 328 | if (req.backend == F_old_bucket) { |
| 329 | set resp.http.X-Bucket = "${module.cache-staging-202010.bucket}"; |
| 330 | } else if (req.backend == F_new_bucket) { |
| 331 | set resp.http.X-Bucket = "${module.cache-staging-202410.bucket}"; |
| 332 | } |
| 333 | EOT |
| 334 | } |
| 335 | |
| 336 | logging_s3 { |
| 337 | name = "${local.cache_staging_domain}-to-s3" |
| 338 | bucket_name = local.fastlylogs["bucket_name"] |
| 339 | compression_codec = "zstd" |
| 340 | domain = local.fastlylogs["s3_domain"] |
| 341 | format = local.fastlylogs["format"] |
| 342 | format_version = 2 |
| 343 | path = "${local.cache_staging_domain}/" |
| 344 | period = local.fastlylogs["period"] |
| 345 | message_type = "blank" |
| 346 | s3_iam_role = local.fastlylogs["iam_role_arn"] |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | resource "fastly_tls_subscription" "cache-staging-2025-11" { |
| 351 | domains = [for domain in fastly_service_vcl.cache-staging.domain : domain.name] |
| 352 | configuration_id = local.fastly_tls13_quic_configuration_id |
| 353 | certificate_authority = "lets-encrypt" |
| 354 | } |