Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7d4db5bd3 | ||
|
|
0fee1b54c4 | ||
|
|
9baa7a6029 | ||
|
|
3a6cab19ee | ||
|
|
6a11fc9894 | ||
|
|
05ad47a833 | ||
|
|
d1547f3418 | ||
|
|
a00a3c7fc3 | ||
|
|
3334f94cd5 | ||
|
|
76fc53358c | ||
|
|
fc60ac5ef3 | ||
|
|
472fdcbc4d | ||
|
|
cb0cecb557 | ||
|
|
2b9e7f302c | ||
|
|
18f65c6d84 | ||
|
|
b2bec5b3ee | ||
|
|
fa7808897f |
No files matched your search
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
## resolve-base-image.sh IMAGE:TAG — CI only (not copied into images).
|
||||
## Prints IMAGE@sha256:<digest> for a Docker Hub tag, looked up DIRECTLY at
|
||||
## registry-1.docker.io over HTTPS (never via the pkg-cache mirror), so the
|
||||
## build can say `FROM IMAGE@sha256:...` and the mirror can only serve
|
||||
## content-addressed blobs that BuildKit verifies. Exits non-zero (prints
|
||||
## nothing on stdout) if the lookup fails; the workflow then skips the mirror
|
||||
## and pulls the tag directly from Docker Hub. HEAD requests do not count
|
||||
## against Docker Hub pull rate limits.
|
||||
## Canonical copy: cloud-apache-container/.gitea/scripts/resolve-base-image.sh
|
||||
## (docs/pkg-cache.md there); other repos carry identical copies.
|
||||
set -euo pipefail
|
||||
ref="${1:?usage: resolve-base-image.sh IMAGE:TAG}"
|
||||
name_re='^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)?$'
|
||||
tag_re='^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$'
|
||||
name="${ref%:*}"; tag="${ref##*:}"
|
||||
[ "$name" != "$ref" ] || { echo "resolve-base-image: '$ref' has no :tag" >&2; exit 2; }
|
||||
[[ "$name" =~ $name_re ]] || { echo "resolve-base-image: bad image name '$name'" >&2; exit 2; }
|
||||
[[ "$tag" =~ $tag_re ]] || { echo "resolve-base-image: bad tag '$tag'" >&2; exit 2; }
|
||||
repo="$name"; [[ "$repo" == */* ]] || repo="library/$repo" # official images
|
||||
|
||||
curl_opts=(-fsS --proto '=https' --retry 3 --retry-all-errors --max-time 20)
|
||||
token=$(curl "${curl_opts[@]}" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" |
|
||||
python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
|
||||
[ -n "$token" ] || { echo "resolve-base-image: no Docker Hub token for $repo" >&2; exit 1; }
|
||||
hdrs=$(curl "${curl_opts[@]}" -I \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
-H 'Accept: application/vnd.oci.image.index.v1+json' \
|
||||
-H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
|
||||
-H 'Accept: application/vnd.oci.image.manifest.v1+json' \
|
||||
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
|
||||
"https://registry-1.docker.io/v2/${repo}/manifests/${tag}")
|
||||
digest=$(printf '%s\n' "$hdrs" | tr -d '\r' | awk 'tolower($1)=="docker-content-digest:" {print $2}' | tail -n1)
|
||||
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "resolve-base-image: no valid digest for $ref (got '${digest}')" >&2; exit 1; }
|
||||
printf '%s@%s\n' "$name" "$digest"
|
||||
@@ -57,7 +57,8 @@ jobs:
|
||||
scripts/lsphp-abi-gate.sh \
|
||||
scripts/tune-mpm.sh \
|
||||
scripts/tests/lsphp-info-probe.test.sh \
|
||||
scripts/tests/lsphp-abi-gate.test.sh
|
||||
scripts/tests/lsphp-abi-gate.test.sh \
|
||||
.gitea/scripts/resolve-base-image.sh
|
||||
|
||||
- name: lsphp probe regression test
|
||||
run: ./scripts/tests/lsphp-info-probe.test.sh
|
||||
@@ -80,15 +81,68 @@ jobs:
|
||||
# security builds of the others.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
phpver: [74, 80, 81, 82, 83, 84, 85]
|
||||
phpver: [82, 83, 84, 85]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||
- name: Resolve base image digest (Docker Hub, direct)
|
||||
id: base
|
||||
env:
|
||||
BASE_TAG: almalinux/10-base:latest
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||
# exactly as before. The repo cache and the registry mirror are probed
|
||||
# separately so either can fail back on its own.
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
# The registry mirror is only used for a digest-pinned base image.
|
||||
env:
|
||||
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||
run: |
|
||||
url=""; mirror=""
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||
if [ "$BASE_PINNED" = true ] && curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<not used: Docker Hub direct>}"
|
||||
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||
# back to Docker Hub if the mirror stops answering mid-run.
|
||||
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||
if: steps.pkgcache.outputs.mirror != ''
|
||||
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"]
|
||||
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.pkgcache.outputs.mirror == ''
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||
@@ -135,6 +189,8 @@ jobs:
|
||||
build-args: |
|
||||
PHPVER=${{ matrix.phpver }}
|
||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
|
||||
@@ -147,15 +203,68 @@ jobs:
|
||||
# security builds of the others.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
phpver: [74, 80, 81, 82, 83, 84, 85]
|
||||
phpver: [82, 83, 84, 85]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||
- name: Resolve base image digest (Docker Hub, direct)
|
||||
id: base
|
||||
env:
|
||||
BASE_TAG: almalinux/10-base:latest
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||
# exactly as before. The repo cache and the registry mirror are probed
|
||||
# separately so either can fail back on its own.
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
# The registry mirror is only used for a digest-pinned base image.
|
||||
env:
|
||||
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||
run: |
|
||||
url=""; mirror=""
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||
if [ "$BASE_PINNED" = true ] && curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<not used: Docker Hub direct>}"
|
||||
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||
# back to Docker Hub if the mirror stops answering mid-run.
|
||||
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||
if: steps.pkgcache.outputs.mirror != ''
|
||||
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"]
|
||||
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.pkgcache.outputs.mirror == ''
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||
@@ -203,6 +312,8 @@ jobs:
|
||||
build-args: |
|
||||
PHPVER=${{ matrix.phpver }}
|
||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
|
||||
@@ -210,6 +321,10 @@ jobs:
|
||||
|
||||
Build-LiteSpeed-Images:
|
||||
runs-on: ubuntu-latest
|
||||
# One place for the OLS base tag: digest lookup and build-args both read it.
|
||||
# OLS_VERSION pinned to 1.8.4 (see the build step); Dockerfile default differs.
|
||||
env:
|
||||
OLS_VERSION: "1.8.4"
|
||||
strategy:
|
||||
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||
# security builds of the others.
|
||||
@@ -219,15 +334,68 @@ jobs:
|
||||
# images stop at older OLS releases for those PHP versions, and the
|
||||
# cac-litespeed tier is a paid premium offering: 8.1+ is the
|
||||
# modernization story we're selling.
|
||||
phpver: [81, 82, 83, 84, 85]
|
||||
phpver: [82, 83, 84, 85]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||
- name: Resolve base image digest (Docker Hub, direct)
|
||||
id: base
|
||||
env:
|
||||
BASE_TAG: litespeedtech/openlitespeed:${{ env.OLS_VERSION }}-lsphp${{ matrix.phpver }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||
# exactly as before. The repo cache and the registry mirror are probed
|
||||
# separately so either can fail back on its own.
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
# The registry mirror is only used for a digest-pinned base image.
|
||||
env:
|
||||
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||
run: |
|
||||
url=""; mirror=""
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||
if [ "$BASE_PINNED" = true ] && curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<not used: Docker Hub direct>}"
|
||||
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||
# back to Docker Hub if the mirror stops answering mid-run.
|
||||
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||
if: steps.pkgcache.outputs.mirror != ''
|
||||
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"]
|
||||
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.pkgcache.outputs.mirror == ''
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||
@@ -298,14 +466,15 @@ jobs:
|
||||
push: true
|
||||
build-args: |
|
||||
PHPVER=${{ matrix.phpver }}
|
||||
OLS_VERSION=1.8.4
|
||||
OLS_VERSION=${{ env.OLS_VERSION }}
|
||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
|
||||
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
|
||||
# OLS_VERSION pinned to 1.8.4 — only release with prebuilt images
|
||||
# for every PHP version we ship (1.8.5 and 1.9.0 don't have an
|
||||
# lsphp81 variant on Docker Hub). Bump alongside a local rebuild
|
||||
# test when LiteSpeed publishes lsphp81 on a newer OLS release.
|
||||
# OLS_VERSION pinned to 1.8.4. It was the only release with an lsphp81
|
||||
# variant; 8.1 and older are no longer built (2026-10-21), so the pin
|
||||
# can move to a newer OLS release after a local rebuild test.
|
||||
# See spec: docs/superpowers/specs/2026-06-01-cac-litespeed-design.md
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${{ matrix.phpver }}
|
||||
@@ -313,6 +482,10 @@ jobs:
|
||||
|
||||
Build-LSPHP-Images:
|
||||
runs-on: ubuntu-latest
|
||||
# One place for the OLS base tag: digest lookup and build-args both read it.
|
||||
# OLS_VERSION pinned to 1.8.4 (see the build step); Dockerfile default differs.
|
||||
env:
|
||||
OLS_VERSION: "1.8.4"
|
||||
strategy:
|
||||
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||
# security builds of the others.
|
||||
@@ -322,15 +495,68 @@ jobs:
|
||||
# backend for the shared-ols tier and shares the litespeed prebuilt
|
||||
# base, which only ships lsphp for 8.1+. Keep this matrix in lockstep
|
||||
# with Build-LiteSpeed-Images.
|
||||
phpver: [81, 82, 83, 84, 85]
|
||||
phpver: [82, 83, 84, 85]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||
- name: Resolve base image digest (Docker Hub, direct)
|
||||
id: base
|
||||
env:
|
||||
BASE_TAG: litespeedtech/openlitespeed:${{ env.OLS_VERSION }}-lsphp${{ matrix.phpver }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||
# exactly as before. The repo cache and the registry mirror are probed
|
||||
# separately so either can fail back on its own.
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
# The registry mirror is only used for a digest-pinned base image.
|
||||
env:
|
||||
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||
run: |
|
||||
url=""; mirror=""
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||
if [ "$BASE_PINNED" = true ] && curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<not used: Docker Hub direct>}"
|
||||
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||
# back to Docker Hub if the mirror stops answering mid-run.
|
||||
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||
if: steps.pkgcache.outputs.mirror != ''
|
||||
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"]
|
||||
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.pkgcache.outputs.mirror == ''
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||
@@ -401,7 +627,9 @@ jobs:
|
||||
push: true
|
||||
build-args: |
|
||||
PHPVER=${{ matrix.phpver }}
|
||||
OLS_VERSION=1.8.4
|
||||
OLS_VERSION=${{ env.OLS_VERSION }}
|
||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
|
||||
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
|
||||
@@ -417,10 +645,63 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||
- name: Resolve base image digest (Docker Hub, direct)
|
||||
id: base
|
||||
env:
|
||||
BASE_TAG: almalinux/10-base:latest
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||
# exactly as before. The repo cache and the registry mirror are probed
|
||||
# separately so either can fail back on its own.
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
# The registry mirror is only used for a digest-pinned base image.
|
||||
env:
|
||||
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||
run: |
|
||||
url=""; mirror=""
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||
if [ "$BASE_PINNED" = true ] && curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<not used: Docker Hub direct>}"
|
||||
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||
# back to Docker Hub if the mirror stops answering mid-run.
|
||||
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||
if: steps.pkgcache.outputs.mirror != ''
|
||||
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"]
|
||||
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.pkgcache.outputs.mirror == ''
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea
|
||||
@@ -436,19 +717,79 @@ jobs:
|
||||
file: ./Dockerfile.shared-httpd
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest
|
||||
|
||||
Build-Shared-OLS:
|
||||
runs-on: ubuntu-latest
|
||||
# One place for the OLS base tag: digest lookup and build-args both read it.
|
||||
env:
|
||||
OLS_VERSION: "1.8.4"
|
||||
PHPVER: "83"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||
- name: Resolve base image digest (Docker Hub, direct)
|
||||
id: base
|
||||
env:
|
||||
BASE_TAG: litespeedtech/openlitespeed:${{ env.OLS_VERSION }}-lsphp${{ env.PHPVER }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||
# exactly as before. The repo cache and the registry mirror are probed
|
||||
# separately so either can fail back on its own.
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
# The registry mirror is only used for a digest-pinned base image.
|
||||
env:
|
||||
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||
run: |
|
||||
url=""; mirror=""
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||
if [ "$BASE_PINNED" = true ] && curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<not used: Docker Hub direct>}"
|
||||
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||
# back to Docker Hub if the mirror stops answering mid-run.
|
||||
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||
if: steps.pkgcache.outputs.mirror != ''
|
||||
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"]
|
||||
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.pkgcache.outputs.mirror == ''
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea
|
||||
@@ -467,7 +808,9 @@ jobs:
|
||||
# Single image (runs no PHP). PHPVER just selects the OLS base tag;
|
||||
# pinned to 83 / OLS 1.8.4 to match the rest of the litespeed family.
|
||||
build-args: |
|
||||
PHPVER=83
|
||||
OLS_VERSION=1.8.4
|
||||
PHPVER=${{ env.PHPVER }}
|
||||
OLS_VERSION=${{ env.OLS_VERSION }}
|
||||
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/shared-ols:latest
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
phpver: [81, 82, 83, 84, 85]
|
||||
phpver: [82, 83, 84, 85]
|
||||
image: [lsphp, litespeed]
|
||||
steps:
|
||||
- name: Select this job's target (skip if not requested)
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
case "$IN_PROD" in true|false) ;; *) echo "::error::push_production_tags must be true|false, got '$IN_PROD'"; exit 1 ;; esac
|
||||
v=""
|
||||
for pair in $IN_VERSIONS; do
|
||||
[[ "$pair" =~ ^(8[1-5])=([0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo "::error::bad php_versions entry '$pair' (want NN=X.Y.Z, NN in 81..85)"; exit 1; }
|
||||
[[ "$pair" =~ ^(8[2-5])=([0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo "::error::bad php_versions entry '$pair' (want NN=X.Y.Z, NN in 82..85)"; exit 1; }
|
||||
[ "${BASH_REMATCH[1]}" = "$PHPVER" ] && v="${BASH_REMATCH[2]}"
|
||||
done
|
||||
echo "prod=$IN_PROD" >> "$GITHUB_OUTPUT"
|
||||
|
||||
+29
-6
@@ -1,4 +1,9 @@
|
||||
FROM almalinux/10-base
|
||||
# Base image. CI passes BASE_IMAGE=almalinux/10-base@sha256:<digest>, resolved
|
||||
# DIRECTLY from Docker Hub in the same run (.gitea/scripts/resolve-base-image.sh),
|
||||
# so the pkg-cache registry mirror can only serve content-addressed blobs.
|
||||
# Local builds use the tag. docs/pkg-cache.md "Base-image digest pinning".
|
||||
ARG BASE_IMAGE=almalinux/10-base
|
||||
FROM ${BASE_IMAGE}
|
||||
ARG PHPVER=83
|
||||
# Per-CI-run value: the first RUN below (dnf update) and everything after it can
|
||||
# never be a layer-cache hit, so every build gets the repos' current packages.
|
||||
@@ -7,10 +12,17 @@ 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=https://192.168.1.40
|
||||
# (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||
# GPG-verified against keys/pkg-cache/ either way. 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
# 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 && \
|
||||
/usr/local/sbin/pkg-cache-repos.sh release-install epel remi && \
|
||||
# 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 +30,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 +44,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);' || \
|
||||
@@ -69,7 +87,12 @@ COPY ./configs/whp-security.conf /etc/httpd/conf.d/
|
||||
# Set up cron job in a single layer
|
||||
RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
||||
|
||||
## Healthy = Apache answered with anything but a 5xx. `curl -f` also failed on
|
||||
## 4xx, so a new site with an empty docroot (403) or an app with no route for /
|
||||
## (404) showed unhealthy forever, and WHP's monitor restarted it as "PHP
|
||||
## wedged". 000 (no answer) and 5xx (PHP/app error) still fail.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost/ || exit 1
|
||||
CMD code=$(curl -s -o /dev/null -m 4 -w '%{http_code}' http://localhost/); \
|
||||
case "$code" in 000|5*) echo "http $code"; exit 1;; esac
|
||||
|
||||
ENTRYPOINT [ "/scripts/entrypoint.sh" ]
|
||||
+23
-5
@@ -1,4 +1,9 @@
|
||||
FROM almalinux/10-base
|
||||
# Base image. CI passes BASE_IMAGE=almalinux/10-base@sha256:<digest>, resolved
|
||||
# DIRECTLY from Docker Hub in the same run (.gitea/scripts/resolve-base-image.sh),
|
||||
# so the pkg-cache registry mirror can only serve content-addressed blobs.
|
||||
# Local builds use the tag. docs/pkg-cache.md "Base-image digest pinning".
|
||||
ARG BASE_IMAGE=almalinux/10-base
|
||||
FROM ${BASE_IMAGE}
|
||||
ARG PHPVER=83
|
||||
# Per-CI-run value: the first RUN below (dnf update) and everything after it can
|
||||
# never be a layer-cache hit, so every build gets the repos' current packages.
|
||||
@@ -7,10 +12,17 @@ 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=https://192.168.1.40
|
||||
# (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||
# GPG-verified against keys/pkg-cache/ either way. 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
# 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 && \
|
||||
/usr/local/sbin/pkg-cache-repos.sh release-install epel remi && \
|
||||
# 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 +30,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 +44,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);' || \
|
||||
|
||||
+18
-3
@@ -22,12 +22,18 @@
|
||||
## after FROM for any RUN steps that need it.
|
||||
ARG OLS_VERSION=1.8.5
|
||||
ARG PHPVER=83
|
||||
## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256:<digest> of the
|
||||
## ${OLS_VERSION}-lsphp${PHPVER} tag, resolved DIRECTLY from Docker Hub in the same
|
||||
## run (.gitea/scripts/resolve-base-image.sh), so the pkg-cache registry mirror can
|
||||
## only serve content-addressed blobs. Local builds use the tag. Every stage below
|
||||
## (incl. the emergency lsphp-src stage) starts from this one image.
|
||||
ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
|
||||
## ---- EMERGENCY ONLY: lsphp compiled from php.net source ----------------------
|
||||
## Idle unless PHP_SRC_VERSION is set (then /lsphp-src-out stays empty and the
|
||||
## overlay step below is a no-op). Same stage as Dockerfile.lsphp — see there and
|
||||
## docs/lsphp-source-emergency.md.
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} AS lsphp-src
|
||||
FROM ${BASE_IMAGE} AS lsphp-src
|
||||
ARG PHPVER=83
|
||||
ARG CACHE_BUST=
|
||||
ARG EXPECT_LSPHP_PKG=
|
||||
@@ -42,7 +48,7 @@ RUN --mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||
fi; \
|
||||
PHPVER="${PHPVER}" bash /mnt/scripts/lsphp-src-build.sh
|
||||
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
FROM ${BASE_IMAGE}
|
||||
ARG PHPVER=83
|
||||
ENV PHPVER=${PHPVER}
|
||||
|
||||
@@ -80,11 +86,20 @@ ENV PHPVER=${PHPVER}
|
||||
ARG CACHE_BUST=
|
||||
ARG EXPECT_LSPHP_PKG=
|
||||
ARG EXPECT_PHP_VERSION=
|
||||
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||
## (CT130 pkg-cache, https only). Unset = upstream repositories (Ubuntu +
|
||||
## rpms.litespeedtech.com). Repo files are restored at the end of the 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||
RUN /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
||||
RUN /usr/local/sbin/pkg-cache-repos.sh apt && \
|
||||
/usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
||||
gettext-base sudo cron \
|
||||
ca-certificates curl wget \
|
||||
lsphp${PHPVER}-ldap && \
|
||||
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
|
||||
+27
-3
@@ -21,6 +21,12 @@
|
||||
|
||||
ARG OLS_VERSION=1.8.4
|
||||
ARG PHPVER=83
|
||||
## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256:<digest> of the
|
||||
## ${OLS_VERSION}-lsphp${PHPVER} tag, resolved DIRECTLY from Docker Hub in the same
|
||||
## run (.gitea/scripts/resolve-base-image.sh), so the pkg-cache registry mirror can
|
||||
## only serve content-addressed blobs. Local builds use the tag. Every stage below
|
||||
## (incl. the emergency lsphp-src stage) starts from this one image.
|
||||
ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
|
||||
## ---- stage 0 (EMERGENCY ONLY): lsphp compiled from php.net source -----------
|
||||
## Idle unless PHP_SRC_VERSION is set: lsphp-src-build.sh then exits at once and
|
||||
@@ -30,7 +36,7 @@ ARG PHPVER=83
|
||||
## — CI: .gitea/workflows/lsphp-source-emergency.yaml; runbook:
|
||||
## docs/lsphp-source-emergency.md. The tarball is sha256- AND OpenPGP-verified
|
||||
## against keys/php-src/; configure flags are replayed from the packaged lsphp.
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} AS lsphp-src
|
||||
FROM ${BASE_IMAGE} AS lsphp-src
|
||||
ARG PHPVER=83
|
||||
ARG CACHE_BUST=
|
||||
ARG EXPECT_LSPHP_PKG=
|
||||
@@ -77,7 +83,7 @@ RUN --mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||
## Separate stage on purpose: the compiler + headers (~400MB) stay out of the
|
||||
## shipped image, which gains only the ~40KB .so. Costs ~1-2 min of CI per PHP
|
||||
## version; both stages share the same base layer, so no extra pull.
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} AS ext-build
|
||||
FROM ${BASE_IMAGE} AS ext-build
|
||||
ARG PHPVER=83
|
||||
|
||||
## Toolchain layer, deliberately BEFORE the source COPY so editing the extension
|
||||
@@ -94,11 +100,20 @@ ARG PHPVER=83
|
||||
ARG CACHE_BUST=
|
||||
ARG EXPECT_LSPHP_PKG=
|
||||
ARG EXPECT_PHP_VERSION=
|
||||
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||
## (CT130 pkg-cache, https only). Unset = upstream repositories (Ubuntu +
|
||||
## rpms.litespeedtech.com). Repo files are restored at the end of the 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||
RUN set -e; \
|
||||
/usr/local/sbin/pkg-cache-repos.sh apt; \
|
||||
/usr/local/sbin/lsphp-apt-upgrade.sh --no-os-upgrade "${PHPVER}" \
|
||||
build-essential autoconf pkg-config \
|
||||
lsphp${PHPVER}-dev; \
|
||||
/usr/local/sbin/pkg-cache-repos.sh restore; \
|
||||
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
||||
DEVV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}-dev); \
|
||||
if [ "$RTV" != "$DEVV" ]; then \
|
||||
@@ -168,7 +183,7 @@ RUN set -e; \
|
||||
cp modules/cac_path_parity.so /build-out/
|
||||
|
||||
## ---- stage 2: the shipped sidecar image ------------------------------------
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
FROM ${BASE_IMAGE}
|
||||
ARG PHPVER=83
|
||||
ENV PHPVER=${PHPVER}
|
||||
|
||||
@@ -196,10 +211,18 @@ ENV PHPVER=${PHPVER}
|
||||
ARG CACHE_BUST=
|
||||
ARG EXPECT_LSPHP_PKG=
|
||||
ARG EXPECT_PHP_VERSION=
|
||||
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||
## (CT130 pkg-cache, https only). Unset = upstream repositories (Ubuntu +
|
||||
## rpms.litespeedtech.com). Repo files are restored at the end of the 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
COPY --from=ext-build /build-out/lsphp.version /etc/cac-lsphp-build.version
|
||||
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||
RUN set -e; \
|
||||
V=$(cat /etc/cac-lsphp-build.version); \
|
||||
/usr/local/sbin/pkg-cache-repos.sh apt; \
|
||||
if ! /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
||||
ca-certificates \
|
||||
lsphp${PHPVER}="$V" lsphp${PHPVER}-common="$V" lsphp${PHPVER}-ldap="$V"; then \
|
||||
@@ -211,6 +234,7 @@ RUN set -e; \
|
||||
echo " build (CI) / rebuild with --no-cache (local)." >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
/usr/local/sbin/pkg-cache-repos.sh restore; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
||||
|
||||
+19
-3
@@ -1,10 +1,26 @@
|
||||
FROM almalinux/10-base
|
||||
# Base image. CI passes BASE_IMAGE=almalinux/10-base@sha256:<digest>, resolved
|
||||
# DIRECTLY from Docker Hub in the same run (.gitea/scripts/resolve-base-image.sh),
|
||||
# so the pkg-cache registry mirror can only serve content-addressed blobs.
|
||||
# Local builds use the tag. docs/pkg-cache.md "Base-image digest pinning".
|
||||
ARG BASE_IMAGE=almalinux/10-base
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||
# (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||
# GPG-verified against keys/pkg-cache/ either way. 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
|
||||
# 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 && \
|
||||
/usr/local/sbin/pkg-cache-repos.sh release-install epel && \
|
||||
/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/*
|
||||
|
||||
|
||||
+18
-2
@@ -17,7 +17,21 @@
|
||||
|
||||
ARG OLS_VERSION=1.8.4
|
||||
ARG PHPVER=83
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256:<digest> of the
|
||||
## ${OLS_VERSION}-lsphp${PHPVER} tag, resolved DIRECTLY from Docker Hub in the same
|
||||
## run (.gitea/scripts/resolve-base-image.sh), so the pkg-cache registry mirror can
|
||||
## only serve content-addressed blobs. Local builds use the tag.
|
||||
ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||
## (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||
## GPG-verified against keys/pkg-cache/ either way. 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
|
||||
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||
|
||||
## Tooling the shared tier needs on top of the base:
|
||||
## - inotify-tools: the .htaccess watcher (spec 5.3)
|
||||
@@ -28,9 +42,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/*
|
||||
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
# Internal package + image cache (`PKG_CACHE`)
|
||||
|
||||
Opt-in build support for the LAN package cache **CT130 `pkg-cache` at `https://192.168.1.40`** (Proxmox node `pve`, storage `shared-storage-tn`).
|
||||
|
||||
Without `PKG_CACHE`, builds use the upstream repositories. The one change from before is that the EPEL and Remi release RPMs are now **GPG-verified** against `keys/pkg-cache/` before they are installed.
|
||||
|
||||
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;
|
||||
- an EPEL mirror had a broken certificate.
|
||||
|
||||
On that day a build without the cache spent **1031 s** in its first dnf step. Through the cache the same step took **40 s cold and 21 s warm**.
|
||||
|
||||
## What the cache serves
|
||||
|
||||
| URL (https, pkg-cache CA) | Upstream tiers, tried in order (stale copy last) |
|
||||
|---|---|
|
||||
| `https://192.168.1.40/remi/` | `https://rpms.remirepo.net`, then `http://rpms.remirepo.net` (origin only, no mirrors) |
|
||||
| `https://192.168.1.40/epel/` | `https://dl.fedoraproject.org/pub/epel`, `https://mirror.math.princeton.edu/pub/epel`, `https://mirrors.kernel.org/fedora-epel` |
|
||||
| `https://192.168.1.40/almalinux/` | `https://repo.almalinux.org/almalinux`, `https://mirror.math.princeton.edu/pub/almalinux` |
|
||||
| `https://192.168.1.40/ubuntu/` | `https://archive.ubuntu.com/ubuntu`, `https://us.archive.ubuntu.com/ubuntu` |
|
||||
| `https://192.168.1.40/ubuntu-security/` | `https://security.ubuntu.com/ubuntu`, `https://archive.ubuntu.com/ubuntu` |
|
||||
| `https://192.168.1.40/litespeed/` | `https://rpms.litespeedtech.com`, then `http://rpms.litespeedtech.com` |
|
||||
| `https://192.168.1.40/nodesource/` | `https://rpm.nodesource.com` (origin only; RPMs signed, repomd not) — added 2026-10-22 |
|
||||
| `https://192.168.1.40/debian/` | `https://deb.debian.org/debian` (CDN origin; princeton was 6 h behind on trixie-updates) — added 2026-10-22 |
|
||||
| `https://192.168.1.40/debian-security/` | `https://security.debian.org/debian-security`, `https://deb.debian.org/debian-security` — added 2026-10-22 |
|
||||
| `https://192.168.1.40/goproxy/` | `https://proxy.golang.org` (Go module proxy; `/sumdb/` is 404 there, so `go` checks sum.golang.org directly) — added 2026-10-22 |
|
||||
| `192.168.1.40:5000` (TLS, pkg-cache CA) | Docker Hub pull-through cache (`registry-1.docker.io`) |
|
||||
|
||||
Plain `http://192.168.1.40/` answers **403** to LAN clients.
|
||||
|
||||
Cache rules:
|
||||
- **Metadata** has a 3-minute TTL. After that it is revalidated upstream (`If-Modified-Since`). Metadata means:
|
||||
- `repomd.xml*`
|
||||
- repodata files whose names don't start with a hash
|
||||
- apt `dists/`, except `by-hash/`
|
||||
- `*-latest*.rpm` and `remi-release-N.rpm`
|
||||
- `RPM-GPG-KEY*`
|
||||
- Go proxy `/@v/list` and `/@latest`
|
||||
- directory listings
|
||||
- **Packages** (`*.rpm`, `*.deb`, hash-named repodata, `by-hash/`) are cached for 30 days and evicted after 60 days unused. Total size is capped at 110 GB, with 15 GB kept free.
|
||||
- On a connect error, timeout or 5xx, the request falls through to the next tier. Only the **last** tier may answer from a stale copy, so the order is primary, then fallback, then stale.
|
||||
- `proxy_cache_lock` is on.
|
||||
- Responses carry `X-Cache-Status` and `X-Pkgcache-Tier`.
|
||||
|
||||
## Building with the cache
|
||||
|
||||
```bash
|
||||
docker buildx build --build-arg PKG_CACHE=https://192.168.1.40 --build-arg PHPVER=85 -f Dockerfile .
|
||||
```
|
||||
|
||||
`scripts/pkg-cache-repos.sh` (installed as `/usr/local/sbin/`) and `keys/pkg-cache/` (installed as `/usr/local/share/pkg-cache/`) are wired into `Dockerfile`, `Dockerfile.fpm`, `Dockerfile.shared-httpd` and `Dockerfile.shared-ols`. The script has four modes.
|
||||
|
||||
**`rpm`** handles AlmaLinux, EPEL and Remi:
|
||||
- Each `baseurl` becomes `<cache>/<prefix>/... <origin>/...`. dnf fails over to the origin if the cache dies.
|
||||
- That section's mirrorlist/metalink is commented out.
|
||||
- The section gets `sslcacert=` pointing at a bundle of the system CAs plus the pkg-cache CA. The bundle lives in the state dir and is deleted by `restore`.
|
||||
- It also gets `gpgcheck=1`.
|
||||
- AlmaLinux and Remi also get `repo_gpgcheck=1`, because both sign `repomd.xml` (measured 2026-10-21: `repomd.xml.asc` returns 200). EPEL publishes no repomd signature (404), so its metadata integrity rests on HTTPS end to end.
|
||||
|
||||
**`apt`** handles Ubuntu archive/security and LiteSpeed:
|
||||
- Their URIs are rewritten to the cache.
|
||||
- `/etc/apt/apt.conf.d/99pkg-cache` sets `Acquire::https::192.168.1.40::CaInfo`, so the CA is trusted for the cache host only.
|
||||
- apt verifies the signed `InRelease`/`Release.gpg` as always. It has no failover, so CI must probe the cache first.
|
||||
|
||||
**`release-install epel [remi]`**:
|
||||
- Downloads the release RPM over HTTPS, from the cache or from the origin.
|
||||
- Checks the package name.
|
||||
- Imports the key committed in `keys/pkg-cache/`.
|
||||
- Requires `rpm -K` to report `digests signatures OK`.
|
||||
- Installs with `--setopt=localpkg_gpgcheck=1`.
|
||||
|
||||
**`nodesource MAJOR`** (added 2026-10-22) replaces `curl https://rpm.nodesource.com/setup_N.x | bash`:
|
||||
- writes `/etc/yum.repos.d/nodesource-nodejs.repo` with the ORIGIN baseurl, `gpgcheck=1` and `gpgkey=file:///usr/local/share/pkg-cache/RPM-GPG-KEY-nodesource` (key committed in `keys/pkg-cache/`);
|
||||
- with `PKG_CACHE` it is then rewritten like the other repos; `restore` puts the origin version back, so the shipped image keeps a normal NodeSource repo.
|
||||
|
||||
**`go-env`** (added 2026-10-22): `eval "$(pkg-cache-repos.sh go-env)"` inside the RUN that runs `go mod download`/`go build`. Prints `GOPROXY=<cache>/goproxy|https://proxy.golang.org|direct` (`|` = fall through on ANY error) and `SSL_CERT_FILE=<state>/ca-bundle.pem`; nothing without `PKG_CACHE`. Modules stay verified by `go.sum` + sum.golang.org. End the RUN with `restore` (removes the bundle).
|
||||
|
||||
**EL9**: `release-install epel|remi` picks the EL major from `rpm -E %rhel` (9 or 10); `RPM-GPG-KEY-EPEL-9` is committed.
|
||||
|
||||
**Debian** (`apt` mode): `deb.debian.org/debian`, `deb.debian.org/debian-security` and `security.debian.org` (classic and deb822 `.sources`) are rewritten to `<cache>/debian/` and `<cache>/debian-security/`.
|
||||
|
||||
**Not proxied, on purpose:** PyPI and the npm registry. Our pip/npm installs are not hash-locked (`pip install -r` without `--require-hashes`, `npm install -g pm2@latest`), so the client would trust whatever the cache served — a compromised cache would mean code execution in shipped images. Proxy them only after the installs are hash-pinned. GitHub-release downloads are likewise left direct.
|
||||
|
||||
**`restore`**:
|
||||
- Puts rewritten files back.
|
||||
- Deletes files the script created.
|
||||
- Deletes any repo/apt file that **appeared during the RUN** and still references the cache.
|
||||
- Then **fails the build** if any file under `/etc/yum.repos.d`, `/etc/dnf` or `/etc/apt` still mentions the cache.
|
||||
|
||||
Every RUN that rewrites ends with `restore`, so no shipped layer carries the LAN address. This was verified on the built images: 0 references, no state dir, no apt drop-in.
|
||||
|
||||
`PKG_CACHE` must start with `https://`. `http://` is refused.
|
||||
|
||||
`Dockerfile.litespeed` and `Dockerfile.lsphp` are wired since 2026-10-22 (normal apt path; the php.net EMERGENCY stage `lsphp-src` stays on upstream). `lsphp-apt-upgrade.sh`'s madison gate accepts `<PKG_CACHE>/litespeed/` only when `PKG_CACHE` is set.
|
||||
|
||||
## Repositories wired (2026-10-22)
|
||||
|
||||
The script and `keys/pkg-cache/` are **canonical here**; every other repo carries byte-identical copies (`sha256sum scripts/pkg-cache-repos.sh` to compare). Change it here first, then sync. Same for `.gitea/scripts/resolve-base-image.sh`.
|
||||
|
||||
| Repo | Image(s) | Bases | Through the cache | Base digest-pinned |
|
||||
|---|---|---|---|---|
|
||||
| cloud-apache-container | cac, cac-fpm, shared-httpd (almalinux/10-base); shared-ols, cac-litespeed, cac-lsphp (litespeedtech/openlitespeed) | Docker Hub | AlmaLinux/EPEL/Remi (rpm); Ubuntu + LiteSpeed (apt) | yes, all 6 jobs |
|
||||
| cloud-node-container | cnoc:node18..26 | almalinux/10-base | AlmaLinux/EPEL + NodeSource (`nodesource N`) | yes |
|
||||
| cloud-memcached-container | cmc | almalinux/9-base | AlmaLinux 9 + EPEL 9 | yes |
|
||||
| cloud-haproxy-container | chc | almalinux/9-base | AlmaLinux 9 + EPEL 9 (inotify-tools GitHub tarball direct) | yes |
|
||||
| whp-default-container | whp-default | httpd:alpine | nothing to install (mirror only) | yes |
|
||||
| versitygw-container | versitygw | ubuntu:latest | Ubuntu (versitygw .deb from GitHub direct) | yes |
|
||||
| haproxy-manager-base | haproxy-manager-base, coraza-spoa | repo.anhonesthost.net python:3.12-slim / golang:1.25 (not Docker Hub: no mirror); gcr.io distroless | Debian (apt) + Go modules (`go-env`); pip direct | n/a; `mirror-base-image.yaml` now copies Docker Hub images by the digest resolved directly from Docker Hub |
|
||||
| cpanel-importer | cpanel-importer | almalinux:10-minimal | AlmaLinux/EPEL/Remi — **PR #1 open, not merged** (pending rebase onto the PHP 8.5 work) | in the PR |
|
||||
|
||||
Not image builds (nothing to wire): kb-anhonesthost, anhonesthost-blog, dao-mail-signup, renovate-runner. ols-mail-client builds images in CI only for scanning/smoke tests (never pushed), its bases are already digest-pinned, and they install no OS packages.
|
||||
|
||||
## Adding a new upstream
|
||||
|
||||
1. On CT130: back up, then add the prefix to `UPSTREAMS` in `/usr/local/sbin/pkgcache-gen-nginx` (origin first; add a mirror tier only if you measured it is as fresh as the origin). If its mutable metadata isn't covered by `META_RE`, extend it there AND in `/usr/local/sbin/pkgcache-refresh`. Add the prefix to the `location = /` text in `/etc/nginx/pkgcache/server-common.conf`.
|
||||
2. `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx` (`pct exec` has no /usr/local/sbin in PATH: use full paths). Verify old and new prefixes with `curl --cacert keys/pkg-cache/ca.crt https://192.168.1.40/<prefix>/...` (look at `X-Cache-Status`/`X-Pkgcache-Tier`). CT130 has no egress filter, so no firewall change is needed for new origins.
|
||||
3. Only proxy upstreams whose content the CLIENT verifies end to end (GPG-signed packages/metadata, Go sumdb, content digests). Commit any signing key under `keys/pkg-cache/` with its fingerprint and provenance in the README.
|
||||
4. Teach `scripts/pkg-cache-repos.sh` the rewrite (keep the origin as a fallback where the client supports it), test through the cache from a runner-network host, then sync the copies.
|
||||
|
||||
## Trust model
|
||||
|
||||
`keys/pkg-cache/ca.crt` is the *pkg-cache internal CA*:
|
||||
- EC P-256, valid to 2036.
|
||||
- **Name-constrained** to IP `192.168.1.40` and DNS `pkg-cache.internal.cloud-hosting.io` / `pkg-cache.geekhome.local`. Even its private key, which exists only on CT130 at `/etc/pkgcache-ca/ca.key`, cannot mint a certificate that clients would accept for any other host.
|
||||
- It is trusted only by the rewritten repos, the apt per-host option and the BuildKit/dockerd registry config. It is never added to a system trust store.
|
||||
|
||||
The server certificate `/etc/pkgcache-ca/server.crt` **expires 2029-10-20**. `pkgcache-stats` prints the expiry. Renew it with the same `openssl x509 -req ... -extfile srv.cnf` steps in `/etc/pkgcache-ca/`.
|
||||
|
||||
Remaining risks:
|
||||
- **Replay of old but validly signed metadata.** Signatures stop tampering, not freshness. The cache's own plain-http tiers for Remi and LiteSpeed are the leg where an internet MITM could replay. The php.net gates (`EXPECT_PHP_MIN`, `EXPECT_LSPHP_PKG`/`EXPECT_PHP_VERSION`) fail an image build that gets old PHP. **Josh to decide** whether to drop those http tiers: you gain replay resistance and lose availability when Remi's HTTPS is down, as it was all day on 2026-10-21.
|
||||
- **Proposed, not implemented:** the same kind of minimum-version gate for other security-relevant packages:
|
||||
- CI resolves the newest `openssl`/`openssl-libs` and `httpd` from AlmaLinux BaseOS/AppStream, and `ImageMagick` from EPEL, **directly from the origin over HTTPS** (e.g. `dnf repoquery --repofrompath=o,https://repo.almalinux.org/almalinux/10/BaseOS/x86_64/os/ --repo=o --latest-limit=1 --qf '%{evr}' openssl-libs`).
|
||||
- It passes the results as `EXPECT_OPENSSL_MIN` / `EXPECT_HTTPD_MIN` / `EXPECT_IMAGEMAGICK_MIN`.
|
||||
- The Dockerfile asserts `rpm -q --qf '%{EVR}'` is not older (`rpm --eval "%{lua: print(rpm.vercmp('A','B'))}"`).
|
||||
|
||||
## CI integration (APPLIED 2026-10-21)
|
||||
|
||||
1. **Runner network.** Ephemeral runners are NATed and `iptables FORWARD` drops
|
||||
all RFC1918 destinations:
|
||||
- `eph-pve-*` on `pve`, vmbr2, 10.99.98.0/24;
|
||||
- `eph-pve3-*` on `pve3`, vmbr1, 10.99.99.0/24.
|
||||
|
||||
One rule per node now sits **above** those DROPs. It is persistent through a
|
||||
`post-up`/`post-down` pair placed before the first DROP line in
|
||||
`/etc/network/interfaces.d/vmbr2-ci-isolated` (pve) and
|
||||
`/etc/network/interfaces.d/vmbr1-ci-isolated` (pve3). The Proxmox firewall is
|
||||
disabled on both nodes, so its reloads do not touch these chains. Backups:
|
||||
`/root/vmbr{2,1}-ci-isolated.bak-pkgcache-20261021`.
|
||||
```
|
||||
iptables -I FORWARD 1 -i vmbr2 -o vmbr0 -d 192.168.1.40/32 -p tcp -m multiport --dports 443,5000 \
|
||||
-m comment --comment pkg-cache-ct130 -j ACCEPT
|
||||
```
|
||||
Verified from throwaway CTs on vmbr2 and vmbr1:
|
||||
- :443 and :5000 work with TLS verified;
|
||||
- CT130 :22/:80, pve :8006, .193 :22, the router's :53 and 10.10.14.20 stay **blocked**;
|
||||
- internet access is unchanged.
|
||||
|
||||
The LAN runner VM *Git-Builders* (VM103, 192.168.1.126 on vmbr0) needs no
|
||||
rule. `gitea-runner.internal.cloud-hosting.io` resolves to 10.10.14.20, which
|
||||
is unroutable from both the LAN and the DC (stale record?).
|
||||
2. **Workflow** (`.gitea/workflows/build-push.yaml`). Jobs Build-and-Push,
|
||||
Build-FPM-Images, Build-Shared-httpd and Build-Shared-OLS run a
|
||||
**"Probe package cache"** step. It curls `https://192.168.1.40/` and
|
||||
`https://192.168.1.40:5000/v2/` with `--cacert keys/pkg-cache/ca.crt
|
||||
--max-time 5`.
|
||||
- If both answer: `PKG_CACHE=https://192.168.1.40` is passed as a build arg,
|
||||
and buildx is set up with the Docker Hub mirror (`buildkitd-config-inline`,
|
||||
`ca=`).
|
||||
- If either fails: that output is empty, the build uses upstream repos /
|
||||
Docker Hub, and a `::warning::` is emitted.
|
||||
|
||||
Litespeed and lsphp jobs are unchanged (their Dockerfiles are not wired).
|
||||
**Fail-back measured** with the workflow's own step code, from a runner-bridge CT:
|
||||
|
||||
| cache state | probe result | time |
|
||||
|---|---|---|
|
||||
| up | url + mirror set | <1 s |
|
||||
| nginx + registry stopped | both empty, warning | <1 s (connection refused) |
|
||||
| silently dropped | both empty, warning | 10 s (2 x 5 s) |
|
||||
|
||||
Mid-build cache loss is also covered: dnf has the origin as a 2nd baseurl, and
|
||||
BuildKit falls back from the mirror to Docker Hub (measured with the registry stopped).
|
||||
3. **dockerd clients** (plain `docker pull`). Untested, because it needs a daemon restart.
|
||||
Use TLS, no `insecure-registries`:
|
||||
```
|
||||
install -D -m0644 keys/pkg-cache/ca.crt /etc/docker/certs.d/192.168.1.40:5000/ca.crt
|
||||
# /etc/docker/daemon.json: { "registry-mirrors": ["https://192.168.1.40:5000"] } then restart docker
|
||||
```
|
||||
|
||||
## Base-image digest pinning (2026-10-22)
|
||||
|
||||
Gap closed (security review): with the BuildKit mirror on, `FROM almalinux/10-base`
|
||||
was resolved **by tag through the cache**, so a compromised cache could hand out a
|
||||
different image for that tag.
|
||||
|
||||
Now every job that uses the mirror first runs **"Resolve base image digest (Docker
|
||||
Hub, direct)"**:
|
||||
- `.gitea/scripts/resolve-base-image.sh IMAGE:TAG` gets an anonymous pull token from
|
||||
`auth.docker.io` and sends a `HEAD /v2/<repo>/manifests/<tag>` to
|
||||
`registry-1.docker.io` over HTTPS (never the mirror; HEADs don't count against the
|
||||
pull rate limit). It prints `IMAGE@sha256:<index digest>`. Input and output are
|
||||
regex-validated; values reach `run:` only via `env:`.
|
||||
- The job passes `BASE_IMAGE=<that>` as a build arg; the Dockerfiles say
|
||||
`ARG BASE_IMAGE=<tag>` / `FROM ${BASE_IMAGE}`. The mirror can then only serve
|
||||
content-addressed manifests/blobs, which BuildKit verifies against the digest.
|
||||
- **Lookup fails:** `BASE_IMAGE` falls back to the tag, the probe step does **not**
|
||||
enable the registry mirror (`registry-mirror=<not used: Docker Hub direct>`), and
|
||||
the tag is pulled straight from Docker Hub, as before the cache existed. The
|
||||
package-repo cache (`PKG_CACHE`) is unaffected, since its content is GPG-checked.
|
||||
- Freshness is automatic: the digest is looked up on every run, nothing is
|
||||
hard-coded. Local builds without the arg use the tag.
|
||||
- Log lines to look for: `base-image: almalinux/10-base:latest -> almalinux/10-base@sha256:...`
|
||||
and `pkg-cache: repos=https://192.168.1.40 registry-mirror=192.168.1.40:5000`.
|
||||
|
||||
Applied to every job here (Build-and-Push, FPM, Shared-httpd, Shared-OLS, LiteSpeed, LSPHP) and to every other repo with a Docker Hub base (see "Repositories wired").
|
||||
The script is canonical here (`.gitea/scripts/resolve-base-image.sh`); other repos
|
||||
carry byte-identical copies.
|
||||
|
||||
## WHP hosts (`scripts/host/pkgcache-host-repos.sh`)
|
||||
|
||||
`pkgcache-host-repos.sh apply https://192.168.1.40` does the following:
|
||||
- backs up every `/etc/yum.repos.d/*.repo` to `/root/pkgcache-repo-backup-<UTC>/`;
|
||||
- installs the name-constrained CA as a system anchor;
|
||||
- rewrites the AlmaLinux, EPEL and Remi sections to `baseurl=<cache>/... <origin>/...`;
|
||||
- comments out mirrorlist/metalink in those sections;
|
||||
- sets `gpgcheck=1`, plus `repo_gpgcheck=1` for AlmaLinux and Remi.
|
||||
|
||||
`revert [DIR]` restores byte-identical files (verified on AL9 and AL10) and
|
||||
removes the anchor. `status` shows the current state.
|
||||
|
||||
**Why not "cache first, mirrorlist as fallback":** measured on AL10, with
|
||||
mirrorlist/metalink left in place dnf **ignores `baseurl` completely** (0 cache
|
||||
requests). The origin as second baseurl is the fallback dnf really performs.
|
||||
|
||||
**Measured on 192.168.1.148** (AlmaLinux 10.2), 2026-10-21:
|
||||
|
||||
| test | result |
|
||||
|---|---|
|
||||
| baseline `dnf makecache --refresh` (original mirrorlists) | rc=0, **1209 s** |
|
||||
| via cache `makecache --refresh` | rc=0, **22 s** (2nd run, non-interactive: 2 s) |
|
||||
| via cache `dnf download php-cli` | rc=0, 1 s |
|
||||
| `dnf -y --downloadonly update php-cli` | "Nothing to do" (already 8.5.12) |
|
||||
| cache **down** (nginx stopped): makecache --refresh / download | rc=0 **16 s** / rc=0 24 s, served by origin |
|
||||
| cache **silently dropped** (iptables DROP): makecache --refresh / download | rc=0 **225 s** / rc=0 59 s (dnf's 30 s connect timeout per attempt) |
|
||||
|
||||
**Datacenter hosts** (whp01, whp02, sdbees, TrueSelfCA) cannot reach
|
||||
192.168.1.40 and are **not configured**. That needs CT130 on NetBird (see the
|
||||
report: setup key / route pending).
|
||||
|
||||
## Operating the cache (CT130)
|
||||
|
||||
- **nginx config.**
|
||||
- `/etc/nginx/conf.d/pkgcache-http.conf` holds the cache zone, maps and log format.
|
||||
- `/etc/nginx/pkgcache/{server-common,meta,pkg}.conf` hold the shared settings.
|
||||
- `/etc/nginx/sites-available/pkgcache` is **generated** by `/usr/local/sbin/pkgcache-gen-nginx`. Edit the `UPSTREAMS` table, then run `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx`.
|
||||
- Listeners: :443 TLS for the LAN, `127.0.0.1:80` for local tools, and :80 for the LAN answers 403.
|
||||
- **Registry.** Config is `/etc/docker/registry/config.yml` (Debian `docker-registry` 2.8.3, the same code as `registry:2`, with TLS). Service `docker-registry`.
|
||||
- **TLS.** Everything is in `/etc/pkgcache-ca/`: `ca.key` (0600), `ca.crt`, `server.key`, `server-chain.crt`, and the `ca.cnf`/`srv.cnf` openssl configs.
|
||||
- **Data.** nginx uses `/srv/cache/nginx`, the registry uses `/srv/cache/registry`. Both live on the 150 GB mount point `mp0` (`backup=0`).
|
||||
- **Security-release day.** `pkgcache-refresh` re-fetches every cached metadata entry right away. Variants:
|
||||
- `pkgcache-refresh remi` refreshes one prefix;
|
||||
- `--list` lists the entries;
|
||||
- `--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. Metadata is at most 3 minutes old anyway.
|
||||
- **Monitoring.** `pkgcache-stats` shows:
|
||||
- disk usage;
|
||||
- hit ratio;
|
||||
- fallback-tier use;
|
||||
- the STALE count;
|
||||
- upstream errors;
|
||||
- certificate expiry.
|
||||
|
||||
Logs are `/var/log/nginx/pkgcache.access.log` (fields `cache=`, `tier=`, `up=`) and `journalctl -u docker-registry`. Suggested Uptime Kuma checks are `https://192.168.1.40/` and `https://192.168.1.40:5000/v2/`, with certificate checking against the CA.
|
||||
- **Docker Hub rate limits.** Anonymous pulls are limited per egress IP, and all clients share it. Add `username`/`password` (a Hub access token) under `proxy:` if needed.
|
||||
@@ -0,0 +1,32 @@
|
||||
Trust material for the OPT-IN internal package cache (scripts/pkg-cache-repos.sh,
|
||||
docs/pkg-cache.md). Copied into images at /usr/local/share/pkg-cache/; nothing
|
||||
here is added to the system trust store.
|
||||
|
||||
ca.crt "pkg-cache internal CA (CT130)", EC P-256, valid to 2036-10-18.
|
||||
NAME-CONSTRAINED to IP 192.168.1.40 and DNS
|
||||
pkg-cache.internal.cloud-hosting.io / pkg-cache.geekhome.local.
|
||||
SHA-256 fingerprint BB:5B:9A:08:05:ED:2F:58:83:92:D0:99:9D:11:0A:30:14:88:68:F0:F0:88:65:5E:94:0B:75:EB:C2:A2:BD:F3
|
||||
(private key lives only on CT130: /etc/pkgcache-ca/ca.key)
|
||||
RPM-GPG-KEY-EPEL-10 Fedora (epel10) 7D8D 15CB FC4E 6268 8591 FB26 33D9 8517 E37E D158
|
||||
fetched 2026-10-21 over HTTPS from dl.fedoraproject.org and
|
||||
mirror.math.princeton.edu (identical); fingerprint matches
|
||||
https://fedoraproject.org/security/
|
||||
RPM-GPG-KEY-remi2024 Remi's RPM repository 2024 CF1D F005 7CE8 5DFF 5B2F 2A37 C2FD 3B2C 2A09 48E4
|
||||
fetched 2026-10-21 over HTTPS from rpms.remirepo.net; byte-identical
|
||||
to the key shipped in remi-release-10.2-1.el10.remi. Not found on
|
||||
keys.openpgp.org / keyserver.ubuntu.com - confirm out of band.
|
||||
Remi rotates keys (remi2025, ...): when remi-release starts being
|
||||
signed by a new key, `release-install remi` fails loudly - add the
|
||||
new key here (and to pkg-cache-repos.sh).
|
||||
RPM-GPG-KEY-EPEL-9 Fedora (epel9) FF8A D134 4597 106E CE81 3B91 8A38 72BF 3228 467C
|
||||
fetched 2026-10-22 over HTTPS from dl.fedoraproject.org and
|
||||
mirror.math.princeton.edu (identical); used by `release-install epel`
|
||||
on EL9 images (cloud-memcached-container, cloud-haproxy-container).
|
||||
RPM-GPG-KEY-nodesource Nodesource Operations <operations@nodesource.com>
|
||||
242B 8138 31AF 0956 2B6C 46F7 6B88 DA4E 3AF2 8A14 (rsa4096, 2023-12-22)
|
||||
fetched 2026-10-22 over HTTPS from
|
||||
https://rpm.nodesource.com/gpgkey/ns-operations-public.key (the key
|
||||
NodeSource's own setup_N.x scripts reference); verified by dnf
|
||||
gpgcheck against the nodejs RPMs. Not on keys.openpgp.org /
|
||||
keyserver.ubuntu.com - confirm out of band. Used by
|
||||
`pkg-cache-repos.sh nodesource MAJOR` (cloud-node-container).
|
||||
@@ -0,0 +1,29 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGV4X6kBEAC3eQxgiWuo08uc3mHo4ELux++uqTnYz/tJzEf9Ou3h36WnhumA
|
||||
Nvs+Ts5h8PBx879Y9/aIX1Z20p1kf6tBCinZnEJu59n+TAAsph0+XQlV1l5YkleK
|
||||
Z2ff/Fg65k8QcLXWaIGykA/FaKznRiSurGuD6tRGhJw7DawEwBJr8QZSkRUpnH1L
|
||||
URW97Q/iKrRPiE5VEayE0y8eAL28jIIiFvR+4oJMzvCsRRB/2wYZ2MlJOW91hcYf
|
||||
mbUoXKOBD5UzsJylu7kj25K/ge8rEJ7KicOOwcdYddxsU3DxGSSfwF8AMagENcm2
|
||||
XROeXknjm84A8sNlUkFZBJwfuc7eRTiZGJrnQQVYLrkKj8Mxpq9Ts7hU51TqAWNI
|
||||
uvGDlJdYNE3D2RMqjMEsZ8ej08Thrib6xslu4NzTBkt+6QNnXL4E3hEgYtoyio60
|
||||
GswSz2ulogKg7X4JrNdJYE8/qNowyF3hoVgj5TG1/wQRq+5HlMMOLjgGu9wzLUix
|
||||
fnVfEUnzaofbrUf4/GabCaeY8xRe4tFQrvzigQ4g+kgwKKnfAeqBmPov0yljkw9z
|
||||
BYJWR5zvaw0ffg9Ing00KUSaXBXA5jSlgk1603Y+LefY1SlXsTyqohiRvGH6FI77
|
||||
HNMo72DwoJfFcYjncZUzKgXWJECR4nhVsdj6pKoOjcQ4aSuyVxtsR86ASQARAQAB
|
||||
tChGZWRvcmEgKGVwZWwxMCkgPGVwZWxAZmVkb3JhcHJvamVjdC5vcmc+iQJOBBMB
|
||||
CAA4FiEEfY0Vy/xOYmiFkfsmM9mFF+N+0VgFAmV4X6kCGw8FCwkIBwIGFQoJCAsC
|
||||
BBYCAwECHgECF4AACgkQM9mFF+N+0Vhv/A/+PlhPLSctGRCUEahE+cN4764Acc3p
|
||||
l40ZYzXRhqR0/Tc1/cSDjlA3qVTc8SPohi5OJXwCyr9EiMqKoyoDN097euqbYpyp
|
||||
yN/Pj0lBjsXwcpdDtZ21WGeQU0Khb04N68bMtJbDaxeBciTvDDQravZuPPh0m4Rg
|
||||
Z6myEoa6Aa6EK0hI1Qwi1qIWeRiuEkVT671IaKVETBW5XiUpNBXDAB/L+6DzUF9u
|
||||
scBzfsUDiPO6NrpYDtV3jwq22y6gWluIct/Ka8brwPbqK2sBfFzrHboRhfqlTGjs
|
||||
7F9qUGwIQZn/A8iozXZYQ0+JG1bhQyvjA8eN1GOcRpT+O7H7JXN49o6IG2As4+iK
|
||||
F04+qjqAu2sVfpD8mzM2VubFNllcKKiyCzRYHhSbObRCPzsudDL9GPiXeGGaCuWg
|
||||
sDkiA1MESvf2tLETAGBs/TziO4GwmXUtlKbRiq1FYm90mVq9mBxPZ/Idn+yZusNB
|
||||
0O5SXIbI8lYZw5n4XTK4b+byHRBYsOTHiTsGvjTF2Y7oSwW2CVUmL6RZ23mI4qoY
|
||||
1p5kzRS+GjT1acnTei/FTsOlIKCsjfeHx7uxCkX6xpAD8P3UtLQqfsgH0CL4vSZt
|
||||
TGO6L1InQlp4ZG3OYIomTKbD3/R0wod3U3dTqdulQMXL895u6OLTY3spY2m2MO2k
|
||||
p9Dfd2pKuxK9Mys=
|
||||
=mhQZ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,29 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGE3mOsBEACsU+XwJWDJVkItBaugXhXIIkb9oe+7aadELuVo0kBmc3HXt/Yp
|
||||
CJW9hHEiGZ6z2jwgPqyJjZhCvcAWvgzKcvqE+9i0NItV1rzfxrBe2BtUtZmVcuE6
|
||||
2b+SPfxQ2Hr8llaawRjt8BCFX/ZzM4/1Qk+EzlfTcEcpkMf6wdO7kD6ulBk/tbsW
|
||||
DHX2lNcxszTf+XP9HXHWJlA2xBfP+Dk4gl4DnO2Y1xR0OSywE/QtvEbN5cY94ieu
|
||||
n7CBy29AleMhmbnx9pw3NyxcFIAsEZHJoU4ZW9ulAJ/ogttSyAWeacW7eJGW31/Z
|
||||
39cS+I4KXJgeGRI20RmpqfH0tuT+X5Da59YpjYxkbhSK3HYBVnNPhoJFUc2j5iKy
|
||||
XLgkapu1xRnEJhw05kr4LCbud0NTvfecqSqa+59kuVc+zWmfTnGTYc0PXZ6Oa3rK
|
||||
44UOmE6eAT5zd/ToleDO0VesN+EO7CXfRsm7HWGpABF5wNK3vIEF2uRr2VJMvgqS
|
||||
9eNwhJyOzoca4xFSwCkc6dACGGkV+CqhufdFBhmcAsUotSxe3zmrBjqA0B/nxIvH
|
||||
DVgOAMnVCe+Lmv8T0mFgqZSJdIUdKjnOLu/GRFhjDKIak4jeMBMTYpVnU+HhMHLq
|
||||
uDiZkNEvEEGhBQmZuI8J55F/a6UURnxUwT3piyi3Pmr2IFD7ahBxPzOBCQARAQAB
|
||||
tCdGZWRvcmEgKGVwZWw5KSA8ZXBlbEBmZWRvcmFwcm9qZWN0Lm9yZz6JAk4EEwEI
|
||||
ADgWIQT/itE0RZcQbs6BO5GKOHK/MihGfAUCYTeY6wIbDwULCQgHAgYVCgkICwIE
|
||||
FgIDAQIeAQIXgAAKCRCKOHK/MihGfFX/EACBPWv20+ttYu1A5WvtHJPzwbj0U4yF
|
||||
3zTQpBglQ2UfkRpYdipTlT3Ih6j5h2VmgRPtINCc/ZE28adrWpBoeFIS2YAKOCLC
|
||||
nZYtHl2nCoLq1U7FSttUGsZ/t8uGCBgnugTfnIYcmlP1jKKA6RJAclK89evDQX5n
|
||||
R9ZD+Cq3CBMlttvSTCht0qQVlwycedH8iWyYgP/mF0W35BIn7NuuZwWhgR00n/VG
|
||||
4nbKPOzTWbsP45awcmivdrS74P6mL84WfkghipdmcoyVb1B8ZP4Y/Ke0RXOnLhNe
|
||||
CfrXXvuW+Pvg2RTfwRDtehGQPAgXbmLmz2ZkV69RGIr54HJv84NDbqZovRTMr7gL
|
||||
9k3ciCzXCiYQgM8yAyGHV0KEhFSQ1HV7gMnt9UmxbxBE2pGU7vu3CwjYga5DpwU7
|
||||
w5wu1TmM5KgZtZvuWOTDnqDLf0cKoIbW8FeeCOn24elcj32bnQDuF9DPey1mqcvT
|
||||
/yEo/Ushyz6CVYxN8DGgcy2M9JOsnmjDx02h6qgWGWDuKgb9jZrvRedpAQCeemEd
|
||||
fhEs6ihqVxRFl16HxC4EVijybhAL76SsM2nbtIqW1apBQJQpXWtQwwdvgTVpdEtE
|
||||
r4ArVJYX5LrswnWEQMOelugUG6S3ZjMfcyOa/O0364iY73vyVgaYK+2XtT2usMux
|
||||
VL469Kj5m13T6w==
|
||||
=Mjs/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGWFsqMBEAC4YlSuNpHjOiQVTC8StolVx3J8E9SC6G4wmVWbytMD1Zivfanj
|
||||
3LKDqdy7vpbViXA9whpyGQgQYdOlPKaNHWw4GSeGTnP1FbvaK1NJ9MnAzUysh49R
|
||||
cqoktYeIQbqgOgZHw5/gVH5bYObf1KJQ2LY/je9WTlW3QMRe1TaW0F0AZnJMBIOG
|
||||
JllE33QIpxDnOQHnvdUGq+HscUOTrW4Q3AdzwsQxU4GX82d9qFFr8HjAmUjlUKyh
|
||||
PgvMpb8VDvR7NE6et+wWh6BMYILo3VJFP7/gTXGiy3WWs5GW/PNXIXw5/f2w1B+e
|
||||
Dh5tlfGG4q/4P8mbv7C6RPHoF2Lq+W69HLqSq/l42WUL41tHlFTsluCIKpeeMxAu
|
||||
olxx3Z5momHMMyh7rbZtQDqFMTAqxNjT1NinKkWyj7KP5WE9hVosyd3UMR4UYDMm
|
||||
Zb6LI4N7Hht887+CY7grxTJ9cGQhMFfvLyvlf/FWQY4S6G7sEaUV71N9StvXBNG/
|
||||
iNoIMzhwjBmrmvT/UGPkLSi0SIBOxeGw2rwngnYikIJxuTQ4V40lm8o6eMFca7rW
|
||||
BQvAEncV8pvi/3FqrUBZ/U53s7NO24hBDoaQPzFbVCw0GHHf9GD7KoQb5c9ONoCZ
|
||||
Qu2YWo7ajPLzl/h2pJ2Kn29Ywev9MpuuPTGvifiKilcBVLYP3ZJg4ZfRBwARAQAB
|
||||
tDFOb2Rlc291cmNlIE9wZXJhdGlvbnMgPG9wZXJhdGlvbnNAbm9kZXNvdXJjZS5j
|
||||
b20+iQJOBBMBCgA4FiEEJCuBODGvCVYrbEb3a4jaTjryihQFAmWFsqMCGwMFCwkI
|
||||
BwIGFQoJCAsCBBYCAwECHgECF4AACgkQa4jaTjryihTD+hAAtTU155ke7xmnT0ph
|
||||
odbGjm3mOGdkdaDH+xmWtPtufiPncgHMCYh4aIDRloog8rFE+pP3qfwJmUKxthwD
|
||||
drrLAzfUwOrGxPnYH3MWwg66RVVoiiU+Hh42gJTez+P/K0VMQZXH0XTZD96TiwT/
|
||||
S0+WvQPuJ9pda2L4cjV+YP2+m0ERUG9QPj0Shgw+/PWCGWKD6J9DRToiwfwCTOvc
|
||||
DNolF1EcRlfPMTsqI2Bdc+TQywqU1Py1MUDSAPXxdK6nW2oM7eC5l3XXefcQ6JOS
|
||||
AuNdEDIsaf+H76j6/wSGsuIcJjUo9wmKjEV1iJYwT6jLbjaVQxXgEPHBSb+kzanx
|
||||
FWyrYP6TZpeY2gT4bBMsOKd7ay1I3/nIHwe6x8Yzkqzz+7YVhNpMbybH72NTxDWQ
|
||||
UmiboJs9r7j1lkHlBjxU9RLF5laYSeCIXD62KQVoP5ao1LSPKsCXFqcdpUgLYtRv
|
||||
CXxdriiHp4m9eGzbS7RW1TLm6tMOKbI4LzOtMm4DoZzGD2FTfsUtn6ALx9FSgvl/
|
||||
VMlzqZes+rclcukACtutvtB8e0yRhuzPUN56ctLEZfI1XzQFoN8Vdj6HWtiXWRiJ
|
||||
EohuufE2cEebmX90NRsbh4KAFXNEQROlMeMf7ufuSsk7QXLTutxmWSQvWwyS+P5/
|
||||
ZqCapIDlRCJFZX4N9a1IXo0OgCC5Ag0EZYWyowEQAKz7sA6WBKFVcmzcyr6y9TI5
|
||||
RemnzotRUdqXj8tD70xvV1nBEf+BfTw3hsiV019ZboPBae+cRaD4plQbNb0du/+Q
|
||||
UFq4ja055ZEld01RqE49L562pWvA5cwSmjs9+ZMI1ja0HbTb2K4razpzX3be+Zul
|
||||
TbH20X5FHTz+Jyfmw+G3Y9okw3OIzuYo16Lv/Qs8VoBmj01PItzq8dX5wJiIlVxU
|
||||
8wKHKy1kiKGCgyPnvqcXCGen0EoUS9wsWK1QHfryscYGVF9y1TBQvJa9HIpWPrIZ
|
||||
slSiVS4tQhGHfLDVlaDsYFrH9SIUkvW7peSEiPE1TKQjvbMS3CcUZRaMcGfgyDPe
|
||||
gXwnNwZbJ2/cisXbdAJaGT49V1Y+Sc1oKSGSG4FaGUNhS3p3cB7qlX2zS/sR2ael
|
||||
90fXR6nhjvhMAS98i7663pCUv9q0Z/NXNZwdXjbSF4W8gq3PVt7hCnJrngsGSkGu
|
||||
Qz0dc227/sAjh9CxYm8kQqUuQtiPVfKwkcUUT+O7jr2/6eAXZbTYVZbvaLEGMrKA
|
||||
bdWKpZMMOKAI45f+ZAcEmJG4D68N0jzVrLCXMuuLq/yqqyEmxDjSOH0qVutl8jxg
|
||||
vfxPxKYX8nnAzJZJHGHmV/ihjfClSdYePKExHuFVVhvc4BxE1XfPUeCZG4JpB9/B
|
||||
gNCiDPsg4JIw6GO5LS2xABEBAAGJAjYEGAEKACAWIQQkK4E4Ma8JVitsRvdriNpO
|
||||
OvKKFAUCZYWyowIbDAAKCRBriNpOOvKKFOTKD/40WN6ZRfP6hVLAX26DdyN9Bqzg
|
||||
D+0hJilmHL1YFeLt9U/YPhOZJxPaDGrMfldC0FSXKKr7H5C7FQErl936108wGohz
|
||||
53KGbNid0crNeaWrQuWk3QjaMF62Yc+NVB8b5LoEk0TEt9KPO6kveYDtW6Ygkw9i
|
||||
5aaS/AC+8GChOH6lWhLHcO91T7+Hi5qRez7qyme/AlHUTV9/jE6JgFJ7thCCAqBQ
|
||||
AfX3mGFlTwSTMHDeYq81MMl9UhYgoFJtx8PuqmSV7Eb29ccJofz3J+b5OJvpmLSZ
|
||||
Hu22Csa8k2V/1ojeL0DjQHD06V75F7BerxLR/rbJ64GkIiOdm4ntXC4a335gjQfM
|
||||
5tI6LtGsIpr+Le4C068eDj7YpzS0ql3g6o4A1TbtapLccgEj4iw95H4KKh0Ro5r0
|
||||
0s6LoSLu9uva3LiLVn6xXvpw3iv8FnhTr5PEiPnorXvxcCnUrB3Rkk6Wh1SKjpxa
|
||||
u6KiFzH0CXR1AgUdWSC0rAmvEdFjXgaoQd+Z2eJFHEAsg91URd9NtdeDsyMwdbHh
|
||||
iBZPGX5ddPjJA7nwKbkibCv7xiodd9ph8NmGLqD/2SDU1rqvPuCDMqLuxc89Gvhs
|
||||
3CeLTSJK9Uzb3oNdZLxL8CECip1ZkowVfTL+jY23phS9+Z0Db/yZqot3ZZJAAG8g
|
||||
oSVkzT/Mwp9zFVQu5w==
|
||||
=drMP
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=meaU
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPzCCAeagAwIBAgIUJI0QJZ+PCrSQ/nKZFENwwcp8Zr0wCgYIKoZIzj0EAwIw
|
||||
TDEmMCQGA1UEAwwdcGtnLWNhY2hlIGludGVybmFsIENBIChDVDEzMCkxIjAgBgNV
|
||||
BAoMGWNsb3VkLWhvc3RpbmcuaW8gaW50ZXJuYWwwHhcNMjYxMDIxMTkzNjIyWhcN
|
||||
MzYxMDE4MTkzNjIyWjBMMSYwJAYDVQQDDB1wa2ctY2FjaGUgaW50ZXJuYWwgQ0Eg
|
||||
KENUMTMwKTEiMCAGA1UECgwZY2xvdWQtaG9zdGluZy5pbyBpbnRlcm5hbDBZMBMG
|
||||
ByqGSM49AgEGCCqGSM49AwEHA0IABBJh75sC5vSzTnkYl0CYYEA/TKUHEXhbtanV
|
||||
OVNW4NlD3za5+bu5YuKjpbFAva4TCZqWsaS9IMd/JfsaFbf5XVejgaUwgaIwEgYD
|
||||
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP7UCTf/
|
||||
v7KU1KBrs1vqa71/TZl0MF0GA1UdHgEB/wRTMFGgTzAKhwjAqAEo/////zAlgiNw
|
||||
a2ctY2FjaGUuaW50ZXJuYWwuY2xvdWQtaG9zdGluZy5pbzAaghhwa2ctY2FjaGUu
|
||||
Z2Vla2hvbWUubG9jYWwwCgYIKoZIzj0EAwIDRwAwRAIgOod4wPHu0kAzYAw8KeGg
|
||||
cIrFMwQ4ATKVTkDioiSFOqICIEhY+sGj/YZ4oBMst3b/AAz9zMEJAXpnCkSTI6tM
|
||||
YAIl
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
## pkgcache-host-repos.sh — point a WHP HOST's dnf repos (AlmaLinux 9/10 BaseOS/
|
||||
## AppStream/CRB/extras/..., EPEL, Remi) at the internal package cache, with the
|
||||
## ORIGIN as automatic fallback. Not used by image builds (see pkg-cache-repos.sh).
|
||||
##
|
||||
## pkgcache-host-repos.sh apply https://192.168.1.40 # backs up, then rewrites
|
||||
## pkgcache-host-repos.sh status
|
||||
## pkgcache-host-repos.sh revert [BACKUP_DIR] # default: newest backup
|
||||
##
|
||||
## Fallback design (measured 2026-10-21 on AlmaLinux 10.2):
|
||||
## * each rewritten section gets baseurl=<cache>/<prefix>/<path> <origin>/<path>
|
||||
## — dnf/librepo treats the second URL as a failover mirror.
|
||||
## * the section's mirrorlist=/metalink= is COMMENTED OUT. Measured: with a
|
||||
## mirrorlist/metalink present dnf ignores baseurl entirely (0 cache requests),
|
||||
## so "cache first, mirrorlist as fallback" is not something dnf can do.
|
||||
## * gpgcheck=1 everywhere; repo_gpgcheck=1 for AlmaLinux + Remi (both sign
|
||||
## repomd.xml); EPEL has no repomd signature and relies on HTTPS end to end.
|
||||
## * trust: the name-constrained pkg-cache CA is installed as a system anchor
|
||||
## (/etc/pki/ca-trust/source/anchors/pkg-cache-ca.crt). Its nameConstraints
|
||||
## (IP 192.168.1.40, pkg-cache.* DNS) are enforced by OpenSSL — a cert it
|
||||
## signs for any other name fails ("permitted subtree violation").
|
||||
## Every changed file is copied to /root/pkgcache-repo-backup-<UTC>/ first.
|
||||
set -euo pipefail
|
||||
|
||||
MODE="${1:-status}"
|
||||
CA_SRC="$(dirname "$0")/pkg-cache-ca.crt"
|
||||
ANCHOR=/etc/pki/ca-trust/source/anchors/pkg-cache-ca.crt
|
||||
die() { echo "FATAL(pkgcache-host-repos): $*" >&2; exit 1; }
|
||||
same() { [ "$(sha256sum < "$1")" = "$(sha256sum < "$2")" ]; } # cmp/diffutils is not on minimal hosts
|
||||
|
||||
rewrite() { # $1 file $2 cache url
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import re, sys
|
||||
f, C = sys.argv[1], sys.argv[2].rstrip('/')
|
||||
maps = [ # regex on a (commented or live) baseurl, cache prefix, origin base, repomd signed?
|
||||
(r'^#?\s*baseurl\s*=\s*https?://repo\.almalinux\.org/almalinux/(\S+)', 'almalinux', 'https://repo.almalinux.org/almalinux/', True),
|
||||
(r'^#?\s*baseurl\s*=\s*https?://download\.example/pub/epel/(\S+)', 'epel', 'https://dl.fedoraproject.org/pub/epel/', False),
|
||||
(r'^#?\s*baseurl\s*=\s*https?://rpms\.remirepo\.net/(\S+)', 'remi', 'https://rpms.remirepo.net/', True),
|
||||
]
|
||||
out, sec = [], []
|
||||
state = {'hit': False, 'sig': False}
|
||||
def flush():
|
||||
for x in sec:
|
||||
if state['hit']:
|
||||
if re.match(r'^(mirrorlist|metalink)\s*=', x):
|
||||
x = '#' + x
|
||||
elif re.match(r'^(gpgcheck|repo_gpgcheck)\s*=', x):
|
||||
continue
|
||||
out.append(x)
|
||||
if state['hit']:
|
||||
# keep blank-line layout: insert before trailing blanks
|
||||
tail = []
|
||||
while out and out[-1].strip() == '':
|
||||
tail.append(out.pop())
|
||||
out.append('gpgcheck=1')
|
||||
if state['sig']:
|
||||
out.append('repo_gpgcheck=1')
|
||||
out.extend(tail)
|
||||
sec.clear(); state['hit'] = state['sig'] = False
|
||||
for line in open(f).read().split('\n'):
|
||||
if line.startswith('['):
|
||||
flush()
|
||||
for rx, prefix, origin, sig in maps:
|
||||
m = re.match(rx, line)
|
||||
if m:
|
||||
p = m.group(1)
|
||||
line = f'baseurl={C}/{prefix}/{p} {origin}{p}'
|
||||
state['hit'] = True; state['sig'] = state['sig'] or sig
|
||||
break
|
||||
sec.append(line)
|
||||
flush()
|
||||
open(f, 'w').write('\n'.join(out))
|
||||
PY
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
apply)
|
||||
C="${2:?usage: apply https://<cache>}"; C="${C%/}"
|
||||
case "$C" in https://*) ;; *) die "cache URL must be https://" ;; esac
|
||||
[ -s "$CA_SRC" ] || die "missing $CA_SRC (copy keys/pkg-cache/ca.crt next to this script)"
|
||||
B=/root/pkgcache-repo-backup-$(date -u +%Y%m%dT%H%M%SZ)
|
||||
mkdir -p "$B/yum.repos.d"
|
||||
cp -a /etc/yum.repos.d/*.repo "$B/yum.repos.d/"
|
||||
[ -e "$ANCHOR" ] && cp -a "$ANCHOR" "$B/" || touch "$B/.no-anchor-before"
|
||||
install -m 0644 "$CA_SRC" "$ANCHOR" && update-ca-trust
|
||||
for f in /etc/yum.repos.d/almalinux*.repo /etc/yum.repos.d/epel*.repo /etc/yum.repos.d/remi*.repo; do
|
||||
[ -f "$f" ] || continue
|
||||
grep -q -E '^#?\s*baseurl\s*=\s*https?://(repo\.almalinux\.org|download\.example|rpms\.remirepo\.net)/' "$f" && rewrite "$f" "$C"
|
||||
done
|
||||
echo "backup: $B"
|
||||
echo "changed files:"; for f in "$B"/yum.repos.d/*.repo; do same "$f" "/etc/yum.repos.d/$(basename "$f")" || echo " /etc/yum.repos.d/$(basename "$f")"; done
|
||||
;;
|
||||
revert)
|
||||
B="${2:-$(ls -d /root/pkgcache-repo-backup-* 2>/dev/null | sort | tail -1)}"
|
||||
[ -d "$B/yum.repos.d" ] || die "no backup dir ($B)"
|
||||
for f in "$B"/yum.repos.d/*.repo; do same "$f" "/etc/yum.repos.d/$(basename "$f")" || { cp -a "$f" /etc/yum.repos.d/; echo "restored /etc/yum.repos.d/$(basename "$f")"; }; done
|
||||
if [ -e "$B/.no-anchor-before" ]; then rm -f "$ANCHOR"; update-ca-trust; echo "removed $ANCHOR"; fi
|
||||
dnf clean metadata -q || true
|
||||
echo "reverted from $B"
|
||||
;;
|
||||
status)
|
||||
grep -l -E '^baseurl=https://[^ ]+/(almalinux|epel|remi)/' /etc/yum.repos.d/*.repo 2>/dev/null | sed 's/^/via cache: /' || echo "no repo uses the cache"
|
||||
ls -d /root/pkgcache-repo-backup-* 2>/dev/null | sed 's/^/backup: /' || true
|
||||
[ -e "$ANCHOR" ] && echo "CA anchor: $ANCHOR" || echo "CA anchor: absent"
|
||||
;;
|
||||
*) die "usage: apply URL | status | revert [DIR]" ;;
|
||||
esac
|
||||
@@ -18,7 +18,8 @@
|
||||
## Fails LOUDLY — never ships the base image's PHP by accident:
|
||||
## * `apt-get update --error-on=any`: plain `apt-get update` only WARNS when a
|
||||
## repo is unreachable and carries on with no/old lists, exit 0.
|
||||
## * the LiteSpeed repo must be the candidate source for lsphp${PHPVER}.
|
||||
## * the LiteSpeed repo must be the candidate source for lsphp${PHPVER}
|
||||
## (rpms.litespeedtech.com, or ${PKG_CACHE}/litespeed/ when PKG_CACHE is set).
|
||||
## * after upgrading, no lsphp${PHPVER}* package may remain upgradable.
|
||||
## * if CI passed the version it resolved from the repo index (EXPECT_LSPHP_PKG,
|
||||
## EXPECT_PHP_VERSION), the installed package / running PHP must match it;
|
||||
@@ -52,8 +53,18 @@ echo "lsphp-apt-upgrade: base image lsphp${PHPVER}=$(dpkg-query -W -f='${Version
|
||||
# Read each output into a variable and match in the shell: piping into an
|
||||
# early-exit reader (grep -q, awk ... exit) under pipefail can SIGPIPE the writer
|
||||
# and report "missing" for a package that is there (scripts/tests/lsphp-info-probe.test.sh).
|
||||
# With the internal package cache (PKG_CACHE build-arg, scripts/pkg-cache-repos.sh
|
||||
# apt) the LiteSpeed source reads ${PKG_CACHE}/litespeed/... instead; that URL is
|
||||
# accepted ONLY when PKG_CACHE is set, and only under its /litespeed/ path.
|
||||
MADISON=$(apt-cache madison "lsphp${PHPVER}")
|
||||
if [[ "$MADISON" != *rpms.litespeedtech.com* ]]; then
|
||||
LS_REPO_OK=0
|
||||
if [[ "$MADISON" == *rpms.litespeedtech.com* ]]; then
|
||||
LS_REPO_OK=1
|
||||
elif [ -n "${PKG_CACHE:-}" ] && [[ "$MADISON" == *"${PKG_CACHE%/}/litespeed/"* ]]; then
|
||||
LS_REPO_OK=1
|
||||
echo "lsphp-apt-upgrade: LiteSpeed repo via package cache ${PKG_CACHE%/}/litespeed/"
|
||||
fi
|
||||
if [ "$LS_REPO_OK" != 1 ]; then
|
||||
apt-cache policy "lsphp${PHPVER}" >&2 || true
|
||||
die "the LiteSpeed apt repo does not offer lsphp${PHPVER} (repo missing from sources, or its index is empty)."
|
||||
fi
|
||||
|
||||
Executable
+296
@@ -0,0 +1,296 @@
|
||||
#!/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; without it the repo files are left alone and builds
|
||||
## use the upstream repositories as before.
|
||||
##
|
||||
## PKG_CACHE=https://192.168.1.40 pkg-cache-repos.sh rpm # AlmaLinux/EPEL/Remi/NodeSource
|
||||
## PKG_CACHE=https://192.168.1.40 pkg-cache-repos.sh apt # Ubuntu, Debian, LiteSpeed
|
||||
## pkg-cache-repos.sh release-install epel [remi] # GPG-verified release RPMs (EL9/EL10)
|
||||
## pkg-cache-repos.sh nodesource MAJOR # NodeSource repo file + pinned key
|
||||
## eval "$(pkg-cache-repos.sh go-env)" # GOPROXY via the cache (go.sum/sumdb verified)
|
||||
## pkg-cache-repos.sh restore # undo everything above
|
||||
##
|
||||
## CANONICAL copy: cloud-apache-container/scripts/pkg-cache-repos.sh (docs/pkg-cache.md
|
||||
## there). Other repos carry byte-identical copies - change it there first.
|
||||
##
|
||||
## Every RUN that rewrites must `restore` before it ends: it puts rewritten files
|
||||
## back, deletes files this script created, deletes any NEW repo/apt file that
|
||||
## still references the cache, then FAILS if any reference is left — so no shipped
|
||||
## layer carries the cache's LAN address (images run on hosts that can't reach it).
|
||||
##
|
||||
## 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 ~3 minutes and
|
||||
## immutable .rpm/.deb files long-term.
|
||||
##
|
||||
## Integrity (security review 2026-10-21):
|
||||
## * PKG_CACHE must be https://. The cache's certificate is issued by the
|
||||
## name-constrained "pkg-cache internal CA" (keys/pkg-cache/ca.crt, valid only
|
||||
## for 192.168.1.40 / pkg-cache.*). That CA is trusted ONLY by the rewritten
|
||||
## repos (dnf sslcacert= bundle, apt per-host CaInfo) and only during the RUN —
|
||||
## never added to the system trust store.
|
||||
## * gpgcheck=1 is forced on every rewritten repo; repo_gpgcheck=1 on Remi and
|
||||
## AlmaLinux, which sign repomd.xml (measured: repomd.xml.asc 200). EPEL
|
||||
## publishes no repomd signature (404), so its metadata relies on HTTPS end
|
||||
## to end (client->cache->dl.fedoraproject.org/mirrors, all verified).
|
||||
## * release-install downloads epel-release / remi-release (from the cache, or
|
||||
## from the HTTPS origin without PKG_CACHE), checks the package name, imports
|
||||
## the signing keys committed in keys/pkg-cache/ and requires
|
||||
## `rpm -K` "signatures OK" before installing with localpkg_gpgcheck=1.
|
||||
## * The php.net version gates (EXPECT_PHP_MIN / EXPECT_PHP_VERSION) still fail
|
||||
## a build that gets old PHP (replay of old-but-signed metadata).
|
||||
##
|
||||
## 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 (with the CA), and a build without PKG_CACHE uses upstream sources.
|
||||
set -euo pipefail
|
||||
|
||||
MODE="${1:?usage: pkg-cache-repos.sh rpm|apt|release-install NAME...|nodesource MAJOR|go-env|restore}"
|
||||
shift || true
|
||||
STATE=/var/lib/pkg-cache-repos # saved originals + manifest + created files
|
||||
SHARE="${PKG_CACHE_SHARE:-/usr/local/share/pkg-cache}" # ca.crt + RPM-GPG-KEY-*
|
||||
C="${PKG_CACHE:-}"
|
||||
C="${C%/}"
|
||||
|
||||
die() { echo "FATAL(pkg-cache-repos): $*" >&2; exit 1; }
|
||||
|
||||
if [ -n "$C" ]; then
|
||||
case "$C" in
|
||||
https://*) ;;
|
||||
*) die "PKG_CACHE must be an https:// URL (plain http is refused), got '$C'" ;;
|
||||
esac
|
||||
[[ "$C" =~ ^https://[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._/-]*)?$ ]] || die "PKG_CACHE has unexpected characters: '$C'"
|
||||
HOSTPORT="${C#https://}"; HOSTPORT="${HOSTPORT%%/*}"
|
||||
[ -s "$SHARE/ca.crt" ] || die "PKG_CACHE set but $SHARE/ca.crt (pkg-cache CA) is missing"
|
||||
fi
|
||||
|
||||
# System CA bundle (EL vs Debian/Ubuntu path); the pkg-cache CA is appended to a
|
||||
# copy in the state dir, never to the system store.
|
||||
sys_bundle() {
|
||||
local b
|
||||
for b in /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt; do
|
||||
[ -s "$b" ] && { echo "$b"; return 0; }
|
||||
done
|
||||
die "no system CA bundle found"
|
||||
}
|
||||
make_bundle() { cat "$(sys_bundle)" "$SHARE/ca.crt" > "$STATE/ca-bundle.pem"; }
|
||||
|
||||
# Files that may hold repo configuration; restore re-checks all of them.
|
||||
repo_files() {
|
||||
find /etc/yum.repos.d /etc/dnf /etc/apt -type f 2>/dev/null | sort || true
|
||||
}
|
||||
|
||||
start_session() {
|
||||
[ -d "$STATE" ] && return 0
|
||||
mkdir -p "$STATE/orig"
|
||||
repo_files > "$STATE/manifest" # what existed before this RUN touched anything
|
||||
: > "$STATE/created"
|
||||
printf '%s\n' "$HOSTPORT" > "$STATE/host"
|
||||
}
|
||||
save() { # keep the first-seen (pre-rewrite) copy
|
||||
[ -e "$STATE/orig$1" ] && return 0
|
||||
mkdir -p "$STATE/orig$(dirname "$1")"
|
||||
cp -p "$1" "$STATE/orig$1"
|
||||
}
|
||||
created() { printf '%s\n' "$1" >> "$STATE/created"; }
|
||||
|
||||
if [ "$MODE" = restore ]; then
|
||||
[ -d "$STATE" ] || exit 0
|
||||
host=$(cat "$STATE/host")
|
||||
# mapfile from a process substitution, not `cmd | while read`: no pipeline,
|
||||
# so nothing for pipefail to misreport (scripts/tests/lsphp-info-probe.test.sh).
|
||||
mapfile -t saved < <(cd "$STATE/orig" && find . -type f)
|
||||
for rel in "${saved[@]}"; do
|
||||
cat "$STATE/orig/$rel" > "/${rel#./}"
|
||||
done
|
||||
while read -r f; do [ -n "$f" ] && rm -f -- "$f"; done < "$STATE/created"
|
||||
# A repo/apt file that appeared during this RUN and still names the cache
|
||||
# (e.g. written by a package from the rewritten repos) is removed too.
|
||||
mapfile -t current < <(repo_files)
|
||||
for f in "${current[@]}"; do
|
||||
if ! grep -qxF -- "$f" "$STATE/manifest" && grep -qF -- "$host" "$f"; then
|
||||
echo "pkg-cache-repos: removing new file that references the cache: $f"
|
||||
rm -f -- "$f"
|
||||
fi
|
||||
done
|
||||
left=$(repo_files | xargs -r grep -lF -- "$host" 2>/dev/null || true)
|
||||
rm -rf "$STATE"
|
||||
[ -z "$left" ] || die "cache address $host still present after restore in: $left"
|
||||
echo "pkg-cache-repos: original repository files restored (no reference to $host left)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MODE" = release-install ]; then
|
||||
[ $# -gt 0 ] || die "release-install needs: epel and/or remi"
|
||||
tmp=$(mktemp -d)
|
||||
curl_opts=(-fsS --proto '=https' --retry 5 --retry-all-errors --max-time 120)
|
||||
if [ -n "$C" ]; then
|
||||
start_session
|
||||
make_bundle
|
||||
curl_opts+=(--cacert "$STATE/ca-bundle.pem")
|
||||
fi
|
||||
files=()
|
||||
el=$(rpm -E '%{?rhel}' 2>/dev/null || true)
|
||||
case "$el" in 9|10) ;; *) die "release-install supports EL9/EL10 only (rpm -E %rhel = '$el')" ;; esac
|
||||
for name in "$@"; do
|
||||
case "$name" in
|
||||
epel) pkg=epel-release key=RPM-GPG-KEY-EPEL-$el
|
||||
path=/epel/epel-release-latest-$el.noarch.rpm
|
||||
origin=https://dl.fedoraproject.org/pub/epel/epel-release-latest-$el.noarch.rpm ;;
|
||||
remi) pkg=remi-release key=RPM-GPG-KEY-remi2024
|
||||
path=/remi/enterprise/remi-release-$el.rpm
|
||||
origin=https://rpms.remirepo.net/enterprise/remi-release-$el.rpm ;;
|
||||
*) die "unknown release package '$name'" ;;
|
||||
esac
|
||||
url="$origin"; [ -n "$C" ] && url="$C$path"
|
||||
f="$tmp/$pkg.rpm"
|
||||
curl "${curl_opts[@]}" -o "$f" "$url" || die "download failed: $url"
|
||||
rpm --import "$SHARE/$key" || die "cannot import $SHARE/$key"
|
||||
got=$(rpm -qp --nosignature --qf '%{NAME}' "$f" 2>/dev/null || true)
|
||||
[ "$got" = "$pkg" ] || die "$url is '$got', expected $pkg"
|
||||
chk=$(rpm -K "$f" 2>&1 || true)
|
||||
[[ "$chk" == *": digests signatures OK" ]] || die "signature check failed for $url: $chk"
|
||||
echo "pkg-cache-repos: $pkg verified ($chk) from $url"
|
||||
files+=("$f")
|
||||
done
|
||||
dnf install -y --setopt=localpkg_gpgcheck=1 "${files[@]}"
|
||||
rm -rf "$tmp"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$C" ] && [ "$MODE" != nodesource ] && [ "$MODE" != go-env ]; then
|
||||
echo "pkg-cache-repos($MODE): PKG_CACHE not set - using upstream repositories"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rewrite_rpm() {
|
||||
local f="$1"
|
||||
grep -q -E '^#? ?baseurl=https?://(repo\.almalinux\.org|download\.example|rpms\.remirepo\.net|rpm\.nodesource\.com)/' "$f" || return 0
|
||||
save "$f"
|
||||
# Per [section]: a (commented or live) baseurl on a known origin becomes
|
||||
# "baseurl=<cache>/<prefix>/<path> <origin>/<path>"; that section's
|
||||
# mirrorlist/metalink is commented out (dnf prefers them over baseurl) and it
|
||||
# gets sslcacert=<system bundle + pkg-cache CA>, gpgcheck=1 and, for repos
|
||||
# that sign repomd.xml, repo_gpgcheck=1. Other sections are untouched.
|
||||
awk -v C="$C" -v BUNDLE="$STATE/ca-bundle.pem" '
|
||||
function flush( i) {
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (hit && buf[i] ~ /^(mirrorlist|metalink)=/) buf[i] = "#" buf[i]
|
||||
if (hit && buf[i] ~ /^(sslcacert|gpgcheck|repo_gpgcheck)[ \t]*=/) continue
|
||||
print buf[i]
|
||||
}
|
||||
if (hit) {
|
||||
print "sslcacert=" BUNDLE
|
||||
print "gpgcheck=1"
|
||||
if (signed) print "repo_gpgcheck=1"
|
||||
}
|
||||
n = 0; hit = 0; signed = 0
|
||||
}
|
||||
function rw(line, re, prefix, origin, sig, p) {
|
||||
if (match(line, re)) {
|
||||
p = substr(line, RSTART + RLENGTH); sub(/[ \t].*$/, "", p)
|
||||
hit = 1; if (sig) signed = 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", 1)
|
||||
l = rw(l, "^#? ?baseurl=https?://download\\.example/pub/epel/", "epel", "https://dl.fedoraproject.org/pub/epel", 0)
|
||||
l = rw(l, "^#? ?baseurl=https?://rpms\\.remirepo\\.net/", "remi", "https://rpms.remirepo.net", 1)
|
||||
# NodeSource signs its RPMs but not repomd.xml (404): gpgcheck only.
|
||||
l = rw(l, "^#? ?baseurl=https?://rpm\\.nodesource\\.com/", "nodesource", "https://rpm.nodesource.com", 0)
|
||||
}
|
||||
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/|https?://(deb|security)\.debian\.org/' "$f" || return 0
|
||||
save "$f"
|
||||
# Debian: debian-security first, and "debian" only as a whole path segment,
|
||||
# so .../debian-security is never rewritten as /debian/.
|
||||
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" \
|
||||
-e "s%https?://(deb|security)\.debian\.org/debian-security/?([[:space:]]|\$)%$C/debian-security/\\2%g" \
|
||||
-e "s%https?://security\.debian\.org/?([[:space:]]|\$)%$C/debian-security/\\1%g" \
|
||||
-e "s%https?://deb\.debian\.org/debian/?([[:space:]]|\$)%$C/debian/\\1%g" \
|
||||
"$f"
|
||||
}
|
||||
|
||||
if [ "$MODE" = go-env ]; then
|
||||
# Prints shell exports for `eval`. Without PKG_CACHE: nothing (Go defaults).
|
||||
# GOPROXY "a|b" falls through on ANY error, so a dead cache costs one
|
||||
# failed request, not the build. GOSUMDB stays the default sum.golang.org,
|
||||
# reached directly: modules are still verified against go.sum + the sumdb.
|
||||
[ -n "$C" ] || { echo "pkg-cache-repos(go-env): PKG_CACHE not set - default GOPROXY" >&2; exit 0; }
|
||||
start_session
|
||||
make_bundle
|
||||
printf "export GOPROXY='%s/goproxy|https://proxy.golang.org|direct' SSL_CERT_FILE='%s'\n" "$C" "$STATE/ca-bundle.pem"
|
||||
echo "pkg-cache-repos(go-env): GOPROXY via $C/goproxy (fallback proxy.golang.org)" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MODE" = nodesource ]; then
|
||||
# Replaces `curl https://rpm.nodesource.com/setup_N.x | bash` (unverified
|
||||
# remote script): writes the same nodesource-nodejs repo (origin URL, so the
|
||||
# shipped image keeps a normal repo file) with gpgcheck=1 and the signing key
|
||||
# committed in keys/pkg-cache/; with PKG_CACHE it is then rewritten like any
|
||||
# other repo and `restore` puts the origin version back.
|
||||
major="${1:?usage: pkg-cache-repos.sh nodesource MAJOR}"
|
||||
[[ "$major" =~ ^[0-9]{1,3}$ ]] || die "bad Node major '$major'"
|
||||
key="$SHARE/RPM-GPG-KEY-nodesource"
|
||||
[ -s "$key" ] || die "missing $key"
|
||||
rpm --import "$key" || die "cannot import $key"
|
||||
rm -f /etc/yum.repos.d/nodesource*.repo
|
||||
f=/etc/yum.repos.d/nodesource-nodejs.repo
|
||||
printf '%s\n' "[nodesource-nodejs]" \
|
||||
"name=Node.js Packages for Linux RPM based distros - \$basearch" \
|
||||
"baseurl=https://rpm.nodesource.com/pub_${major}.x/nodistro/nodejs/\$basearch" \
|
||||
"priority=9" "enabled=1" "gpgcheck=1" \
|
||||
"gpgkey=file://$key" "module_hotfixes=1" > "$f"
|
||||
echo "pkg-cache-repos: wrote $f (Node ${major}.x, key $(basename "$key"))"
|
||||
if [ -n "$C" ]; then
|
||||
start_session
|
||||
make_bundle
|
||||
rewrite_rpm "$f"
|
||||
grep -h '^baseurl=' "$f" | sed 's/^/ /'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$MODE" in
|
||||
rpm)
|
||||
start_session
|
||||
make_bundle
|
||||
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)
|
||||
start_session
|
||||
conf=/etc/apt/apt.conf.d/99pkg-cache
|
||||
# Trust the pkg-cache CA for the cache host ONLY (apt per-host option).
|
||||
printf 'Acquire::https::%s::CaInfo "%s";\n' "${HOSTPORT%%:*}" "$SHARE/ca.crt" > "$conf"
|
||||
created "$conf"
|
||||
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:"
|
||||
# (|| true: Debian 13 images have no /etc/apt/sources.list; grep's exit 2
|
||||
# for the missing file must not fail the build under pipefail)
|
||||
{ grep -h -E '^(deb|URIs:)' /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null || true; } | grep -F "$C/" | sort -u | sed 's/^/ /' || true
|
||||
;;
|
||||
*) die "unknown mode '$MODE'" ;;
|
||||
esac
|
||||
Reference in new issue
Block a user