devxy/build-cran-binaries
Watch
1
0
Fork
You've already forked build-cran-binaries
0
build-cran-binaries/cdn.tf
pat-s 2614d40bd9 feat(stats): collect CDN access logs and publish download statistics
Add an hourly download-stats pipeline that reads the cran-rpkgs Bunny
access logs into a download_stats schema in the build metadata database
and publishes aggregated counts to /stats/downloads.json for the
rpkgs.com /stats page.

- Classify each request (download, index, redirect reason, probe, noise)
  mirroring the edge router, and split cloud/CI networks from the rest.
- De-duplicate downloads per client, package version and day, and key
  clients on a salted HMAC so no IP is stored or published.
- Exclude our own CI runners and record run windows to show gaps.
- Declare the zone's logging settings and allow CORS for JSON.
2026-11-14 13:02:53 +01:00

355 lines
11 KiB
HCL

# https://registry.terraform.io/providers/BunnyWay/bunnynet/latest/docs/resources/pullzone
# terraform import bunnynet_pullzone.devxy-r-binaries cran
# resource "bunnynet_pullzone" "devxy-r-binaries" {
# name = "cran"
# origin {
# type = "OriginUrl"
# url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
# }
# routing {
# tier = "Standard"
# }
# s3_auth_enabled = true
# s3_auth_key = var.B2_S3_ACCESS_KEY
# s3_auth_secret = var.B2_S3_SECRET_KEY
# s3_auth_region = "eu-central-003"
# cache_enabled = true
# cache_errors = true
# request_coalescing_enabled = true
# block_post_requests = true
# limit_requests = 500
# limit_connections = 50
# safehop_enabled = true
# add_canonical_header = true
# cache_stale = ["offline", "updating"]
# use_background_update = true
# block_ips = var.cdn_block_ips
# # 50 TB
# limit_bandwidth = 50000000000000
# permacache_storagezone = bunnynet_storage_zone.devxy-r-binaries.id
# # rec from docs: https://www.backblaze.com/docs/cloud-storage-integrate-bunnynet-with-backblaze-b2
# block_root_path = true
# }
# resource "bunnynet_pullzone_hostname" "devxy-r-binaries" {
# pullzone = bunnynet_pullzone.devxy-r-binaries.id
# name = "cran.devxy.io"
# force_ssl = true
# tls_enabled = true
# }
### cran.rpkgs.com
locals {
rpkgs_slots = [
for pair in setproduct(
["amd64", "arm64"],
["resolute", "noble", "jammy", "rhel8", "rhel9", "rhel10", "alpine323", "alpine324"]
) : "${pair[0]}/${pair[1]}"
]
# The supported R minors: the current one plus the two previous, which is
# exactly what build-env-images installs as R_VERSION_LATEST / PREV1 / PREV2.
# These must stay in step. A minor listed here without a published index
# sends those clients to a 404; a published minor missing from this list
# sends them to CRAN for sources instead of serving the binaries we built.
rpkgs_supported_minors = ["4.4", "4.5", "4.6"]
# bunny.net serves every pull zone on <name>.b-cdn.net, so staging needs no
# DNS record and is never advertised.
rpkgs_test_hostname = "cran-rpkgs-test.b-cdn.net"
}
# The edge middleware that resolves the bare cran.rpkgs.com form to an
# <arch>/<os> slot and routes PACKAGES* to the per-R-minor slot. The source of
# truth is edge/rpkgs-router.ts; `tofu apply` publishes a new release.
#
# The script pre-dates this configuration, so it is adopted rather than created:
# tofu import bunnynet_compute_script.rpkgs_router 29277
resource "bunnynet_compute_script" "rpkgs_router" {
type = "middleware"
name = "rpkgs-router"
content = file("${path.module}/edge/rpkgs-router.ts")
}
# Slots ("<arch>/<os>", comma separated) whose per-minor index bincraft has
# already republished as a union of the per-minor and flat slots. Routing to a
# slot that is not listed here would hide every package the per-minor index does
# not carry, so this stays empty until a slot has been backfilled.
resource "bunnynet_compute_script_variable" "rpkgs_router_union_slots" {
script = bunnynet_compute_script.rpkgs_router.id
name = "UNION_SLOTS"
# Enabled. Until this was set, every client resolved against the generic
# index and never reached a per-minor binary: an R 4.6.1 client on resolute
# downloaded the 4.5-built rlang (2079570 bytes) while the correct 4.6 build
# (2075106 bytes) sat unused one directory away, and died at load with
# `undefined symbol: SETLENGTH`.
#
# Verified before enabling, against the staging zone with the same script and
# the same origin: all 16 slots report zero regressions against the generic
# slot, an excluded R minor is sent to CRAN, a client without an R minor
# still gets the generic index, and tarball requests are never rewritten.
default_value = join(",", local.rpkgs_slots)
required = false
}
resource "bunnynet_compute_script_variable" "rpkgs_router_known_minors" {
script = bunnynet_compute_script.rpkgs_router.id
name = "KNOWN_MINORS"
default_value = join(",", local.rpkgs_supported_minors)
required = false
}
resource "bunnynet_pullzone" "cran_rpkgs_com" {
name = "cran-rpkgs"
cache_errors = false
cache_expiration_time = 31919000
websockets_enabled = false
errorpage_whitelabel = true
origin {
type = "OriginUrl"
url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
middleware_script = bunnynet_compute_script.rpkgs_router.id
}
routing {
filters = [
"scripting",
]
}
s3_auth_enabled = true
s3_auth_key = var.B2_S3_ACCESS_KEY
s3_auth_secret = var.B2_S3_SECRET_KEY
s3_auth_region = "eu-central-003"
cache_enabled = true
request_coalescing_enabled = true
block_post_requests = true
# Set on the zone since before this configuration existed; declared here so
# `tofu apply` stops silently removing it.
#
# The router makes it redundant on paper: the only UA-dependent responses it
# produces are redirects, and those carry `Cache-Control: no-store`, while
# their targets are concrete per-slot, per-minor URLs whose content depends
# only on the path. Dropping it would also be a real win, because otherwise
# every distinct R version string keys its own copy of every tarball.
#
# It stays for now anyway: it is the second line of defence against the one
# failure that would be quiet and confusing (an R 4.6 client served the 4.5
# index), and removing it is worth doing on its own once per-minor routing is
# confirmed live, not as a side effect of enabling that routing.
cache_vary_headers = ["User-Agent"]
limit_requests = 5000
limit_connections = 1000
safehop_enabled = true
add_canonical_header = true
# Access logs feed the public download statistics (`stats/`, published to
# `/stats/downloads.json`). Both settings were already on; declaring them
# keeps an apply from quietly switching off the only source of that data.
# Bunny keeps the logs for three days, which is why `download-stats` runs on
# a schedule. IPs stay anonymized to /24 (/64 for IPv6) at the edge.
log_enabled = true
log_anonymized = true
log_anonymized_style = "OneDigit"
# The rpkgs.com website reads `/stats/downloads.json` from the browser, so
# JSON needs the CORS header too. The rest is the zone's existing list.
cors_enabled = true
cors_extensions = [
"css", "eot", "gif", "jpeg", "jpg", "js", "json", "mp3", "mp4", "mpeg", "png", "svg", "ttf", "webm", "webp",
"woff", "woff2",
]
cache_stale = ["offline", "updating"]
block_ips = var.cdn_block_ips
# 50 TB
limit_bandwidth = 50000000000000
# permacache_storagezone = bunnynet_storage_zone.devxy-r-binaries.id
# rec from docs: https://www.backblaze.com/docs/cloud-storage-integrate-bunnynet-with-backblaze-b2
block_root_path = true
}
resource "bunnynet_pullzone_hostname" "cran_rpkgs_com" {
pullzone = bunnynet_pullzone.cran_rpkgs_com.id
name = "cran.rpkgs.com"
force_ssl = true
tls_enabled = true
}
### Staging zone for edge-router changes
# Every published <arch>/<os> slot. The staging zone enables per-minor routing
# for all of them at once; production adopts the same list only after
# `scripts/verify-r-minor-routing.sh --live` passes against staging.
# A second copy of the same router, bound to the same B2 origin, so UNION_SLOTS
# can be exercised end to end before production is touched.
resource "bunnynet_compute_script" "rpkgs_router_test" {
type = "middleware"
name = "rpkgs-router-test"
content = file("${path.module}/edge/rpkgs-router.ts")
}
resource "bunnynet_compute_script_variable" "rpkgs_router_test_union_slots" {
script = bunnynet_compute_script.rpkgs_router_test.id
name = "UNION_SLOTS"
default_value = join(",", local.rpkgs_slots)
required = false
}
# Without this the staging zone rewrites to PUBLIC_CDN_ORIGIN, so its redirects
# land on production and the test silently measures the wrong system.
resource "bunnynet_compute_script_variable" "rpkgs_router_test_extra_hosts" {
script = bunnynet_compute_script.rpkgs_router_test.id
name = "EXTRA_PUBLIC_HOSTS"
default_value = local.rpkgs_test_hostname
required = false
}
resource "bunnynet_compute_script_variable" "rpkgs_router_test_known_minors" {
script = bunnynet_compute_script.rpkgs_router_test.id
name = "KNOWN_MINORS"
default_value = join(",", local.rpkgs_supported_minors)
required = false
}
resource "bunnynet_pullzone" "cran_rpkgs_test" {
name = "cran-rpkgs-test"
cache_errors = false
cache_expiration_time = 31919000
websockets_enabled = false
errorpage_whitelabel = true
origin {
type = "OriginUrl"
url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
middleware_script = bunnynet_compute_script.rpkgs_router_test.id
}
routing {
filters = [
"scripting",
]
}
s3_auth_enabled = true
s3_auth_key = var.B2_S3_ACCESS_KEY
s3_auth_secret = var.B2_S3_SECRET_KEY
s3_auth_region = "eu-central-003"
cache_enabled = true
request_coalescing_enabled = true
block_post_requests = true
cache_vary_headers = ["User-Agent"]
# Staging carries only synthetic verification traffic, so the production
# ceilings would be pure headroom.
limit_requests = 500
limit_connections = 100
safehop_enabled = true
add_canonical_header = true
cache_stale = ["offline", "updating"]
block_ips = var.cdn_block_ips
# 1 TB
limit_bandwidth = 1000000000000
block_root_path = true
}
# Alliance SwissPass historically used a separate, manually configured pull
# zone. Adopt it so both public repositories use the same B2 origin, middleware
# release and cache behavior.
import {
to = bunnynet_pullzone.cran_allianceswisspass
id = "3265648"
}
resource "bunnynet_pullzone" "cran_allianceswisspass" {
name = "cran-allianceswisspass"
cache_errors = false
cache_expiration_time = 31919000
websockets_enabled = false
errorpage_whitelabel = true
origin {
type = "OriginUrl"
url = "https://devxy-rpkgs-binaries.s3.eu-central-003.backblazeb2.com"
middleware_script = bunnynet_compute_script.rpkgs_router.id
}
routing {
filters = [
"scripting",
]
}
s3_auth_enabled = true
s3_auth_key = var.B2_S3_ACCESS_KEY
s3_auth_secret = var.B2_S3_SECRET_KEY
s3_auth_region = "eu-central-003"
cache_enabled = true
request_coalescing_enabled = true
block_post_requests = true
cache_vary_headers = ["User-Agent"]
limit_requests = 5000
limit_connections = 1000
safehop_enabled = true
add_canonical_header = true
cache_stale = ["offline", "updating"]
block_ips = var.cdn_block_ips
# 50 TB
limit_bandwidth = 50000000000000
block_root_path = true
}
resource "bunnynet_pullzone_hostname" "cran_allianceswisspass" {
pullzone = bunnynet_pullzone.cran_allianceswisspass.id
name = "cran.allianceswisspass.devxy.io"
force_ssl = true
tls_enabled = true
}
# resource "bunnynet_storage_zone" "devxy-r-binaries" {
# name = "devxy-r-binaries-storage"
# region = "DE"
# zone_tier = "Standard"
# # Los Angeles and Singapore
# replication_regions = ["LA", "SG"]
# }