Skip to content
Open
32 changes: 32 additions & 0 deletions ansible/files/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
}
17 changes: 15 additions & 2 deletions ansible/files/query-latest-repo-versions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}..."
Expand All @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions docs/infrastructure-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions terraform/dns.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
29 changes: 29 additions & 0 deletions terraform/repo_buckets.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}