From 71ca114529c0a7b7be373400602ab3d1612b885e Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 21 Oct 2026 06:07:48 -0700 Subject: [PATCH] feat(images): upgrade lsphp from the LiteSpeed repo at build time; label PHP version cac-litespeed and cac-lsphp start FROM litespeedtech/openlitespeed:1.8.4-lsphpNN, which ships lsphp85 8.5.0 / lsphp84 8.4.15 (built 2025-12). lsphp only moved forward as a side effect of installing lsphpNN-ldap. lsphp-apt-upgrade.sh now sweeps every installed lsphpNN* package (and OS packages) to the repo's current release on every build and fails the build if the repo is unreachable or the result doesn't match what CI resolved from the repo index. CI passes a per-run CACHE_BUST so the upgrade layer is never a cache hit, and images carry org.whp.php-version / org.whp.lsphp-package-version labels. Tested on .148 (2026-10-21): cac-lsphp php85/php84 and cac-litespeed php85 built from this branch report the same PHP (8.5.11 / 8.4.26), identical module lists and OLS 1.8.4 as the current registry images; with the hardening flags each serves/runs PHP as the customer uid, restarts, and stops cleanly (exit 0). Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build-push.yaml | 71 +++++++++++++++++++++++ Dockerfile | 5 ++ Dockerfile.fpm | 5 ++ Dockerfile.litespeed | 27 ++++++++- Dockerfile.lsphp | 47 +++++++++++++--- scripts/lsphp-apt-upgrade.sh | 97 ++++++++++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 10 deletions(-) create mode 100755 scripts/lsphp-apt-upgrade.sh diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml index c17e418..9a4f875 100644 --- a/.gitea/workflows/build-push.yaml +++ b/.gitea/workflows/build-push.yaml @@ -51,6 +51,7 @@ jobs: scripts/ols-htaccess-watcher.sh \ scripts/create-vhost-litespeed.sh \ scripts/install-lscache-wp.sh \ + scripts/lsphp-apt-upgrade.sh \ scripts/tune-mpm.sh \ scripts/tests/lsphp-info-probe.test.sh @@ -72,6 +73,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + # Per-run cache-bust value. Each job's buildx builder is created fresh by + # setup-buildx-action (no cache-from/cache-to here), so today nothing is + # cached between runs — this keeps it that way if a cache is ever added: + # the package-upgrade layers consume CACHE_BUST and can never be reused. + - name: Cache-bust value + id: bust + run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT" + - name: Login to Gitea uses: docker/login-action@v3 with: @@ -86,6 +95,7 @@ jobs: push: true build-args: | PHPVER=${{ matrix.phpver }} + CACHE_BUST=${{ steps.bust.outputs.value }} tags: | repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }} ${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }} @@ -105,6 +115,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + # Per-run cache-bust value. Each job's buildx builder is created fresh by + # setup-buildx-action (no cache-from/cache-to here), so today nothing is + # cached between runs — this keeps it that way if a cache is ever added: + # the package-upgrade layers consume CACHE_BUST and can never be reused. + - name: Cache-bust value + id: bust + run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT" + - name: Login to Gitea uses: docker/login-action@v3 with: @@ -120,6 +138,7 @@ jobs: push: true build-args: | PHPVER=${{ matrix.phpver }} + CACHE_BUST=${{ steps.bust.outputs.value }} tags: | repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }} ${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }} @@ -143,6 +162,29 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + # Per-run cache-bust value. Each job's buildx builder is created fresh by + # setup-buildx-action (no cache-from/cache-to here), so today nothing is + # cached between runs — this keeps it that way if a cache is ever added: + # the package-upgrade layers consume CACHE_BUST and can never be reused. + - name: Cache-bust value + id: bust + run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT" + + # Ask the LiteSpeed apt repo (the same one the base image's apt uses) for the + # CURRENT lsphp version. The Dockerfile asserts the installed package equals + # this (build fails if the repo changed mid-build — just re-run) and writes + # it into the image labels org.whp.php-version / org.whp.lsphp-package-version. + - name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo + id: lsphp + run: | + set -euo pipefail + idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages) + pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1) + if [ -z "$pkg" ]; then echo "::error::lsphp${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi + echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})" + echo "pkg=$pkg" >> "$GITHUB_OUTPUT" + echo "php=${pkg%%-*}" >> "$GITHUB_OUTPUT" + - name: Login to Gitea uses: docker/login-action@v3 with: @@ -159,6 +201,9 @@ jobs: build-args: | PHPVER=${{ matrix.phpver }} OLS_VERSION=1.8.4 + 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 @@ -187,6 +232,29 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + # Per-run cache-bust value. Each job's buildx builder is created fresh by + # setup-buildx-action (no cache-from/cache-to here), so today nothing is + # cached between runs — this keeps it that way if a cache is ever added: + # the package-upgrade layers consume CACHE_BUST and can never be reused. + - name: Cache-bust value + id: bust + run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT" + + # Ask the LiteSpeed apt repo (the same one the base image's apt uses) for the + # CURRENT lsphp version. The Dockerfile asserts the installed package equals + # this (build fails if the repo changed mid-build — just re-run) and writes + # it into the image labels org.whp.php-version / org.whp.lsphp-package-version. + - name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo + id: lsphp + run: | + set -euo pipefail + idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages) + pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1) + if [ -z "$pkg" ]; then echo "::error::lsphp${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi + echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})" + echo "pkg=$pkg" >> "$GITHUB_OUTPUT" + echo "php=${pkg%%-*}" >> "$GITHUB_OUTPUT" + - name: Login to Gitea uses: docker/login-action@v3 with: @@ -203,6 +271,9 @@ jobs: build-args: | PHPVER=${{ matrix.phpver }} OLS_VERSION=1.8.4 + 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 to match Build-LiteSpeed-Images — same # prebuilt base, same lsphp binaries. Bump both together. tags: | diff --git a/Dockerfile b/Dockerfile index e999c5e..1b10086 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ FROM almalinux/10-base 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. +# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache. +ARG CACHE_BUST= # Install repos, update, install only needed packages, clean up in one layer RUN dnf install -y \ @@ -23,6 +27,7 @@ RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \ 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/ && \ /scripts/install-php$PHPVER.sh && \ + echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \ rm -rf /tmp/* # Download and install wp-cli (consider pinning version for reproducibility) diff --git a/Dockerfile.fpm b/Dockerfile.fpm index 6685497..3500108 100644 --- a/Dockerfile.fpm +++ b/Dockerfile.fpm @@ -1,5 +1,9 @@ FROM almalinux/10-base 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. +# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache. +ARG CACHE_BUST= # Install repos, update, install only needed packages (no httpd/mod_ssl), clean up in one layer RUN dnf install -y \ @@ -22,6 +26,7 @@ RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \ # Create needed dirs, install PHP, clean up (no SSL cert, no httpd) RUN mkdir -p /run/php-fpm/ && \ /scripts/install-php$PHPVER.sh && \ + echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \ rm -rf /tmp/* # Download and install wp-cli diff --git a/Dockerfile.litespeed b/Dockerfile.litespeed index a0b5332..7282054 100644 --- a/Dockerfile.litespeed +++ b/Dockerfile.litespeed @@ -45,8 +45,23 @@ ENV PHPVER=${PHPVER} ## lsphp${PHPVER}-ldap is the only extra ext we add (everything else WP needs ## ships in the prebuilt base). lsphp84 + lsphp85 don't ship imap or pspell ## from LiteSpeed — customers needing imap should pin to 8.3 or earlier. -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +## +## SECURITY UPDATES AT BUILD TIME: lsphp-apt-upgrade.sh also upgrades the OS and +## sweeps EVERY installed lsphp${PHPVER}* package to the LiteSpeed repo's current +## release, failing the build if the repo is unreachable — so a rebuild ships a +## PHP security fix as soon as LiteSpeed publishes the .deb, without waiting for +## a new litespeedtech/openlitespeed base tag. See the script header. +## +## CACHE_BUST: CI passes a per-run value so this layer can never be a cache hit +## (an ARG's first use in a RUN is part of that layer's cache key). Local builds: +## pass --build-arg CACHE_BUST=$(date +%s) or use --no-cache, or you may get the +## lsphp from your last build. EXPECT_* are resolved by CI from the repo index +## and asserted here; the same values become the image labels below. +ARG CACHE_BUST= +ARG EXPECT_LSPHP_PKG= +ARG EXPECT_PHP_VERSION= +COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh +RUN /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \ gettext-base sudo cron \ ca-certificates curl wget \ lsphp${PHPVER}-ldap && \ @@ -87,6 +102,14 @@ RUN sed -i 's|^[[:space:]]*address[[:space:]]\+\*:| address 127.0 ## Cron entry for log rotation (mirrors cac:phpXX). RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab +## Inspect without running the image: +## docker buildx imagetools inspect --format '{{json .Image.Config.Labels}}' +## Values come from CI's repo-index lookup and were asserted against the +## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build). +LABEL org.whp.php-version="${EXPECT_PHP_VERSION:-unverified}" \ + org.whp.lsphp-package-version="${EXPECT_LSPHP_PKG:-unverified}" \ + org.whp.php-flavour="lsphp${PHPVER}" + EXPOSE 80 443 ## Healthcheck: the entrypoint drops a static /healthz into the customer diff --git a/Dockerfile.lsphp b/Dockerfile.lsphp index 9171365..47c178a 100644 --- a/Dockerfile.lsphp +++ b/Dockerfile.lsphp @@ -60,9 +60,20 @@ ARG PHPVER=83 ## Toolchain layer, deliberately BEFORE the source COPY so editing the extension ## does not re-resolve the PHP version (which is what caused the skew above). ## Records the exact lsphp version the headers belong to; stage 2 pins to it. +## +## lsphp-apt-upgrade.sh (see its header) refuses to run on stale/unreachable +## repo lists and sweeps every lsphp${PHPVER}* package to the repo's current +## release. CACHE_BUST is per-CI-run so this layer — and therefore the recorded +## version stage 2 pins to — is never a cache hit; it is re-declared in stage 2. +## --no-os-upgrade: this stage is thrown away, OS updates would only cost time. +## EXPECT_* (CI's repo-index lookup) are asserted here too, so a mismatch fails +## before the compile rather than after it. +ARG CACHE_BUST= +ARG EXPECT_LSPHP_PKG= +ARG EXPECT_PHP_VERSION= +COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh RUN set -e; \ - apt-get update; \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + /usr/local/sbin/lsphp-apt-upgrade.sh --no-os-upgrade "${PHPVER}" \ build-essential autoconf pkg-config \ lsphp${PHPVER}-dev; \ RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \ @@ -141,17 +152,29 @@ ENV PHPVER=${PHPVER} ## cache hit, new version => this layer re-runs and lands on the same one. The ## explicit `=$V` pins then make a mid-build repo roll a LOUD apt failure instead ## of a silent skew. Verified satisfiable on PHP 8.1/8.3/8.5 (2026-08-05). +## +## SECURITY UPDATES AT BUILD TIME: lsphp-apt-upgrade.sh upgrades the Ubuntu OS +## packages and every other lsphp${PHPVER}* package (extensions such as -msgpack +## are NOT dragged along by the -common pin) to the repos' current versions, and +## fails the build if the repo is unreachable. CACHE_BUST makes this layer a +## guaranteed cache miss per CI run; EXPECT_* are CI's repo-index lookup, +## asserted by the script and written into the image labels below. +ARG CACHE_BUST= +ARG EXPECT_LSPHP_PKG= +ARG EXPECT_PHP_VERSION= 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); \ - apt-get update; \ - if ! DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + if ! /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \ ca-certificates \ lsphp${PHPVER}="$V" lsphp${PHPVER}-common="$V" lsphp${PHPVER}-ldap="$V"; then \ - echo "FATAL: lsphp${PHPVER} $V is what cac_path_parity was compiled against," >&2; \ - echo " but the LiteSpeed repo no longer offers it (it keeps only the" >&2; \ - echo " current release). The ext-build stage is almost certainly a stale" >&2; \ - echo " cache hit — rebuild with --no-cache." >&2; \ + echo "FATAL: lsphp-apt-upgrade.sh failed (reason above). If apt said" >&2; \ + echo " \"Version '$V' ... was not found\" or refused a downgrade: $V is what" >&2; \ + echo " cac_path_parity was compiled against, but the LiteSpeed repo (which" >&2; \ + echo " keeps only the current release) now offers a newer one — either it" >&2; \ + echo " published mid-build or ext-build was a stale cache hit. Re-run the" >&2; \ + echo " build (CI) / rebuild with --no-cache (local)." >&2; \ exit 1; \ fi; \ apt-get clean; \ @@ -218,6 +241,14 @@ RUN bash -c 'set -e; \ ## default so the runuser exec path can't drop it. ENV LSPHP_ENABLE_USER_INI=on +## Inspect without running the image: +## docker buildx imagetools inspect --format '{{json .Image.Config.Labels}}' +## Values come from CI's repo-index lookup and were asserted against the +## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build). +LABEL org.whp.php-version="${EXPECT_PHP_VERSION:-unverified}" \ + org.whp.lsphp-package-version="${EXPECT_LSPHP_PKG:-unverified}" \ + org.whp.php-flavour="lsphp${PHPVER}" + EXPOSE 9000 ## TCP-connect + lsphp-alive check (LSAPI isn't FastCGI, so no cgi-fcgi ping). diff --git a/scripts/lsphp-apt-upgrade.sh b/scripts/lsphp-apt-upgrade.sh new file mode 100755 index 0000000..377cd73 --- /dev/null +++ b/scripts/lsphp-apt-upgrade.sh @@ -0,0 +1,97 @@ +#!/bin/bash +## lsphp-apt-upgrade.sh — BUILD-TIME ONLY. Brings every installed lsphp${PHPVER}* +## package (and, by default, the Ubuntu OS packages) up to what the repos offer +## RIGHT NOW, then proves it. +## +## Why this exists: cac-litespeed / cac-lsphp start FROM the LiteSpeed prebuilt +## image litespeedtech/openlitespeed:-lsphpNN, which LiteSpeed republishes +## rarely (the 1.8.4 tags were built 2025-12-16 and ship lsphp85 8.5.0 / +## lsphp84 8.4.15, measured 2026-10-21, while rpms.litespeedtech.com/debian +## offers 8.5.11 / 8.4.26). The base image already carries that apt repo, so a +## PHP security release reaches us the moment LiteSpeed publishes the .deb — +## provided every build actually asks for it. Before this script the lsphp core +## only moved forward as a SIDE EFFECT of `apt-get install lsphpNN-ldap` (whose +## exact-version dependency on lsphpNN-common drags the family along); lsphp +## extension packages without that coupling (e.g. lsphpNN-msgpack) and every +## Ubuntu package stayed at the base image's December versions. +## +## 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}. +## * 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; +## that same value becomes the image LABEL, so the label cannot lie. +## +## Usage: lsphp-apt-upgrade.sh [--no-os-upgrade] PHPVER [extra apt packages / pkg=ver pins...] +## Extra packages are installed in the same transaction set (after the OS +## upgrade, before the lsphp sweep). Does NOT clean apt lists — the caller +## decides (multi-stage builders want them; shipped layers delete them). +set -euo pipefail + +OS_UPGRADE=1 +if [ "${1:-}" = "--no-os-upgrade" ]; then OS_UPGRADE=0; shift; fi +PHPVER="${1:?usage: lsphp-apt-upgrade.sh [--no-os-upgrade] PHPVER [pkgs...]}" +shift + +export DEBIAN_FRONTEND=noninteractive +APT=(apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold) +LSPHP="/usr/local/lsws/lsphp${PHPVER}/bin/lsphp" +PHPCLI="/usr/local/lsws/lsphp${PHPVER}/bin/php" + +die() { echo "FATAL(lsphp-apt-upgrade): $*" >&2; exit 1; } + +echo "lsphp-apt-upgrade: PHPVER=${PHPVER} os_upgrade=${OS_UPGRADE} cache_bust=${CACHE_BUST:-}" +echo "lsphp-apt-upgrade: base image lsphp${PHPVER}=$(dpkg-query -W -f='${Version}' "lsphp${PHPVER}" 2>/dev/null || echo none)" + +"${APT[@]}" update --error-on=any || die "apt-get update failed (repo unreachable?). Refusing to build with stale package lists." + +# The candidate for lsphpNN must come from the LiteSpeed repo — not "none", and +# not merely whatever is already installed. +if ! apt-cache madison "lsphp${PHPVER}" | grep -q 'rpms.litespeedtech.com'; 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 +CAND=$(apt-cache policy "lsphp${PHPVER}" | awk '/Candidate:/ {print $2; exit}') +echo "lsphp-apt-upgrade: repo candidate lsphp${PHPVER}=${CAND}" + +if [ "$OS_UPGRADE" = 1 ]; then + # --with-new-pkgs: let an upgrade pull in a new dependency instead of being + # silently "kept back". Never removes packages (that would be dist-upgrade). + "${APT[@]}" upgrade --with-new-pkgs +fi + +if [ $# -gt 0 ]; then + "${APT[@]}" install --no-install-recommends "$@" +fi + +# Sweep EVERY installed lsphpNN* package (core + all extensions) to the candidate. +mapfile -t PKGS < <(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' "lsphp${PHPVER}*" 2>/dev/null | awk '$1 == "ii" {print $2}') +[ "${#PKGS[@]}" -gt 0 ] || die "no lsphp${PHPVER}* packages installed — wrong base image?" +"${APT[@]}" install --only-upgrade --no-install-recommends "${PKGS[@]}" + +LEFT=$(apt list --upgradable 2>/dev/null | grep "^lsphp${PHPVER}" || true) +[ -z "$LEFT" ] || die "lsphp packages still upgradable after the sweep: +$LEFT" + +INST=$(dpkg-query -W -f='${Version}' "lsphp${PHPVER}") +RUNV=$("$PHPCLI" -r 'echo PHP_VERSION;') || die "lsphp${PHPVER} CLI does not run" + +if [ -n "${EXPECT_LSPHP_PKG:-}" ] && [ "$INST" != "$EXPECT_LSPHP_PKG" ]; then + die "installed lsphp${PHPVER}=$INST but CI resolved $EXPECT_LSPHP_PKG from the repo index (repo changed mid-build? re-run)." +fi +if [ -n "${EXPECT_PHP_VERSION:-}" ] && [ "$RUNV" != "$EXPECT_PHP_VERSION" ]; then + die "lsphp${PHPVER} reports PHP $RUNV but the image will be labelled $EXPECT_PHP_VERSION." +fi +if [ -z "${EXPECT_PHP_VERSION:-}" ]; then + echo "lsphp-apt-upgrade: WARNING no EXPECT_PHP_VERSION build-arg — image label will read 'unverified' (fine for local builds; CI always sets it)." >&2 +fi + +printf '%s\n' "$RUNV" > /etc/cac-php-version + +echo "lsphp-apt-upgrade: ===== version report =====" +dpkg-query -W -f=' ${Package} ${Version}\n' "lsphp${PHPVER}*" +echo " lsphp -v: $("$LSPHP" -v | head -n1)" +echo " OS packages still upgradable: $(apt list --upgradable 2>/dev/null | grep -vc '^Listing' || true)" +echo "lsphp-apt-upgrade: OK lsphp${PHPVER}=${INST} (PHP ${RUNV})" -- 2.52.0