From 3a6cab19ee3f22f6d0b55b9805cd08858c188a06 Mon Sep 17 00:00:00 2001 From: jknapp Date: Thu, 22 Oct 2026 07:11:52 -0700 Subject: [PATCH] ci: wire cac-litespeed and cac-lsphp to the package cache and digest-pinned base Dockerfile.litespeed / Dockerfile.lsphp: one global ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} used by every stage (incl. the emergency lsphp-src stage, which stays on upstream repos). The stages that run lsphp-apt-upgrade.sh (litespeed final, lsphp ext-build + final) take ARG PKG_CACHE=, ship pkg-cache-repos.sh + keys/pkg-cache/, and wrap the upgrade in `pkg-cache-repos.sh apt` ... `pkg-cache-repos.sh restore`. Without PKG_CACHE both are no-ops. lsphp-apt-upgrade.sh: the madison gate also accepts ${PKG_CACHE}/litespeed/ as the lsphp source, but only when PKG_CACHE is set. build-push.yaml: Build-LiteSpeed-Images and Build-LSPHP-Images resolve the base digest directly from Docker Hub, probe the cache, set up buildx with the registry mirror only for a pinned base, and pass BASE_IMAGE + PKG_CACHE. OLS_VERSION (1.8.4) is now a job-level env read by both the digest lookup and the build-arg. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build-push.yaml | 122 ++++++++++++++++++++++++++++++- Dockerfile.litespeed | 21 +++++- Dockerfile.lsphp | 30 +++++++- scripts/lsphp-apt-upgrade.sh | 15 +++- 4 files changed, 178 insertions(+), 10 deletions(-) diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml index 43e7620..403d6ed 100644 --- a/.gitea/workflows/build-push.yaml +++ b/.gitea/workflows/build-push.yaml @@ -321,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. @@ -335,10 +339,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: 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:-} registry-mirror=${mirror:-}" + [ -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 @@ -409,7 +466,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 }} @@ -423,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. @@ -437,10 +500,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: 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:-} registry-mirror=${mirror:-}" + [ -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 @@ -511,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 }} diff --git a/Dockerfile.litespeed b/Dockerfile.litespeed index f362f9f..7960e7f 100644 --- a/Dockerfile.litespeed +++ b/Dockerfile.litespeed @@ -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: 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/* diff --git a/Dockerfile.lsphp b/Dockerfile.lsphp index 9820a31..c057c4e 100644 --- a/Dockerfile.lsphp +++ b/Dockerfile.lsphp @@ -21,6 +21,12 @@ ARG OLS_VERSION=1.8.4 ARG PHPVER=83 +## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256: 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}); \ diff --git a/scripts/lsphp-apt-upgrade.sh b/scripts/lsphp-apt-upgrade.sh index 6f59090..88c123d 100755 --- a/scripts/lsphp-apt-upgrade.sh +++ b/scripts/lsphp-apt-upgrade.sh @@ -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