#!/bin/bash ## pkg-cache-repos.sh — BUILD-TIME ONLY, OPT-IN. Points dnf/apt at the internal ## package cache (nginx proxy_cache, CT130 "pkg-cache" on the Proxmox cluster) ## when PKG_CACHE is set; a no-op otherwise, so builds behave exactly as before ## without it. ## ## PKG_CACHE=http://192.168.1.40 pkg-cache-repos.sh rpm # AlmaLinux/EPEL/Remi ## PKG_CACHE=http://192.168.1.40 pkg-cache-repos.sh apt # Ubuntu + LiteSpeed ## pkg-cache-repos.sh release-urls # prints EPEL+Remi release RPM URLs ## pkg-cache-repos.sh restore # put the original repo files back ## ## Every RUN that rewrites must `restore` before it ends, so no shipped layer ## carries the cache's LAN address in /etc/yum.repos.d or /etc/apt (images run on ## hosts that cannot reach it). restore is a no-op when nothing was rewritten. ## ## Why: on 2026-10-21 (PHP security release) builds failed on Remi mirrors lagging ## hours behind origin, Remi's origin timing out over HTTPS, and an EPEL mirror ## with a broken certificate. The cache fetches from the ORIGINS over HTTPS (with ## origin/mirror fallbacks and stale-on-error), keeps metadata only ~3 minutes, ## and keeps immutable .rpm/.deb files long-term. ## ## Integrity is unchanged: dnf/apt still verify package GPG signatures (and apt ## the signed InRelease/Release.gpg); the build's php.net version gates ## (EXPECT_PHP_MIN / EXPECT_PHP_VERSION) still fail a build that gets old PHP. ## ## rpm: each rewritten baseurl keeps the upstream origin as a SECOND url, so if ## the cache dies mid-build dnf fails over to the origin by itself. ## apt: apt has no per-source failover; CI only sets PKG_CACHE after probing the ## cache, and a build without PKG_CACHE uses the upstream sources. set -euo pipefail MODE="${1:?usage: pkg-cache-repos.sh rpm|apt|release-urls|restore}" STATE=/var/lib/pkg-cache-repos # pristine copies of every file we rewrite if [ "$MODE" = restore ]; then [ -d "$STATE" ] || exit 0 (cd "$STATE" && find . -type f) | while read -r rel; do cat "$STATE/$rel" > "/${rel#./}" done rm -rf "$STATE" echo "pkg-cache-repos: original repository files restored" exit 0 fi # Keep the first-seen (pre-rewrite) copy of a file; later calls in the same RUN # never overwrite it. save() { [ -e "$STATE$1" ] && return 0 mkdir -p "$STATE$(dirname "$1")" cp -p "$1" "$STATE$1" } C="${PKG_CACHE:-}" C="${C%/}" if [ "$MODE" = release-urls ]; then if [ -n "$C" ]; then echo "$C/epel/epel-release-latest-10.noarch.rpm $C/remi/enterprise/remi-release-10.rpm" else echo "https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm https://rpms.remirepo.net/enterprise/remi-release-10.rpm" fi exit 0 fi if [ -z "$C" ]; then echo "pkg-cache-repos($MODE): PKG_CACHE not set - using upstream repositories" exit 0 fi case "$C" in http://*|https://*) ;; *) echo "FATAL(pkg-cache-repos): PKG_CACHE must be an http(s) URL, got '$C'" >&2; exit 1 ;; esac rewrite_rpm() { local f="$1" grep -q -E '^#? ?baseurl=https?://(repo\.almalinux\.org|download\.example|rpms\.remirepo\.net)/' "$f" || return 0 save "$f" # Per [section]: a (commented or live) baseurl on a known origin becomes # "baseurl=// /", and that section's # mirrorlist/metalink is commented out (dnf prefers them over baseurl). # Sections without a known origin (vault debug/source, ...) are untouched. awk -v C="$C" ' function flush( i) { for (i = 1; i <= n; i++) { if (hit && buf[i] ~ /^(mirrorlist|metalink)=/) buf[i] = "#" buf[i] print buf[i] } n = 0; hit = 0 } function rw(line, re, prefix, origin, p) { if (match(line, re)) { p = substr(line, RSTART + RLENGTH); sub(/[ \t].*$/, "", p) hit = 1 return "baseurl=" C "/" prefix "/" p " " origin "/" p } return line } /^\[/ { flush() } { l = $0 if (l ~ /^#? ?baseurl=/) { l = rw(l, "^#? ?baseurl=https?://repo\\.almalinux\\.org/almalinux/", "almalinux", "https://repo.almalinux.org/almalinux") l = rw(l, "^#? ?baseurl=https?://download\\.example/pub/epel/", "epel", "https://dl.fedoraproject.org/pub/epel") l = rw(l, "^#? ?baseurl=https?://rpms\\.remirepo\\.net/", "remi", "https://rpms.remirepo.net") } buf[++n] = l } END { flush() } ' "$f" > "$f.pkgcache" && cat "$f.pkgcache" > "$f" && rm -f "$f.pkgcache" } rewrite_apt() { local f="$1" grep -q -E 'https?://([a-z]{2}\.)?(archive|security)\.ubuntu\.com/|https?://rpms\.litespeedtech\.com/' "$f" || return 0 save "$f" sed -i -E \ -e "s%https?://(archive|[a-z]{2}\.archive)\.ubuntu\.com/ubuntu/?%$C/ubuntu/%g" \ -e "s%https?://security\.ubuntu\.com/ubuntu/?%$C/ubuntu-security/%g" \ -e "s%https?://rpms\.litespeedtech\.com/%$C/litespeed/%g" \ "$f" } case "$MODE" in rpm) for f in /etc/yum.repos.d/*.repo; do [ -f "$f" ] && rewrite_rpm "$f"; done echo "pkg-cache-repos(rpm): repos now via $C:" grep -h '^baseurl=' /etc/yum.repos.d/*.repo | grep -F "$C/" | sort -u | sed 's/^/ /' ;; apt) for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do [ -f "$f" ] && rewrite_apt "$f" done echo "pkg-cache-repos(apt): sources now via $C:" grep -h -E '^(deb|URIs:)' /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null | grep -F "$C/" | sort -u | sed 's/^/ /' ;; *) echo "FATAL(pkg-cache-repos): unknown mode '$MODE'" >&2; exit 1 ;; esac