| 1 | # Artifacts Proxy Service |
| 2 | # |
| 3 | # This service provides IPv6-enabled access to GitHub releases through Fastly CDN. |
| 4 | # It transparently follows GitHub's S3 redirects to provide direct file access. |
| 5 | # |
| 6 | # Supported URL patterns: |
| 7 | # - /nix-installer/tag/* -> /NixOS/nix-installer/releases/download/* |
| 8 | # - /nix-installer -> /NixOS/nix-installer/releases/latest/download/nix-installer.sh |
| 9 | # - /nix-installer/* -> /NixOS/nix-installer/releases/latest/download/* |
| 10 | # - /experimental-installer/tag/* -> /NixOS/experimental-nix-installer/releases/download/* (legacy) |
| 11 | # - /experimental-installer -> /NixOS/experimental-nix-installer/releases/latest/download/nix-installer.sh (legacy) |
| 12 | # - /experimental-installer/* -> /NixOS/experimental-nix-installer/releases/latest/download/* (legacy) |
| 13 | # - /patchelf/* -> /NixOS/patchelf/releases/download/* |
| 14 | # |
| 15 | # Testing commands: |
| 16 | # |
| 17 | # Basic functionality tests: |
| 18 | # curl -I https://artifacts.nixos.org/nix/0.27.0/nix-installer.sh |
| 19 | # curl -s https://artifacts.nixos.org/nix/0.27.0/nix-installer.sh | head -n 5 |
| 20 | # |
| 21 | # IPv6 connectivity test: |
| 22 | # curl -6 -I https://artifacts.nixos.org/nix/0.27.0/nix-installer.sh |
| 23 | # |
| 24 | # Performance comparison (should show redirect following): |
| 25 | # time curl -s https://artifacts.nixos.org/nix/0.27.0/nix-installer-x86_64-linux > /dev/null |
| 26 | # time curl -s https://github.com/NixOS/experimental-nix-installer/releases/download/0.27.0/nix-installer-x86_64-linux > /dev/null |
| 27 | # |
| 28 | # Error cases (should return 404): |
| 29 | # curl -I https://artifacts.nixos.org/invalid/path |
| 30 | # curl -I https://artifacts.nixos.org/patchelf/999.999.999/nonexistent-file |
| 31 | |
| 32 | locals { |
| 33 | artifacts_domain = "artifacts.nixos.org" |
| 34 | } |
| 35 | |
| 36 | resource "fastly_service_vcl" "artifacts" { |
| 37 | name = local.artifacts_domain |
| 38 | default_ttl = 3600 |
| 39 | |
| 40 | backend { |
| 41 | address = "github.com" |
| 42 | auto_loadbalance = false |
| 43 | between_bytes_timeout = 10000 |
| 44 | connect_timeout = 1000 |
| 45 | error_threshold = 0 |
| 46 | first_byte_timeout = 15000 |
| 47 | max_conn = 200 |
| 48 | name = "github.com" |
| 49 | override_host = "github.com" |
| 50 | port = 443 |
| 51 | ssl_cert_hostname = "github.com" |
| 52 | ssl_check_cert = true |
| 53 | use_ssl = true |
| 54 | weight = 100 |
| 55 | request_condition = "Use GitHub backend" |
| 56 | } |
| 57 | |
| 58 | backend { |
| 59 | address = "objects.githubusercontent.com" |
| 60 | auto_loadbalance = false |
| 61 | between_bytes_timeout = 10000 |
| 62 | connect_timeout = 1000 |
| 63 | error_threshold = 0 |
| 64 | first_byte_timeout = 15000 |
| 65 | max_conn = 200 |
| 66 | name = "objects_githubusercontent_com" |
| 67 | override_host = "objects.githubusercontent.com" |
| 68 | port = 443 |
| 69 | ssl_cert_hostname = "objects.githubusercontent.com" |
| 70 | ssl_check_cert = true |
| 71 | use_ssl = true |
| 72 | weight = 100 |
| 73 | request_condition = "Use Objects backend" |
| 74 | } |
| 75 | |
| 76 | condition { |
| 77 | name = "Use GitHub backend" |
| 78 | priority = 10 |
| 79 | statement = "!req.http.X-Use-Objects-Backend" |
| 80 | type = "REQUEST" |
| 81 | } |
| 82 | |
| 83 | condition { |
| 84 | name = "Use Objects backend" |
| 85 | priority = 10 |
| 86 | statement = "req.http.X-Use-Objects-Backend" |
| 87 | type = "REQUEST" |
| 88 | } |
| 89 | |
| 90 | |
| 91 | request_setting { |
| 92 | name = "Redirect HTTP to HTTPS" |
| 93 | force_ssl = true |
| 94 | } |
| 95 | |
| 96 | domain { |
| 97 | name = local.artifacts_domain |
| 98 | } |
| 99 | |
| 100 | # Main VCL snippet to handle the redirect logic |
| 101 | snippet { |
| 102 | content = <<-EOT |
| 103 | # Only rewrite if this is the first request (not a restart) |
| 104 | if (!req.http.X-Rewritten) { |
| 105 | # New nix-installer routes (NixOS/nix-installer) |
| 106 | if (req.url ~ "^/nix-installer/tag/") { |
| 107 | set req.url = regsub(req.url.path, "^/nix-installer/tag/", "/NixOS/nix-installer/releases/download/"); |
| 108 | set req.http.X-Rewritten = "true"; |
| 109 | } else if (req.url ~ "^(/nix-installer|/nix-installer/)$") { |
| 110 | set req.url = regsub(req.url.path, "^(/nix-installer|/nix-installer/)$", "/NixOS/nix-installer/releases/latest/download/nix-installer.sh"); |
| 111 | set req.http.X-Rewritten = "true"; |
| 112 | } else if (req.url ~ "^/nix-installer/") { |
| 113 | set req.url = regsub(req.url.path, "^/nix-installer", "/NixOS/nix-installer/releases/latest/download/"); |
| 114 | set req.http.X-Rewritten = "true"; |
| 115 | # Legacy experimental-installer routes (NixOS/experimental-nix-installer) |
| 116 | } else if (req.url ~ "^/experimental-installer/tag/") { |
| 117 | set req.url = regsub(req.url.path, "^/experimental-installer/tag/", "/NixOS/experimental-nix-installer/releases/download/"); |
| 118 | set req.http.X-Rewritten = "true"; |
| 119 | } else if (req.url ~ "^(/experimental-installer|/experimental-installer/)$") { |
| 120 | set req.url = regsub(req.url.path, "^(/experimental-installer|/experimental-installer/)$", "/NixOS/experimental-nix-installer/releases/latest/download/nix-installer.sh"); |
| 121 | set req.http.X-Rewritten = "true"; |
| 122 | } else if (req.url ~ "^/experimental-installer/") { |
| 123 | set req.url = regsub(req.url.path, "^/experimental-installer", "/NixOS/experimental-nix-installer/releases/latest/download/"); |
| 124 | set req.http.X-Rewritten = "true"; |
| 125 | } else if (req.url ~ "^/patchelf/") { |
| 126 | set req.url = regsub(req.url.path, "^/patchelf/", "/NixOS/patchelf/releases/download/"); |
| 127 | set req.http.X-Rewritten = "true"; |
| 128 | } else { |
| 129 | error 600; |
| 130 | } |
| 131 | } |
| 132 | EOT |
| 133 | name = "GitHub releases redirect" |
| 134 | priority = 100 |
| 135 | type = "recv" |
| 136 | } |
| 137 | |
| 138 | # Handle redirects from GitHub to S3 |
| 139 | snippet { |
| 140 | content = <<-EOT |
| 141 | if (beresp.status == 302 && beresp.http.Location ~ "^https://objects\.githubusercontent\.com/") { |
| 142 | # Extract the full path including query parameters |
| 143 | set req.url = regsub(beresp.http.Location, "^https://objects\.githubusercontent\.com", ""); |
| 144 | set req.http.X-Use-Objects-Backend = "true"; |
| 145 | # Set correct host header for S3 |
| 146 | set req.http.Host = "objects.githubusercontent.com"; |
| 147 | # Clear GitHub-specific headers that might interfere |
| 148 | unset req.http.Authorization; |
| 149 | unset req.http.Cookie; |
| 150 | restart; |
| 151 | } |
| 152 | EOT |
| 153 | name = "Follow GitHub redirects" |
| 154 | priority = 100 |
| 155 | type = "fetch" |
| 156 | } |
| 157 | |
| 158 | |
| 159 | |
| 160 | |
| 161 | # Handle 404 errors |
| 162 | snippet { |
| 163 | content = <<-EOT |
| 164 | if (obj.status == 600) { |
| 165 | set obj.status = 404; |
| 166 | set obj.http.Content-Type = "text/html"; |
| 167 | synthetic {"<h1>Not Found</h1>"}; |
| 168 | return(deliver); |
| 169 | } |
| 170 | EOT |
| 171 | name = "Handle 404 errors" |
| 172 | priority = 100 |
| 173 | type = "error" |
| 174 | } |
| 175 | |
| 176 | # Add HSTS header for security |
| 177 | header { |
| 178 | destination = "http.Strict-Transport-Security" |
| 179 | type = "response" |
| 180 | action = "set" |
| 181 | name = "Add HSTS" |
| 182 | source = "\"max-age=300\"" |
| 183 | } |
| 184 | |
| 185 | logging_s3 { |
| 186 | name = "${local.artifacts_domain}-to-s3" |
| 187 | bucket_name = local.fastlylogs["bucket_name"] |
| 188 | compression_codec = "zstd" |
| 189 | domain = local.fastlylogs["s3_domain"] |
| 190 | format = local.fastlylogs["format"] |
| 191 | format_version = 2 |
| 192 | path = "${local.artifacts_domain}/" |
| 193 | period = local.fastlylogs["period"] |
| 194 | message_type = "blank" |
| 195 | s3_iam_role = local.fastlylogs["iam_role_arn"] |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | resource "fastly_tls_subscription" "artifacts" { |
| 200 | domains = [for domain in fastly_service_vcl.artifacts.domain : domain.name] |
| 201 | configuration_id = local.fastly_tls13_quic_configuration_id |
| 202 | certificate_authority = "lets-encrypt" |
| 203 | } |
| 204 | |
| 205 | output "artifacts-managed_dns_challenge" { |
| 206 | value = fastly_tls_subscription.artifacts.managed_dns_challenges |
| 207 | } |