build-cran-binaries/docs/superpowers/plans/2026-05-25-multi-r-version-images.md

1031 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Multi-R-version image refactor — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
> Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Refactor every `.crow/` workflow, the `Justfile` recipes, and the commented-out repo-root example to use the new multi-R-version images — drop `R_VERSION` from the image tag and invoke R via `/opt/R/${R_VERSION}/bin/R` instead of `R`/`Rscript` on `PATH`.
**Architecture:** Pure mechanical refactor across 64 `.crow/*.yaml` workflows, 1 `Justfile`, and 1 commented-root file.
Each workflow gets three kinds of edits: image tag (drop `-<R version>` suffix), `R_VERSION` env var (added where it wasn't present), and explicit R path substitution at every call site.
Three correctness bug fixes are folded in: alpine audits use the matching alpine image, package-index workflows use the matching platform image, and `ubuntu-2404` process-updates align to R 4.4.3.
**Tech Stack:** Woodpecker CI / `crow` (YAML workflows), `just` (Justfile), R 4.4.3 / 4.5.3.
**Spec:** [`docs/superpowers/specs/2026-05-25-multi-r-version-images-design.md`](../specs/2026-05-25-multi-r-version-images-design.md)
---
## Conventions used throughout this plan
**Platform → image + R_VERSION mapping:**
| Platform | New image | `R_VERSION` |
|--------------|-------------------------------------------------|-------------|
| alpine-322 | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| alpine-323 | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| ubuntu-2204 | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy` | 4.4.3 |
| ubuntu-2404 | `reg.devxy.io/rpkgs/build-env-ubuntu:noble` | 4.4.3 |
| redhat-8 | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| redhat-9 | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| redhat-10 | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
`alpine-321` (audit-only) has no matching new image — falls back to `build-env-alpine:3.23` + `R_VERSION: 4.5.3`.
**Editing recipe per file (pattern-1 workflows, i.e. those with a hard-coded image tag):**
1. Replace the `image:` line — drop `-<R version>` from the tag.
2. Insert `R_VERSION: <value>` into the `environment:` block. Anchor the insertion to a line that already exists in that file (typically `R_LIBS_USER:`, falling back to `GIT_USER:` for files that don't cache R libraries).
3. Replace every bare `R ` invocation in `commands:` with `/opt/R/${R_VERSION}/bin/R ` (including `xvfb-run R`, `R CMD`, etc.).
4. Replace every bare `Rscript ` invocation in `commands:` with `/opt/R/${R_VERSION}/bin/Rscript `.
**Pattern-2 workflows** (image already parameterised via `${OS}`/`${OS_VERSION}`/`${R_VERSION}`) skip step 2 — `R_VERSION` already arrives via `--var`.
**Why `replace_all` is safe:** The `Edit` tool's `replace_all` is used in the steps below only on tokens that appear nowhere except in workflow command lines (`R -q -e`, `R CMD INSTALL`, `Rscript local/`, `xvfb-run R `).
These tokens never appear inside YAML keys, env vars, comments, or quoted strings within R code in these files (verified by grep).
If a future workflow contains any of these tokens in a non-command context, switch that file to per-line targeted edits.
---
## Task 1: build-all-versions-* workflows (4 files, pattern 2)
**Files (modify):**
- `.crow/build-all-versions-amd64.yaml`
- `.crow/build-all-versions-arm64.yaml`
- `.crow/build-all-versions-install-deps-amd64.yaml`
- `.crow/build-all-versions-install-deps-arm64.yaml`
These workflows already receive `R_VERSION` from `crow pipeline create --var R_VERSION=…`.
Only the image tag and R invocations change.
- [ ] **Step 1.1: Edit `.crow/build-all-versions-amd64.yaml` — image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-${OS}:${OS_VERSION}-${R_VERSION}
new_string: image: reg.devxy.io/rpkgs/build-env-${OS}:${OS_VERSION}
```
- [ ] **Step 1.2: Edit `.crow/build-all-versions-amd64.yaml` — Rscript invocation**
Use `Edit`:
```
old_string: - $XVFB $XVFB_ARGS -n $SPLIT_INDEX -- Rscript local/build-all.R $SPLIT_INTO $SPLIT_INDEX $NCPUS 2>&1
new_string: - $XVFB $XVFB_ARGS -n $SPLIT_INDEX -- /opt/R/${R_VERSION}/bin/Rscript local/build-all.R $SPLIT_INTO $SPLIT_INDEX $NCPUS 2>&1
```
- [ ] **Step 1.3: Edit `.crow/build-all-versions-amd64.yaml` — `R -q -e` invocation**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
(The file contains one such line: `R -q -e "bincraft::process_unarchived_pkgs(...)"`.)
- [ ] **Step 1.4: Repeat steps 1.11.3 for `.crow/build-all-versions-arm64.yaml`**
The three edits are textually identical to steps 1.11.3 because the build-arm64 file uses the same parameterised image tag and the same `R -q -e` / `Rscript` invocations.
- [ ] **Step 1.5: Edit `.crow/build-all-versions-install-deps-amd64.yaml` — image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-${OS}:${OS_VERSION}-${R_VERSION}
new_string: image: reg.devxy.io/rpkgs/build-env-${OS}:${OS_VERSION}
```
- [ ] **Step 1.6: Edit `.crow/build-all-versions-install-deps-amd64.yaml` — `R -q -e` invocations**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
(The file contains four `R -q -e` lines.)
- [ ] **Step 1.7: Repeat steps 1.51.6 for `.crow/build-all-versions-install-deps-arm64.yaml`**
- [ ] **Step 1.8: Validate**
Run:
```bash
grep -nE '(^|[^/])R(script)? ' .crow/build-all-versions-*.yaml | grep -v '/opt/R/'
```
Expected: no output.
Any line returned is a missed substitution — investigate before continuing.
Run:
```bash
grep -nE 'build-env-.*\$\{OS_VERSION\}-' .crow/build-all-versions-*.yaml
```
Expected: no output.
- [ ] **Step 1.9: Commit**
```bash
git add .crow/build-all-versions-amd64.yaml .crow/build-all-versions-arm64.yaml .crow/build-all-versions-install-deps-amd64.yaml .crow/build-all-versions-install-deps-arm64.yaml
git commit -m "refactor(ci): use multi-R-version images in build-all-versions workflows
Drop -\${R_VERSION} from the image tag and invoke R/Rscript via the
explicit /opt/R/\${R_VERSION}/bin/ path."
```
---
## Task 2: process-updates workflows (14 files, pattern 1)
**Files (modify):**
| File | New image | `R_VERSION` |
|-----------------------------------------------|--------------------------------------------|-------------|
| `process-updates-alpine-322-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| `process-updates-alpine-322-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| `process-updates-alpine-323-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| `process-updates-alpine-323-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| `process-updates-ubuntu-2204-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 |
| `process-updates-ubuntu-2204-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 |
| `process-updates-ubuntu-2404-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 |
| `process-updates-ubuntu-2404-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 |
| `process-updates-redhat-8-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| `process-updates-redhat-8-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| `process-updates-redhat-9-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| `process-updates-redhat-9-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| `process-updates-redhat-10-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
| `process-updates-redhat-10-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
The two `process-updates-ubuntu-2404-*` files also bump R from `4.4` to `4.4.3` (folded-in bug fix per spec).
### Worked example: `.crow/process-updates-alpine-322-amd64.yaml`
- [ ] **Step 2.1: Edit image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.22-4.5
new_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.22
```
- [ ] **Step 2.2: Add `R_VERSION` env var**
Use `Edit` (anchor on `R_LIBS_USER:`, which appears in every process-updates file):
```
old_string: R_LIBS_USER: /mnt/cache/R-pkgs
new_string: R_LIBS_USER: /mnt/cache/R-pkgs
R_VERSION: 4.5.3
```
(Preserve the existing six-space indentation.)
- [ ] **Step 2.3: Substitute R path**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
This covers both bare `R -q -e` and `xvfb-run R -q -e` lines (the latter becomes `xvfb-run /opt/R/${R_VERSION}/bin/R -q -e`, which is correct).
- [ ] **Step 2.4: Validate this file**
Run:
```bash
grep -nE '(^|[^/])R(script)? ' .crow/process-updates-alpine-322-amd64.yaml | grep -v '/opt/R/'
```
Expected: no output.
### Apply the same three-step pattern to the remaining 13 files
- [ ] **Step 2.5: Apply to `process-updates-alpine-322-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-alpine:3.22` · `R_VERSION: 4.5.3`.
Old image tag suffix: `-4.5` (so `old_string` is `image: reg.devxy.io/rpkgs/build-env-alpine:3.22-4.5`).
- [ ] **Step 2.6: Apply to `process-updates-alpine-323-amd64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-alpine:3.23` · `R_VERSION: 4.5.3`.
Old image tag suffix: `-4.5`.
- [ ] **Step 2.7: Apply to `process-updates-alpine-323-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-alpine:3.23` · `R_VERSION: 4.5.3`.
Old image tag suffix: `-4.5`.
- [ ] **Step 2.8: Apply to `process-updates-ubuntu-2204-amd64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-ubuntu:jammy` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4.3`.
- [ ] **Step 2.9: Apply to `process-updates-ubuntu-2204-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-ubuntu:jammy` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4.3`.
- [ ] **Step 2.10: Apply to `process-updates-ubuntu-2404-amd64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-ubuntu:noble` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4` (note: `4.4` without the patch — bug fix).
- [ ] **Step 2.11: Apply to `process-updates-ubuntu-2404-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-ubuntu:noble` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4`.
- [ ] **Step 2.12: Apply to `process-updates-redhat-8-amd64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-redhat:8` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4.3`.
- [ ] **Step 2.13: Apply to `process-updates-redhat-8-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-redhat:8` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4.3`.
- [ ] **Step 2.14: Apply to `process-updates-redhat-9-amd64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-redhat:9` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4.3`.
- [ ] **Step 2.15: Apply to `process-updates-redhat-9-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-redhat:9` · `R_VERSION: 4.4.3`.
Old image tag suffix: `-4.4.3`.
- [ ] **Step 2.16: Apply to `process-updates-redhat-10-amd64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-redhat:10` · `R_VERSION: 4.5.3`.
Old image tag suffix: `-4.5.3`.
- [ ] **Step 2.17: Apply to `process-updates-redhat-10-arm64.yaml`**
Image: `reg.devxy.io/rpkgs/build-env-redhat:10` · `R_VERSION: 4.5.3`.
Old image tag suffix: `-4.5.3`.
- [ ] **Step 2.18: Validate all 14 files**
Run:
```bash
grep -nE '(^|[^/])R(script)? ' .crow/process-updates-*.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
```
Expected: no output (comments starting with `#` are filtered out).
Run:
```bash
grep -nE 'build-env-.*:[^[:space:]]*-[0-9]' .crow/process-updates-*.yaml
```
Expected: no output.
Run:
```bash
grep -nE 'R_VERSION:' .crow/process-updates-*.yaml | wc -l
```
Expected: `14` (one `R_VERSION:` per file).
- [ ] **Step 2.19: Commit**
```bash
git add .crow/process-updates-*.yaml
git commit -m "refactor(ci): use multi-R-version images in process-updates workflows
Drop the R-version suffix from each image tag, add an explicit
R_VERSION env var per file, and invoke R via
/opt/R/\${R_VERSION}/bin/R at every call site.
Also aligns ubuntu-2404 process-updates from R 4.4 to R 4.4.3,
matching the audit and rebuild counterparts."
```
---
## Task 3: weekly-rebuild-missing workflows (14 files, pattern 1)
**Files (modify):**
| File | New image | `R_VERSION` |
|---------------------------------------------------|--------------------------------------------|-------------|
| `weekly-rebuild-missing-alpine-322-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| `weekly-rebuild-missing-alpine-322-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| `weekly-rebuild-missing-alpine-323-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| `weekly-rebuild-missing-alpine-323-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| `weekly-rebuild-missing-ubuntu-2204-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 |
| `weekly-rebuild-missing-ubuntu-2204-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 |
| `weekly-rebuild-missing-ubuntu-2404-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 |
| `weekly-rebuild-missing-ubuntu-2404-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 |
| `weekly-rebuild-missing-redhat-8-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| `weekly-rebuild-missing-redhat-8-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| `weekly-rebuild-missing-redhat-9-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| `weekly-rebuild-missing-redhat-9-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| `weekly-rebuild-missing-redhat-10-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
| `weekly-rebuild-missing-redhat-10-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
### Worked example: `.crow/weekly-rebuild-missing-alpine-322-amd64.yaml`
- [ ] **Step 3.1: Edit image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.22-4.5
new_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.22
```
- [ ] **Step 3.2: Add `R_VERSION` env var**
Use `Edit` (anchor on `R_LIBS_USER:`):
```
old_string: R_LIBS_USER: /mnt/cache/R-pkgs
new_string: R_LIBS_USER: /mnt/cache/R-pkgs
R_VERSION: 4.5.3
```
- [ ] **Step 3.3: Substitute R path**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
(Also covers `$XVFB $XVFB_ARGS -- R -q -e ...` since the matched substring becomes `/opt/R/${R_VERSION}/bin/R -q -e`.)
- [ ] **Step 3.4: Validate this file**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/weekly-rebuild-missing-alpine-322-amd64.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
```
Expected: no output.
### Apply the same three-step pattern to the remaining 13 files
- [ ] **Step 3.5: Apply to `weekly-rebuild-missing-alpine-322-arm64.yaml`** (image alpine:3.22, R_VERSION 4.5.3, old suffix `-4.5`)
- [ ] **Step 3.6: Apply to `weekly-rebuild-missing-alpine-323-amd64.yaml`** (image alpine:3.23, R_VERSION 4.5.3, old suffix `-4.5`)
- [ ] **Step 3.7: Apply to `weekly-rebuild-missing-alpine-323-arm64.yaml`** (image alpine:3.23, R_VERSION 4.5.3, old suffix `-4.5`)
- [ ] **Step 3.8: Apply to `weekly-rebuild-missing-ubuntu-2204-amd64.yaml`** (image ubuntu:jammy, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.9: Apply to `weekly-rebuild-missing-ubuntu-2204-arm64.yaml`** (image ubuntu:jammy, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.10: Apply to `weekly-rebuild-missing-ubuntu-2404-amd64.yaml`** (image ubuntu:noble, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.11: Apply to `weekly-rebuild-missing-ubuntu-2404-arm64.yaml`** (image ubuntu:noble, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.12: Apply to `weekly-rebuild-missing-redhat-8-amd64.yaml`** (image redhat:8, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.13: Apply to `weekly-rebuild-missing-redhat-8-arm64.yaml`** (image redhat:8, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.14: Apply to `weekly-rebuild-missing-redhat-9-amd64.yaml`** (image redhat:9, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.15: Apply to `weekly-rebuild-missing-redhat-9-arm64.yaml`** (image redhat:9, R_VERSION 4.4.3, old suffix `-4.4.3`)
- [ ] **Step 3.16: Apply to `weekly-rebuild-missing-redhat-10-amd64.yaml`** (image redhat:10, R_VERSION 4.5.3, old suffix `-4.5.3`)
- [ ] **Step 3.17: Apply to `weekly-rebuild-missing-redhat-10-arm64.yaml`** (image redhat:10, R_VERSION 4.5.3, old suffix `-4.5.3`)
- [ ] **Step 3.18: Validate all 14 files**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/weekly-rebuild-missing-*.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
grep -nE 'build-env-.*:[^[:space:]]*-[0-9]' .crow/weekly-rebuild-missing-*.yaml
grep -nE 'R_VERSION:' .crow/weekly-rebuild-missing-*.yaml | wc -l
```
Expected: first two return no output; the third returns `14`.
- [ ] **Step 3.19: Commit**
```bash
git add .crow/weekly-rebuild-missing-*.yaml
git commit -m "refactor(ci): use multi-R-version images in weekly-rebuild-missing workflows
Drop the R-version suffix from each image tag, add an explicit
R_VERSION env var per file, and invoke R via
/opt/R/\${R_VERSION}/bin/R at every call site."
```
---
## Task 4: weekly-audit-missing workflows (16 files, pattern 1)
This task folds in the **alpine audit bug fix**: all six `weekly-audit-missing-alpine-*` files currently point at `alpine:3.23-4.5` regardless of platform, even when the platform is alpine-321 or alpine-322.
**Files (modify):**
| File | New image | `R_VERSION` | Old tag |
|---------------------------------------------------|--------------------------------------------|-------------|---------|
| `weekly-audit-missing-alpine-321-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 | `3.23-4.5` |
| `weekly-audit-missing-alpine-321-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 | `3.23-4.5` |
| `weekly-audit-missing-alpine-322-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 | `3.23-4.5` (bug fix: was wrong) |
| `weekly-audit-missing-alpine-322-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 | `3.23-4.5` (bug fix) |
| `weekly-audit-missing-alpine-323-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 | `3.23-4.5` |
| `weekly-audit-missing-alpine-323-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 | `3.23-4.5` |
| `weekly-audit-missing-ubuntu-2204-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 | `jammy-4.4.3` |
| `weekly-audit-missing-ubuntu-2204-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 | `jammy-4.4.3` |
| `weekly-audit-missing-ubuntu-2404-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 | `noble-4.4.3` |
| `weekly-audit-missing-ubuntu-2404-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 | `noble-4.4.3` |
| `weekly-audit-missing-redhat-8-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 | `8-4.4.3` |
| `weekly-audit-missing-redhat-8-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 | `8-4.4.3` |
| `weekly-audit-missing-redhat-9-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 | `9-4.4.3` |
| `weekly-audit-missing-redhat-9-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 | `9-4.4.3` |
| `weekly-audit-missing-redhat-10-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 | `10-4.5.3` |
| `weekly-audit-missing-redhat-10-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 | `10-4.5.3` |
### Worked example: `.crow/weekly-audit-missing-alpine-322-amd64.yaml` (includes bug fix)
- [ ] **Step 4.1: Edit image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.23-4.5
new_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.22
```
Note: this both drops the R version AND fixes the OS-version mismatch (was 3.23, should be 3.22).
- [ ] **Step 4.2: Add `R_VERSION` env var**
Use `Edit` (anchor on `R_LIBS_USER:`, which appears in every weekly-audit-missing file):
```
old_string: R_LIBS_USER: /mnt/cache/R-pkgs
new_string: R_LIBS_USER: /mnt/cache/R-pkgs
R_VERSION: 4.5.3
```
- [ ] **Step 4.3: Substitute R path**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
- [ ] **Step 4.4: Validate this file**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/weekly-audit-missing-alpine-322-amd64.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
```
Expected: no output.
### Apply the same three-step pattern to the remaining 15 files
For each file, use its row in the table above to get the new image and `R_VERSION`.
The `old_string` for the image tag edit is `image: reg.devxy.io/rpkgs/build-env-<old tag>`, where `<old tag>` is the "Old tag" column.
- [ ] **Step 4.5: Apply to `weekly-audit-missing-alpine-321-amd64.yaml`** (new image alpine:3.23, R_VERSION 4.5.3, old tag suffix `-4.5`)
- [ ] **Step 4.6: Apply to `weekly-audit-missing-alpine-321-arm64.yaml`** (alpine:3.23, 4.5.3, old `-4.5`)
- [ ] **Step 4.7: Apply to `weekly-audit-missing-alpine-322-arm64.yaml`** (alpine:3.22, 4.5.3, old `-4.5` — image bug fix)
- [ ] **Step 4.8: Apply to `weekly-audit-missing-alpine-323-amd64.yaml`** (alpine:3.23, 4.5.3, old `-4.5`)
- [ ] **Step 4.9: Apply to `weekly-audit-missing-alpine-323-arm64.yaml`** (alpine:3.23, 4.5.3, old `-4.5`)
- [ ] **Step 4.10: Apply to `weekly-audit-missing-ubuntu-2204-amd64.yaml`** (ubuntu:jammy, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.11: Apply to `weekly-audit-missing-ubuntu-2204-arm64.yaml`** (ubuntu:jammy, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.12: Apply to `weekly-audit-missing-ubuntu-2404-amd64.yaml`** (ubuntu:noble, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.13: Apply to `weekly-audit-missing-ubuntu-2404-arm64.yaml`** (ubuntu:noble, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.14: Apply to `weekly-audit-missing-redhat-8-amd64.yaml`** (redhat:8, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.15: Apply to `weekly-audit-missing-redhat-8-arm64.yaml`** (redhat:8, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.16: Apply to `weekly-audit-missing-redhat-9-amd64.yaml`** (redhat:9, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.17: Apply to `weekly-audit-missing-redhat-9-arm64.yaml`** (redhat:9, 4.4.3, old `-4.4.3`)
- [ ] **Step 4.18: Apply to `weekly-audit-missing-redhat-10-amd64.yaml`** (redhat:10, 4.5.3, old `-4.5.3`)
- [ ] **Step 4.19: Apply to `weekly-audit-missing-redhat-10-arm64.yaml`** (redhat:10, 4.5.3, old `-4.5.3`)
- [ ] **Step 4.20: Validate all 16 files**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/weekly-audit-missing-*.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
grep -nE 'build-env-.*:[^[:space:]]*-[0-9]' .crow/weekly-audit-missing-*.yaml
grep -nE 'R_VERSION:' .crow/weekly-audit-missing-*.yaml | wc -l
```
Expected: first two return no output; the third returns `16`.
Also verify the alpine bug fix took effect:
```bash
grep -E 'image:' .crow/weekly-audit-missing-alpine-*.yaml
```
Expected:
```
.crow/weekly-audit-missing-alpine-321-amd64.yaml: image: reg.devxy.io/rpkgs/build-env-alpine:3.23
.crow/weekly-audit-missing-alpine-321-arm64.yaml: image: reg.devxy.io/rpkgs/build-env-alpine:3.23
.crow/weekly-audit-missing-alpine-322-amd64.yaml: image: reg.devxy.io/rpkgs/build-env-alpine:3.22
.crow/weekly-audit-missing-alpine-322-arm64.yaml: image: reg.devxy.io/rpkgs/build-env-alpine:3.22
.crow/weekly-audit-missing-alpine-323-amd64.yaml: image: reg.devxy.io/rpkgs/build-env-alpine:3.23
.crow/weekly-audit-missing-alpine-323-arm64.yaml: image: reg.devxy.io/rpkgs/build-env-alpine:3.23
```
- [ ] **Step 4.21: Commit**
```bash
git add .crow/weekly-audit-missing-*.yaml
git commit -m "refactor(ci): use multi-R-version images in weekly-audit-missing workflows
Drop the R-version suffix from each image tag, add an explicit
R_VERSION env var per file, and invoke R via
/opt/R/\${R_VERSION}/bin/R at every call site.
Also fixes the alpine-322 audit image, which was previously pointing
at alpine:3.23 instead of alpine:3.22. The alpine-321 audits stay on
alpine:3.23 since no 3.21 image exists in the new scheme — they only
query S3/CRAN, so the container OS does not affect correctness."
```
---
## Task 5: update-package-index workflows (14 files, pattern 1)
This task folds in the **package-index image bug fix**: every `update-package-index-*` file currently uses `build-env-ubuntu:noble-4.4` (or `noble-4.5` in the redhat-10 case) regardless of which platform's package index it uploads.
After the refactor, each file uses the image that matches its own platform per the mapping table.
The first step (`Upload PACKAGES files`) needs the refactor; the second step (`Purge CDN cache`) uses `alpine:3.23` directly, has no R calls, and is unchanged.
**Files (modify):**
| File | New image | `R_VERSION` |
|---------------------------------------------------|--------------------------------------------|-------------|
| `update-package-index-alpine-322-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| `update-package-index-alpine-322-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.22` | 4.5.3 |
| `update-package-index-alpine-323-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| `update-package-index-alpine-323-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-alpine:3.23` | 4.5.3 |
| `update-package-index-ubuntu-2204-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 |
| `update-package-index-ubuntu-2204-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:jammy`| 4.4.3 |
| `update-package-index-ubuntu-2404-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 |
| `update-package-index-ubuntu-2404-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-ubuntu:noble`| 4.4.3 |
| `update-package-index-redhat-8-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| `update-package-index-redhat-8-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:8` | 4.4.3 |
| `update-package-index-redhat-9-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| `update-package-index-redhat-9-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:9` | 4.4.3 |
| `update-package-index-redhat-10-amd64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
| `update-package-index-redhat-10-arm64.yaml` | `reg.devxy.io/rpkgs/build-env-redhat:10` | 4.5.3 |
### Worked example: `.crow/update-package-index-alpine-322-amd64.yaml`
The current image is `build-env-ubuntu:noble-4.4` (a *wrong* OS); we repoint to `build-env-alpine:3.22` (the matching OS) and add R_VERSION.
- [ ] **Step 5.1: Edit image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-ubuntu:noble-4.4
new_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.22
```
- [ ] **Step 5.2: Add `R_VERSION` env var**
Use `Edit` (anchor on `R_LIBS_USER:`, which appears in every update-package-index file):
```
old_string: R_LIBS_USER: /mnt/cache/R-pkgs
new_string: R_LIBS_USER: /mnt/cache/R-pkgs
R_VERSION: 4.5.3
```
- [ ] **Step 5.3: Substitute R path**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
- [ ] **Step 5.4: Validate this file**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/update-package-index-alpine-322-amd64.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
```
Expected: no output.
### Apply the same three-step pattern to the remaining 13 files
Old image tag for every file in this task except `update-package-index-redhat-10-amd64.yaml` is `reg.devxy.io/rpkgs/build-env-ubuntu:noble-4.4`.
For `update-package-index-redhat-10-amd64.yaml`, the old image tag is `reg.devxy.io/rpkgs/build-env-ubuntu:noble-4.5` (verify with `grep image: .crow/update-package-index-redhat-10-amd64.yaml` before editing).
- [ ] **Step 5.5: Apply to `update-package-index-alpine-322-arm64.yaml`** (new alpine:3.22, R_VERSION 4.5.3, old `noble-4.4`)
- [ ] **Step 5.6: Apply to `update-package-index-alpine-323-amd64.yaml`** (alpine:3.23, 4.5.3, old `noble-4.4`)
- [ ] **Step 5.7: Apply to `update-package-index-alpine-323-arm64.yaml`** (alpine:3.23, 4.5.3, old `noble-4.4`)
- [ ] **Step 5.8: Apply to `update-package-index-ubuntu-2204-amd64.yaml`** (ubuntu:jammy, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.9: Apply to `update-package-index-ubuntu-2204-arm64.yaml`** (ubuntu:jammy, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.10: Apply to `update-package-index-ubuntu-2404-amd64.yaml`** (ubuntu:noble, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.11: Apply to `update-package-index-ubuntu-2404-arm64.yaml`** (ubuntu:noble, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.12: Apply to `update-package-index-redhat-8-amd64.yaml`** (redhat:8, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.13: Apply to `update-package-index-redhat-8-arm64.yaml`** (redhat:8, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.14: Apply to `update-package-index-redhat-9-amd64.yaml`** (redhat:9, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.15: Apply to `update-package-index-redhat-9-arm64.yaml`** (redhat:9, 4.4.3, old `noble-4.4`)
- [ ] **Step 5.16: Apply to `update-package-index-redhat-10-amd64.yaml`** (redhat:10, 4.5.3, old `noble-4.5` — note the `4.5` not `4.4`!)
- [ ] **Step 5.17: Apply to `update-package-index-redhat-10-arm64.yaml`** (redhat:10, 4.5.3, old `noble-4.4`)
- [ ] **Step 5.18: Validate all 14 files**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/update-package-index-*.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
grep -nE 'build-env-.*:[^[:space:]]*-[0-9]' .crow/update-package-index-*.yaml
grep -nE '^\s*R_VERSION:' .crow/update-package-index-*.yaml | wc -l
```
Expected: first two return no output; the third returns `14`.
Verify each file's image matches its platform:
```bash
grep -E '^\s*image: reg.devxy.io/rpkgs/build-env' .crow/update-package-index-*.yaml
```
Expected: each file's image OS/version matches its platform suffix (alpine-322 → alpine:3.22, ubuntu-2404 → ubuntu:noble, redhat-10 → redhat:10, etc.).
- [ ] **Step 5.19: Commit**
```bash
git add .crow/update-package-index-*.yaml
git commit -m "refactor(ci): use multi-R-version images in update-package-index workflows
Drop the R-version suffix from each image tag, add an explicit
R_VERSION env var per file, and invoke R via
/opt/R/\${R_VERSION}/bin/R at every call site.
Also repoints every update-package-index workflow at the image that
matches its own platform (was previously pinned to
build-env-ubuntu:noble-4.4 / noble-4.5 regardless of platform)."
```
---
## Task 6: archive-missed-packages workflow (1 file)
**File (modify):** `.crow/archive-missed-packages.yaml`
The image OS doesn't matter for this workflow (it only writes to S3 + Postgres).
It currently uses `build-env-alpine:3.23-4.5`; the new image keeps alpine:3.23 and picks R 4.5.3 explicitly.
Note: this file has no `R_LIBS_USER` env var, so anchor the `R_VERSION` insertion on `GIT_USER: pat-s` (a line that *is* present).
- [ ] **Step 6.1: Edit image tag**
Use `Edit`:
```
old_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.23-4.5
new_string: image: reg.devxy.io/rpkgs/build-env-alpine:3.23
```
- [ ] **Step 6.2: Add `R_VERSION` env var**
Use `Edit` (anchor on `GIT_USER:`):
```
old_string: GIT_USER: pat-s
new_string: GIT_USER: pat-s
R_VERSION: 4.5.3
```
- [ ] **Step 6.3: Substitute R path**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
- [ ] **Step 6.4: Validate**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/archive-missed-packages.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
grep -nE 'build-env-.*:[^[:space:]]*-[0-9]' .crow/archive-missed-packages.yaml
```
Expected: both return no output.
- [ ] **Step 6.5: Commit**
```bash
git add .crow/archive-missed-packages.yaml
git commit -m "refactor(ci): use multi-R-version image in archive-missed-packages
Drop the R-version suffix from the image tag and invoke R via the
explicit /opt/R/\${R_VERSION}/bin/R path. The image OS does not
matter for this workflow; it stays on alpine:3.23."
```
---
## Task 7: build-r-minor-sensitive-packages workflow (1 file)
**File (modify):** `.crow/build-r-minor-sensitive-packages.yaml`
This is the only workflow on the `docker.io/devxygmbh/` registry, with lowercase matrix variables.
Per the spec decision: keep the registry, drop `-${r_version}` from the tag, update the matrix to use full patch versions, and substitute the explicit R path (including `R CMD INSTALL`).
- [ ] **Step 7.1: Bump matrix to full-patch R versions**
Use `Edit`:
```
old_string: - os: alpine
os_version: 3.21
r_version: 4.5
- os: alpine
os_version: 3.21
r_version: 4.4
new_string: - os: alpine
os_version: 3.21
r_version: 4.5.3
- os: alpine
os_version: 3.21
r_version: 4.4.3
```
- [ ] **Step 7.2: Edit image tag**
Use `Edit`:
```
old_string: image: "docker.io/devxygmbh/rpkgs-build-env-${os}:${os_version}-${r_version}"
new_string: image: "docker.io/devxygmbh/rpkgs-build-env-${os}:${os_version}"
```
- [ ] **Step 7.3: Substitute `R CMD INSTALL` invocation**
Use `Edit`:
```
old_string: git clone -q https://codefloe.com/rpkgs/bincraft.git /tmp/bincraft && R CMD INSTALL --library=/tmp/R-libs /tmp/bincraft && R -q -e 'packageVersion("bincraft")'
new_string: git clone -q https://codefloe.com/rpkgs/bincraft.git /tmp/bincraft && /opt/R/${r_version}/bin/R CMD INSTALL --library=/tmp/R-libs /tmp/bincraft && /opt/R/${r_version}/bin/R -q -e 'packageVersion("bincraft")'
```
- [ ] **Step 7.4: Substitute the remaining R invocation**
Use `Edit`:
```
old_string: $XVFB -- R -q -e
new_string: $XVFB -- /opt/R/${r_version}/bin/R -q -e
```
- [ ] **Step 7.5: Validate**
```bash
grep -nE '(^|[^/])R(script)? ' .crow/build-r-minor-sensitive-packages.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:#'
grep -nE 'rpkgs-build-env-.*:[^[:space:]]*-[0-9]' .crow/build-r-minor-sensitive-packages.yaml
```
Expected: both return no output.
- [ ] **Step 7.6: Commit**
```bash
git add .crow/build-r-minor-sensitive-packages.yaml
git commit -m "refactor(ci): use multi-R-version image in build-r-minor-sensitive-packages
Drop -\${r_version} from the docker.io/devxygmbh tag and invoke R
via the explicit /opt/R/\${r_version}/bin/R path (including
R CMD INSTALL). Bumps the matrix r_version values from 4.5/4.4 to
the full-patch 4.5.3/4.4.3, matching the rest of the refactor's
'always full patch' rule."
```
---
## Task 8: Justfile (3 recipes)
**File (modify):** `Justfile`
Three recipes use `docker run … reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}}-{{R_VERSION}} …`.
Drop `-{{R_VERSION}}` from each image tag and substitute `/opt/R/{{R_VERSION}}/bin/R` for every `R ` inside the `bash -c '…'` strings.
The example comment lines above each recipe still mention `4.5.0` and `3.21` (which no longer have matching images).
Update those examples to a current platform/R combination so they remain runnable.
- [ ] **Step 8.1: Update `build-all` recipe and its example comment**
Use `Edit`:
```
old_string: # just build-all alpine 3.21 arm64 4.5.0 odbc 1
build-all OS OS_VERSION ARCH R_VERSION PACKAGE NCPUS:
docker run --rm -it --platform linux/{{ARCH}} -v ./:/package -e AWS_ACCESS_KEY_ID="$HETZNER_S3_ACCESS_KEY_K3S" -e AWS_SECRET_ACCESS_KEY="$HETZNER_S3_SECRET_KEY_K3S" -e PGPASS="$PGPASS" -e NCPUS={{NCPUS}} --pull=always reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}}-{{R_VERSION}} bash -c 'R -q -e "install.packages(\"pak\", repos = sprintf(\"https://r-lib.github.io/p/pak/stable/%s/%s/%s\", .Platform\$pkgType, R.Version()\$os, R.Version()\$arch))" && R -q -e "pak::pak(\"git::https://codefloe.com/rpkgs/bincraft.git\")" && R -q -e "bincraft::build_binary_package(\"{{PACKAGE}}\", platform = \"{{OS}}\", force=TRUE, s3_endpoint = \"https://hel1.your-objectstorage.com\", s3_region = \"hel1\", s3_bucket = \"devxy-r-package-binaries-hel1\", s3_access_key_id = Sys.getenv(\"HETZNER_S3_ACCESS_KEY_K3S\"), s3_secret_access_key = Sys.getenv(\"HETZNER_S3_SECRET_KEY_K3S\"), metadata_db_host = \"r-binaries.devxy.io\", metadata_db_name = \"build_metadata\", metadata_db_table = \"single_builds\", metadata_db_user = \"rpkgs\", metadata_db_password = Sys.getenv(\"PGPASS\"), metadata_db_sslmode = \"require\", metadata_db_port = 15432, archive = TRUE, upload = TRUE, store_build_metadata = TRUE)"'
new_string: # just build-all alpine 3.22 arm64 4.5.3 odbc 1
build-all OS OS_VERSION ARCH R_VERSION PACKAGE NCPUS:
docker run --rm -it --platform linux/{{ARCH}} -v ./:/package -e AWS_ACCESS_KEY_ID="$HETZNER_S3_ACCESS_KEY_K3S" -e AWS_SECRET_ACCESS_KEY="$HETZNER_S3_SECRET_KEY_K3S" -e PGPASS="$PGPASS" -e NCPUS={{NCPUS}} --pull=always reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}} bash -c '/opt/R/{{R_VERSION}}/bin/R -q -e "install.packages(\"pak\", repos = sprintf(\"https://r-lib.github.io/p/pak/stable/%s/%s/%s\", .Platform\$pkgType, R.Version()\$os, R.Version()\$arch))" && /opt/R/{{R_VERSION}}/bin/R -q -e "pak::pak(\"git::https://codefloe.com/rpkgs/bincraft.git\")" && /opt/R/{{R_VERSION}}/bin/R -q -e "bincraft::build_binary_package(\"{{PACKAGE}}\", platform = \"{{OS}}\", force=TRUE, s3_endpoint = \"https://hel1.your-objectstorage.com\", s3_region = \"hel1\", s3_bucket = \"devxy-r-package-binaries-hel1\", s3_access_key_id = Sys.getenv(\"HETZNER_S3_ACCESS_KEY_K3S\"), s3_secret_access_key = Sys.getenv(\"HETZNER_S3_SECRET_KEY_K3S\"), metadata_db_host = \"r-binaries.devxy.io\", metadata_db_name = \"build_metadata\", metadata_db_table = \"single_builds\", metadata_db_user = \"rpkgs\", metadata_db_password = Sys.getenv(\"PGPASS\"), metadata_db_sslmode = \"require\", metadata_db_port = 15432, archive = TRUE, upload = TRUE, store_build_metadata = TRUE)"'
```
- [ ] **Step 8.2: Update `build-single` recipe and its example comments**
Use `Edit`:
```
old_string: # just build-single alpine 3.21 arm64 4.5.0 odbc 1.5.0 1
# just build-single alpine 3.22 arm64 4.5.0 sf latest 1
# just build-single ubuntu noble arm64 4.2.3 rlang 1.1.6 1
build-single OS OS_VERSION ARCH R_VERSION PACKAGE TAG NCPUS:
docker run --rm -it --platform linux/{{ARCH}} -v ./:/package -e AWS_ACCESS_KEY_ID="$HETZNER_S3_ACCESS_KEY_K3S" -e AWS_SECRET_ACCESS_KEY="$HETZNER_S3_SECRET_KEY_K3S" -e PGPASS="$PGPASS" -e NCPUS={{NCPUS}} --pull=always reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}}-{{R_VERSION}} bash -c 'R -q -e "install.packages(\"pak\", repos = sprintf(\"https://r-lib.github.io/p/pak/stable/%s/%s/%s\", .Platform\$pkgType, R.Version()\$os, R.Version()\$arch))" && R -q -e "pak::pak(\"git::https://codefloe.com/rpkgs/bincraft.git\")" && R -q -e "bincraft::build_binary_package(\"{{PACKAGE}}\", tag = \"{{TAG}}\", platform = \"{{OS}}\", force=TRUE, s3_endpoint = \"https://hel1.your-objectstorage.com\", s3_region = \"hel1\", s3_bucket = \"devxy-r-package-binaries-hel1\", s3_access_key_id = Sys.getenv(\"HETZNER_S3_ACCESS_KEY_K3S\"), s3_secret_access_key = Sys.getenv(\"HETZNER_S3_SECRET_KEY_K3S\"), metadata_db_host = \"r-binaries.devxy.io\", metadata_db_name = \"build_metadata\", metadata_db_table = \"single_builds\", metadata_db_user = \"rpkgs\", metadata_db_password = Sys.getenv(\"PGPASS\"), metadata_db_sslmode = \"require\", metadata_db_port = 15432, archive = TRUE, upload = TRUE, store_build_metadata = TRUE)"'
new_string: # just build-single alpine 3.22 arm64 4.5.3 odbc 1.5.0 1
# just build-single alpine 3.22 arm64 4.5.3 sf latest 1
# just build-single ubuntu noble arm64 4.4.3 rlang 1.1.6 1
build-single OS OS_VERSION ARCH R_VERSION PACKAGE TAG NCPUS:
docker run --rm -it --platform linux/{{ARCH}} -v ./:/package -e AWS_ACCESS_KEY_ID="$HETZNER_S3_ACCESS_KEY_K3S" -e AWS_SECRET_ACCESS_KEY="$HETZNER_S3_SECRET_KEY_K3S" -e PGPASS="$PGPASS" -e NCPUS={{NCPUS}} --pull=always reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}} bash -c '/opt/R/{{R_VERSION}}/bin/R -q -e "install.packages(\"pak\", repos = sprintf(\"https://r-lib.github.io/p/pak/stable/%s/%s/%s\", .Platform\$pkgType, R.Version()\$os, R.Version()\$arch))" && /opt/R/{{R_VERSION}}/bin/R -q -e "pak::pak(\"git::https://codefloe.com/rpkgs/bincraft.git\")" && /opt/R/{{R_VERSION}}/bin/R -q -e "bincraft::build_binary_package(\"{{PACKAGE}}\", tag = \"{{TAG}}\", platform = \"{{OS}}\", force=TRUE, s3_endpoint = \"https://hel1.your-objectstorage.com\", s3_region = \"hel1\", s3_bucket = \"devxy-r-package-binaries-hel1\", s3_access_key_id = Sys.getenv(\"HETZNER_S3_ACCESS_KEY_K3S\"), s3_secret_access_key = Sys.getenv(\"HETZNER_S3_SECRET_KEY_K3S\"), metadata_db_host = \"r-binaries.devxy.io\", metadata_db_name = \"build_metadata\", metadata_db_table = \"single_builds\", metadata_db_user = \"rpkgs\", metadata_db_password = Sys.getenv(\"PGPASS\"), metadata_db_sslmode = \"require\", metadata_db_port = 15432, archive = TRUE, upload = TRUE, store_build_metadata = TRUE)"'
```
- [ ] **Step 8.3: Update `process-updates` recipe and its example comment**
Use `Edit`:
```
old_string: # just process-updates redhat 9 arm64 4.4.3 'lubridate::interval(lubridate::today() - 4, lubridate::today() - 4)'
process-updates OS OS_VERSION ARCH R_VERSION interval:
docker run --rm -it --platform linux/{{ARCH}} -e AWS_ACCESS_KEY_ID="$HETZNER_S3_ACCESS_KEY_K3S" -e AWS_SECRET_ACCESS_KEY="$HETZNER_S3_SECRET_KEY_K3S" -e PGPASS="$PGPASS" --pull=always reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}}-{{R_VERSION}} R -q -e "bincraft::process_cran_updates(interval = {{interval}}, platform = \"{{OS}}\", s3_endpoint = \"https://hel1.your-objectstorage.com\", s3_region = \"hel1\", s3_bucket = \"devxy-r-package-binaries-hel1\", s3_access_key_id = Sys.getenv(\"HETZNER_S3_ACCESS_KEY_K3S\"), s3_secret_access_key = Sys.getenv(\"HETZNER_S3_SECRET_KEY_K3S\"), metadata_db_host = \"r-binaries.devxy.io\", metadata_db_name = \"build_metadata\", metadata_db_table = \"single_builds\", metadata_db_user = \"rpkgs\", metadata_db_password = Sys.getenv(\"PGPASS\"), metadata_db_sslmode = \"require\", metadata_db_port = 15432, archive = TRUE, upload = TRUE, store_build_metadata = TRUE)"
new_string: # just process-updates redhat 9 arm64 4.4.3 'lubridate::interval(lubridate::today() - 4, lubridate::today() - 4)'
process-updates OS OS_VERSION ARCH R_VERSION interval:
docker run --rm -it --platform linux/{{ARCH}} -e AWS_ACCESS_KEY_ID="$HETZNER_S3_ACCESS_KEY_K3S" -e AWS_SECRET_ACCESS_KEY="$HETZNER_S3_SECRET_KEY_K3S" -e PGPASS="$PGPASS" --pull=always reg.devxy.io/rpkgs/build-env-{{OS}}:{{OS_VERSION}} /opt/R/{{R_VERSION}}/bin/R -q -e "bincraft::process_cran_updates(interval = {{interval}}, platform = \"{{OS}}\", s3_endpoint = \"https://hel1.your-objectstorage.com\", s3_region = \"hel1\", s3_bucket = \"devxy-r-package-binaries-hel1\", s3_access_key_id = Sys.getenv(\"HETZNER_S3_ACCESS_KEY_K3S\"), s3_secret_access_key = Sys.getenv(\"HETZNER_S3_SECRET_KEY_K3S\"), metadata_db_host = \"r-binaries.devxy.io\", metadata_db_name = \"build_metadata\", metadata_db_table = \"single_builds\", metadata_db_user = \"rpkgs\", metadata_db_password = Sys.getenv(\"PGPASS\"), metadata_db_sslmode = \"require\", metadata_db_port = 15432, archive = TRUE, upload = TRUE, store_build_metadata = TRUE)"
```
- [ ] **Step 8.4: Validate**
```bash
grep -nE 'build-env-.*\{\{OS_VERSION\}\}-' Justfile
grep -nE "(^|[^/])R " Justfile | grep -v '/opt/R/'
```
Expected: both return no output.
- [ ] **Step 8.5: Commit**
```bash
git add Justfile
git commit -m "refactor(justfile): use multi-R-version images in build/process recipes
Drop -{{R_VERSION}} from the image tag and invoke R via the explicit
/opt/R/{{R_VERSION}}/bin/R path in build-all, build-single, and
process-updates. Updates example comments to use current
platform/R combinations."
```
---
## Task 9: Commented-out build-all-versions-install-deps.yaml in repo root (1 file)
**File (modify):** `build-all-versions-install-deps.yaml` (the *commented-out* template at repo root, not the active files under `.crow/`).
Keep the example in sync with the active workflows so it remains a faithful template.
Every line in this file is prefixed with `# ` (block-comment); the substitutions still happen inside the comments.
- [ ] **Step 9.1: Edit image tag (inside comment)**
Use `Edit`:
```
old_string: # image: reg.devxy.io/rpkgs/build-env-${OS}:${OS_VERSION}-${R_VERSION}
new_string: # image: reg.devxy.io/rpkgs/build-env-${OS}:${OS_VERSION}
```
- [ ] **Step 9.2: Substitute R path inside commented commands**
Use `Edit` with `replace_all: true`:
```
old_string: R -q -e
new_string: /opt/R/${R_VERSION}/bin/R -q -e
```
(Three `R -q -e` lines are inside the commented commands block.)
- [ ] **Step 9.3: Validate**
```bash
grep -nE '(^|[^/])R(script)? ' build-all-versions-install-deps.yaml | grep -v '/opt/R/' | grep -v '^[^:]*:[0-9]*:# *#'
```
Expected: no output (filters out lines that are nested-commented).
- [ ] **Step 9.4: Commit**
```bash
git add build-all-versions-install-deps.yaml
git commit -m "refactor: keep commented build-all-versions-install-deps example in sync
Mirror the multi-R-version image refactor in the commented-out
template so the example remains faithful to active .crow workflows."
```
---
## Task 10: Final repo-wide validation
No code edits — just a comprehensive grep sweep across every file the previous tasks touched.
Any failures discovered here mean a previous task missed an edit; go back and fix the offending file, commit separately, then re-run this validation.
- [ ] **Step 10.1: Verify no bare R/Rscript invocations remain in any workflow**
Run:
```bash
grep -rnE '(^|[^/])R(script)? ' .crow/ Justfile build-all-versions-install-deps.yaml | grep -v '/opt/R/' | grep -vE '^[^:]*:[0-9]+:\s*#'
```
Expected: no output.
If output appears, inspect each match. False positives are possible only for content unrelated to R invocation (e.g., a yaml key starting with "R" or text inside an R code string).
True positives are missed substitutions — fix and recommit.
- [ ] **Step 10.2: Verify no old-style image tags remain**
Run:
```bash
grep -rnE 'build-env-[a-z]+:[^[:space:]]*-[0-9]+\.[0-9]' .crow/ Justfile build-all-versions-install-deps.yaml
```
Expected: no output.
- [ ] **Step 10.3: Verify every pattern-1 workflow declares `R_VERSION`**
Pattern-1 workflows hard-code the image tag and therefore need an explicit `R_VERSION:` env var.
Pattern-2 workflows (the four `build-all-versions-*` files) receive `R_VERSION` from `--var` and should NOT have it in their `environment:` block.
Count files in each group:
```bash
# Pattern-1 files that MUST have R_VERSION: in their environment block.
# Total expected: 14 (process-updates) + 14 (weekly-rebuild) + 16 (weekly-audit)
# + 14 (update-package-index) + 1 (archive-missed-packages) = 59
ls .crow/process-updates-*.yaml .crow/weekly-rebuild-missing-*.yaml .crow/weekly-audit-missing-*.yaml .crow/update-package-index-*.yaml .crow/archive-missed-packages.yaml | wc -l
# Expected: 59
grep -lE '^\s+R_VERSION:' .crow/process-updates-*.yaml .crow/weekly-rebuild-missing-*.yaml .crow/weekly-audit-missing-*.yaml .crow/update-package-index-*.yaml .crow/archive-missed-packages.yaml | wc -l
# Expected: 59
# Pattern-2 files that MUST NOT have a top-level R_VERSION: env var
grep -nE '^\s+R_VERSION:' .crow/build-all-versions-*.yaml
# Expected: no output
```
- [ ] **Step 10.4: Spot-check one file end-to-end**
Read `.crow/process-updates-alpine-322-amd64.yaml` and visually confirm:
1. `image: reg.devxy.io/rpkgs/build-env-alpine:3.22` (no `-4.5`)
2. `R_VERSION: 4.5.3` appears in the `environment:` block
3. Every `R …` line in `commands:` is prefixed by `/opt/R/${R_VERSION}/bin/`
Repeat for `.crow/update-package-index-redhat-10-amd64.yaml` (the one with the old `noble-4.5` tag).
Repeat for `.crow/build-r-minor-sensitive-packages.yaml` (the lowercase-var, docker.io-registry file).
- [ ] **Step 10.5: Verify smoke-test list is ready**
Confirm the following workflows exist and are unchanged in shape; they are the targets for post-merge smoke runs (one per workflow type, per spec §Validation):
```bash
ls -1 \
.crow/process-updates-alpine-322-amd64.yaml \
.crow/weekly-rebuild-missing-redhat-9-amd64.yaml \
.crow/weekly-audit-missing-ubuntu-2204-amd64.yaml \
.crow/update-package-index-redhat-10-amd64.yaml \
.crow/archive-missed-packages.yaml \
.crow/build-all-versions-amd64.yaml \
.crow/build-all-versions-install-deps-amd64.yaml \
.crow/build-r-minor-sensitive-packages.yaml
```
Expected: all eight files listed, no errors.
Smoke runs are out of scope for this plan (they happen after the PR merges).
- [ ] **Step 10.6: Final no-op commit only if any fix-up was needed**
If steps 10.110.5 surfaced any issues that required edits, commit those fixes here:
```bash
git add -A
git commit -m "fix(ci): catch missed substitutions from multi-R-version refactor"
```
If everything was clean, no commit is needed for this step.
---
## Notes for the executing engineer
- **No tests to run.** This refactor changes CI workflow files; correctness is validated by `grep` checks at each task boundary and by smoke runs after merge.
- **Order doesn't matter between tasks 17.** Each task is independent and self-committing. Tasks 89 (Justfile, commented file) are also independent. Task 10 is final and depends on all others being complete.
- **If `Edit` complains that an `old_string` isn't unique:** add more surrounding context to disambiguate. The Edit tool requires the `old_string` to match exactly one location in the file.
- **`replace_all` safety:** the tokens we use it on (`R -q -e`, `R CMD INSTALL`, etc.) were verified by grep to appear only inside workflow command lines, never inside YAML structure or unrelated content. If a future workflow violates that assumption, switch to per-line targeted edits.
- **Don't squash commits.** Each task produces a logically coherent commit; keeping them separate makes `git bisect` useful if a smoke run regresses.