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>
This commit is contained in:
2026-06-10 01:22:14 -07:00
parent 19092911a3
commit 19db8f170a
7 changed files with 501 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
## ---- shared-ols append (do not edit below) ----
## Server-level config for the SHARED OpenLiteSpeed tier. Appended to the
## stock httpd_config.conf AFTER render-shared-ols-config.sh strips the stock
## listeners, vhTemplate docker, AND the stock `extProcessor lsphp` +
## `scriptHandler` (so this server NEVER runs PHP locally — every site's PHP
## goes to its own detached cac-lsphp sidecar over LSAPI). Rendered with
## envsubst; only ${LSCACHE_ROOT} is substituted here.
serverName shared-ols
## Real client IP behind HAProxy. HAProxy sets X-Forwarded-For (the real
## client) and X-Forwarded-Proto. Mode 2 = trust the proxy header. HAProxy is
## the only thing that ever connects to this tier (it's not publicly exposed),
## so trusting the header from the docker-network peer is safe — same trust
## model as the shared httpd's RemoteIPInternalProxy.
useIpInProxyHeader 2
## LSCache enabled at MODULE scope for the whole tier (dedicated cache volume,
## ephemeral across rebuilds; OLS auto-keys a per-vhost subdir under storagePath).
## enableCache/enablePrivateCache ON here means the cache module is ACTIVE, but a
## response is only cached if it's marked cacheable — the LiteSpeed Cache WP
## plugin sets X-LiteSpeed-Cache-Control headers, and checkPublic/PrivateCache +
## ignoreRespCacheCtrl=0 make OLS honor them. No plugin → nothing cached (safe).
module cache {
storagePath ${LSCACHE_ROOT}
checkPrivateCache 1
checkPublicCache 1
maxCacheObjSize 10000000
maxStaleAge 200
qsCache 1
reqCookieCache 1
respCookieCache 1
ignoreReqCacheCtrl 0
ignoreRespCacheCtrl 0
enableCache 1
enablePrivateCache 1
}
## ---- end shared-ols server append ----

View File

@@ -0,0 +1,70 @@
## Per-site OLS vhost detail — rendered by the WHP panel (shared_ols_manager)
## to $SITES_ROOT/<vhname>/vhconf.conf and referenced from the vhost stanza's
## `configFile` in httpd_config.conf. ~~PLACEHOLDERS~~ are filled by the panel
## (matches the shared-vhost-template.tpl convention). One directive per line —
## OLS PlainConf does NOT accept ';' separators.
##
## CRITICAL (feedback_ols_lsapi_no_script_filename_remap): docRoot here MUST be
## the SAME absolute path the cac-lsphp sidecar has mounted, because OLS hands
## lsphp exactly docRoot+URI as SCRIPT_FILENAME and lsphp opens it. Both are
## /mnt/users/<user>/<domain>/public_html. The panel asserts this parity.
docRoot ~~DOCROOT~~
enableScript 1
## Remote detached lsphp over LSAPI/TCP. address = the site's sidecar container
## on the docker network. autoStart 0 = OLS NEVER spawns it (it's a separate
## container). maxConns MUST equal the sidecar's PHP_LSAPI_CHILDREN — the panel
## writes both from the single fpm_max_children value so they can't drift.
## NO `env` lines: detached lsphp owns its env in the sidecar (spec 5.2).
## NOTE on `path`: required syntactically but UNUSED for a remote autoStart-0
## processor (OLS never spawns it). Point it at a path that always exists in the
## shared-ols image (the stock fcgi-bin/lsphp), NOT a version-specific
## /usr/local/lsws/lsphpNN — the shared-ols image carries only one lsphp build,
## while sites may run any PHP version on their sidecar. The sidecar owns the
## real PHP runtime/version.
extprocessor ~~VHNAME~~_lsphp {
type lsapi
address ~~SIDECAR~~:9000
maxConns ~~MAXCONNS~~
autoStart 0
path /usr/local/lsws/fcgi-bin/lsphp
initTimeout 60
retryTimeout 0
respBuffer 0
persistConn 1
}
scripthandler {
add lsapi:~~VHNAME~~_lsphp php
}
## context / drives static serving + .htaccess. RewriteFile .htaccess is OLS's
## autoLoadHtaccess equivalent — re-read on graceful restart (the watcher
## triggers that within the documented window).
context / {
allowBrowse 1
location $DOC_ROOT/
rewrite {
enable 1
RewriteFile .htaccess
}
addDefaultCharset off
}
## LSCache is enabled at MODULE scope (httpd_config_base.tpl) and honored per
## response via the LiteSpeed Cache WP plugin's X-LiteSpeed-Cache-Control
## headers — a `configFile`-loaded vhost in OLS 1.8.4 does NOT accept a bare
## `cache {}` block (verified 2026-06-10), so there is intentionally no per-vhost
## cache block here. OLS stores each vhost's cache in its own subdir under the
## module storagePath automatically (per-vhost isolation, spec 5.2).
errorlog ~~LOG_DIR~~/error_log {
logLevel WARN
rollingSize 50M
keepDays 7
}
accesslog ~~LOG_DIR~~/access_log {
rollingSize 50M
keepDays 7
}