6a11fc989472f01f824f7f037490668aeba05511
66
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 ( |
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
07378506a7 |
harden(cac-lsphp): close the two remaining unvetted ini emissions; stop MINFO lying
Three non-blocking findings from the re-review of this branch. No design change:
the extension's fail-open-absolute invariant is untouched (still zero error
emitters, every RINIT return is SUCCESS) and both RINIT guards stay pure
narrowing.
1. entrypoint-lsphp.sh: 99-user-opcache.ini was still emitted unquoted
-------------------------------------------------------------------
Twenty-five lines below the mapping fix, the opcache override block
interpolated the raw env into an unquoted `echo` — the same injection class
the mapping fix closed. Measured on this branch's image, before this commit:
OPCACHE_MEMORY_MB=$'128\nprecision = 7\n; '
-> 99-user-opcache.ini gained a `precision = 7` line
-> lsphp -i reported precision => 7 => 7
WHP casts (int) and clamps 32-512 / 2000-32000 (site-pool-env.php), so this
is not exploitable today — but "the panel validates it" is precisely the
argument this branch already rejected for `domain`, and the panel is a
different repo on a different release cadence. Both siblings in the block are
now validated at the point of use (digits only, length-capped, range-checked)
and emitted double-quoted. A rejected value is dropped with a WARNING and the
image default applies; nothing here is ever fatal.
The accepted ranges are PHP's own limits for these directives (>= 8 MB;
[200, 1000000] files), deliberately a strict SUPERSET of the panel's clamps,
so widening a panel clamp later cannot start silently rejecting real sites.
The block now also removes a stale fragment when it has nothing valid to
write: the container filesystem outlives `docker restart`, so without that an
override that is later cleared — or rejected — would keep applying from the
previous boot's file.
2. 99-user-error-log.ini was written from an unvetted $user
--------------------------------------------------------
It was emitted before the INI_TOKENS_OK branch. Contained in practice (a
newline is inert inside the quotes, and a `${`-bearing user cannot exist
because useradd would have failed under `set -euo pipefail`), but "this
particular unvetted value happens to be contained" is the reasoning this
branch rejected one screenful up. Now gated identically.
Costs a rejected user nothing it needs: `log_errors = On` is already baked in
by 99-prod-overrides.ini, so PHP still logs — to stderr, i.e. `docker logs`,
which is more visible than a per-site file, not less. Verified fleet-wide
that no legitimate user reaches the branch (30 shared_ols sites, 4 hosts).
3. MINFO reported "active" for mappings RINIT ignores
---------------------------------------------------
The absolute-path guard was added to RINIT and MINFO kept testing only "both
values non-empty", so:
from=mnt/users/bob/site.com (relative -> INERT since the guard landed)
lsphp -i -> Rewriting => active
That row is what the post-deploy fleet canary greps to confirm parity is live,
so the diagnostic would have masked exactly the failure the canary exists to
find — and the C comment added by this branch documents it as the only runtime
signal. RINIT and MINFO now share one predicate pair
(cacpp_mapping_configured / cacpp_mapping_active) rather than two longhand
copies, which is what drifted. MINFO now distinguishes "inactive (mapping not
absolute)" from "inactive (unconfigured)" — different operational problems.
Pure reporting change: the predicates are side-effect-free and cannot fail, so
MINFO gains no error path.
Tests: two new .phpt cover both directions of the MINFO fix (009 relative
mapping must report inactive, 010 well-formed mapping must still report active),
so tightening it cannot overshoot into the opposite lie. The build gate's
EXPECTED count is derived from `ls tests/*.phpt`, so it picked them up: 10/10.
Non-vacuity, all five demonstrated by mutation:
- opcache quoting reverted -> injection lands, `precision => 7` observed
- error-log gate removed -> fragment written from the unvetted user
- MINFO reverted to non-empty -> 009 FAILS, build gate exits 1
- MINFO forced always-inactive -> 010 FAILS, build gate exits 1
- all restored -> 10/10, build exit 0
|
||
|
|
690ff8738d |
fix(cac-lsphp): stop generating php.ini fragments from unquoted interpolation
The two generated ini drop-ins interpolated $user/$domain into an unquoted
heredoc. Measured against the pre-fix script in a real cac-lsphp:php83 container
with domain=$'evil.com\nprecision = 7\n; ':
99-cac-path-parity.ini contained the injected line and lsphp reported
`precision => 7 => 7` — an arbitrary ini directive supplied through the domain
env var and applied to every request. The `from` value was silently truncated
at the newline too, so the site also got a wrong (but "active") mapping.
Both values are panel-validated and both already feed `ln -sfn` and the
shared-ols vhost config, so this is defense-in-depth rather than a live hole. It
is worth closing anyway because the OTHER two hostile inputs the reviewer
measured — `$(...)` (ini parse error) and `"` (empty value) — leave the parity
extension INERT, which is precisely the silent failure this whole change set
exists to eliminate.
Two layers, neither of which can fatal a request:
- values are emitted double-quoted via printf instead of heredoc
interpolation. php.ini double-quoted values may span newlines, so a newline
is data, not a new directive.
- $user/$SAFE_DOMAIN are checked against [A-Za-z0-9._-]+ first, because
quoting does NOT stop php.ini's own ${VAR} interpolation. A rejected value
logs a WARNING, writes no mapping at all (not even the degraded
auto_prepend fallback, which would not be right for such a site either) and
reports `path parity = none (user/domain rejected)` on the startup line.
After: same container, same hostile domain — no 99-cac-path-parity.ini is
written, `precision => 14` (default), and the warning names the rejected values.
Happy path re-verified for domain=site.com and domain=*.site.com: mapping
written, `Rewriting => active`, from/to parse back byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
da16faaff5 |
feat(cac-lsphp): guarantee $_SERVER path parity via a PHP extension
A site moved from cac-fpm to cac-lsphp must see byte-identical $_SERVER['DOCUMENT_ROOT'] and ['SCRIPT_FILENAME'] (/home/<user>/...). The auto_prepend_file normaliser that did this was PHP_INI_PERDIR, so any site with its own .user.ini auto_prepend_file silently displaced it — the state 7 live shared_ols sites (Wordfence, cPanel imports) are actually in. Hardening the hook was not an option either: making our prepend win would have disabled those Wordfence WAFs. Replace it with cac_path_parity, a small PHP extension that rewrites the filesystem-path $_SERVER keys from RINIT. RINIT cannot be displaced by .user.ini, and it occupies no userland hook, so the customer's own auto_prepend_file stays the only prepend in play and keeps working. The mapping lives in two PHP_INI_SYSTEM settings, which .user.ini (PERDIR / USER only) and ini_set() cannot reach. Mechanism is a path-component-bounded string prefix swap, not realpath(): byte-identical to cac-fpm by construction (realpath would resolve a customer's own symlinked public_html to some third path), no syscall, and no failure path. Every guard fails open and leaves $_SERVER untouched; nothing here can warn, throw or 500 a site. Unconfigured it is fully inert, so cac-fpm and cac-litespeed are unaffected. Built in a separate Dockerfile stage keyed off the existing ARG PHPVER — gcc/phpize/headers never reach the shipped image (verified absent; the image grows ~155kB), and a base-image PHP bump recompiles with no human step. A `lsphp -i | grep` assertion fails the build if the .so does not load, so an image can never ship having silently lost parity. The entrypoint selects the extension when present and removes any stale prepend ini left by an older image; if the extension is somehow not loadable it falls back to the old normaliser and logs a WARNING rather than losing normalisation entirely. It also now logs the active parity mode, and warns when lsphp reports no ini scan dir (previously silent). Probe lsphp with `-i` only: it is the LSAPI SAPI, not the CLI, and answers `-m`/`-r` by printing usage and exiting 0 — a `lsphp -m | grep` check never matches and never errors, which is the exact class of silent always-false assertion this change exists to remove. Verified: 6 .phpt tests; tests/fpm-parity-check.sh proves under the FPM SAPI that with a customer .user.ini auto_prepend_file present both keys are still corrected AND the customer's prepend still runs, and that the old mechanism does not; and in a real built cac-lsphp:php83 container that SCRIPT_FILENAME is rewritten, the customer prepend still fires, and another tenant's path is left untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
433e74975c |
fix(cac-lsphp): enable .user.ini support (LSPHP_ENABLE_USER_INI)
php-lsapi compiles .user.ini support in but leaves it DISABLED by default.
sapi/litespeed/lsapi_main.c has `static int parse_user_ini = 0;` and only
sets it when the process environment contains LSPHP_ENABLE_USER_INI=on.
WHP never set it, so lsphp never entered the user-ini chain at all.
The failure was silent: phpinfo() still reports user_ini.filename=.user.ini
and user_ini.cache_ttl=300, because those are core INI defaults that are
simply inert under this SAPI. Every other WHP PHP tier (cac, cac-fpm,
cac-litespeed) honors .user.ini, so shared_ols was quietly inconsistent.
Impact found in production (whp01/whp02/sdbees/TrueSelfCA, 29 sites):
- Per-site memory_limit / max_input_vars overrides were ignored. A Divi
site's max_input_vars stayed at the 2000 default while its .user.ini
asked for 20000.
- Wordfence's auto_prepend_file WAF never loaded on ANY shared_ols site.
11 sites had the plugin installed and reporting "Extended Protection"
enabled while the prepend was never executed.
Verified on a live sidecar (shadowdao.com, whp01) before this commit by
injecting the env var via whp.container_types.startup_env and recreating:
before: auto_prepend_file=/scripts/cac-lsphp-normalize.php (WAF absent)
after: auto_prepend_file=/home/shadowdao/public_html/wordfence-waf.php
wordfence-waf.php present in get_included_files()
class_exists('wfWAF') === true
The platform normalize prepend still chains in behind Wordfence's bootstrap,
so DOCUMENT_ROOT canonicalisation is not lost.
Set in two places on purpose: the Dockerfile ENV makes the value visible in
`docker inspect` and survives an entrypoint override, and the entrypoint
re-exports it with the same default because the runuser fallback exec path
resets the environment. Still overridable per-container
(LSPHP_ENABLE_USER_INI=off) as an escape hatch for a site whose legacy
cPanel-generated .user.ini has not been remediated yet.
NOTE: enabling this activates every previously-inert .user.ini at once.
Audit the fleet for stale cPanel directives before rolling this image —
session.save_path values under /var/cpanel/ that do not exist in the
container, memory_limit above the cgroup cap, and upload_max_filesize
values below the platform default were all found and remediated first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
08f35032c5 |
fix(shared-ols): re-review hardening — bounded flock + stale-tmp sweep
Follow-up to the review fixes, from a second review pass: - flock now uses -w 30 (bounded wait) so a hung render can't block the panel's docker-exec (and the site-save request) indefinitely; the dead-code timeout error path is now reachable. - sweep stale .httpd_config.conf.tmp.* left by a prior SIGKILL (trap EXIT doesn't run on SIGKILL); safe under flock since each render uses a unique $$ suffix. Verified: render still produces a valid config + serves; stale tmp is swept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6bb494c72f |
fix(shared-ols): review fixes — watcher starvation, atomic render, O(N) chown, safe meta parse
Addresses the local code-review on the OLS-tier images: - [HIGH] ols-htaccess-watcher.sh: the debounce drain read ALL inotify events unfiltered, so on a busy multi-tenant server it never timed out and the restart was STARVED (rewrite changes silently never applied). Now coalesces with a hard DEBOUNCE-bounded window. Verified under continuous noise. - [HIGH] render-shared-ols-config.sh: built httpd_config.conf in-place across several appends, so a concurrent OLS restart (watcher) or parallel render could read a half-written config and 503 the whole tier. Now flock-serialized, built in a temp file and atomically moved into place; refuses to publish empty. - [MED] render + entrypoint: replaced recursive chown of the whole conf tree (O(N-sites) on every single-site change / boot) with a targeted chown of just the file written. - [MED] render: parse site.meta with sed instead of sourcing it (do not execute panel-written data as shell). - [cleanup] removed the unused configs/shared-ols/vhconf.tpl (the panel copy is the single source; the image never read it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7552760ba0 |
fix(cac-lsphp): normalize $_SERVER DOCUMENT_ROOT/SCRIPT_FILENAME to /home
The symlink makes __FILE__/__DIR__/realpath/getcwd report /home/<user>/public_html (WordPress/frameworks), but $_SERVER['DOCUMENT_ROOT']/['SCRIPT_FILENAME'] are raw env vars OLS sets to its /mnt/users view — apps that build/compare paths from them would see /mnt/users. Added a tiny auto_prepend (cac-lsphp-normalize.php, wired via a scan-dir ini) that realpath-canonicalises those two back to /home. Customer sites have no auto_prepend by default, so no conflict. Verified clean-room (committed image, fresh boot): DOCUMENT_ROOT and SCRIPT_FILENAME both report /home/<user>/public_html through the shared OLS. Now byte-for-byte 1:1 with cac-fpm/cac-litespeed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fc65b68bd6 |
fix(cac-lsphp): mount docroot at /home/$user + symlink for true 1:1 compatibility
Customer concern: sites with /home/<user>/public_html baked into config or the DB must keep working — a changed in-container docroot path would break WordPress ABSPATH, hardcoded includes, cached absolute paths, etc., making the upgrade a non-drop-in. Fix: the sidecar now mounts the docroot at /home/$user (IDENTICAL to cac-fpm/cac-litespeed) and the entrypoint symlinks /mnt/users/<user>/<domain> -> /home/$user. OLS still serves from its bulk /mnt/users mount and sends lsphp that path (no remap available), but the symlink resolves it to the real /home/$user files AND PHP canonicalises it — so __FILE__/__DIR__/realpath/ABSPATH all report /home/<user>/public_html. Verified end-to-end through the shared OLS: a request reports __FILE__=/home/homeuser/public_html/probe.php, ABSPATH=/home/homeuser/public_html/, and stored /home paths resolve. True 1:1 drop-in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e99b8cb2d1 |
fix(cac-lsphp): entrypoint operates on the /mnt/users docroot, not /home/$user
Code-review integration fixes: - entrypoint-lsphp.sh: the shared-ols tier mounts the docroot at /mnt/users/<user>/<domain> (NOT /home/$user). Discover the mount via glob (one site per sidecar; wildcard-safe), create public_html + logs/php-fpm under it (so OLS docRoot exists), point lsphp error_log there, and chown just those dirs. Verified: sidecar creates public_html under the mount, runs as the per-site user, OLS serves PHP (SAPI=litespeed) end-to-end. - shared-ols vhconf.tpl: per-vhost logs -> /usr/local/lsws/logs/<vhname>.* (the shared-ols container has no /home/<user>). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
19db8f170a |
feat(shared-ols): shared OpenLiteSpeed tier image (webserver-only, fronts cac-lsphp sidecars)
One OLS container fronting many tenants' detached cac-lsphp sidecars — the
OLS analogue of shared-httpd. Runs NO PHP locally; every site's PHP goes to
its own sidecar over LSAPI (extProcessor type lsapi, address <sidecar>:9000).
Key design fact (established by PoC): OLS has NO top-level 'include' directive,
so render-shared-ols-config.sh assembles httpd_config.conf from the panel's
per-site files (vhconf.conf + site.meta) at boot and on every change — the
'include' OLS lacks. Per-site detail uses the OLS-native configFile +
vhost-scoped extprocessor model. LSCache is module-level (a configFile-loaded
vhost rejects a bare cache{} block); the WP LiteSpeed plugin controls
cacheability via X-LiteSpeed-Cache-Control headers.
- Dockerfile.shared-ols: litespeed base + inotify-tools/envsubst/openssl,
admin bound to loopback, :80/:443 self-signed, healthz HEALTHCHECK.
- entrypoint-shared-ols.sh: cert + health vhost + render + watcher, then
daemon-mode OLS supervision (reused from cac-litespeed so self-restarts
don't kill PID 1).
- render-shared-ols-config.sh: strip stock (incl local lsphp) + append base +
per-site stanzas + listeners with all maps + catch-all health vhost.
- ols-htaccess-watcher.sh: inotify debounce+floor -> lswsctrl restart (spec 5.3).
- configs/shared-ols/{httpd_config_base,vhconf}.tpl.
- CI: Build-Shared-OLS job.
Verified locally end-to-end: zero-site boot healthy on :443; add site via the
panel contract -> Host-routed to the right sidecar (SAPI=litespeed); real
client IP + HTTPS behind X-Forwarded headers; LSCache miss->hit; .htaccess
change triggers graceful restart; unknown Host hits health catch-all (200).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
19092911a3 |
feat(cac-lsphp): detached lsphp (LSAPI) site image for the shared-ols tier
New slim per-site PHP backend that runs 'lsphp -b 0.0.0.0:9000' (detached LSAPI) and nothing else — the LiteSpeed analogue of cac-fpm, sitting behind a shared OpenLiteSpeed container. Built on the same litespeedtech prebuilt base as cac-litespeed so the lsphp runtime/extensions are identical. - Dockerfile.lsphp: base + lsphpNN-ldap parity, reuses shared lsphp-overrides.ini, exposes only :9000, no webserver started (guaranteed by entrypoint, not by stripping OLS binaries). - entrypoint-lsphp.sh: same uid/user contract + /home/$user/logs layout + ini drop-in mechanism as entrypoint-litespeed.sh; sizes PHP_LSAPI_CHILDREN from container memory (detect-memory-lsphp.sh) with panel override precedence; execs lsphp -b as the per-site user via setpriv (PID 1). - detect-memory-lsphp.sh: LSAPI_CHILDREN sizing, no OLS daemon reserve. - healthcheck-lsphp.sh: TCP :9000 + lsphp-alive (LSAPI isn't FastCGI). - CI: Build-LSPHP-Images job, php81-85 matrix, OLS 1.8.4, cac-lsphp:phpNN. Verified locally: builds php83+php85; sidecar runs lsphp as the per-site user (uid 61045) as PID 1, healthcheck green, and a real shared OLS in front serves PHP over LSAPI (HTTP 200, SAPI=litespeed) with identical docroot path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
50202538e4 |
cac-litespeed: supervise OLS in daemon mode so self-restarts don't kill PID 1
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m24s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m17s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m25s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m21s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m15s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m18s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m33s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m24s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 30s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 31s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 31s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 32s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m23s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m15s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m19s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 28s
cac-litespeed containers were dying at random intervals and staying 503 until manually restarted. Root-caused on whp02 (alsacorp, 2026-06-06): the LiteSpeed Cache / QUIC.cloud integration refreshes the QUIC.cloud IP allowlist on a schedule and, when it changes, sends SIGUSR1 → "request a graceful server restart". The entrypoint ran `openlitespeed -n & wait "$OLS_PID"`, so when the OLD main PID exited after the zero-downtime handoff, `wait` returned, PID 1 (bash) exited, and the whole container went down. The exit was clean (code 0), so even a restart policy wouldn't reliably catch it — HAProxy just served 503 until someone ran `docker start`. Replace the `-n` foreground+wait model with a daemon-mode supervisor: start OLS via `lswsctrl start` (its native model, where it owns the SIGUSR1 handoff and keeps listeners bound across generations) and have PID 1 follow `lswsctrl status`. A graceful self-restart is now invisible here (verified zero-downtime); PID 1 only relaunches on a genuine crash (no live main), with a 5-in-60s crash-loop cap that bails out to Docker's restart policy / the site monitor. SIGTERM still drains and exits cleanly for docker stop / recreate. Verified on a scratch php85 container: survives `lswsctrl restart`, survives a raw SIGUSR1 to the main (the exact QUIC.cloud path that used to kill it), relaunches after `kill -9` of the main, and stops cleanly in ~6s on docker stop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cfdaae116a |
tune(litespeed): bump opcache 32→64 MB / 4000→8000 files + add per-site override
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m37s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m42s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m50s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m51s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 3m18s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m21s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 3m49s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m44s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m30s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m40s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m15s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 29s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m0s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m48s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m58s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 26s
32M/4000 was too aggressive for heavy WP+Divi+WC sites: 3000+4000 unique PHP files each blow through max_accelerated_files, causing constant eviction + recompilation thrash. Manifested 2026-06-03 as ~40% sustained CPU on alphaoneaminos and 5378 oom_kills/9h on brain-jar. 64M/8000 fits Divi + WC + WP core bytecode without eviction. N lsphp × 64 MB ≈ 512 MiB shmem worst case — still under the per-instance setUIDMode fan-out from the original 128M problem (which was 1+ GiB). Per-site override (OPCACHE_MEMORY_MB / OPCACHE_MAX_FILES env vars) lets the panel push down for low-traffic sites or up for outliers without rebuilding the image. WHP panel UI ships in a follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
87f154cdc8 |
refactor(litespeed): drop setUIDMode for shared lsphp + cut opcache 128→32M
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m19s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m35s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m16s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m29s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m2s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m15s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m22s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m14s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m6s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 3m20s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m41s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 43s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m16s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 56s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 2m2s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m30s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m20s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m19s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 51s
OLS runs as the customer user end-to-end (server-level user/group set by create-vhost-litespeed.sh), so lsphp inherits that uid without per-request suEXEC. Eliminates the per-httpd-worker lsphp instance fan-out — one shared lsphp parent now serves all httpd workers via the shared socket. Combined with opcache.memory_consumption 128→32M, brain-jar measured shmem dropped from ~880 MiB → 32 MiB and memory.current from ~1.1 GiB → 67 MiB at the 1.5 GiB cap. No new oom_kills since the change. Safe because cac-litespeed is one-customer-per-container — the container boundary is the privsep boundary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f463519998 |
tune(litespeed): bump LSPHP_WORKER_ESTIMATE_MB 115 → 130
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m33s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m24s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m8s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m23s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m21s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m23s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m21s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m14s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 3m26s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m22s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m28s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 1m30s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 39s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 1m12s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 30s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 30s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m16s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m1s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 1m30s
115 was set from idle-state per-worker memory. Active workers on heavy WP/Divi grow to ~130-150 MB (shmem + anon + file), and the 115 formula gave brain-jar.com CHILDREN=8 at 1 GiB — which produced 142 OOM-kills overnight because there was zero headroom once page renders started. 130 backs off slightly on the bigger sites: 512 MiB: 3 workers (unchanged) 1 GiB: 7 workers (was 8 — brain-jar's failure point) 1.5 GiB: 11 workers (was 12) 2 GiB: 15 workers (was 17) 4 GiB: 30 workers (was 33) Per-site FPM_MAX_CHILDREN override still wins for sites that need tighter caps regardless of formula default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
03cca745f7 |
feat(litespeed): wire up dynamic LSAPI tuning + idle reduction
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m18s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m14s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 3m21s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m18s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m15s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m11s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m22s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 3m46s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m21s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m15s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 3m29s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 31s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 31s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 30s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 32s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 31s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 4m22s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m21s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 1m33s
Two correctness fixes and a tuning improvement.
CORRECTNESS:
1. Strip the stock 'extProcessor lsphp' from httpd_config.conf before
appending ours. Previously the stock block (hard-coded
PHP_LSAPI_CHILDREN=10 regardless of container memory) always won
because our APPEND fragment didn't include an extProcessor block.
detect-memory-litespeed.sh was computing LSAPI_CHILDREN but never
plumbing it anywhere — silent dead code.
2. Bump LSPHP_WORKER_ESTIMATE_MB from 96 → 115 per the 2026-06-02
memory-sizing finding (vantagehealth OOM-spawn loop). Each lsphp
carries ~115 MB shmem-rss accounted per worker. 115 MB matches the
real per-worker baseline.
TUNING (idle reduction, the original ask):
- LSAPI_MAX_IDLE_CHILDREN=2 (was CHILDREN/2 = 5 default)
- LSAPI_MAX_IDLE=60s (was 300s default)
- PHP_LSAPI_MAX_REQUESTS=500 (recycle workers, prevents bloat)
- memSoftLimit=1024M / memHardLimit=1500M per worker (RLIMIT_AS;
catches runaway scripts at the worker level, cgroup still backstops
the container)
Effective LSAPI_CHILDREN per container:
2 GiB → ~17 (was 10 — brain-jar was saturating)
1 GiB → ~8
512 MiB → ~3 (cap-marginal per the memory note; bump container if
site grows)
Dropped LSAPI_MEM_SOFT/HARD computation in detect-memory: AVAILABLE/CHILDREN
was conflating VSZ with RSS-budget arithmetic and would have killed
legitimate workers. The 1024/1500 hard-coded values in the template
comfortably fit typical Divi/WooCommerce VSZ (280-365 MB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d1c3cfadc0 |
feat(litespeed): make log paths drop-in compatible with cac:phpNN
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m35s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m20s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m18s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m13s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m21s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m22s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m19s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m25s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m26s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m15s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m27s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 30s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 33s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m27s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m14s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m15s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m58s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 24s
OLS now writes: access -> /home/$user/logs/apache/access_log error -> /home/$user/logs/apache/error_log PHP -> /home/$user/logs/php-fpm/error.log Matches the cac:phpNN bundled image convention exactly, so existing WHP log-gathering code (whp-traffic-aggregator.php, process-log-review.php) works for migrated sites without any panel-side changes. Customer-facing paths are stable across migrations — "where do I find my access log?" gets the same answer regardless of image family. Server-level OLS logs (/usr/local/lsws/logs/) are unchanged — those are internal diagnostics, not customer-relevant. PHP error_log is set via a runtime-rendered tiny ini in lsphp's scan dir (can't be in the static lsphp-overrides.ini because the path is per-customer). Customers on the four whp01 migrations (alphaone, peptides, shadowdao, brain-jar) need a container recreate after CI publishes the new tags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
80fa06592b |
perf(litespeed): defer mariadb-server + memcached install to DEV runtime
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m23s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m58s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m0s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m14s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m12s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m24s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m41s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 3m33s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m17s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m16s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 1m19s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 46s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 31s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 1m26s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 52s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m44s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 2m18s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m21s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 58s
Drops these from the build-time apt install in Dockerfile.litespeed; they now install at entrypoint time only when environment=DEV, guarded by 'command -v mysqld' so container restarts skip the apt step. Mirrors the cac:phpNN pattern. The mysql CLI client is already in the litespeedtech/openlitespeed base, so wp-cli + DEV creds-bootstrap still work without a build-time client install. Measured (php83 / OLS 1.8.4): PROD image: 1.64 GB -> 1.20 GB (~440 MB savings) PROD first-200 boot: unchanged at ~1.5s DEV first boot: ~51s (apt install cost — one-time per container) DEV second boot: ~6s (cache hit, same as PROD) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
55c28a0c11 |
Add cac-litespeed image family (OpenLiteSpeed, native LSAPI)
New paid-tier per-customer image built on litespeedtech/openlitespeed:1.8.4-lsphpNN.
Matrix: 8.1-8.5. Native LSAPI suexec to customer uid, server-level LSCache,
all WP/WooCommerce extensions (memcached, redis, imagick, mbstring, etc.) baked in.
Files:
- Dockerfile.litespeed (FROM prebuilt LiteSpeed base, layers wp-cli/composer/mariadb)
- configs/litespeed/{httpd_config,site-template,lsphp-overrides}.tpl
- scripts/{entrypoint,create-vhost,detect-memory}-litespeed.sh + install-lscache-wp.sh
CI: new Build-LiteSpeed-Images matrix job. OLS_VERSION pinned to 1.8.4 (only
release with prebuilt images for all 5 PHP versions on Docker Hub).
Spec: whp/docs/superpowers/specs/2026-06-01-cac-litespeed-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1756d496e5 |
detect-memory: raise PHP_WORKER_ESTIMATE_MB default 60→128
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m20s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m15s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m19s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m17s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m25s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m14s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m21s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m15s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m15s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m23s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 27s
The 60 MB worker estimate was optimistic for plugin-heavy WordPress
and WooCommerce stacks. Concrete measurement on alphaone 2026-06-01:
Container memory : 1024 MiB (later 2048 MiB)
Pool sized by formula : pm.max_children = (1024-100)/60 = 15
Actual per-worker RSS : ~193 MB (anon+file+shmem from kernel OOM dumps)
Worst-case peak : 15 × 193 MB ≈ 2.9 GB
That math put traffic-burst peak demand well over the container cap,
producing 1,586 cumulative oom_kills across alphaone's two containers
over 18 days and intermittent fork-starvation for unrelated tenants
on the host.
128 MB is a more realistic baseline: closer to actual WP+Woo+page-
builder worker footprint, still conservative enough that lighter
sites continue to get reasonable concurrency. The matrix at common
container tiers:
Tier (MiB) | old children | new children | new peak demand
256 | 2 (floored) | 2 (floored) | ~256 MB
512 | 6 | 3 | ~384 MB
768 | 11 | 5 | ~640 MB
1024 | 15 | 7 | ~896 MB
2048 | 15 (capped*) | 15 | ~1.9 GB
(* old formula returned 32 at 2 GiB but production containers were
booted at lower tiers and never recalculated; see whp01 audit.)
Existing containers keep their boot-time pm.max_children until they
are recreated — this change only affects new containers. Customers
or operators can override per-container via FPM_MAX_CHILDREN env.
|
||
|
|
e9604b8721 |
Fix shared httpd log tailing for dynamically added vhosts
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m25s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m23s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m22s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m21s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m21s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m19s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m20s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m15s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m14s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m19s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m33s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m18s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m18s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 1m22s
The entrypoint used 'tail -f /var/log/httpd/*' which expands the glob at startup. Log files created later (when new vhost configs are added) were never tailed, so 'docker logs' showed nothing for sites added after the container started. Replaced with a loop that re-discovers log files every 60 seconds and restarts tail to include new ones. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
e81b0df5b8 |
Reduce idle PHP-FPM memory footprint
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m7s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m16s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m13s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m23s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 3m31s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m2s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m23s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m51s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m4s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m21s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m6s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 26s
Opcache: - memory_consumption: 128MB → 64MB (most WordPress sites use <40MB) - max_accelerated_files: 10000 → 4000 (sufficient for WordPress) - revalidate_freq: 2s → 60s (reduce stat() calls in production) - enable_cli: Off (don't cache scripts run from command line) FPM workers: - process_idle_timeout: 10s → 5s (faster worker teardown when idle) - max_requests: 500 → 200 (recycle workers sooner to release leaked memory) These changes primarily reduce the baseline memory of idle containers where opcache was reserving 128MB even for small sites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
e20f5620d7 |
Fix DOCUMENT_ROOT for PHP-FPM in shared httpd mode
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m19s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m5s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 2m9s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m15s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m11s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m12s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m14s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m14s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m51s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m0s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m6s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m18s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m27s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m12s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 1m13s
WordPress plugins like WordFence use $_SERVER['DOCUMENT_ROOT'] to locate config/log files. With ProxyPassMatch, Apache sends its own mount path (/mnt/users/...) as DOCUMENT_ROOT, which doesn't exist in the FPM container. ProxyFCGISetEnvIf can't override DOCUMENT_ROOT when using ProxyPassMatch (Apache sets it after the directive evaluates). Instead, set it via the FPM pool config's env[] directive which takes precedence. create-php-config.sh now adds env[DOCUMENT_ROOT] = /home/$user/public_html when in TCP listen mode (shared httpd), giving PHP the correct path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
c78167871c |
Add shared httpd + PHP-FPM-only container architecture
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 3m14s
Cloud Apache Container / Build-and-Push (82) (push) Has been cancelled
Cloud Apache Container / Build-and-Push (83) (push) Has been cancelled
Cloud Apache Container / Build-and-Push (84) (push) Has been cancelled
Cloud Apache Container / Build-and-Push (85) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (80) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (81) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (83) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (85) (push) Has been cancelled
Cloud Apache Container / Build-and-Push (81) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (74) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (82) (push) Has been cancelled
Cloud Apache Container / Build-FPM-Images (84) (push) Has been cancelled
Cloud Apache Container / Build-Shared-httpd (push) Has been cancelled
Separate Apache and PHP-FPM into distinct container roles to reduce per-customer memory overhead on shared servers. Adds three new images: - Dockerfile.fpm: PHP-FPM only (no Apache), listens on TCP port 9000 - Dockerfile.shared-httpd: Apache only (no PHP), with SSL and proxy_fcgi - Existing Dockerfile unchanged for standalone mode Key changes: - detect-memory.sh: CONTAINER_ROLE env var (combined/fpm_only/httpd_only) controls the memory budget split - create-php-config.sh: FPM_LISTEN env var for TCP port vs Unix socket, added /fpm-ping and /fpm-status health endpoints - New entrypoints for each container role - tune-mpm.sh for hot-adjusting Apache MPM settings - shared-vhost-template.tpl with proxy_fcgi and SSL on port 443 - CI/CD builds all three image types in parallel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
87c4f2befc |
Optimize Apache & PHP-FPM memory for lower idle usage
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m31s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m54s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m51s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m52s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m39s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m58s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m51s
Switch PHP-FPM from pm=dynamic to pm=ondemand (zero idle workers), auto-detect container memory via cgroups to calculate appropriate limits, and generate Apache MPM config at runtime. All tuning values are now overridable via environment variables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
a153385d8f |
Adding support for PHP 8.5
Cloud Apache Container / Build-and-Push (74) (push) Successful in 2m12s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m46s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m47s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m44s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m47s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m46s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m47s
|
||
|
|
468bc7b088 |
Move user crontab to persistent home directory
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m52s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m48s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m45s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m54s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m50s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m48s
- Created user-specific crontab file at /home/$user/crontab - Crontab now persists through container restarts/refreshes - Users can manage their own cron jobs by editing their crontab file - Automatically loads user crontab on container start - Updated DEV environment to use user crontab for MySQL backups 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
92ed9885ec |
Remove php-ioncube-loader from PHP 8.1 to fix Composer installation
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m48s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m44s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m42s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m43s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m47s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m15s
The php-ioncube-loader package is incompatible with PHP 8.1 and was causing a segmentation fault (exit code 139) when the Composer installer tried to run PHP. This aligns PHP 8.1 with other PHP versions that already had ioncube-loader removed. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
3d903b437f |
Fix PHP error log path to use correct user directory
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m45s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m39s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m38s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m39s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m42s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m50s
PHP error logs were incorrectly being written to /etc/httpd/logs/error_log instead of the expected /home/$user/logs/php-fpm/ directory. Updated the php_admin_value[error_log] setting to point to the proper location. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
617fdbcd21 |
Add PostgreSQL support for all PHP versions
- Added postgresql-devel package to Dockerfile for client libraries - Added php-pgsql extension to all PHP versions (7.4, 8.0, 8.1, 8.2, 8.3, 8.4) - Enables PHP applications to connect to PostgreSQL databases 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> |