Commit Graph
178 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5.5 9baa7a6029 docs(pkg-cache): new upstreams, modes, wired repos, how to add an upstream
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:35:36 -07:00
shadowdaoandClaude Opus 5.5 3a6cab19ee 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 <noreply@anthropic.com>
2026-10-22 07:11:52 -07:00
shadowdaoandClaude Opus 5.5 6a11fc9894 feat(pkg-cache): Debian, NodeSource, Go proxy and EL9 support in pkg-cache-repos.sh
Backward compatible; existing modes unchanged for existing callers.
- apt: also rewrites deb.debian.org/debian, deb.debian.org/debian-security and
  security.debian.org to <cache>/debian and <cache>/debian-security; the
  listing no longer fails under pipefail when /etc/apt/sources.list is absent
  (Debian 13 images).
- rpm: rewrites rpm.nodesource.com repos to <cache>/nodesource (gpgcheck=1;
  NodeSource does not sign repomd).
- new `nodesource MAJOR`: writes the nodesource-nodejs repo itself with the
  committed signing key (replaces curl setup_N.x | bash).
- new `go-env`: prints GOPROXY=<cache>/goproxy|https://proxy.golang.org|direct
  plus SSL_CERT_FILE for the RUN; go.sum + sum.golang.org still verify.
- release-install: EL9 or EL10 picked from rpm %rhel; adds RPM-GPG-KEY-EPEL-9.
- PKG_CACHE is regex-validated.
Tested through the cache on a throwaway runner CT (pve vmbr2): EL9+EPEL9,
NodeSource 22 on EL10, Debian trixie apt, golang:1.25 apt + go-env; restore
left no cache reference in each case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:08:22 -07:00
jknapp 05ad47a833 Merge pull request 'ci: pin base images by digest resolved directly from Docker Hub' (#35) from ci/base-image-digest-pin into trunk
Cloud Apache Container / Build-and-Push (84) (push) Successful in 3m50s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 5m24s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 5m21s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m15s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 7m3s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 7m30s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 3m48s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 3m47s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 14m41s
Cloud Apache Container / Shell-Checks (push) Successful in 1m8s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 5m55s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 6m7s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 6m1s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 6m4s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 7m3s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 5m55s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m28s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 7m2s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 7m21s
2026-10-22 13:59:04 +00:00
shadowdaoandClaude Opus 5.5 d1547f3418 ci: pin base images by digest resolved directly from Docker Hub
With the pkg-cache BuildKit mirror on, `FROM almalinux/10-base` was resolved
by tag through the cache, so a compromised cache could substitute the image.

Each job using the mirror now looks up the base tag's digest DIRECTLY at
registry-1.docker.io (.gitea/scripts/resolve-base-image.sh, HTTPS, HEAD on
the manifest, regex-validated) and builds FROM image@sha256:... via the
BASE_IMAGE build-arg, so the mirror can only serve content-addressed blobs.
If the lookup fails, the mirror is NOT used for that job and the tag is pulled
straight from Docker Hub. The digest is resolved every run (no manual bumps);
local builds default to the tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:58:16 -07:00
jknapp 3334f94cd5 Merge pull request 'ci: stop building PHP 7.4, 8.0 and 8.1 images' (#33) from ci/drop-eol-php into trunk
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m36s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 4m26s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 5m13s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 4m59s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m57s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 55s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 47s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 5m47s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 6m29s
Cloud Apache Container / Shell-Checks (push) Successful in 17s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 4m57s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 4m40s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 4m12s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 5m6s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m12s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m52s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 2m40s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 5m19s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 5m40s
2026-10-22 01:13:20 +00:00
shadowdaoandClaude Opus 5.5 76fc53358c ci: stop building PHP 7.4, 8.0 and 8.1 images
All four image matrices (cac, cac-fpm, cac-litespeed, cac-lsphp) and the
emergency lsphp-from-source workflow now build PHP 8.2-8.5 only. 7.4-8.1 are
end of life and got no upstream fix for CVE-2026-109618/109619; no fleet site
runs them any more. Existing :php74/:php80/:php81 tags stay in the registry
but are no longer rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:13:18 -07:00
jknapp fc60ac5ef3 Merge pull request 'fix(pkg-cache): no | while read pipelines under pipefail (Shell-Checks)' (#32) from fix/pkg-cache-shellcheck-pipefail into trunk
Cloud Apache Container / Build-and-Push (80) (push) Successful in 5m14s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 4m4s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 4m11s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 4m10s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 5m4s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 2m7s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 2m1s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 52s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 5m47s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 3m59s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 6m4s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 4m8s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 3m39s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 4m10s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 4m45s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 4m34s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 4m10s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 4m9s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 4m14s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Failing after 3m52s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 2m5s
Cloud Apache Container / Shell-Checks (push) Successful in 16s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 4m24s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 4m28s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m31s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 4m59s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 4m51s
2026-10-21 23:48:21 +00:00
shadowdaoandClaude Opus 5.5 472fdcbc4d fix(pkg-cache): no | while read pipelines under pipefail
Shell-Checks' structural scan (scripts/tests/lsphp-info-probe.test.sh)
flagged two `cmd | while read` loops in pkg-cache-repos.sh restore.
Read the lists with mapfile from a process substitution instead, so
there is no pipeline. The test now passes locally (36/0), shellcheck is
clean, and the restore tests (new cache-referencing file removed,
leftover reference -> FATAL) behave as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:48:17 -07:00
jknapp cb0cecb557 Merge pull request 'feat(build): opt-in PKG_CACHE build arg for the internal package cache (HTTPS, GPG-verified)' (#31) from feat/pkg-cache-build-arg into trunk
Cloud Apache Container / Build-and-Push (80) (push) Successful in 5m21s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 4m24s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 5m40s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 3m35s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 4m12s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 4m57s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 2m20s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 51s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 4m55s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 5m39s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 4m45s
Cloud Apache Container / Shell-Checks (push) Failing after 9s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 3m47s
Cloud Apache Container / Build-FPM-Images (74) (push) Failing after 1m35s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 5m37s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 5m1s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 5m19s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 1m27s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 4m37s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 5m13s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Failing after 3m30s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 5m36s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 6m15s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 6m0s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m33s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m39s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 4m20s
2026-10-21 23:46:39 +00:00
shadowdaoandClaude Opus 5.5 2b9e7f302c ci: enable PKG_CACHE via a TLS probe that falls back to upstream; host repo tool
- build-push.yaml: Build-and-Push, Build-FPM-Images, Build-Shared-httpd
  and Build-Shared-OLS probe https://192.168.1.40 and :5000 with the
  pinned pkg-cache CA (5 s timeouts). When the cache answers they pass
  PKG_CACHE and configure the BuildKit Docker Hub mirror (ca=). When it
  doesn't, they build from upstream exactly as before and emit a warning.
  Fallback measured from the runner bridge: refused <1 s, silent drop 10 s.
- scripts/host/pkgcache-host-repos.sh: apply/revert/status for WHP hosts.
  Each rewritten repo uses baseurl=<cache> <origin> as the real fallback;
  mirrorlist/metalink stay commented because measured dnf ignores baseurl
  when they are present. Keeps gpgcheck=1 / repo_gpgcheck=1 and backs up
  every file. Tested on 192.168.1.148, including cache-down and
  silent-drop fallback.
- docs: runner firewall rule (applied), measured fallback results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:46:03 -07:00
shadowdaoandClaude Opus 5.5 18f65c6d84 fix(pkg-cache): HTTPS-only cache, GPG-verified release RPMs, strict restore
Security review follow-up for the opt-in PKG_CACHE path:

- PKG_CACHE must be https:// (http is refused). The cache's certificate
  comes from a name-constrained internal CA (keys/pkg-cache/ca.crt, valid
  only for 192.168.1.40 / pkg-cache.*). The CA is trusted only by the
  rewritten repos (dnf sslcacert bundle, apt per-host CaInfo) and only
  for the duration of the RUN.
- epel-release / remi-release are no longer installed straight from a
  URL. release-install downloads them over HTTPS, checks the package name,
  imports the signing keys committed in keys/pkg-cache/ and requires
  `rpm -K` "signatures OK", then installs with localpkg_gpgcheck=1. This
  applies with and without the cache.
- Rewritten repos get gpgcheck=1. Remi and AlmaLinux also get
  repo_gpgcheck=1, since both sign repomd.xml. EPEL does not publish a
  repomd signature, so it relies on end-to-end HTTPS.
- restore also deletes files it created and any new repo/apt file that
  still references the cache, then fails the build if a reference remains
  under /etc/yum.repos.d, /etc/dnf or /etc/apt.
- docs: trust model, replay risk, proposed openssl/httpd/ImageMagick
  minimum-version gates, TLS registry mirror (BuildKit ca= / dockerd
  certs.d, no insecure-registries).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:53:24 -07:00
shadowdaoandClaude Opus 5.5 b2bec5b3ee feat(build): opt-in PKG_CACHE build arg for the internal package cache
Adds scripts/pkg-cache-repos.sh and wires it into Dockerfile,
Dockerfile.fpm, Dockerfile.shared-httpd and Dockerfile.shared-ols. With
--build-arg PKG_CACHE=http://192.168.1.40 (CT130 pkg-cache), dnf/apt go
through the LAN cache. That cache fetches from the Remi/EPEL/AlmaLinux/
Ubuntu/LiteSpeed origins over HTTPS, with fallback tiers and
stale-on-error. Without the arg the script is a no-op and builds are
unchanged.

- rpm: each baseurl becomes "<cache>/<prefix>/... <origin>/...", so dnf
  fails over to the origin if the cache dies mid-build
- every RUN restores the original repo files before it ends, so no
  shipped layer carries the LAN address
- EPEL/Remi release RPMs come from the cache when it is set
- the php.net version gates are untouched

Dockerfile.litespeed/.lsphp are not wired yet (see docs/pkg-cache.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:29:54 -07:00
jknapp fa7808897f Merge pull request 'feat(lsphp): emergency lsphp build from php.net source (same support as packaged)' (#30) from feat/lsphp-source-emergency into trunk
Cloud Apache Container / Build-and-Push (80) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (84) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (80) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (83) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (85) (push) Canceled after 0s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Canceled after 0s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Canceled after 0s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Canceled after 0s
Cloud Apache Container / Build-Shared-httpd (push) Canceled after 0s
Cloud Apache Container / Build-Shared-OLS (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (74) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (83) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (85) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (74) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (81) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (82) (push) Canceled after 0s
Cloud Apache Container / Build-FPM-Images (84) (push) Canceled after 0s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Canceled after 0s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Canceled after 0s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Canceled after 0s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Canceled after 0s
Cloud Apache Container / Shell-Checks (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (81) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (82) (push) Canceled after 0s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Canceled after 0s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Canceled after 0s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Canceled after 0s
2026-10-21 16:48:40 +00:00
shadowdaoandClaude Opus 5.5 42993f42bd ci(security): no ${{ }} in run:, validate resolved versions, LiteSpeed index over HTTPS
Security review of #30: step outputs derived from the LiteSpeed apt index
(fetched over plain HTTP) were spliced into shell via ${{ }} in jobs that
hold registry push secrets — a MITM'd "version" would be shell injection.

- every run: block in build-push.yaml and lsphp-source-emergency.yaml now
  receives matrix values, step outputs, inputs and run_id via env: only
  (incl. the pre-existing php.net / LiteSpeed resolve steps from #24/#29)
- resolved versions are regex-checked before GITHUB_OUTPUT: PHP
  ^[0-9]+\.[0-9]+\.[0-9]+$, deb ^[0-9][0-9A-Za-z.+~:-]*$
- LiteSpeed Packages index fetched over HTTPS (--proto =https); measured
  rpms.litespeedtech.com serves it (HTTP/2 200)
- lsphp-src-build.sh / lsphp-src-overlay.sh validate PHPVER too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:47:44 -07:00
shadowdaoandClaude Opus 5.5 83c2ee9c2a feat(lsphp): emergency build path compiling lsphp from php.net source
For when a PHP security release is out and the LiteSpeed apt repo lags
(2026-10-21: php2shell CVE-2026-109618/-109619 fixed in 8.2.35/8.3.36/
8.4.27/8.5.12, LiteSpeed still on 8.2.34/8.3.35/8.4.26/8.5.11).

Dockerfile.lsphp / Dockerfile.litespeed gain an `lsphp-src` stage that is
idle unless PHP_SRC_VERSION is set; normal builds are unchanged (measured:
module list, file manifest, ldd, ini and dpkg identical to the published
images).

When set (lsphp-src-build.sh):
- tarball sha256 (php.net releases JSON) + OpenPGP signature against the
  committed php.net keyring, signer pinned per branch
- configure flags replayed from the packaged lsphp's own Configure Command
- LiteSpeed's php-litespeed-8.3 SAPI (what the packages actually compile,
  measured) instead of php-src's sapi/litespeed; sha256-pinned and checked
  against the packaged binary's LSAPI_* names
- packaged compiled-in ini defaults reproduced (session.save_path on 8.2/
  8.3) and gated (`php -n` diff)
- ABI gate (lsphp-abi-gate.sh, pahole) before reusing LiteSpeed's packaged
  PECL .so files; regression-tested in Shell-Checks

lsphp-src-overlay.sh swaps only php-src-owned files (decided by dpkg) and
fails the build unless extensions, versions, effective ini and feature
lines match the packaged lsphp under both CLI and litespeed SAPIs.

CI: lsphp-source-emergency.yaml (workflow_dispatch only). build-push.yaml
refuses to overwrite a newer php.net image with an older packaged one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:46:22 -07:00
jknapp be1540f559 Merge pull request 'fix(images): Remi origin over HTTPS first; fail builds older than php.net' (#29) from fix/remi-origin-https into trunk
Cloud Apache Container / Build-and-Push (84) (push) Failing after 3m25s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 11m11s
Cloud Apache Container / Build-FPM-Images (83) (push) Failing after 3m29s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 7m45s
Cloud Apache Container / Build-FPM-Images (85) (push) Failing after 3m25s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 10m49s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 2m4s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 5m17s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 35s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 4m6s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 5m42s
Cloud Apache Container / Shell-Checks (push) Successful in 15s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 5m36s
Cloud Apache Container / Build-FPM-Images (74) (push) Failing after 43s
Cloud Apache Container / Build-and-Push (83) (push) Failing after 3m43s
Cloud Apache Container / Build-FPM-Images (81) (push) Failing after 4m11s
Cloud Apache Container / Build-FPM-Images (82) (push) Failing after 4m15s
Cloud Apache Container / Build-FPM-Images (84) (push) Failing after 3m47s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 2m2s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 5m41s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 6m32s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 1m53s
Cloud Apache Container / Build-and-Push (81) (push) Failing after 3m35s
Cloud Apache Container / Build-and-Push (82) (push) Failing after 3m18s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m28s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 4m24s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 6m20s
2026-10-21 15:29:41 +00:00
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 / Build-and-Push (80) (push) Failing after 4m54s
Cloud Apache Container / Build-and-Push (84) (push) Failing after 4m55s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 6m22s
Cloud Apache Container / Build-FPM-Images (83) (push) Failing after 4m22s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 6m49s
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-Shared-httpd (push) Successful in 3m12s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 5m38s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 5m20s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 6m6s
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 (81) (push) Successful in 5m57s
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-LiteSpeed-Images (83) (push) Successful in 4m43s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 21m58s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 4m52s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 5m16s
Cloud Apache Container / Shell-Checks (push) Successful in 17s
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-LiteSpeed-Images (82) (push) Successful in 4m33s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 4m26s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 4m49s
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 / Build-and-Push (85) (push) Failing after 3m27s
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-FPM-Images (85) (push) Failing after 1m19s
Cloud Apache Container / Build-FPM-Images (83) (push) Failing after 4m30s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 2m26s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 2m11s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 36s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 4m5s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 6m21s
Cloud Apache Container / Shell-Checks (push) Successful in 42s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 5m26s
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 (83) (push) Failing after 18m2s
Cloud Apache Container / Build-FPM-Images (81) (push) Failing after 7m47s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 5m20s
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 (81) (push) Successful in 6m5s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m14s
Cloud Apache Container / Build-and-Push (81) (push) Failing after 2m8s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 15m24s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m46s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m31s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 5m2s
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 / Build-and-Push (80) (push) Canceled after 3m58s
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 (80) (push) Failing after 5m45s
Cloud Apache Container / Build-FPM-Images (83) (push) Canceled after 5m0s
Cloud Apache Container / Build-FPM-Images (85) (push) Canceled after 2m52s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 2m35s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 46s
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-Shared-httpd (push) Successful in 4m54s
Cloud Apache Container / Shell-Checks (push) Successful in 1m25s
Cloud Apache Container / Build-and-Push (74) (push) Canceled after 4m13s
Cloud Apache Container / Build-and-Push (83) (push) Canceled after 2m29s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 5m56s
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 (84) (push) Canceled after 3m56s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 1m27s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 5m53s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 5m28s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m36s
Cloud Apache Container / Build-and-Push (82) (push) Failing after 2m58s
Cloud Apache Container / Build-and-Push (81) (push) Canceled after 3m41s
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
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 / Build-and-Push (84) (push) Canceled after 0s
Cloud Apache Container / Build-and-Push (80) (push) Canceled after 56s
Cloud Apache Container / Build-FPM-Images (80) (push) Failing after 4m2s
Cloud Apache Container / Build-FPM-Images (83) (push) Canceled after 1m3s
Cloud Apache Container / Build-FPM-Images (85) (push) Canceled after 33s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m56s
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 (85) (push) Successful in 5m43s
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 (85) (push) Canceled after 0s
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 (81) (push) Canceled after 3m46s
Cloud Apache Container / Build-FPM-Images (84) (push) Canceled after 47s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 4m7s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 6m22s
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-and-Push (81) (push) Canceled after 41s
Cloud Apache Container / Build-and-Push (82) (push) Canceled after 26s
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 (85) (push) Successful in 5m15s
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 / 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 (85) (push) Successful in 5m37s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 5m53s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m43s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 6m32s
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 / 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-and-Push (83) (push) Successful in 5m55s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m52s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m39s
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 (83) (push) Successful in 5m33s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 6m41s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 6m5s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 6m6s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 6m29s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m27s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 5m24s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 4m58s
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 / Build-FPM-Images (80) (push) Successful in 1m51s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 9m14s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 8m24s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 8m43s
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 (83) (push) Successful in 4m10s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 9m24s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 9m38s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 5m13s
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 (81) (push) Successful in 3m16s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 8m51s
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 (81) (push) Successful in 3m16s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m26s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 3m34s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 9m38s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 8m48s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 8m45s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m22s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 3m11s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 3m38s
2026-10-21 00:01:29 +00:00
shadowdaoandClaude Opus 5.5 74648ba34b fix(security): deny config/VCS dotfiles over HTTP on standalone cac and cac-litespeed
Measured on .148: standalone cac served /.env with its contents, and
cac-litespeed served /.env even at mode 0640 (OLS runs as the customer uid
there). shared-httpd and shared-ols already deny these via WHP's drop-ins; the
standalone tiers never got them.

- cac: new configs/whp-security.conf in /etc/httpd/conf.d — the same named
  FilesMatch + DirectoryMatch list as WHP's shared-httpd-security.conf,
  re-rooted to /home.
- cac-litespeed: the same `context exp:` regex as WHP's shared-ols vhconf
  template, added to the vhost template.

Named list (.user.ini .env* .git .svn .hg .bzr .ht* .whp .site-builder), not a
blanket dotfile deny, so /.well-known/ stays reachable. HTTP only: PHP still
reads these files from disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:31:56 -07:00
shadowdaoandClaude Opus 5.5 86a88c87db fix(safe-paths): unlink planted symlinks as the customer, then fail closed
Root rm -f in drop_symlink could be redirected by a parent-directory swap to
delete arbitrary files as root. drop_symlink and the logs/ symlink sweep now
unlink AS THE CUSTOMER (can never delete anything the customer couldn't), and
drop_symlink still refuses to start if the path is a symlink afterwards; the
pre-daemon logs/ gate re-checks and fails closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:06:41 -07:00
shadowdaoandClaude Opus 5.5 02d93f4d1a fix(safe-paths): fail closed on planted symlinks before root daemons start
drop_symlink unlinks as root (rm on the final component never follows the
target and the customer cannot deny it) and refuses to start if the path is
still a symlink. user_touch refuses unless it ends up a regular file. New
require_safe_logs runs immediately before httpd / php-fpm start (they open
files under /home/$user/logs as root): logs/, logs/apache, logs/php-fpm must be
real directories and nothing under logs/ may be other than a regular file or
directory, else the entrypoint exits 1 without starting any daemon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:04:21 -07:00
shadowdaoandClaude Opus 5.5 94a73ea8ab fix(safe-paths): unlink symlinks and create files as the customer, never as root
drop_symlink and the logs/ symlink sweep now unlink AS THE CUSTOMER, and
user_touch no longer falls back to a root chown -h (a parent dir swapped for a
symlink between check and use could still redirect a root path operation).
A root-owned leftover error_log is fixed by the existing chown -R, which never
follows symlinks. Every operation these helpers perform is now safe even if the
customer races it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:52:01 -07:00
shadowdaoandClaude Opus 5.5 0ffccac4b8 fix(entrypoint): root never follows customer symlinks under /home/$user
/home/$user is the customer's bind mount; they can plant symlinks in it. The
entrypoints ran as root and wrote/chowned through customer paths, so e.g.
logs/php-fpm/error.log -> /etc/shadow (or a dangling crontab -> /etc/...)
handed the customer that file, i.e. container root. New scripts/safe-paths.sh,
used by entrypoint.sh and entrypoint-fpm.sh:

- secure_home: refuse to start if /home/$user is not a real dir; logs/ must be
  a real dir and every symlink under logs/ is removed and logged (apache and
  php-fpm open those files as root at start).
- dirs, the pool error_log and the default crontab are created AS THE CUSTOMER
  (setpriv, runuser fallback); a pre-existing root-owned error_log is fixed
  with chown -h (never dereferences).
- the user crontab is read as the customer and piped to `crontab -u $user -`
  (root reading it could install any root-readable file where the customer
  can `crontab -l` it). DEV-mode crontab/mysql_creds are written via as_user tee.
- the o+x directory pass (find -type d, no -L) runs as the customer, so a dir
  swapped for a symlink before chmod can only reach what the customer owns.
- chown -R is unchanged: GNU default -P lchown()s symlinks, never follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:49:18 -07:00
shadowdaoandClaude Opus 5.5 eaf616eb30 fix(entrypoint): graceful docker stop + cac-fpm error_log owner on first start
Graceful stop (cac, cac-fpm, shared-httpd, and form-sender which runs cac):
entrypoint.sh, entrypoint-fpm.sh and entrypoint-shared-httpd.sh end with bash
as PID 1 tailing logs. PID 1 only receives signals it has a handler for, so
docker stop's SIGTERM was dropped; docker waited the full 10s and SIGKILLed
apache / php-fpm mid-request (exit 137). The entrypoints now trap TERM/INT and
stop each daemon they started via the new scripts/stop-services.sh: httpd
graceful-stop (WINCH), php-fpm QUIT, crond TERM, each with a bounded wait and a
TERM/KILL fallback, budgets summing to < 10s, then exit 0. The log tail is
backgrounded + `wait`ed (and shared-httpd's 60s rescan sleep likewise) so the
trap runs immediately. Log streaming to docker logs is unchanged.

cac-fpm error_log: entrypoint-fpm.sh `touch`ed logs/php-fpm/error.log as root
AFTER its chown -R, so on a site's first start the pool (customer uid) could
not open it and PHP errors went out as FastCGI stderr to shared-httpd's log
until the next restart. The file is now created and chowned to the customer
right after the logs dir is made, before php-fpm starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:44:14 -07:00
shadowdaoandClaude Opus 5.5 14761597bb fix(entrypoint): stop chmod -R 755 on the customer docroot every start
entrypoint.sh and entrypoint-fpm.sh ran `chmod -R 755 /home/$user` on every
container start. /home/$user is the bind-mounted customer site directory, so
every file (wp-config.php included) became world-readable and executable, and
any tighter permissions the customer set were reverted on each restart.

The line dates from the initial commit (95b9397) and was copied into the FPM
entrypoint (c781678); neither gives a reason. The legitimate need is that
Apache (bundled httpd, or shared-httpd reading /mnt/users/...) runs as
`apache`, not the customer, and must be able to traverse directories to serve
static files.

Keep the recursive chown. Replace the recursive chmod with:
- chmod 755 on the docroot directory itself (matches entrypoint-litespeed.sh)
- o+x on directories that lack it (find -type d ! -perm -o=x)
Regular files are never touched: execute is never added, and wp-config.php or
any other customer-restricted file is never loosened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:33:29 -07:00
jknapp db19ee24c3 ci: add workflow_dispatch trigger for manual rebuilds
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m32s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 3m32s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m25s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 4m9s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 1m19s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m51s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 3m47s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 33s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m37s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 6m47s
Cloud Apache Container / Shell-Checks (push) Successful in 33s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m32s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 3m13s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m31s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m23s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 32s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 43s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 4m28s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 1m41s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 7m42s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m21s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 6m19s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m15s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 2m36s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m26s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 4m1s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 6m3s
2026-09-28 03:15:17 +00:00
shadowdaoandClaude Sonnet 5 3841b18820 fix(shared-ols): enableCache 0 so headerless responses are never cached
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m48s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m58s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m14s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m30s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m47s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m44s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m2s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 2m6s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m1s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 33s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 31s
Cloud Apache Container / Shell-Checks (push) Successful in 31s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m11s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m35s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 3m12s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m30s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m15s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m52s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 37s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 38s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m1s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 58s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 3m18s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m25s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m24s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m31s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 34s
LSCWP's Divi compat disables the plugin for ?et_fb= / ?et_pb_preview= /
?preview=true, so those responses carry no X-LiteSpeed-Cache-Control header.
With module-level enableCache 1, OLS cached them anyway (Divi Visual Builder
pages served from cache; the plugin's own QS exclusion never runs).

enableCache 0 caches only responses that opt in via an explicit
X-LiteSpeed-Cache-Control: public header. Measured on OLS 1.8.4: headerless
= miss,hit under 1 / not cached under 0; explicit public cached under both.
Matches the dedicated cac-litespeed tier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 06:55:02 -07:00
shadowdaoandClaude Opus 5 48b714447f chore: pin the shared-memory project key
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m1s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m8s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m28s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m46s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m9s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m19s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 2m33s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 1m56s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m2s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 29s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 33s
Cloud Apache Container / Shell-Checks (push) Successful in 12s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m31s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 3m27s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m59s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m10s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m26s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 6m24s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 1m14s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 34s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m17s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 59s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m28s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m32s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 42s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 35s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m25s
Ties every collaborator's Claude Code to the same shared-memory project
automatically, instead of each machine inferring a key from the directory
name. Read by project.identify at session start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 08:09:04 -07:00
shadowdao ba9650ee45 Merge branch 'fix/ols-watcher-scope'
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m18s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m6s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m19s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m45s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m37s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m16s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m20s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 1m7s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m1s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 35s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 29s
Cloud Apache Container / Shell-Checks (push) Successful in 10s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m16s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m14s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m26s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m28s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m25s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 35s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 34s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m7s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 1m4s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m11s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m6s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m22s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 35s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 40s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 35s
2026-08-13 21:39:37 -07:00
shadowdao 77af001af6 fix(ols): pin procps explicitly for pgrep dependency
entrypoint-shared-ols.sh's ols_running() liveness check now shells out to
pgrep, but procps was never in Dockerfile.shared-ols's apt-get install
list — pgrep works today only because Ubuntu 24.04's base image pulls
procps in transitively. If that stops being true, pgrep: command not
found -> exit 127 -> ols_running() false forever -> the crash-loop
breaker (MAX_STARTS/WINDOW) escalates to a hard exit 1 at boot. Make
the dependency explicit so it can't be pruned as unused.
2026-08-13 21:39:14 -07:00
shadowdaoandClaude Opus 5 cf6936e225 fix(ols): scope the htaccess watcher to docroots, stop lswsctrl status log spam
ols-htaccess-watcher.sh matched .htaccess by basename only, so ANY .htaccess
under a tenant (WordPress plugin guard files, not just the docroot OLS reads)
triggered a full graceful restart. Measured on whp01 over 24h: 63 restarts,
0 of them from a docroot .htaccess actually changing — all from Wordfence/W3TC/
WPForms/etc. self-healing files, mostly on tenants that aren't even on this
tier. Now matches the full path (%w%f) against */public_html/.htaccess, the
only .htaccess OLS ever reads, and logs which path triggered each restart.

entrypoint-shared-ols.sh's 3s supervisor poll called `lswsctrl status`, which
appends a line to lsrestart.log on every invocation. Measured on whp01:
1,819,286 status lines vs 2,429 real restarts in a 96 MB, never-rotated log.
ols_running() now checks the process table directly (pgrep -f 'lshttpd -
main', verified against the litespeedtech/openlitespeed base image) instead of
shelling out to a logging tool on a fixed timer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 21:29:48 -07:00
shadowdao b92725d2ec Merge branch 'fix/lsphp-keep-listen'
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m28s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m27s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m28s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m29s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m27s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m27s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m3s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 1m1s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 57s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 34s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 31s
Cloud Apache Container / Shell-Checks (push) Successful in 9s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m39s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m29s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m29s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m26s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m31s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m24s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 37s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 35s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m1s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 57s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m27s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m27s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 35s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 33s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 35s
2026-08-13 15:03:49 -07:00
shadowdaoandClaude Opus 5 5e83c8db3b fix(lsphp): set LSAPI_KEEP_LISTEN=2 to stop idle-exit timing from following the leaked busy counter
lsphp's master keeps a `busy` worker counter in a MAP_SHARED page that drifts
negative over days of uptime (measured live on whp01: busy=-8 after 6.9 days
on arclightcourt.com-01 vs 0..9 on a healthy sibling). php-src's
sapi/litespeed/lsapilib.c derives each child's idle-exit grace period from
that counter (10 + busy*10, capped by LSAPI_MAX_IDLE) only inside
`if (s_keep_listener == 1)`; with busy=-8 that's -70s, so workers exit after
~1s idle instead of 10-30s, no worker ever lingers in accept(), the
"don't fork, one's already listening" guard never fires, and the master
forks for every connection -- confirmed hitting the max-children ceiling and
producing 503s under bot traffic (306 OLS-side ExtConn-timeout/503 errors on
the affected site vs 0 on an identical healthy sibling).

LSAPI_KEEP_LISTEN=2 skips the `== 1` branch entirely so idle-exit timing
falls back to LSAPI_MAX_IDLE (already 30 by default here) instead of the
leaked counter. is_enough_free_mem() sits above that branch, not inside it,
so the memory-pressure guard is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 14:47:08 -07:00
shadowdaoandClaude Opus 5 a325615690 Merge branch 'fix/lsphp-pipefail-sigpipe'
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m28s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m41s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m22s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m23s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m22s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 1m1s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 1m0s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m1s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 37s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 31s
Cloud Apache Container / Shell-Checks (push) Successful in 9s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m27s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m24s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m39s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m20s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m28s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m25s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 32s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 33s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 58s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 59s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m52s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m23s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 32s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 34s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 32s
Fix nine 'writer | early-exiting-reader' pipelines that return 141 under
set -euo pipefail when the reader exits before the writer's final write().

The one that mattered: entrypoint-lsphp.sh reported the cac_path_parity
extension as 'not loadable' BECAUSE it was present, and fell back to the
degraded userland auto_prepend normaliser — the exact thing the extension
exists to eliminate. Measured on whp02 against the published image: 141 on
5/5 runs. It shipped past a green build gate, a 10/10 .phpt suite, a 9/9 FPM
harness and two review rounds, and was only caught by pulling the published
image and running it on a real host.

Two of the nine were bare command substitutions in entrypoints, where 141
under set -e kills PID 1 and the container never boots (entrypoint-lsphp.sh
SCAN_DIR, entrypoint-litespeed.sh — boot-critical for cac-litespeed).

Payload size is NOT the variable: 41144 bytes fails 32/300 well under a
65536-byte pipe capacity, while 500 KB into a 1 MiB pipe fails 200/200 with
small writes and 0/200 with one large write. It is a race on whether the
reader closes before the writer's last write() returns. The same image
failed 5/5 on whp02 and 0/10 in a dev container because bash <=5.2.15 writes
~37 KB per syscall while >=5.2.21 writes 80-160 bytes.

Boot-critical sites use pure-bash matching (no temp file); a regression test
extracts the shipped helpers verbatim and statically outlaws the shape
repo-wide, failing against the pre-fix tree with all 9 lines named.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:04:13 -07:00
shadowdaoandClaude Opus 5 e28c3dcce4 fix(lsphp): correct the SIGPIPE explanation, drop the temp-dir boot precondition, close two scanner blind spots
Three non-blocking findings from the review of 8790b02. The fix itself is
unchanged in intent; this makes the reasoning around it true, removes a
regression the fix introduced on the boot path, and stops the new test from
under-reporting.

F1 — the shipped comments explained the bug wrongly, and a wrong rule is what
the next maintainer reasons from. entrypoint-lsphp.sh and
render-shared-ols-config.sh both said the race is decided by PIPE CAPACITY:
"while the output fits the pipe the writer always wins; once it doesn't, SIGPIPE
is guaranteed." Both halves are refuted by measurement against a default
65536-byte pipe (F_GETPIPE_SZ):

    41144 bytes -> 141 in  32/300 runs (11%)  — well UNDER capacity
    65012 bytes -> 141 in  25/30  runs        — not certain even AT capacity
    500 KB into a 1 MiB pipe -> 200/200 with 4096-byte writes, 0/200 with one
                                500 KB write

and strace caught printf dying having written 12086 of 40406 bytes into a
65536-byte pipe. The mechanism is a race on whether the reader closes before the
writer's final write() returns; capacity only modulates how many syscalls the
writer needs. What actually separated whp02 (5/5 failures) from a dev container
(10/10 clean) is the WRITER's syscall size: bash <= 5.2.15 writes ~37 KB at a
time, bash >= 5.2.21 writes 80-160 bytes. The fs.pipe-user-pages-soft aside was
also wrong: it clamps to two pages not one, needs one uid holding >1024 pipes,
and is skipped for CAP_SYS_RESOURCE.

Both blocks now state the rule that is actually true — any
`writer | early-exiting-reader` under pipefail is a latent 141; payload size is
not a safety argument; the only sound reasons a call site is safe are structural
(no pipefail, reader provably reads to EOF, or the status is discarded) — and
the same correction is applied to the three other comments that leaned on size
(`ols_running` x2, fpm-parity-check.sh's pre-flight). Nor is the reader's
implementation a defence: at 248 KB, mawk, gawk, `grep -q` and `head -1` all
gave 141 on 10/10, and these images already differ (mawk 1.3.4 vs gawk 5.2.1).
Comment-only; the test file's own section-4 output no longer contradicts the
prose next to it.

F2 — `<<<` added a writable-temp-dir precondition to the boot path. Above a
build-dependent size bash materialises a here-string as /tmp/sh-thd.XXXXXX
(measured switch: 65536 in this image's bash 5.2.21, and Debian's 5.2.15
switches between 4096 and 16384, where a ~40 KB `lsphp -i` WOULD spill). On a
bare assignment a temp file it cannot create is `set -e` killing PID 1 — the
exact failure this branch exists to remove, re-acquired from a different
direction and gated on which bash the base image ships. In cac-lsphp:f1f2f3
under `docker run --read-only`, same payload, same statement shape:

    OLD (here-string) : bash: cannot create temp file for here-document
                        -> exit 1, script dead
    NEW (pure bash)   : REACHED NEXT STATEMENT, SCAN=[…/mods-available/], exit 0

So the boot-critical sites — the three probe helpers in entrypoint-lsphp.sh and
the SCAN_DIR extraction in entrypoint-litespeed.sh — now match with `[[ ]]` and
parameter expansion, which allocate nothing. The non-boot sites keep their
here-strings and say why at the call site: `ols_running` in both OLS entrypoints
(`lswsctrl status` is under 100 bytes, orders below any spill threshold) and
fpm-parity-check.sh's `php-fpm -m` pre-flight (~1 KB, in a harness that has
already written a docroot and a pool config).

Matching semantics are preserved, not approximated: the anchored whole-line
grep becomes a glob over a subject wrapped in newlines at BOTH ends (so first
and unterminated-last lines still match), and awk's `-F'=> ' {print $2; exit}`
becomes first-matching-line then the text between the FIRST and SECOND
separator. Section 6 of the test asserts that against the original grep/awk
patterns reading a FILE — 24 cases incl. trailing-space, prefix decoys, CRLF,
a second separator, an empty value, two candidate lines, glob metacharacters in
the body, and the full 40 KB fixture. Mutations verify the assertions bite:
dropping the trailing-newline wrap fails 3 cases, taking the whole rest of the
line fails "second separator", `##` instead of `#` fails "first of two wins",
dropping the `^` anchor on the banner fails "banner not at line start".

F3 — the structural scan missed shapes it implied it caught, and the extractor
was unbounded.

  * `grep -l`/`-L`/`--quiet`/`--files-with-matches`, `-im1`-style clusters, a
    bare `head` before `;`, and `sed q` / `sed 'q'` / `sed 2q` / `sed '$q'` were
    all invisible. grep is now walked option by option the way grep reads them
    (so `grep -eq foo` stays the pattern "q", not --quiet), and the sed test
    reads the script with quote characters stripped but their contents kept.
    Replaying the old regexes against the new fixtures: 10 shapes missed and 2
    false positives (`sed s/a/q/`, `grep -eq foo`) — both now correct.
  * new section 7 pins that coverage from both sides: 19 early-exit shapes must
    be reported, 18 read-to-EOF / quoted / non-pipeline forms must not. Without
    it the scan's regexes are unfalsified and can quietly stop matching, which
    is precisely how `grep -l` and `sed q` stayed missing.
  * the helper extraction is bounded. It buffers and emits nothing until it has
    seen the END marker (exit 4 = BEGIN without END, exit 3 = no markers), so a
    half-deleted pair is a marker error instead of a slurp. Measured on this
    entrypoint with the END marker removed: the old extractor produced 301 lines
    including `mkdir -p "$SCAN_DIR"` and three `rm -f "$SCAN_DIR/…"` — which the
    harness then sourced and ran. It failed loudly last time only because `set
    -u` happened to trip two statements in. The new one emits 0 bytes and says
    what is wrong.
  * the stated scope limits now include what remains: the reader list is an
    enumeration, not a proof (nothing knows about `perl -ne … last`, `jq`,
    `head -c`), and only the first word after a pipe is inspected.

Verified: PHP 8.3 `--no-cache` build exit 0, 10/10 .phpt; cac-lsphp boots and
logs `path parity = extension` with `Rewriting => active` and .from/.to
populated from the rendered ini; cac-litespeed boots, resolves SCAN_DIR and
writes 99-user-error-log.ini, OLS reports "running with PID", /healthz 200. The
FPM parity harness — never executed by the previous review because no cac-fpm
image existed locally — was built (Dockerfile.fpm, PHPVER=83), the extension
compiled inside it, and it reports 9/9 ALL PASS, exit 0. The new test exits 0
here and exits 1 against a `git archive 9343a56` export naming all 9 offending
lines. `bash -n` clean repo-wide; `shellcheck -S warning` clean on the CI set;
`-S style` is byte-identical to before this commit (5 pre-existing info-level
findings, 0 added — the earlier report's claim of `-S style` clean was wrong).
No `.c`/`.h` file touched and the C fail-open invariant grep is still empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:03:08 -07:00
shadowdaoandClaude Opus 5 584099ff19 docs: record boot-posture and CI-gating decisions
Two judgement calls arising from the pipefail/SIGPIPE fix, reasoned through
and written up rather than decided silently:

1. Boot posture when 'lsphp -i' genuinely fails. Recommendation is fail-open
   plus a machine-detectable degraded marker and one probe retry, NOT
   fail-closed: a transient fork() failure during this fleet's backup windows
   produces the identical empty-LSPHP_INFO signature, so fail-closed would
   refuse to boot healthy sites under host pressure. Includes the caution
   that degraded must not map onto Docker 'unhealthy', or a watchdog would
   restart-loop a serving site over something restarts cannot fix.

   Corrects a premise the fix rested on: the two entrypoints were NOT already
   consistent — entrypoint-litespeed.sh fails open silently, with no warning.

2. Whether the new static check should gate releases. Recommendation is to
   hard-gate, but only after back-testing the job against trunk and recent
   tags to bound false positives.

Recommendations only; no implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:43:18 -07:00
shadowdaoandClaude Opus 5 8790b027a9 fix(lsphp): stop SIGPIPE+pipefail reporting the parity extension as missing
`entrypoint-lsphp.sh` decided whether cac_path_parity was loaded with

    printf '%s\n' "$LSPHP_INFO" | grep -q '^cac_path_parity support => enabled$'

under `set -euo pipefail`. `grep -q` exits on its first match; printf is still
writing the remaining ~40 KB of `lsphp -i`, takes SIGPIPE, exits 141, and
pipefail prefers 141 over grep's 0. The branch therefore evaluated FALSE
*because the extension was present* — present early enough to stop the reader —
and every affected container fell back to the auto_prepend normaliser that a
customer's own .user.ini silently displaces, i.e. the exact failure the
extension exists to remove. Measured on whp02 against the published
cac-lsphp:php83: 5/5 runs status=141 with pipefail, 0 without.

The race is decided by pipe capacity, which is why it reproduced on whp02 and
not on other daemons: while the payload fits the pipe the writer never blocks
and always finishes first. Forced over the limit it is deterministic — 3x the
same `lsphp -i` (122100 bytes) gives 141 every time in the built image.

Fixed by reading with here-strings, which are not pipelines at all, so there is
no second exit status for pipefail to adopt. Same grep/awk patterns; plumbing
only. Same class fixed everywhere it existed under pipefail:

  * entrypoint-lsphp.sh      parity probe, and the SCAN_DIR awk probe
  * entrypoint-litespeed.sh  SCAN_DIR probe (a bare assignment: 141 there does
                             not degrade, `set -e` kills PID 1), and ols_running
  * entrypoint-shared-ols.sh ols_running
  * render-shared-ols-config.sh  site.meta parsing (`sed | head -1`): measured
                             141 at 6000 duplicate keys, which under `set -e`
                             aborts the whole render
  * fpm-parity-check.sh      the `php-fpm -m` pre-flight, whose whole job is to
                             stop a harness fault being blamed on the extension

Also: the fallback used to announce "cac_path_parity extension not loadable in
this image" for every reason the branch was reached, including its own plumbing
breaking — a false diagnosis that sends operators to rebuild a good image whose
build gate passed. Verdicts now carry the evidence they rest on, and a probe
that produced nothing is reported as a probe failure that establishes nothing
about the image. Fail-open posture is unchanged: no probe failure is fatal.

Adds scripts/tests/lsphp-info-probe.test.sh, which runs the shipped probes
(extracted verbatim, so they cannot drift from what runs in production) under
`set -euo pipefail` against a realistic ~40 KB phpinfo body, and statically
outlaws the shape repo-wide. Against trunk it fails, naming all 9 offending
lines. Wired into CI as a new Shell-Checks job, because no existing gate ever
executed the entrypoint's branch logic — the .phpt suite and the Dockerfile's
own `lsphp -i | grep -q` probe (which has no pipefail) were both green for the
release whose entrypoint declared that same extension missing.

Verified: PHP 8.3 --no-cache build green, 10/10 .phpt, 9/9 FPM harness; the
built image logs `path parity = extension` and reports `Rewriting => active`
with .from/.to populated; ext-removed and probe-broken variants each produce
their own honest message and still start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:23:56 -07:00
shadowdaoandClaude Opus 5 9343a56ccf Merge branch 'fix/lsphp-parity-hardening'
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m6s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m24s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m26s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m29s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m7s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m25s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 59s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 1m2s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 1m4s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 33s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 30s
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m35s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m23s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m30s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 4m40s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m25s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m28s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 33s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 32s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m1s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 1m0s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m23s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m22s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 32s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 34s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 33s
Hardening for the cac-path-parity PHP extension, which moves
cac-lsphp-normalize.php out of userland so the php-fpm -> ols/lsphp switch
is clean.

- pin the runtime PHP to the headers the extension was built against
  (the reverse, pinning -dev to the runtime, is unsatisfiable: the
  LiteSpeed repo carries only the current release)
- close an ini-injection hole in the entrypoint heredoc (quoting plus a
  charset check, since quoting alone does not stop php.ini ${VAR}
  interpolation)
- make the FPM parity harness actually runnable (it exited 0 while
  verifying nothing) and add the .phpt suite as a build gate
- guard to="/" and non-absolute mappings; both narrow what runs, neither
  adds an error path
- fix PHP_MINFO reporting 'active' for an inert mapping — that signal is
  what the post-deploy canary reads

Reviewed twice; RINIT verified byte-identical at the instruction level
across the predicate refactor. Fail-open invariant intact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:09:53 -07:00
shadowdaoandClaude Opus 5 3047123f2b docs(lsphp): correct three comments that overstated what the code does
Review found all three describing behaviour the code does not have:

- The range bound does NOT prevent opcache's shared-memory startup failure.
  With 99-prod-overrides setting interned_strings_buffer=16, a
  memory_consumption of 8 or 16 is accepted here and still aborts opcache.
  Documented rather than raising the floor, which would forfeit the superset
  property.
- memory_consumption's 4096 ceiling is ours, not PHP's — PHP imposes no upper
  bound on that directive. Only the max_accelerated_files range is a vendor
  clamp. Also records that an out-of-range value resets to PHP's COMPILED
  default, discarding the image's own override.
- The stale-fragment rm -f is defensive, not a bug fix: changing these env vars
  requires a recreate, which starts from a fresh layer, so the scenario the
  comment described is not reachable via docker restart.

Comments only; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:08:09 -07:00