- coverity_scan
- run-fuzzer**
- debug-fuzzer-**
+ pull_request:
schedule:
- cron: '0 20 * * *'
# 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
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
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
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
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
# 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
# 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:
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 }}
# 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
#
# 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
- coverity_scan
- run-fuzzer**
- debug-fuzzer-**
+ pull_request:
schedule:
- cron: '0 20 * * *'
# 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
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
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
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
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: |
# 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
#
- 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() }}
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 }}
- 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
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"
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
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
#
- 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
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
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
#
- 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"
--- /dev/null
+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 '*'
--- /dev/null
+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 '*'
#
# 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}
#