Compare commits

..
Author SHA1 Message Date
shadowdaoandClaude Opus 5.5 af7fd193e5 fix(images): Remi origin over HTTPS first; fail builds older than php.net
Security review of #28: plain-http repo metadata could be replayed to keep a
build on old (validly signed) vulnerable PHP. The origin is now tried over
HTTPS first, with http only as a fallback (its HTTPS timed out/reset under the
2026-10-21 release load). CI resolves php.net's current release per branch
(over HTTPS) and the build fails if the installed PHP is older, so neither a
lagging mirror nor replayed metadata can ship a vulnerable PHP.

Tested on 192.168.1.193 (almalinux/10-base): rewritten baseurl installs PHP
8.5.12; the gate passes at 8.5.12 and fails at 8.5.99.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:29:31 -07:00
jknapp e8e3bfd490 Merge pull request 'fix(images): install Remi packages from the origin, not the lagging mirrors' (#28) from fix/remi-origin into trunk
Cloud Apache Container / Shell-Checks (push) Successful in 17s
Cloud Apache Container / Build-and-Push (80) (push) Failing after 4m54s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 6m45s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 6m34s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 6m6s
Cloud Apache Container / Build-and-Push (84) (push) Failing after 4m55s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 6m12s
Cloud Apache Container / Build-FPM-Images (74) (push) Failing after 6m3s
Cloud Apache Container / Build-FPM-Images (82) (push) Failing after 3m30s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 6m22s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 5m57s
Cloud Apache Container / Build-FPM-Images (83) (push) Failing after 4m22s
Cloud Apache Container / Build-FPM-Images (84) (push) Failing after 4m10s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 4m8s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 6m49s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 4m33s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 4m43s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 21m58s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 4m26s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 4m49s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m59s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 36s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 4m52s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 3m12s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 5m16s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 5m38s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 5m20s
2026-10-21 15:02:41 +00:00
shadowdaoandClaude Opus 5.5 c49f456336 fix(images): install Remi packages from the origin, not the lagging mirrors
During the php2shell release the Remi mirrors still served 8.2.34 hours after
8.2.35 was published, so cac/cac-fpm builds shipped vulnerable PHP. Enable the
repo files' baseurl (rpms.remirepo.net) and comment the mirrorlist. Verified on
the build server: a clean almalinux/10-base install now gets PHP 8.2.35.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:02:38 -07:00
jknapp bcd737a9e2 Merge pull request 'ci: fail-fast false on every image matrix' (#27) from fix/ci-no-fail-fast into trunk
Cloud Apache Container / Shell-Checks (push) Successful in 42s
Cloud Apache Container / Build-and-Push (81) (push) Failing after 2m8s
Cloud Apache Container / Build-and-Push (85) (push) Failing after 3m27s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 5m26s
Cloud Apache Container / Build-and-Push (80) (push) Failing after 13m6s
Cloud Apache Container / Build-FPM-Images (80) (push) Failing after 2m7s
Cloud Apache Container / Build-and-Push (84) (push) Failing after 11m45s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 15m27s
Cloud Apache Container / Build-FPM-Images (82) (push) Failing after 2m22s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 15m24s
Cloud Apache Container / Build-FPM-Images (85) (push) Failing after 1m19s
Cloud Apache Container / Build-FPM-Images (83) (push) Failing after 4m30s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m46s
Cloud Apache Container / Build-and-Push (83) (push) Failing after 18m2s
Cloud Apache Container / Build-FPM-Images (81) (push) Failing after 7m47s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m31s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 5m20s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 2m26s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 5m22s
Cloud Apache Container / Build-FPM-Images (84) (push) Failing after 8m36s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 2m11s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 5m2s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 36s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 6m5s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 4m5s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m14s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 6m21s
2026-10-21 14:55:53 +00:00
shadowdaoandClaude Opus 5.5 955aec4573 ci: fail-fast false on every image matrix
One failing PHP version (runs 4645/4646: cac-fpm php80, cac php82) cancelled
every other cac/cac-fpm build, so no PHP security build of 8.3-8.5 was pushed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:55:49 -07:00
jknapp 998eb17f39 Merge pull request 'fix(php): bake php2shell ini mitigation into cac/cac-fpm/cac-lsphp/cac-litespeed' (#26) from fix/php2shell-ini-mitigation into trunk
Cloud Apache Container / Shell-Checks (push) Successful in 1m25s
Cloud Apache Container / Build-and-Push (82) (push) Failing after 2m58s
Cloud Apache Container / Build-and-Push (74) (push) Canceled after 4m13s
Cloud Apache Container / Build-and-Push (80) (push) Canceled after 3m58s
Cloud Apache Container / Build-and-Push (81) (push) Canceled after 3m41s
Cloud Apache Container / Build-and-Push (83) (push) Canceled after 2m29s
Cloud Apache Container / Build-and-Push (84) (push) Canceled after 31s
Cloud Apache Container / Build-and-Push (85) (push) Canceled after 15s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 5m56s
Cloud Apache Container / Build-FPM-Images (80) (push) Failing after 5m45s
Cloud Apache Container / Build-FPM-Images (81) (push) Canceled after 5m31s
Cloud Apache Container / Build-FPM-Images (82) (push) Canceled after 5m16s
Cloud Apache Container / Build-FPM-Images (83) (push) Canceled after 5m0s
Cloud Apache Container / Build-FPM-Images (84) (push) Canceled after 3m56s
Cloud Apache Container / Build-FPM-Images (85) (push) Canceled after 2m52s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 1m27s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m31s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m40s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 4m40s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 2m35s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 5m53s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 46s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 5m28s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 6m32s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 6m19s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m36s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 4m54s
2026-10-21 14:22:07 +00:00
shadowdaoandClaude Opus 5.5 f461d22aae fix(php): bake php2shell ini mitigation into cac/cac-fpm/cac-lsphp/cac-litespeed
CVE-2026-109618 / CVE-2026-109619 (GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp).
Adds configs/zz-php2shell-mitigation.ini (fatal_error_backtraces=0,
session.upload_progress.enabled=0, max_input_nesting_level=16) and installs it
into each image's PHP scan dir as zz-php2shell-mitigation.ini so it sorts after
99-prod-overrides.ini and the entrypoint-written 99-* fragments:

- Dockerfile / Dockerfile.fpm: COPY to /etc/php.d/ next to prod-php.ini.
- Dockerfile.lsphp / Dockerfile.litespeed: COPY to /etc/lsws-templates/ and
  cp into the computed lsphp $SCAN_DIR in the existing overrides RUN.

fatal_error_backtraces is unknown to (and silently ignored by) PHP < 8.5, so
one file serves every version. Remove once all images ship
PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:21:47 -07:00
jknapp cfde82ae44 Merge pull request 'fix(lsphp-apt-upgrade): no early-exit readers under pipefail' (#25) from fix/lsphp-upgrade-pipefail into trunk
Cloud Apache Container / Shell-Checks (push) Successful in 40s
Cloud Apache Container / Build-and-Push (74) (push) Failing after 1m4s
Cloud Apache Container / Build-and-Push (84) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (85) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (80) (push) Canceled after 56s
Cloud Apache Container / Build-and-Push (81) (push) Canceled after 41s
Cloud Apache Container / Build-and-Push (82) (push) Canceled after 26s
Cloud Apache Container / Build-and-Push (83) (push) Canceled after 10s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m54s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m40s
Cloud Apache Container / Build-FPM-Images (80) (push) Failing after 4m2s
Cloud Apache Container / Build-FPM-Images (81) (push) Canceled after 3m46s
Cloud Apache Container / Build-FPM-Images (83) (push) Canceled after 1m3s
Cloud Apache Container / Build-FPM-Images (84) (push) Canceled after 47s
Cloud Apache Container / Build-FPM-Images (85) (push) Canceled after 33s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 4m7s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 4m30s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 4m34s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 6m22s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m56s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 5m15s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 2m2s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 39s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 45s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 5m46s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 5m46s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 5m43s
2026-10-21 14:07:07 +00:00
shadowdaoandClaude Opus 5.5 132e591503 fix(lsphp-apt-upgrade): no early-exit readers under pipefail
apt-cache madison | grep -q and apt-cache policy | awk '...exit' can SIGPIPE
the writer under pipefail and report lsphp missing when it is there. Read
each output into a variable and match in the shell. Fixes the Shell-Checks
failure on trunk (lsphp-info-probe.test.sh structural scan).

Tested: lsphp-info-probe.test.sh 36/36, shellcheck clean, and the script run
in litespeedtech/openlitespeed:1.8.4-lsphp85 upgrades 8.5.0 -> 8.5.11 OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:07:05 -07:00
jknapp 642b97707d Merge pull request 'feat(images): upgrade lsphp from the LiteSpeed repo at build time; label PHP version' (#24) from feat/lsphp-upgrade-at-build into trunk
Cloud Apache Container / Shell-Checks (push) Failing after 12s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m55s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m47s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m45s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 5m50s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 6m6s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 5m55s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 6m29s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m52s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 5m37s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 5m53s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m39s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m43s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m27s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 6m32s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 2m6s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 5m30s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 5m24s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 4m58s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 5m33s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 6m41s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 3m1s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 34s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 3m8s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 5m9s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 5m25s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m5s
2026-10-21 13:08:15 +00:00
shadowdaoandClaude Opus 5.5 71ca114529 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 <noreply@anthropic.com>
2026-10-21 06:07:48 -07:00
jknapp 74e12407f8 Merge pull request 'fix(entrypoint): no recursive chmod, graceful stop, fpm error_log owner, symlink-safe root paths, dotfile deny' (#23) from fix/no-recursive-chmod-on-start into trunk
Cloud Apache Container / Shell-Checks (push) Successful in 12s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m58s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m36s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m51s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 8m48s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 3m16s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 9m14s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 8m45s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 8m24s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 8m51s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 8m43s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m44s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 59s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m22s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 3m16s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 3m11s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 3m38s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m26s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m13s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m57s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 31s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 3m34s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 4m10s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 9m24s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 9m38s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 9m38s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 5m13s
2026-10-21 00:01:29 +00:00
7 changed files with 342 additions and 10 deletions

No files matched your search

+113
View File
@@ -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
@@ -60,6 +61,9 @@ jobs:
Build-and-Push:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
phpver: [74, 80, 81, 82, 83, 84, 85]
steps:
@@ -72,6 +76,28 @@ 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"
# The minimum PHP version this build must ship: php.net's current release for
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
- name: Resolve current PHP release from php.net
id: phpmin
run: |
set -euo pipefail
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
echo "php.net current ${branch}: ${min}"
echo "min=${min}" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
@@ -86,6 +112,8 @@ jobs:
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
@@ -93,6 +121,9 @@ jobs:
Build-FPM-Images:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
phpver: [74, 80, 81, 82, 83, 84, 85]
steps:
@@ -105,6 +136,28 @@ 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"
# The minimum PHP version this build must ship: php.net's current release for
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
- name: Resolve current PHP release from php.net
id: phpmin
run: |
set -euo pipefail
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
echo "php.net current ${branch}: ${min}"
echo "min=${min}" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
@@ -120,6 +173,8 @@ jobs:
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
@@ -127,6 +182,9 @@ jobs:
Build-LiteSpeed-Images:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
# PHP 7.4/8.0 deliberately excluded — the LiteSpeed prebuilt base
# images stop at older OLS releases for those PHP versions, and the
@@ -143,6 +201,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 +240,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
@@ -171,6 +255,9 @@ jobs:
Build-LSPHP-Images:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
# Same PHP matrix as cac-litespeed (81–85): cac-lsphp is the detached
# backend for the shared-ols tier and shares the litespeed prebuilt
@@ -187,6 +274,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 +313,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: |
+24
View File
@@ -1,10 +1,24 @@
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=
# php.net's current release for this branch (CI); the build fails on older PHP.
ARG EXPECT_PHP_MIN=
# 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 && \
# 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
# because the origin's HTTPS dropped/timed out under that day's load. A
# 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 && \
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 && \
@@ -23,6 +37,12 @@ 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 && \
# 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);' || \
{ echo "FATAL: built PHP $(php -r 'echo PHP_VERSION;') is older than php.net $EXPECT_PHP_MIN"; exit 1; }; \
else echo "WARNING: no EXPECT_PHP_MIN (local build) - PHP version not gated"; fi && \
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)
@@ -36,6 +56,10 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
# Copy configs and web files
COPY ./configs/default-index.conf /etc/httpd/conf.d/
COPY ./configs/prod-php.ini /etc/php.ini
# php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
# every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
# all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
COPY ./configs/zz-php2shell-mitigation.ini /etc/php.d/zz-php2shell-mitigation.ini
COPY ./configs/phpinfo.php /var/www/html/
COPY ./configs/mariadb.repo /etc/yum.repos.d/
COPY ./configs/index.php /var/www/html/
+24
View File
@@ -1,10 +1,24 @@
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=
# php.net's current release for this branch (CI); the build fails on older PHP.
ARG EXPECT_PHP_MIN=
# 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 && \
# 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
# because the origin's HTTPS dropped/timed out under that day's load. A
# 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 && \
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 && \
@@ -22,6 +36,12 @@ 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 && \
# 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);' || \
{ echo "FATAL: built PHP $(php -r 'echo PHP_VERSION;') is older than php.net $EXPECT_PHP_MIN"; exit 1; }; \
else echo "WARNING: no EXPECT_PHP_MIN (local build) - PHP version not gated"; fi && \
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
rm -rf /tmp/*
# Download and install wp-cli
@@ -34,6 +54,10 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
# Copy configs (PHP only, no Apache configs)
COPY ./configs/prod-php.ini /etc/php.ini
# php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
# every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
# all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
COPY ./configs/zz-php2shell-mitigation.ini /etc/php.d/zz-php2shell-mitigation.ini
COPY ./configs/mariadb.repo /etc/yum.repos.d/
# Set up cron job for log rotation
+30 -2
View File
@@ -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 && \
@@ -68,6 +83,10 @@ COPY ./scripts/entrypoint-litespeed.sh \
/scripts/
RUN chmod +x /scripts/*
COPY ./configs/litespeed/ /etc/lsws-templates/
## php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
## every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
## all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
COPY ./configs/zz-php2shell-mitigation.ini /etc/lsws-templates/zz-php2shell-mitigation.ini
## Apply our production lsphp ini overrides. Ask lsphp for its scan dir
## directly (varies by PHP minor version: 8.3/8.4/8.5 each have their own
@@ -77,6 +96,7 @@ RUN bash -c 'set -e; \
SCAN_DIR=$(/usr/local/lsws/lsphp${PHPVER}/bin/lsphp -i 2>/dev/null | awk -F"=> " "/^Scan this dir/ {print \$2; exit}"); \
mkdir -p "$SCAN_DIR"; \
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
cp /etc/lsws-templates/zz-php2shell-mitigation.ini "$SCAN_DIR/zz-php2shell-mitigation.ini"; \
echo "wrote overrides to $SCAN_DIR"'
## Disable the OLS WebAdmin port for customer-facing containers. Bind admin
@@ -87,6 +107,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 <ref> --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
+44 -8
View File
@@ -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; \
@@ -178,10 +201,15 @@ RUN chmod +x /scripts/entrypoint-lsphp.sh /scripts/detect-memory-lsphp.sh /scrip
## Apply production lsphp ini overrides into lsphp's scan dir (path varies by
## PHP minor version; ask lsphp directly — same idiom as Dockerfile.litespeed).
COPY ./configs/litespeed/lsphp-overrides.ini /etc/lsws-templates/lsphp-overrides.ini
## php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
## every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
## all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
COPY ./configs/zz-php2shell-mitigation.ini /etc/lsws-templates/zz-php2shell-mitigation.ini
RUN bash -c 'set -e; \
SCAN_DIR=$(/usr/local/lsws/lsphp${PHPVER}/bin/lsphp -i 2>/dev/null | awk -F"=> " "/^Scan this dir/ {print \$2; exit}"); \
mkdir -p "$SCAN_DIR"; \
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
cp /etc/lsws-templates/zz-php2shell-mitigation.ini "$SCAN_DIR/zz-php2shell-mitigation.ini"; \
echo "wrote overrides to $SCAN_DIR"'
## Install the cac_path_parity extension into lsphp's own extension_dir and load
@@ -218,6 +246,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 <ref> --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).
+5
View File
@@ -0,0 +1,5 @@
; php2shell mitigation (CVE-2026-109618 / CVE-2026-109619, GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp)
; Remove once all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
fatal_error_backtraces = 0
session.upload_progress.enabled = 0
max_input_nesting_level = 16
+102
View File
@@ -0,0 +1,102 @@
#!/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:<OLS>-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:-<none>}"
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.
# 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).
MADISON=$(apt-cache madison "lsphp${PHPVER}")
if [[ "$MADISON" != *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
POLICY=$(apt-cache policy "lsphp${PHPVER}")
CAND=$(awk '/Candidate:/ {print $2; exit}' <<<"$POLICY")
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})"