]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
bake CI build environments into pre-baked base images
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 18 May 2026 14:42:04 +0000 (10:42 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 18 May 2026 14:42:04 +0000 (10:42 -0400)
The rpm/deb/crossbuild jobs previously pulled stock distro images on every
run and re-installed the build-dep closure inline. That made every job
flaky in the face of upstream mirror outages and left rocky-10 broken
because nothing was carving out git's `safe.directory` for the workspace
that the runner UID and the in-container root UID disagree on.

Add two new parametrised CI Dockerfiles under scripts/ci/docker/ - one
for the rpm side (rocky 9/10) and one for the deb side (debian 12/13/sid,
ubuntu 22/24/26). Each bakes in the toolchain, NetworkRADIUS extras,
the full mk-build-deps / dnf builddep closure against the source-tree
control/spec, and `git config --system --add safe.directory '*'`. The
matrix in docker-refresh.yml grows entries for all eight images and now
also triggers on changes to debian/control or redhat/freeradius.spec
(plus a nightly cron) so the closure stays current.

ci-rpm/ci-deb switch their build container to the matching internal
image. The per-job setup-the-world steps go away; mk-build-deps /
dnf builddep stay as cheap top-ups. Both workflows are gated to the
main repo (push) or PRs against it - fork pushes can't reach the
internal registry and were already broken by this transition.

The crossbuild Makefile gains a CB_FROM_<distro> override (empty by
default, so local builds still FROM upstream images and need no internal
infrastructure). crossbuild.yml exports the overrides per-distro, which
kills the docker.io FROM-pull inside dind on every job and lets the
Docker Hub login step go. CI Dockerfiles also relocated from scripts/ci
into scripts/ci/docker for clarity now that there are five of them.

.github/workflows/ci-deb.yml
.github/workflows/ci-multi-server-tests.yml
.github/workflows/ci-rpm.yml
.github/workflows/crossbuild.yml
.github/workflows/docker-refresh.yml
scripts/ci/docker/Dockerfile [moved from scripts/ci/Dockerfile with 100% similarity]
scripts/ci/docker/Dockerfile.debian [new file with mode: 0644]
scripts/ci/docker/Dockerfile.docker-cli [moved from scripts/ci/Dockerfile.docker-cli with 100% similarity]
scripts/ci/docker/Dockerfile.docker-dind [moved from scripts/ci/Dockerfile.docker-dind with 100% similarity]
scripts/ci/docker/Dockerfile.rocky [new file with mode: 0644]
scripts/docker/crossbuild.mk

index 9eb5b7e459bda48ad493057f2c8f9314113220ae..2c31ffa3657f0cd033c6f1a1183c0130cea89f73 100644 (file)
@@ -6,6 +6,7 @@ on:
       - coverity_scan
       - run-fuzzer**
       - debug-fuzzer-**
+  pull_request:
   schedule:
     - cron: '0 20 * * *'
 
@@ -22,12 +23,18 @@ jobs:
   #  This job builds the matrix based on the event that trigger this run which
   #  the next job consumes.
   #
+  #  Gated to the main repo: the deb-build container is a BUILD_IMAGE on the
+  #  internal NetworkRADIUS registry, which forks can't reach. PRs targeting
+  #  the main repo run in the base-repo context and pass the gate. Pushes to
+  #  forks short-circuit here so set-matrix never runs and downstream jobs
+  #  are skipped via their `needs:` dependency.
+  #
   set-matrix:
     name: Setup build matrix
+    if: github.repository_owner == 'FreeRADIUS'
     runs-on: ubuntu-latest
     outputs:
       matrix: ${{ steps.set-matrix.outputs.matrix }}
-      selfhosted: ${{ github.repository_owner == 'FreeRADIUS' && '1' || '0' }}
     steps:
     - id: set-matrix
       name: Setup the matrix
@@ -36,12 +43,12 @@ jobs:
           M=$(cat <<EOF
           {
             "env": [
-              { "NAME": "ubuntu-22.04", "OS": "ubuntu:22.04"    },
-              { "NAME": "ubuntu-24.04", "OS": "ubuntu:24.04"    },
-              { "NAME": "ubuntu-26.04", "OS": "ubuntu:26.04"    },
-              { "NAME": "debian-12",    "OS": "debian:bookworm" },
-              { "NAME": "debian-13",    "OS": "debian:trixie"   },
-              { "NAME": "debian-sid",   "OS": "debian:sid"      }
+              { "NAME": "ubuntu-22.04", "OS": "ubuntu:22.04",    "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-ubuntu22"   },
+              { "NAME": "ubuntu-24.04", "OS": "ubuntu:24.04",    "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-ubuntu24"   },
+              { "NAME": "ubuntu-26.04", "OS": "ubuntu:26.04",    "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-ubuntu26"   },
+              { "NAME": "debian-12",    "OS": "debian:bookworm", "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-debian12"   },
+              { "NAME": "debian-13",    "OS": "debian:trixie",   "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-debian13"   },
+              { "NAME": "debian-sid",   "OS": "debian:sid",      "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-debiansid"  }
             ]
           }
         EOF
@@ -50,7 +57,7 @@ jobs:
           M=$(cat <<EOF
           {
             "env": [
-              { "NAME": "debian-12",    "OS": "debian:bookworm" }
+              { "NAME": "debian-12",    "OS": "debian:bookworm", "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-deb-debian12" }
             ]
           }
         EOF
@@ -68,18 +75,10 @@ jobs:
       matrix: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
       fail-fast: false
 
-    runs-on: ${{ needs.set-matrix.outputs.selfhosted == '1' && 'self-hosted' || 'ubuntu-latest' }}
+    runs-on: self-hosted
 
     container:
-      image: ${{ matrix.env.OS }}
-      #
-      #  Authenticate the host docker daemon's pull of the job
-      #  container so we don't hit Docker Hub's anonymous 100/6h
-      #  rate limit when 31 self-hosted runners share egress IPs.
-      #
-      credentials:
-        username: ${{ vars.DOCKERHUB_READ_USER }}
-        password: ${{ secrets.DOCKERHUB_READ_KEY }}
+      image: ${{ matrix.env.BUILD_IMAGE }}
 
     env:
       HOSTAPD_BUILD_DIR: /tmp/eapol_test.ci
@@ -89,58 +88,20 @@ jobs:
 
     steps:
 
-    - name: Package manager performance and stability improvements
-      run: |
-        if [ -f "/etc/apt/sources.list" ]; then
-        sed -i 's/deb.debian.org/debian-archive.trafficmanager.net/' /etc/apt/sources.list
-        sed -i 's/archive.ubuntu.com/azure.archive.ubuntu.com/' /etc/apt/sources.list
-        fi
-        echo 'Acquire::Retries "10";' > /etc/apt/apt.conf.d/80-retries
-        echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/02speedup
-        echo 'man-db man-db/auto-update boolean false' | debconf-set-selections
-        apt-get update
-
-    #
-    #  Required so that the checkout action uses git protocol rather than the GitHub REST API.
-    #  make rpm requires the FR directory to be a git repository.
-    #
-    - name: Install recent git
-      run: |
-        apt-get install -y --no-install-recommends git-core ca-certificates
-
-    - name: Install build tools
-      run: |
-        apt-get install -y --no-install-recommends make gcc libc6-dev equivs file curl gnupg2 lsb-release
-
-    - name: NetworkRADIUS signing key
-      run: |
-        install -d -o root -g root -m 0755 /etc/apt/keyrings
-        curl -s 'https://packages.inkbridgenetworks.com/pgp/packages.networkradius.com.asc' | tee /etc/apt/keyrings/packages.networkradius.com.asc > /dev/null
-
-    - name: Set up NetworkRADIUS extras repository
-      run: |
-        DIST=$(lsb_release -is | tr '[:upper:]' '[:lower:]')
-        RELEASE=$(lsb_release -cs)
-        if [ "$RELEASE" = "n/a" ]; then
-            RELEASE=$(cat /etc/debian_version | awk -F \/ '{ print $(NF) }')
-        fi
-        [ "$RELEASE" != "forky" ] || RELEASE=sid
-        echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/packages.networkradius.com.asc] http://packages.networkradius.com/extras/${DIST}/${RELEASE} ${RELEASE} main" \
-          > /etc/apt/sources.list.d/networkradius-extras.list
-
-    - name: Update apt repository lists
-      run: apt-get update
-
     - uses: actions/checkout@v6
       with:
         path: freeradius
 
+    #
+    #  Top-up build-deps against the just-checked-out debian/control in
+    #  case the image was baked before a recent control change. No-op
+    #  when the image is current.
+    #
     - name: Install build dependencies
       run: |
-        apt-get install -y --no-install-recommends build-essential devscripts quilt fakeroot
         touch -t 202001010000 debian/control
         debian/rules debian/control
-        mk-build-deps -irt"apt-get -y" debian/control
+        mk-build-deps -irt"apt-get -y --no-install-recommends" debian/control
       working-directory: freeradius
 
     - name: Build DEBs
@@ -164,7 +125,6 @@ jobs:
     # Build eapol_test using a minimal make environment to avoid configuring
     - name: Build eapol_test
       run: |
-        apt-get install -y libnl-3-dev libnl-genl-3-dev
         scripts/ci/eapol_test-build.sh
         mv scripts/ci/eapol_test/eapol_test ../debs
       working-directory: freeradius
@@ -180,11 +140,6 @@ jobs:
     #  If the CI has failed and the branch is ci-debug then start a tmate
     #  session. SSH rendezvous point is emited continuously in the job output.
     #
-    - name: "Debug: Package dependancies for tmate"
-      run: |
-        apt-get install -y --no-install-recommends xz-utils
-      if: ${{ github.ref == 'refs/heads/ci-debug' && failure() }}
-
     - name: "Debug: Start tmate"
       uses: mxschmitt/action-tmate@v3
       with:
@@ -208,10 +163,16 @@ jobs:
       matrix: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
       fail-fast: false
 
-    runs-on: ${{ needs.set-matrix.outputs.selfhosted == '1' && 'self-hosted' || 'ubuntu-latest' }}
+    runs-on: self-hosted
 
     container:
       image: ${{ matrix.env.OS }}
+      #
+      #  deb-test intentionally runs on a stock distro image (pulled from
+      #  Docker Hub) so the install test exercises a clean OS. Auth the
+      #  pull to dodge the anonymous 100/6h rate limit shared across
+      #  self-hosted runners.
+      #
       credentials:
         username: ${{ vars.DOCKERHUB_READ_USER }}
         password: ${{ secrets.DOCKERHUB_READ_KEY }}
index 6b6ace8b2e2beb1a6f33d419fead267b61ba6842..ed11a21ef98aa0089328747899bb6f148df95c08 100644 (file)
@@ -26,7 +26,7 @@ jobs:
         #  Custom dind image with /etc/docker/daemon.json baked in
         #  pointing at the internal NR registry as a Docker Hub
         #  pull-through mirror, and the internal CA pre-trusted.
-        #  Built by docker-refresh.yml from scripts/ci/Dockerfile.docker-dind.
+        #  Built by docker-refresh.yml from scripts/ci/docker/Dockerfile.docker-dind.
         #
         image: docker.internal.networkradius.com/self-hosted-docker-dind
         options: --privileged
@@ -53,7 +53,7 @@ jobs:
       #
       #  Custom CI image with docker CLI / buildx / m4 baked on top
       #  of the self-hosted base. Built by docker-refresh.yml from
-      #  scripts/ci/Dockerfile.docker-cli.
+      #  scripts/ci/docker/Dockerfile.docker-cli.
       #
       image: docker.internal.networkradius.com/self-hosted-docker-cli
       #  "privileged" is needed for Samba install
index 9e105e5de5b2bf804d09eb91f7387e4e642bafa9..b1fa318bcd44b303a6c395573e7ae911e0241f3c 100644 (file)
@@ -6,6 +6,7 @@ on:
       - coverity_scan
       - run-fuzzer**
       - debug-fuzzer-**
+  pull_request:
   schedule:
     - cron: '0 20 * * *'
 
@@ -19,12 +20,18 @@ jobs:
   #  This job builds the matrix based on the event that trigger this run which
   #  the next job consumes.
   #
+  #  Gated to the main repo: the rpm-build container is a BUILD_IMAGE on the
+  #  internal NetworkRADIUS registry, which forks can't reach. PRs targeting
+  #  the main repo run in the base-repo context and pass the gate. Pushes to
+  #  forks short-circuit here so set-matrix never runs and downstream jobs
+  #  are skipped via their `needs:` dependency.
+  #
   set-matrix:
     name: Setup build matrix
+    if: github.repository_owner == 'FreeRADIUS'
     runs-on: ubuntu-latest
     outputs:
       matrix: ${{ steps.set-matrix.outputs.matrix }}
-      selfhosted: ${{ github.repository_owner == 'FreeRADIUS' && '1' || '0' }}
     steps:
     - id: set-matrix
       name: Setup the matrix
@@ -33,8 +40,8 @@ jobs:
           M=$(cat <<EOF
           {
             "env": [
-              { "NAME": "rocky-9",  "OS": "rockylinux/rockylinux:9",       "DIST": "rocky",   "VERSION": 9   },
-              { "NAME": "rocky-10", "OS": "rockylinux/rockylinux:10",      "DIST": "rocky",   "VERSION": 10  },
+              { "NAME": "rocky-9",  "OS": "rockylinux/rockylinux:9",  "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-rocky9",  "DIST": "rocky", "VERSION": 9  },
+              { "NAME": "rocky-10", "OS": "rockylinux/rockylinux:10", "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-rocky10", "DIST": "rocky", "VERSION": 10 }
             ]
           }
         EOF
@@ -43,7 +50,7 @@ jobs:
           M=$(cat <<EOF
           {
             "env": [
-              { "NAME": "rocky-9",  "OS": "rockylinux/rockylinux:9",       "DIST": "rocky",   "VERSION": 9   }
+              { "NAME": "rocky-9",  "OS": "rockylinux/rockylinux:9",  "BUILD_IMAGE": "docker.internal.networkradius.com/self-hosted-rocky9",  "DIST": "rocky", "VERSION": 9 }
             ]
           }
         EOF
@@ -61,18 +68,10 @@ jobs:
       matrix: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
       fail-fast: false
 
-    runs-on: ${{ needs.set-matrix.outputs.selfhosted == '1' && 'self-hosted' || 'ubuntu-latest' }}
+    runs-on: self-hosted
 
     container:
-      image: ${{ matrix.env.OS }}
-      #
-      #  Authenticate the host docker daemon's pull of the job
-      #  container so we don't hit Docker Hub's anonymous 100/6h
-      #  rate limit when 31 self-hosted runners share egress IPs.
-      #
-      credentials:
-        username: ${{ vars.DOCKERHUB_READ_USER }}
-        password: ${{ secrets.DOCKERHUB_READ_KEY }}
+      image: ${{ matrix.env.BUILD_IMAGE }}
 
     env:
       HOSTAPD_BUILD_DIR: /tmp/eapol_test.ci
@@ -81,37 +80,15 @@ jobs:
     name: "RPM build"
 
     steps:
-    - name: Enable EPEL and CRB
-      run: |
-        dnf install -y dnf-utils dnf-plugins-core epel-release
-        dnf config-manager --enable crb
-
-    # For pkill
-    - name: Enable procps-ng
-      run: |
-        dnf install -y procps-ng
-
-    - name: Set up NetworkRADIUS extras repository
-      run: |
-        echo '[networkradius-extras]'                                                               >  /etc/yum.repos.d/networkradius-extras.repo
-        echo 'name=NetworkRADIUS-extras-$releasever'                                                >> /etc/yum.repos.d/networkradius-extras.repo
-        echo 'baseurl=http://packages.networkradius.com/extras/${{ matrix.env.DIST }}/$releasever/' >> /etc/yum.repos.d/networkradius-extras.repo
-        echo 'enabled=1'                                                                            >> /etc/yum.repos.d/networkradius-extras.repo
-        echo 'gpgcheck=1'                                                                           >> /etc/yum.repos.d/networkradius-extras.repo
-        echo 'gpgkey=https://packages.networkradius.com/pgp/packages@networkradius.com'             >> /etc/yum.repos.d/networkradius-extras.repo
-        rpm --import https://packages.networkradius.com/pgp/packages@networkradius.com
-
-    - name: Install common tools
-      run: |
-        dnf install -y rpm-build openssl make gcc perl git-core
-
     - uses: actions/checkout@v6
       with:
         path: freeradius
 
     #
-    #  It has been observed that sometimes not all the dependencies are
-    #  installed on the first go.  Give it a second chance.
+    #  Top-up builddep against the just-checked-out spec in case the
+    #  image was baked before a recent dep change. No-op when the image
+    #  is current. Run twice; occasionally dnf misses something first
+    #  time round.
     #
     - name: Install build dependencies
       run: |
@@ -140,7 +117,6 @@ jobs:
     # Build eapol_test using a minimal make environment to avoid configuring
     - name: Build eapol_test
       run: |
-        dnf install -y libnl3-devel which
         [ -r /opt/rh/devtoolset-8/enable ] && source /opt/rh/devtoolset-8/enable || :
         scripts/ci/eapol_test-build.sh
         mv scripts/ci/eapol_test/eapol_test ../rpms
@@ -159,7 +135,6 @@ jobs:
     #
     - name: "Debug: Package dependancies for tmate"
       run: |
-        dnf install -y xz
         ln -s /bin/true /bin/apt-get
       if: ${{ github.ref == 'refs/heads/ci-debug' && failure() }}
 
@@ -186,10 +161,16 @@ jobs:
       matrix: ${{ fromJson(needs.set-matrix.outputs.matrix) }}
       fail-fast: false
 
-    runs-on: ${{ needs.set-matrix.outputs.selfhosted == '1' && 'self-hosted' || 'ubuntu-latest' }}
+    runs-on: self-hosted
 
     container:
       image: ${{ matrix.env.OS }}
+      #
+      #  rpm-test intentionally runs on a stock distro image (pulled from
+      #  Docker Hub) so the install test exercises a clean OS. Auth the
+      #  pull to dodge the anonymous 100/6h rate limit shared across
+      #  self-hosted runners.
+      #
       credentials:
         username: ${{ vars.DOCKERHUB_READ_USER }}
         password: ${{ secrets.DOCKERHUB_READ_KEY }}
index a95c075dc2470670ede2cc7c100c5cc3eb599807..416b6dcda1bad0fb7748f276503dce1b68984357 100644 (file)
@@ -116,19 +116,23 @@ jobs:
     - uses: ./.github/actions/setup-dind
 
     #
-    #  Authenticate dind to Docker Hub. self-hosted-docker-dind has
-    #  registry-mirrors -> docker.internal.networkradius.com baked
-    #  in, but our internal registry isn't actually a Hub pull-
-    #  through cache, so the inner crossbuild's FROM-pulls fall back
-    #  to docker.io and would otherwise hit anonymous rate limits.
+    #  Run the per-distro crossbuild. CB_FROM_<distro> overrides the
+    #  `from` build-arg in scripts/docker/crossbuild.mk so the inner
+    #  docker build FROMs an internal base image instead of docker.io,
+    #  killing the Hub pull (and the anonymous rate-limit headache) on
+    #  every CI run. Local `make crossbuild.<distro>` ignores these
+    #  and uses the upstream image baked into Dockerfile.cb by m4.
     #
-    - name: Login to Docker Hub (via dind)
-      uses: docker/login-action@v4
-      with:
-        username: ${{ vars.DOCKERHUB_READ_USER }}
-        password: ${{ secrets.DOCKERHUB_READ_KEY }}
-
     - name: Run crossbuild tests
+      env:
+        CB_FROM_debian12:  docker.internal.networkradius.com/self-hosted-deb-debian12
+        CB_FROM_debian13:  docker.internal.networkradius.com/self-hosted-deb-debian13
+        CB_FROM_debiansid: docker.internal.networkradius.com/self-hosted-deb-debiansid
+        CB_FROM_ubuntu22:  docker.internal.networkradius.com/self-hosted-deb-ubuntu22
+        CB_FROM_ubuntu24:  docker.internal.networkradius.com/self-hosted-deb-ubuntu24
+        CB_FROM_ubuntu26:  docker.internal.networkradius.com/self-hosted-deb-ubuntu26
+        CB_FROM_rocky9:    docker.internal.networkradius.com/self-hosted-rocky9
+        CB_FROM_rocky10:   docker.internal.networkradius.com/self-hosted-rocky10
       run: |
         make crossbuild.$OS
 
index b699b2de14294b63546b844bbd8751e9bcabcd9d..36661c9f4d59d89efb5aab72584b42ff25369e75 100644 (file)
@@ -4,12 +4,14 @@ on:
   push:
     branches:
       - master
-#    paths:
-#      - .github/workflows/docker-refresh.yml
-#      - scripts/ci/Dockerfile*
+    paths:
+      - .github/workflows/docker-refresh.yml
+      - scripts/ci/docker/Dockerfile*
+      - debian/control
+      - redhat/freeradius.spec
   workflow_dispatch:
-#  schedule:
-#    - cron: '0 1 * * *'
+  schedule:
+    - cron: '0 1 * * *'
 
 env:
   DOCKER_REGISTRY: "docker.internal.networkradius.com"
@@ -24,8 +26,7 @@ jobs:
     timeout-minutes: 20
 
     runs-on: self-hosted
-#    if: github.event_name == 'workflow_dispatch' || github.repository_owner == 'FreeRADIUS'
-    if: github.event_name == 'workflow_dispatch'
+    if: github.repository_owner == 'FreeRADIUS' || github.event_name == 'workflow_dispatch'
 
     strategy:
       fail-fast: false
@@ -35,7 +36,61 @@ jobs:
             image_name: docker.internal.networkradius.com/self-hosted-ubuntu24
             extra_tags:
               - docker.internal.networkradius.com/self-hosted
-            dockerfile: scripts/ci/Dockerfile
+            dockerfile: scripts/ci/docker/Dockerfile
+
+          - base_image: rockylinux/rockylinux:9
+            image_name: docker.internal.networkradius.com/self-hosted-rocky9
+            dockerfile: scripts/ci/docker/Dockerfile.rocky
+            build_args:
+              from: rockylinux/rockylinux:9
+
+          - base_image: rockylinux/rockylinux:10
+            image_name: docker.internal.networkradius.com/self-hosted-rocky10
+            dockerfile: scripts/ci/docker/Dockerfile.rocky
+            build_args:
+              from: rockylinux/rockylinux:10
+
+          #
+          #  Slim deb-build bases for ci-deb.yml. Distinct from the
+          #  full self-hosted-ubuntu24 testbed above - these only carry
+          #  the build-essential toolchain and the debian/control
+          #  build-dep closure, no integration-test services.
+          #
+          - base_image: debian:bookworm
+            image_name: docker.internal.networkradius.com/self-hosted-deb-debian12
+            dockerfile: scripts/ci/docker/Dockerfile.debian
+            build_args:
+              from: debian:bookworm
+
+          - base_image: debian:trixie
+            image_name: docker.internal.networkradius.com/self-hosted-deb-debian13
+            dockerfile: scripts/ci/docker/Dockerfile.debian
+            build_args:
+              from: debian:trixie
+
+          - base_image: debian:sid
+            image_name: docker.internal.networkradius.com/self-hosted-deb-debiansid
+            dockerfile: scripts/ci/docker/Dockerfile.debian
+            build_args:
+              from: debian:sid
+
+          - base_image: ubuntu:22.04
+            image_name: docker.internal.networkradius.com/self-hosted-deb-ubuntu22
+            dockerfile: scripts/ci/docker/Dockerfile.debian
+            build_args:
+              from: ubuntu:22.04
+
+          - base_image: ubuntu:24.04
+            image_name: docker.internal.networkradius.com/self-hosted-deb-ubuntu24
+            dockerfile: scripts/ci/docker/Dockerfile.debian
+            build_args:
+              from: ubuntu:24.04
+
+          - base_image: ubuntu:26.04
+            image_name: docker.internal.networkradius.com/self-hosted-deb-ubuntu26
+            dockerfile: scripts/ci/docker/Dockerfile.debian
+            build_args:
+              from: ubuntu:26.04
 
           #
           #  Custom dind sidecar image used by the dind-based
@@ -46,7 +101,7 @@ jobs:
           #
           - base_image: docker:24-dind
             image_name: docker.internal.networkradius.com/self-hosted-docker-dind
-            dockerfile: scripts/ci/Dockerfile.docker-dind
+            dockerfile: scripts/ci/docker/Dockerfile.docker-dind
             needs_internal_ca: true
 
           - base_image: mariadb
@@ -133,9 +188,14 @@ jobs:
         env:
           DOCKER_IMAGE_NAME: ${{ matrix.os.image_name }}
           DOCKERFILE: ${{ matrix.os.dockerfile }}
+          BUILD_ARGS_JSON: ${{ toJson(matrix.os.build_args) }}
         run: |
+          build_args=()
+          while IFS=$'\t' read -r key value; do
+            [ -n "$key" ] && build_args+=(--build-arg "${key}=${value}")
+          done < <(printf '%s' "$BUILD_ARGS_JSON" | jq -r 'to_entries[]? | [.key, .value] | @tsv')
           for attempt in 1 2 3; do
-            docker build --no-cache -f "$DOCKERFILE" -t "$DOCKER_IMAGE_NAME" --label preserve=true . && exit 0
+            docker build --no-cache -f "$DOCKERFILE" -t "$DOCKER_IMAGE_NAME" --label preserve=true "${build_args[@]}" . && exit 0
             echo "Build attempt $attempt failed; retrying after backoff..."
             sleep 10
           done
@@ -202,7 +262,7 @@ jobs:
     timeout-minutes: 15
 
     runs-on: self-hosted
-    if: github.event_name == 'workflow_dispatch'
+    if: github.repository_owner == 'FreeRADIUS' || github.event_name == 'workflow_dispatch'
 
     strategy:
       fail-fast: false
@@ -215,7 +275,7 @@ jobs:
           #
           - parent_image: docker.internal.networkradius.com/self-hosted
             image_name: docker.internal.networkradius.com/self-hosted-docker-cli
-            dockerfile: scripts/ci/Dockerfile.docker-cli
+            dockerfile: scripts/ci/docker/Dockerfile.docker-cli
 
     name: "process-derived-images"
 
diff --git a/scripts/ci/docker/Dockerfile.debian b/scripts/ci/docker/Dockerfile.debian
new file mode 100644 (file)
index 0000000..438b0c2
--- /dev/null
@@ -0,0 +1,79 @@
+ARG from=debian:bookworm
+FROM ${from}
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+#
+#  Retry apt fetches a few times and shorten the connect timeout so a
+#  hung mirror fails fast and the retry kicks in quickly.
+#
+RUN printf 'Acquire::Retries "3";\nAcquire::http::ConnectTimeout "5";\nAcquire::https::ConnectTimeout "5";\n' \
+               > /etc/apt/apt.conf.d/99-ci-retries && \
+       printf 'force-unsafe-io\n' > /etc/dpkg/dpkg.cfg.d/02speedup && \
+       echo 'man-db man-db/auto-update boolean false' | debconf-set-selections
+
+RUN apt-get update && apt-get dist-upgrade -y
+
+#
+#  Toolchain + everything ci-deb.yml's "Install build tools" /
+#  "Install recent git" / "Install build dependencies" steps install
+#  per-job. Pre-staging these here turns the per-job dance into a
+#  near-no-op when the image is current.
+#
+RUN apt-get install -y --no-install-recommends \
+       apt-transport-https \
+       build-essential \
+       ca-certificates \
+       curl \
+       devscripts \
+       equivs \
+       fakeroot \
+       file \
+       git-core \
+       gnupg2 \
+       lsb-release \
+       make \
+       quilt \
+       xz-utils
+
+#
+#  Set up NetworkRADIUS extras repository
+#
+RUN install -d -o root -g root -m 0755 /etc/apt/keyrings && \
+       curl -fsSL --retry 3 --retry-delay 10 --retry-connrefused \
+               'https://packages.networkradius.com/pgp/packages%40networkradius.com' \
+               > /etc/apt/keyrings/packages.networkradius.com.asc
+
+RUN DIST=$(lsb_release -is | tr '[:upper:]' '[:lower:]') && \
+       RELEASE=$(lsb_release -cs) && \
+       if [ "$RELEASE" = "n/a" ]; then \
+               RELEASE=$(awk -F/ '{print $NF}' /etc/debian_version); \
+       fi && \
+       [ "$RELEASE" != "forky" ] || RELEASE=sid && \
+       echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/packages.networkradius.com.asc] http://packages.networkradius.com/extras/${DIST}/${RELEASE} ${RELEASE} main" \
+               > /etc/apt/sources.list.d/networkradius-extras.list && \
+       apt-get update
+
+#
+#  Pre-install the build-dep closure derived from debian/control. The
+#  source-tree debian/ subtree is baked in at image-build time; ci-deb.yml
+#  still runs `mk-build-deps` per job as a top-up so newly added deps
+#  are picked up without an image rebuild.
+#
+COPY debian/ /tmp/freeradius-debian/
+COPY scripts/ci/extra-packages.debian.control /tmp/freeradius-debian-extras.control
+RUN cd /tmp/freeradius-debian && \
+       touch -t 202001010000 control && \
+       ./rules control && \
+       mk-build-deps -irt"apt-get -y --no-install-recommends" control && \
+       cd / && \
+       mk-build-deps -irt"apt-get -y --no-install-recommends" /tmp/freeradius-debian-extras.control && \
+       rm -rf /tmp/freeradius-debian /tmp/freeradius-debian-extras.control \
+               /freeradius-build-deps_*.deb /freeradius-build-deps_*.buildinfo /freeradius-build-deps_*.changes
+
+#
+#  Trust any workspace path. Mirrors the rocky/ubuntu24 image: the
+#  bind-mounted runner workspace is owned by the runner user, not root,
+#  and git in newer distros refuses to operate on it without this.
+#
+RUN git config --system --add safe.directory '*'
diff --git a/scripts/ci/docker/Dockerfile.rocky b/scripts/ci/docker/Dockerfile.rocky
new file mode 100644 (file)
index 0000000..3aa7783
--- /dev/null
@@ -0,0 +1,68 @@
+ARG from=rockylinux/rockylinux:10
+FROM ${from}
+
+#
+#  Retry transient package-repo failures. dnf retries 10 times by default;
+#  cap that at 3 and shorten the per-request timeout from 30s to 15s so a
+#  hung mirror fails fast and the retry kicks in quickly.
+#
+RUN printf 'retries=3\ntimeout=15\n' >> /etc/dnf/dnf.conf
+
+#
+#  EPEL + CRB give us freetds, hiredis, and the rest of the long tail of
+#  build deps the spec pulls in.
+#
+RUN dnf install -y dnf-utils dnf-plugins-core epel-release && \
+       dnf config-manager --enable crb
+
+#
+#  procps-ng is for pkill, used by the test harness. xz is for the tmate
+#  debug step on ci-debug. Tools below are the union of "Install common
+#  tools" and "Build eapol_test" in ci-rpm.yml.
+#
+RUN dnf install -y \
+       gcc \
+       git-core \
+       libnl3-devel \
+       make \
+       openssl \
+       perl \
+       procps-ng \
+       rpm-build \
+       which \
+       xz
+
+#
+#  Set up NetworkRADIUS extras repository
+#
+RUN curl --retry 3 --retry-delay 10 --retry-connrefused --fail \
+               -o /etc/pki/rpm-gpg/packages.networkradius.com.asc \
+               "https://packages.networkradius.com/pgp/packages%40networkradius.com" && \
+       rpm --import /etc/pki/rpm-gpg/packages.networkradius.com.asc
+
+RUN printf '[networkradius-extras]\n\
+name=NetworkRADIUS-extras-$releasever\n\
+baseurl=http://packages.networkradius.com/extras/rocky/$releasever/\n\
+enabled=1\n\
+gpgcheck=1\n\
+gpgkey=file:///etc/pki/rpm-gpg/packages.networkradius.com.asc\n' \
+       > /etc/yum.repos.d/networkradius-extras.repo
+
+#
+#  Pre-install the build-dep closure for the FreeRADIUS spec. The spec
+#  is baked in at image-build time; ci-rpm.yml still runs `dnf builddep`
+#  per job as a top-up so newly added deps are picked up without an
+#  image rebuild.
+#
+COPY redhat/freeradius.spec /tmp/freeradius.spec
+RUN dnf builddep -y /tmp/freeradius.spec && \
+       dnf builddep -y /tmp/freeradius.spec && \
+       rm /tmp/freeradius.spec
+
+#
+#  Trust any workspace path. The job container runs as root but the
+#  bind-mounted runner workspace is owned by the runner user; without
+#  this, git refuses with "dubious ownership" the moment a Makefile
+#  runs `git rev-parse`. system-level so it applies to every shell.
+#
+RUN git config --system --add safe.directory '*'
index 61443075bd9a4c9492d9da1c8fe8ddd749e4d955..ff4b97639dd3105f87e38193116636d505348d03 100644 (file)
@@ -135,9 +135,14 @@ crossbuild.${1}.status:
 #
 #  Build the docker image
 #
+#  CB_FROM_${1} overrides the `from` build-arg for this target. Empty by
+#  default so local builds use the upstream image baked into Dockerfile.cb
+#  by m4. CI exports CB_FROM_<distro> to point at internal base images
+#  (see .github/workflows/crossbuild.yml).
+#
 $(DD)/stamp-image.${1}:
        ${Q}echo "BUILD ${1} ($(CB_IPREFIX)/${1}) > $(DD)/build.${1}"
-       ${Q}docker build $(DOCKER_BUILD_OPTS) $(DT)/${1} -f $(DT)/${1}/Dockerfile.cb -t $(CB_IPREFIX)/${1} >$(DD)/build.${1} 2>&1
+       ${Q}docker build $(DOCKER_BUILD_OPTS) $(if $(CB_FROM_${1}),--build-arg=from=$(CB_FROM_${1})) $(DT)/${1} -f $(DT)/${1}/Dockerfile.cb -t $(CB_IPREFIX)/${1} >$(DD)/build.${1} 2>&1
        ${Q}touch $(DD)/stamp-image.${1}
 
 #