# Remove flat-slot binaries belonging to ABI-risky packages, then re-index. # # `upload_single_binary()` writes an ABI-risky package only to the per-minor # slot, so a risky package should have no flat binary at all. Packages built # before they were classified risky still do, and the flat slot is what every # client that cannot be routed by R minor receives. That binary was built under # whichever R was primary at the time, so it dies at load under any minor that # dropped a symbol it uses: # # base64enc.so: undefined symbol: SETLENGTH # # Deleting the flat object does not create a hole. The source fallback # republishes the CRAN source under the same key, so the package stays # installable everywhere and compiles against the client's own R, while routed # clients keep getting the per-minor binary. # # Dry run unless PRUNE_APPLY is 'yes'. Start with an explicit package list: # # crow pipeline create devxy/build-cran-binaries \ # --var prune_flat=ubuntu-2604-amd64 \ # --var PRUNE_PACKAGES="base64enc lobstr" \ # --var PRUNE_APPLY=yes variables: # A manual pipeline creation instantiates every file in .crow/, so the default # must match no matrix row. prune_flat: description: "Prune target: a specific -, 'all' for every slot, or 'none'." options: - none - all - alpine-322-amd64 - alpine-322-arm64 - alpine-323-amd64 - alpine-323-arm64 - alpine-324-amd64 - alpine-324-arm64 - redhat-8-amd64 - redhat-8-arm64 - redhat-9-amd64 - redhat-9-arm64 - redhat-10-amd64 - redhat-10-arm64 - ubuntu-2204-amd64 - ubuntu-2204-arm64 - ubuntu-2404-amd64 - ubuntu-2404-arm64 - ubuntu-2604-amd64 - ubuntu-2604-arm64 default: none PRUNE_PACKAGES: description: 'Space-separated package names. Empty means every package the classifier cache calls risky.' default: '' PRUNE_APPLY: description: "Set to 'yes' to actually delete. Anything else is a dry run." options: - 'no' - 'yes' default: 'no' when: - event: manual evaluate: 'prune_flat == "all" || prune_flat == "${OS}-${ARCH}"' skip_clone: true labels: group: rpkgs-${ARCH} matrix: include: - OS: alpine-322 ARCH: amd64 CODENAME: alpine322 IMG: alpine:3.22 - OS: alpine-322 ARCH: arm64 CODENAME: alpine322 IMG: alpine:3.22 - OS: alpine-323 ARCH: amd64 CODENAME: alpine323 IMG: alpine:3.23 - OS: alpine-323 ARCH: arm64 CODENAME: alpine323 IMG: alpine:3.23 - OS: alpine-324 ARCH: amd64 CODENAME: alpine324 IMG: alpine:3.24 - OS: alpine-324 ARCH: arm64 CODENAME: alpine324 IMG: alpine:3.24 - OS: redhat-8 ARCH: amd64 CODENAME: rhel8 IMG: redhat:8 - OS: redhat-8 ARCH: arm64 CODENAME: rhel8 IMG: redhat:8 - OS: redhat-9 ARCH: amd64 CODENAME: rhel9 IMG: redhat:9 - OS: redhat-9 ARCH: arm64 CODENAME: rhel9 IMG: redhat:9 - OS: redhat-10 ARCH: amd64 CODENAME: rhel10 IMG: redhat:10 - OS: redhat-10 ARCH: arm64 CODENAME: rhel10 IMG: redhat:10 - OS: ubuntu-2204 ARCH: amd64 CODENAME: jammy IMG: ubuntu:jammy - OS: ubuntu-2204 ARCH: arm64 CODENAME: jammy IMG: ubuntu:jammy - OS: ubuntu-2404 ARCH: amd64 CODENAME: noble IMG: ubuntu:noble - OS: ubuntu-2404 ARCH: arm64 CODENAME: noble IMG: ubuntu:noble - OS: ubuntu-2604 ARCH: amd64 CODENAME: resolute IMG: ubuntu:resolute - OS: ubuntu-2604 ARCH: arm64 CODENAME: resolute IMG: ubuntu:resolute steps: - name: 'Prune stale flat binaries' image: reg.devxy.io/rpkgs/build-env-${IMG} pull: true environment: OTEL_R_TRACES_EXPORTER: none OTEL_R_LOGS_EXPORTER: none OTEL_R_METRICS_EXPORTER: none B2_S3_ACCESS_KEY: from_secret: B2_S3_ACCESS_KEY B2_S3_SECRET_KEY: from_secret: B2_S3_SECRET_KEY PGPASS: from_secret: PGPASS REPO_RO_TOKEN: from_secret: REPO_RO_TOKEN GIT_USER: pat-s R_LIBS_USER: /tmp/R-pkgs # Pipeline variables are substituted into the YAML, not exported, so they # have to be bound here to be readable as env vars in the script below. PRUNE_ARCH: ${ARCH} PRUNE_CODENAME: ${CODENAME} PRUNE_PACKAGES: ${PRUNE_PACKAGES} PRUNE_APPLY: ${PRUNE_APPLY} commands: - git clone -q https://pat-s:$$REPO_RO_TOKEN@git.devxy.io/devxy/build-cran-binaries.git . - mkdir -p /tmp/R-pkgs - /opt/R/4.5.3/bin/Rscript local/install-bincraft.R - | set -eu APPLY="" if [ "$$PRUNE_APPLY" = "yes" ]; then APPLY="--apply" fi /opt/R/4.5.3/bin/Rscript local/prune-stale-flat-binaries.R \ "$$PRUNE_ARCH" "$$PRUNE_CODENAME" $$APPLY $$PRUNE_PACKAGES backend_options: docker: resources: requests: memory: 3Gi cpu: 1000m # Deleting objects without re-indexing is worse than leaving them: the # published index keeps naming files that no longer exist, so every client # gets a 404 instead of a working source fallback. The re-index is what # republishes them as CRAN source, so it belongs in the same run. - name: 'Re-index the slot' image: reg.devxy.io/rpkgs/build-env-${IMG} pull: true environment: OTEL_R_TRACES_EXPORTER: none OTEL_R_LOGS_EXPORTER: none OTEL_R_METRICS_EXPORTER: none B2_S3_ACCESS_KEY: from_secret: B2_S3_ACCESS_KEY B2_S3_SECRET_KEY: from_secret: B2_S3_SECRET_KEY PGPASS: from_secret: PGPASS R_LIBS_USER: /tmp/R-pkgs PRUNE_APPLY: ${PRUNE_APPLY} commands: - | set -eu if [ "$$PRUNE_APPLY" != "yes" ]; then echo "Dry run: nothing was deleted, so there is nothing to re-index." exit 0 fi /opt/R/4.5.3/bin/R -q -e 'library(bincraft); upload_package_index(s3_endpoint = "https://s3.eu-central-003.backblazeb2.com", s3_region = "eu-central-003", s3_bucket = "devxy-rpkgs-binaries", s3_access_key_id = Sys.getenv("B2_S3_ACCESS_KEY"), s3_secret_access_key = Sys.getenv("B2_S3_SECRET_KEY"))' for RBIN in /opt/R/[0-9]*/bin/R; do RMINOR=$(basename "$(dirname "$(dirname "$RBIN")")" | cut -d. -f1-2) /opt/R/4.5.3/bin/R -q -e "library(bincraft); upload_package_index(r_minor = '$RMINOR', s3_endpoint = 'https://s3.eu-central-003.backblazeb2.com', s3_region = 'eu-central-003', s3_bucket = 'devxy-rpkgs-binaries', s3_access_key_id = Sys.getenv('B2_S3_ACCESS_KEY'), s3_secret_access_key = Sys.getenv('B2_S3_SECRET_KEY'))" || true done backend_options: docker: resources: requests: memory: 3Gi cpu: 1000m # The edge caches indexes for ~370 days, so without this the deletion is # invisible to clients and they keep being handed the removed binary. - name: 'Purge CDN cache' image: reg.devxy.io/docker.io/library/alpine:3.24 environment: BUNNYNET_API_KEY: from_secret: BUNNYNET_API_KEY # Same zones as reindex.yaml: the production pull zone id, the alliance # hostname and the staging zone, which shares the B2 origin. BUNNY_PULLZONES: '3857050 cran.allianceswisspass.devxy.io cran-rpkgs-test.b-cdn.net' PRUNE_APPLY: ${PRUNE_APPLY} commands: - | set -eu if [ "$$PRUNE_APPLY" != "yes" ]; then echo "Dry run: nothing changed at the origin, so no purge is needed." exit 0 fi apk add --no-cache -q bash curl jq bash scripts/purge_cdn_zone.sh "$BUNNYNET_API_KEY" $BUNNY_PULLZONES