diff --git a/ansible/files/Caddyfile b/ansible/files/Caddyfile index 1e35bbe..cc87894 100644 --- a/ansible/files/Caddyfile +++ b/ansible/files/Caddyfile @@ -48,3 +48,35 @@ yum.{$DOMAIN_NAME} { redir https://storage.googleapis.com/{$GCLOUD_BUCKET_PREFIX}-server-edition-yum-repo/versions/{$YUM_LATEST_VERSION}/public{uri} } } + +apt-archive.{$DOMAIN_NAME} { + tls { + dns azure { + tenant_id {$AZURE_TENANT_ID} + subscription_id {$AZURE_SUBSCRIPTION_ID} + resource_group_name fullstaq-ruby-infra-maintainers + client_id {$AZURE_DNS_UPDATER_CLIENT_ID} + client_secret {$AZURE_DNS_UPDATER_CLIENT_SECRET} + } + } + encode gzip + handle { + redir https://storage.googleapis.com/{$GCLOUD_BUCKET_PREFIX}-server-edition-apt-archive-repo/versions/{$APT_ARCHIVE_LATEST_VERSION}/public{uri} + } +} + +yum-archive.{$DOMAIN_NAME} { + tls { + dns azure { + tenant_id {$AZURE_TENANT_ID} + subscription_id {$AZURE_SUBSCRIPTION_ID} + resource_group_name fullstaq-ruby-infra-maintainers + client_id {$AZURE_DNS_UPDATER_CLIENT_ID} + client_secret {$AZURE_DNS_UPDATER_CLIENT_SECRET} + } + } + encode gzip + handle { + redir https://storage.googleapis.com/{$GCLOUD_BUCKET_PREFIX}-server-edition-yum-archive-repo/versions/{$YUM_ARCHIVE_LATEST_VERSION}/public{uri} + } +} diff --git a/ansible/files/query-latest-repo-versions.rb b/ansible/files/query-latest-repo-versions.rb index 3a87c42..8a61300 100755 --- a/ansible/files/query-latest-repo-versions.rb +++ b/ansible/files/query-latest-repo-versions.rb @@ -6,11 +6,14 @@ def main open_io(ARGV[0]) do |io| query_repo_version('apt', io) query_repo_version('yum', io) + query_repo_version('apt-archive', io, allow_missing: true) + query_repo_version('yum-archive', io, allow_missing: true) io.puts "REPO_QUERY_TIME=#{Time.now.to_f}" end end -def query_repo_version(type, output) +def query_repo_version(type, output, allow_missing: false) + env_key = type.upcase.tr('-', '_') uri = URI("https://storage.googleapis.com/#{require_env(:GCLOUD_BUCKET_PREFIX)}-server-edition-#{type}-repo/versions/latest_version.txt") STDERR.puts "Querying #{uri}..." @@ -22,11 +25,21 @@ def query_repo_version(type, output) end if resp.code.to_i / 100 != 2 + # Archive buckets may legitimately not be populated before the first + # migration runs. Only treat 404 as the "not yet populated" case; any + # other non-2xx (auth, 5xx, redirects) is a real failure and must surface + # — silently falling back to version 0 would point clients at + # /versions/0/... 404s. + if allow_missing && resp.code.to_i == 404 + STDERR.puts "Warning: #{type} repo not found (404), skipping" + output.puts "#{env_key}_LATEST_VERSION=0" + return + end abort("Failed to query #{uri}: #{resp.code} #{resp.body}") end STDERR.puts "#{type} latest version: #{resp.body}" - output.puts "#{type.upcase}_LATEST_VERSION=#{resp.body}" + output.puts "#{env_key}_LATEST_VERSION=#{resp.body}" end def open_io(path) diff --git a/docs/infrastructure-overview.md b/docs/infrastructure-overview.md index f95bab9..79abc72 100644 --- a/docs/infrastructure-overview.md +++ b/docs/infrastructure-overview.md @@ -104,6 +104,21 @@ The Server Edition's APT and YUM repositories are stored inside these buckets. T Users don't access these buckets directly. Instead, they access `apt.fullstaqruby.org` and `yum.fullstaqruby.org` (served by the Nginx web servers), which redirect to these buckets. +## Server Edition APT & YUM archive repo buckets + +- Administered by role: Infra Maintainers + +The Server Edition's APT and YUM archive repositories — frozen mirrors for end-of-life distribution packages — are stored in these buckets: + +- `fsruby-server-edition-apt-archive-repo` +- `fsruby-server-edition-yum-archive-repo` + +Both buckets are publicly readable. Unlike the live APT/YUM repo buckets, the archive buckets deliberately have **no CI write access** — the frozen-mirror invariant is enforced in IAM rather than by convention. Migration into these buckets happens out-of-band via scripts in the [server-edition repository](https://github.com/fullstaq-ruby/server-edition). + +Users access these archives via `apt-archive.fullstaqruby.org` and `yum-archive.fullstaqruby.org`, which redirect to the bucket contents. Each archive subdomain has its own Azure DNS zone, delegated via NS records in the `fullstaqruby.org` apex zone, with A/AAAA records pointing at the backend server. + +Before the first migration completes, the `latest_version.txt` file in each archive bucket may not yet exist. The `query-latest-repo-versions.rb` script handles this case explicitly: archive buckets that return 404 fall back to version 0 (`APT_ARCHIVE_LATEST_VERSION=0` / `YUM_ARCHIVE_LATEST_VERSION=0`), allowing the web server to start cleanly. Any other non-2xx response is still treated as a hard failure. + ## Container registry - Administered by role: Infra Maintainers diff --git a/terraform/dns.tf b/terraform/dns.tf index eb72fc0..18c973f 100644 --- a/terraform/dns.tf +++ b/terraform/dns.tf @@ -149,3 +149,75 @@ resource "azurerm_dns_aaaa_record" "yum" { records = [var.backend_server_ipv6] ttl = 86400 } + + +resource "azurerm_dns_zone" "apt-archive" { + name = "apt-archive.${var.dns_name}" + resource_group_name = "fullstaq-ruby-infra-maintainers" +} + +resource "azurerm_role_assignment" "caddy-update-dns-apt-archive" { + scope = azurerm_dns_zone.apt-archive.id + role_definition_name = "DNS Zone Contributor" + principal_id = azuread_service_principal.caddy.object_id +} + +resource "azurerm_dns_ns_record" "apt-archive" { + name = "apt-archive" + zone_name = azurerm_dns_zone.website.name + resource_group_name = azurerm_dns_zone.website.resource_group_name + ttl = 86400 + records = azurerm_dns_zone.apt-archive.name_servers +} + +resource "azurerm_dns_a_record" "apt-archive" { + name = "@" + zone_name = azurerm_dns_zone.apt-archive.name + resource_group_name = azurerm_dns_zone.apt-archive.resource_group_name + records = [var.backend_server_ipv4] + ttl = 86400 +} + +resource "azurerm_dns_aaaa_record" "apt-archive" { + name = "@" + zone_name = azurerm_dns_zone.apt-archive.name + resource_group_name = azurerm_dns_zone.apt-archive.resource_group_name + records = [var.backend_server_ipv6] + ttl = 86400 +} + + +resource "azurerm_dns_zone" "yum-archive" { + name = "yum-archive.${var.dns_name}" + resource_group_name = "fullstaq-ruby-infra-maintainers" +} + +resource "azurerm_role_assignment" "caddy-update-dns-yum-archive" { + scope = azurerm_dns_zone.yum-archive.id + role_definition_name = "DNS Zone Contributor" + principal_id = azuread_service_principal.caddy.object_id +} + +resource "azurerm_dns_ns_record" "yum-archive" { + name = "yum-archive" + zone_name = azurerm_dns_zone.website.name + resource_group_name = azurerm_dns_zone.website.resource_group_name + ttl = 86400 + records = azurerm_dns_zone.yum-archive.name_servers +} + +resource "azurerm_dns_a_record" "yum-archive" { + name = "@" + zone_name = azurerm_dns_zone.yum-archive.name + resource_group_name = azurerm_dns_zone.yum-archive.resource_group_name + records = [var.backend_server_ipv4] + ttl = 86400 +} + +resource "azurerm_dns_aaaa_record" "yum-archive" { + name = "@" + zone_name = azurerm_dns_zone.yum-archive.name + resource_group_name = azurerm_dns_zone.yum-archive.resource_group_name + records = [var.backend_server_ipv6] + ttl = 86400 +} diff --git a/terraform/repo_buckets.tf b/terraform/repo_buckets.tf index 26fc536..09d6050 100644 --- a/terraform/repo_buckets.tf +++ b/terraform/repo_buckets.tf @@ -38,3 +38,32 @@ resource "google_storage_bucket_iam_binding" "server-edition-yum-repo-writable-b role = "roles/storage.objectAdmin" members = ["principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github-ci-deploy.name}/attribute.repository/fullstaq-ruby/server-edition"] } + +resource "google_storage_bucket" "server-edition-apt-archive-repo" { + depends_on = [google_project_service.storage-api] + name = "${var.gcloud_bucket_prefix}-server-edition-apt-archive-repo" + force_destroy = true + uniform_bucket_level_access = true + location = var.gcloud_storage_location +} + +resource "google_storage_bucket_iam_binding" "server-edition-apt-archive-repo-public-viewable" { + bucket = google_storage_bucket.server-edition-apt-archive-repo.self_link + role = "roles/storage.objectViewer" + members = ["allUsers"] +} + + +resource "google_storage_bucket" "server-edition-yum-archive-repo" { + depends_on = [google_project_service.storage-api] + name = "${var.gcloud_bucket_prefix}-server-edition-yum-archive-repo" + force_destroy = true + uniform_bucket_level_access = true + location = var.gcloud_storage_location +} + +resource "google_storage_bucket_iam_binding" "server-edition-yum-archive-repo-public-viewable" { + bucket = google_storage_bucket.server-edition-yum-archive-repo.self_link + role = "roles/storage.objectViewer" + members = ["allUsers"] +}