diff --git a/Dockerfile b/Dockerfile index 10046c8..f09612f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,15 @@ ARG CACHE_BUST= # php.net's current release for this branch (CI); the build fails on older PHP. ARG EXPECT_PHP_MIN= +# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=http://192.168.1.40 +# (CT130 pkg-cache). Unset = upstream repositories exactly as before. Repo files +# are restored at the end of every RUN, so no layer keeps the cache address. +# See scripts/pkg-cache-repos.sh. +ARG PKG_CACHE= +COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh # Install repos, update, install only needed packages, clean up in one layer -RUN dnf install -y \ - https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm \ - https://rpms.remirepo.net/enterprise/remi-release-10.rpm && \ +RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \ + dnf install -y $(/usr/local/sbin/pkg-cache-repos.sh release-urls) && \ # Use Remi's origin, not the mirrorlist: during the 2026-10-21 PHP security # release the mirrors lagged hours behind (still 8.2.34 when 8.2.35 was out), # so builds shipped unpatched PHP. HTTPS first; plain http only as a fallback @@ -18,10 +23,12 @@ RUN dnf install -y \ # replayed (old) http index can't ship old PHP: packages stay GPG-checked and # the EXPECT_PHP_MIN gate below fails the build on anything older than php.net. sed -i -e 's|^#baseurl=http://\(.*\)$|baseurl=https://\1 http://\1|' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \ + /usr/local/sbin/pkg-cache-repos.sh rpm && \ printf 'retries=10\ntimeout=60\n' >> /etc/dnf/dnf.conf && \ dnf update -y && \ dnf install -y httpd mod_ssl openssl wget procps cronie iproute postgresql-devel microdnf less git \ nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ dnf clean all && \ rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* @@ -30,13 +37,17 @@ COPY ./scripts/ /scripts/ RUN chmod +x /scripts/* # Install ImageMagick from EPEL with HEIC/HEIF/AVIF support -RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \ +RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \ + dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ dnf clean all # Generate self-signed cert, create needed dirs, install PHP, clean up RUN openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key -x509 -days 3650 -subj "/CN=localhost" -out /etc/pki/tls/certs/localhost.crt && \ mkdir -p /run/php-fpm/ && \ + /usr/local/sbin/pkg-cache-repos.sh rpm && \ /scripts/install-php$PHPVER.sh && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ # CI passes php.net's current release for the branch; never ship older PHP. if [ -n "$EXPECT_PHP_MIN" ]; then \ php -r 'exit(version_compare(PHP_VERSION, getenv("EXPECT_PHP_MIN"), ">=") ? 0 : 1);' || \ diff --git a/Dockerfile.fpm b/Dockerfile.fpm index 3733d3f..cdcec73 100644 --- a/Dockerfile.fpm +++ b/Dockerfile.fpm @@ -7,10 +7,15 @@ ARG CACHE_BUST= # php.net's current release for this branch (CI); the build fails on older PHP. ARG EXPECT_PHP_MIN= +# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=http://192.168.1.40 +# (CT130 pkg-cache). Unset = upstream repositories exactly as before. Repo files +# are restored at the end of every RUN, so no layer keeps the cache address. +# See scripts/pkg-cache-repos.sh. +ARG PKG_CACHE= +COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh # Install repos, update, install only needed packages (no httpd/mod_ssl), clean up in one layer -RUN dnf install -y \ - https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm \ - https://rpms.remirepo.net/enterprise/remi-release-10.rpm && \ +RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \ + dnf install -y $(/usr/local/sbin/pkg-cache-repos.sh release-urls) && \ # Use Remi's origin, not the mirrorlist: during the 2026-10-21 PHP security # release the mirrors lagged hours behind (still 8.2.34 when 8.2.35 was out), # so builds shipped unpatched PHP. HTTPS first; plain http only as a fallback @@ -18,10 +23,12 @@ RUN dnf install -y \ # replayed (old) http index can't ship old PHP: packages stay GPG-checked and # the EXPECT_PHP_MIN gate below fails the build on anything older than php.net. sed -i -e 's|^#baseurl=http://\(.*\)$|baseurl=https://\1 http://\1|' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \ + /usr/local/sbin/pkg-cache-repos.sh rpm && \ printf 'retries=10\ntimeout=60\n' >> /etc/dnf/dnf.conf && \ dnf update -y && \ dnf install -y openssl wget procps cronie iproute postgresql-devel microdnf less git \ nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix fcgi && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ dnf clean all && \ rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* @@ -30,12 +37,16 @@ COPY ./scripts/ /scripts/ RUN chmod +x /scripts/* # Install ImageMagick from EPEL with HEIC/HEIF/AVIF support -RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \ +RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \ + dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ dnf clean all # Create needed dirs, install PHP, clean up (no SSL cert, no httpd) RUN mkdir -p /run/php-fpm/ && \ + /usr/local/sbin/pkg-cache-repos.sh rpm && \ /scripts/install-php$PHPVER.sh && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ # CI passes php.net's current release for the branch; never ship older PHP. if [ -n "$EXPECT_PHP_MIN" ]; then \ php -r 'exit(version_compare(PHP_VERSION, getenv("EXPECT_PHP_MIN"), ">=") ? 0 : 1);' || \ diff --git a/Dockerfile.shared-httpd b/Dockerfile.shared-httpd index 5e0c157..b9f36c2 100644 --- a/Dockerfile.shared-httpd +++ b/Dockerfile.shared-httpd @@ -1,10 +1,19 @@ FROM almalinux/10-base +# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=http://192.168.1.40 +# (CT130 pkg-cache). Unset = upstream repositories exactly as before. Repo files +# are restored at the end of every RUN, so no layer keeps the cache address. +# See scripts/pkg-cache-repos.sh. +ARG PKG_CACHE= +COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh + # Install Apache and minimal dependencies (no PHP at all) -RUN dnf install -y \ - https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm && \ +RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \ + dnf install -y $(/usr/local/sbin/pkg-cache-repos.sh release-urls | cut -d' ' -f1) && \ + /usr/local/sbin/pkg-cache-repos.sh rpm && \ dnf update -y && \ dnf install -y httpd mod_ssl openssl iproute cronie procps curl && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ dnf clean all && \ rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* diff --git a/Dockerfile.shared-ols b/Dockerfile.shared-ols index fea15f6..36787b5 100644 --- a/Dockerfile.shared-ols +++ b/Dockerfile.shared-ols @@ -19,6 +19,13 @@ ARG OLS_VERSION=1.8.4 ARG PHPVER=83 FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} +## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=http://192.168.1.40 +## (CT130 pkg-cache). Unset = upstream repositories exactly as before. Repo files +## are restored at the end of every RUN, so no layer keeps the cache address. +## See scripts/pkg-cache-repos.sh. +ARG PKG_CACHE= +COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh + ## Tooling the shared tier needs on top of the base: ## - inotify-tools: the .htaccess watcher (spec 5.3) ## - gettext-base: envsubst for render-shared-ols-config.sh @@ -28,9 +35,11 @@ FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} ## liveness check depends on. Only transitively present via the base image ## today (Ubuntu 24.04 pulls it in) — pin it explicitly so it can't be ## pruned as "unused" and silently break the supervisor's crash detection. -RUN apt-get update && \ +RUN /usr/local/sbin/pkg-cache-repos.sh apt && \ + apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ inotify-tools gettext-base openssl ca-certificates curl procps && \ + /usr/local/sbin/pkg-cache-repos.sh restore && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* diff --git a/docs/pkg-cache.md b/docs/pkg-cache.md new file mode 100644 index 0000000..d3171c5 --- /dev/null +++ b/docs/pkg-cache.md @@ -0,0 +1,124 @@ +# Internal package + image cache (`PKG_CACHE`) + +Opt-in build support for the LAN package cache **CT130 `pkg-cache` = `192.168.1.40`** +(Proxmox node `pve`, storage `shared-storage-tn`). Without `PKG_CACHE`, builds use +the upstream repositories exactly as before. + +Why it exists: during the 2026-10-21 PHP security release, builds failed because +Remi mirrors lagged hours behind origin, Remi's origin reset or timed out HTTPS +under load, and an EPEL mirror had a broken certificate. + +## What the cache serves + +| URL on the cache | Upstream tiers, tried in order (stale copy last) | +|---|---| +| `http://192.168.1.40/remi/` | `https://rpms.remirepo.net`, then `http://rpms.remirepo.net` (origin only, no mirrors) | +| `http://192.168.1.40/epel/` | `https://dl.fedoraproject.org/pub/epel`, `https://mirror.math.princeton.edu/pub/epel`, `https://mirrors.kernel.org/fedora-epel` | +| `http://192.168.1.40/almalinux/` | `https://repo.almalinux.org/almalinux`, `https://mirror.math.princeton.edu/pub/almalinux` | +| `http://192.168.1.40/ubuntu/` | `https://archive.ubuntu.com/ubuntu`, `https://us.archive.ubuntu.com/ubuntu` | +| `http://192.168.1.40/ubuntu-security/` | `https://security.ubuntu.com/ubuntu`, `https://archive.ubuntu.com/ubuntu` | +| `http://192.168.1.40/litespeed/` | `https://rpms.litespeedtech.com`, then `http://rpms.litespeedtech.com` | +| `192.168.1.40:5000` (plain http) | Docker Hub pull-through cache (`registry-1.docker.io`) | + +Cache rules: +- **Metadata** has a 3-minute TTL. After that it is revalidated upstream (`If-Modified-Since`). Metadata here means `repomd.xml*`, repodata files whose names don't start with a hash, apt `dists/` except `by-hash/`, `*-latest*.rpm`, `remi-release-N.rpm`, `RPM-GPG-KEY*` and directory listings. +- **Packages** (`*.rpm`, `*.deb`, hash-named repodata, `by-hash/`) are kept for 30 days, and evicted after 60 days without use. The cache is capped at 110 GB (`min_free` 15 GB). +- On a connect error, timeout or 5xx, a request falls through to the next tier. Only the **last** tier answers from a stale copy, so metadata goes primary → fallback → stale. A fresher fallback always wins over stale. +- `proxy_cache_lock` is on: concurrent misses on one file make a single upstream fetch. +- Each response carries `X-Cache-Status` (`HIT`/`MISS`/`REVALIDATED`/`EXPIRED`/`STALE`/`BYPASS`) and `X-Pkgcache-Tier`. On a HIT, the tier header names the location, not the upstream that filled the entry. + +## Building with the cache + +```bash +docker buildx build --build-arg PKG_CACHE=http://192.168.1.40 --build-arg PHPVER=85 -f Dockerfile . +``` + +`scripts/pkg-cache-repos.sh` (copied to `/usr/local/sbin/`) is wired into +`Dockerfile`, `Dockerfile.fpm`, `Dockerfile.shared-httpd` and `Dockerfile.shared-ols`: + +- `rpm`: rewrites the AlmaLinux, EPEL and Remi `baseurl`s to `//... /...`, and comments out that section's mirrorlist/metalink. dnf treats the second URL as a failover, so a dead cache falls back to the origin by itself. +- `apt`: rewrites the Ubuntu archive/security and LiteSpeed URIs to the cache. apt has no failover, so CI must probe the cache first (see below). +- `release-urls`: the EPEL and Remi release RPM URLs, from the cache when it is set. +- `restore`: puts the original repo files back. Every RUN that rewrites ends with `restore`, so **no shipped layer contains the cache address**. Images run on datacenter hosts that cannot reach it. + +`Dockerfile.litespeed` and `Dockerfile.lsphp` are **not wired yet**. Their +multi-stage `lsphp-apt-upgrade.sh` flow needs two changes first: +- a `PKG_CACHE` ARG in each stage; +- the madison check accepting the cache URL. It currently requires `*rpms.litespeedtech.com*`, and through the cache `apt-cache madison` shows `http://192.168.1.40/litespeed/debian`. + +The PHP gates are unchanged and still apply: `EXPECT_PHP_MIN`, +`EXPECT_LSPHP_PKG`/`EXPECT_PHP_VERSION`. + +## Why plain HTTP on the LAN (trade-off) + +- The cache fetches upstream over **HTTPS with certificate verification**. Remi and LiteSpeed also have a plain-http origin fallback, which mirrors what the Dockerfile already did. +- Client to cache is plain HTTP. dnf still verifies **package GPG signatures**, and apt verifies the signed `InRelease`/`Release.gpg` plus package hashes. A LAN attacker therefore cannot inject packages. On 2026-10-21 apt measurably refused the cached LiteSpeed index when given the wrong key (`NO_PUBKEY 011AA62DEDA1F085`). +- The residual risk is **metadata replay or freeze**: an attacker on the LAN path, or on the plain-http fallback leg, serves an *old* but validly signed `repomd.xml`. That would let a build install old PHP. Remi/EPEL/AlmaLinux do not enable `repo_gpgcheck`, so dnf does not check metadata signatures. The **php.net version gates** cover this for our images: a build fails if it gets PHP older than php.net's current release. +- WHP **hosts** have no such gate. They should keep the origin as the second `baseurl` and not rely on the cache for freshness on security-release day. Run `pkgcache-refresh` on the cache instead. +- Signing keys must **not** be fetched through the cache. Take them from the base image or directly over HTTPS. +- HTTPS to the cache (internal CA or a Let's Encrypt DNS-01 certificate for `pkg-cache.internal.cloud-hosting.io`) would close the LAN leg. It needs a CA in every build image or a public DNS name plus certificate. Not done; Josh to decide. + +## CI integration plan (NOT applied — needs Josh) + +1. **Network (blocker).** The ephemeral runners (`eph-pve-*` on `pve` vmbr2 10.99.98.0/24, `eph-pve3-*` on `pve3` vmbr1 10.99.99.0/24) are NATed, and `iptables FORWARD` **drops all RFC1918 destinations**, including 192.168.1.40. A narrow exception is needed on each node, placed before the DROP lines in `/etc/network/interfaces.d/*`: + ``` + # pve (/etc/network/interfaces.d/ vmbr2 stanza), before the 192.168.0.0/16 DROP: + post-up iptables -A FORWARD -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 80,5000 -j ACCEPT + post-down iptables -D FORWARD -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 80,5000 -j ACCEPT + # live, without ifreload: + iptables -I FORWARD 1 -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 80,5000 -j ACCEPT + # pve3: same with vmbr1 + ``` + `gitea-runner.internal.cloud-hosting.io` resolves to 10.10.14.20, which is not on this LAN. Whether it can reach 192.168.1.40 is untested. +2. **Probe, then opt in** (no `${{ }}` inside `run:`, per repo rule): + ```yaml + - name: Probe package cache + id: pkgcache + run: | + if curl -fsS --max-time 5 -o /dev/null http://192.168.1.40/ ; then + echo "url=http://192.168.1.40" >> "$GITHUB_OUTPUT" + else + echo "url=" >> "$GITHUB_OUTPUT"; echo "::warning::pkg-cache unreachable - building from upstream" + fi + # in docker/build-push-action `build-args:` add + # PKG_CACHE=${{ steps.pkgcache.outputs.url }} + ``` +3. **Docker Hub mirror for BuildKit.** The workflows use `docker/setup-buildx-action` (docker-container driver). BuildKit **ignores dockerd's `registry-mirrors`**, so configure the builder instead: + ```yaml + - uses: docker/setup-buildx-action@v3 + with: + buildkitd-config-inline: | + [registry."docker.io"] + mirrors = ["192.168.1.40:5000"] + [registry."192.168.1.40:5000"] + http = true + ``` + This exact config was used for the test builds on 192.168.1.193. Only Docker Hub images go through it; `repo.anhonesthost.net/...` is unaffected. +4. **dockerd clients** (plain `docker pull`, e.g. the build server or test hosts). Untested, because applying it needs a daemon restart. Add to `/etc/docker/daemon.json` and restart docker: + ```json + { "registry-mirrors": ["http://192.168.1.40:5000"], "insecure-registries": ["192.168.1.40:5000"] } + ``` + +## WHP hosts (plan only) + +The production hosts (whp01/whp02/sdbees) are in the datacenter and **cannot reach 192.168.1.40**. Only LAN hosts such as 192.168.1.148 can. Options for Josh: +- (a) a second identical cache CT in the datacenter; +- (b) expose this one over NetBird; +- (c) LAN hosts only. + +For a host that can reach a cache, use the same idea as `pkg-cache-repos.sh rpm`, keeping the origin second so the cache is never a single point of failure: + +```ini +# /etc/yum.repos.d/remi-safe.repo [remi-safe] (AL9: enterprise/9) +baseurl=http:///remi/enterprise/$releasever_major/safe/$basearch/ https://rpms.remirepo.net/enterprise/$releasever_major/safe/$basearch/ +#mirrorlist=... +``` + +## Operating the cache (CT130) + +- Config: `/etc/nginx/conf.d/pkgcache-http.conf` (cache zone, maps, log format) and `/etc/nginx/pkgcache/{server-common,meta,pkg}.conf`. `/etc/nginx/sites-available/pkgcache` is **generated** by `/usr/local/sbin/pkgcache-gen-nginx`: edit its `UPSTREAMS` table, then run `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx`. +- Registry: `/etc/docker/registry/config.yml` (Debian `docker-registry` 2.8.3, the same code as `registry:2`), service `docker-registry`. +- Data: nginx in `/srv/cache/nginx`, registry in `/srv/cache/registry` (150 GB mount point `mp0`, `backup=0`). +- **Security-release day:** run `pkgcache-refresh` to re-fetch every cached metadata entry now (or `pkgcache-refresh remi` for one prefix; `--list` shows them; `--purge` deletes them instead). A refresh bypasses the cache from localhost only and replaces each entry in place. If upstream is down, the old copy stays. Without it, metadata is at most 3 minutes old anyway. +- Monitoring: `pkgcache-stats` shows disk, hit ratio, fallback-tier use, STALE count and upstream errors. Logs are in `/var/log/nginx/pkgcache.access.log` (with `cache=`, `tier=`, `up=` fields) and `journalctl -u docker-registry`. Suggested Uptime Kuma checks: `http://192.168.1.40/` and `http://192.168.1.40:5000/v2/`. +- Docker Hub: anonymous pulls are rate-limited per egress IP, and every client now shares this cache's IP. To raise the limit, add `username`/`password` (a Hub access token) under `proxy:`. diff --git a/scripts/pkg-cache-repos.sh b/scripts/pkg-cache-repos.sh new file mode 100755 index 0000000..b378245 --- /dev/null +++ b/scripts/pkg-cache-repos.sh @@ -0,0 +1,136 @@ +#!/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