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>
This commit is contained in:
@@ -73,6 +73,50 @@ jobs:
|
||||
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
|
||||
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
|
||||
|
||||
Build-LiteSpeed-Images:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# PHP 7.4/8.0 deliberately excluded — the LiteSpeed prebuilt base
|
||||
# images stop at older OLS releases for those PHP versions, and the
|
||||
# cac-litespeed tier is a paid premium offering: 8.1+ is the
|
||||
# modernization story we're selling.
|
||||
phpver: [81, 82, 83, 84, 85]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: repo.anhonesthost.net
|
||||
username: ${{ secrets.CI_USER }}
|
||||
password: ${{ secrets.CI_TOKEN }}
|
||||
|
||||
- name: Build and Push LiteSpeed Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: ./Dockerfile.litespeed
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
PHPVER=${{ matrix.phpver }}
|
||||
OLS_VERSION=1.8.4
|
||||
# OLS_VERSION pinned to 1.8.4 — only release with prebuilt images
|
||||
# for every PHP version we ship (1.8.5 and 1.9.0 don't have an
|
||||
# lsphp81 variant on Docker Hub). Bump alongside a local rebuild
|
||||
# test when LiteSpeed publishes lsphp81 on a newer OLS release.
|
||||
# See spec: docs/superpowers/specs/2026-06-01-cac-litespeed-design.md
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${{ matrix.phpver }}
|
||||
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:latest' || '' }}
|
||||
|
||||
Build-Shared-httpd:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
92
Dockerfile.litespeed
Normal file
92
Dockerfile.litespeed
Normal file
@@ -0,0 +1,92 @@
|
||||
## cac-litespeed — OpenLiteSpeed customer container, LSAPI-native.
|
||||
##
|
||||
## Built on top of the LiteSpeed-maintained prebuilt image rather than
|
||||
## installed-from-RPM on AlmaLinux 10. Rationale:
|
||||
## - The EL10 RPM ships an empty /usr/local/lsws/cgid/ directory (the
|
||||
## lscgid suexec helper is built by the upstream tarball install.sh,
|
||||
## not packaged), which makes LSAPI unusable.
|
||||
## - The prebuilt image is Ubuntu 24.04-based and includes lsphp +
|
||||
## everything WP/WooCommerce needs out of the box (memcached, redis,
|
||||
## imagick, mbstring, mysqlnd, intl, gd, soap, bcmath, gmp, sodium,
|
||||
## opcache, ...) — saves us a dozen explicit installs and avoids the
|
||||
## libonig.so.105 packaging bug entirely.
|
||||
## - LiteSpeed Inc maintains it; OLS upgrades become a base-image bump.
|
||||
##
|
||||
## Tradeoff vs the rest of the CAC family: this image is Ubuntu-based,
|
||||
## not AlmaLinux. The "cac" naming is now slightly misleading (it's no
|
||||
## longer Cloud *Apache* Container, it's Cloud LiteSpeed Container) but
|
||||
## the panel doesn't care and the customer-facing contract is identical.
|
||||
|
||||
## ARG before FROM is special — it can be used in the FROM line, but the
|
||||
## value goes out of scope inside the image, so we re-declare ARG PHPVER
|
||||
## after FROM for any RUN steps that need it.
|
||||
ARG OLS_VERSION=1.8.5
|
||||
ARG PHPVER=83
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
ARG PHPVER=83
|
||||
ENV PHPVER=${PHPVER}
|
||||
|
||||
## Tooling we layer on top of the base:
|
||||
## - gettext-base: envsubst for runtime template rendering
|
||||
## - sudo: install-lscache-wp.sh runs wp-cli as the customer user
|
||||
## - composer: not in the base image (wp-cli is)
|
||||
## - cron: customer crontab support (mirror cac:phpXX behaviour)
|
||||
## - mariadb-server + memcached: DEV-mode parity with the existing CAC
|
||||
## entrypoints. PROD mode never starts these.
|
||||
## - lsphp83-ldap: not in base image, useful for some WP plugins
|
||||
##
|
||||
## All apt cache is cleaned in the same layer to keep image size down.
|
||||
## lsphp${PHPVER}-ldap is the only extra ext we add (everything else WP needs
|
||||
## ships in the prebuilt base). lsphp84 + lsphp85 don't ship imap or pspell
|
||||
## from LiteSpeed — customers needing imap should pin to 8.3 or earlier.
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
gettext-base sudo cron \
|
||||
ca-certificates curl wget \
|
||||
mariadb-server memcached \
|
||||
lsphp${PHPVER}-ldap && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
## Composer (matches the wp-cli pattern in the existing CAC Dockerfile —
|
||||
## phar download, no `php install` pipe since lsphp's CLI mode is fine).
|
||||
RUN curl -fsSL -o /usr/local/bin/composer https://getcomposer.org/download/latest-stable/composer.phar && \
|
||||
chmod +x /usr/local/bin/composer
|
||||
|
||||
## Our scripts + config templates layer in last (they change most often,
|
||||
## keep them off the slow apt layer).
|
||||
COPY ./scripts/entrypoint-litespeed.sh \
|
||||
./scripts/create-vhost-litespeed.sh \
|
||||
./scripts/detect-memory-litespeed.sh \
|
||||
./scripts/install-lscache-wp.sh \
|
||||
./scripts/log-rotate.sh \
|
||||
/scripts/
|
||||
RUN chmod +x /scripts/*
|
||||
COPY ./configs/litespeed/ /etc/lsws-templates/
|
||||
|
||||
## Apply our production lsphp ini overrides. Ask lsphp for its scan dir
|
||||
## directly (varies by PHP minor version: 8.3/8.4/8.5 each have their own
|
||||
## /usr/local/lsws/lsphpNN/etc/php/8.M/mods-available/). Dockerfile RUN uses
|
||||
## /bin/sh so we explicitly `bash -c` for safer scripting.
|
||||
RUN bash -c 'set -e; \
|
||||
SCAN_DIR=$(/usr/local/lsws/lsphp${PHPVER}/bin/lsphp -i 2>/dev/null | awk -F"=> " "/^Scan this dir/ {print \$2; exit}"); \
|
||||
mkdir -p "$SCAN_DIR"; \
|
||||
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
|
||||
echo "wrote overrides to $SCAN_DIR"'
|
||||
|
||||
## Disable the OLS WebAdmin port for customer-facing containers. Bind admin
|
||||
## listener to loopback so it's unreachable even from the docker network.
|
||||
RUN sed -i 's|^[[:space:]]*address[[:space:]]\+\*:| address 127.0.0.1:|' \
|
||||
/usr/local/lsws/admin/conf/admin_config.conf 2>/dev/null || true
|
||||
|
||||
## Cron entry for log rotation (mirrors cac:phpXX).
|
||||
RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
## Healthcheck: the entrypoint drops a static /healthz into the customer
|
||||
## docroot at boot, so this passes even before any customer files exist.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD curl -fsSk https://127.0.0.1/healthz || exit 1
|
||||
|
||||
ENTRYPOINT ["/scripts/entrypoint-litespeed.sh"]
|
||||
48
configs/litespeed/httpd_config.tpl
Normal file
48
configs/litespeed/httpd_config.tpl
Normal file
@@ -0,0 +1,48 @@
|
||||
## OpenLiteSpeed APPEND fragment — added to the stock httpd_config.conf
|
||||
## that ships with litespeedtech/openlitespeed. Keeping the stock config
|
||||
## intact preserves all the cgid/lscgid plumbing (CGIRLimit defaults,
|
||||
## fileAccessControl defaults, etc.) — when we tried writing a fully
|
||||
## custom httpd_config.conf, lscgid never created its IPC socket and
|
||||
## every PHP request 503'd. The upstream OLS docker template uses this
|
||||
## append pattern too (see setup_docker.sh in litespeedtech/ols-dockerfiles).
|
||||
##
|
||||
## Rendered at container start by scripts/create-vhost-litespeed.sh via
|
||||
## envsubst. Templated vars: $user $domain $vhost_map_aliases.
|
||||
|
||||
## --- our listeners (replace stock Default :8088) ---
|
||||
listener HTTP {
|
||||
address *:80
|
||||
secure 0
|
||||
map siteVH *
|
||||
## NB: HTTP→HTTPS redirect is in site-template.tpl's rewrite{} block,
|
||||
## NOT here — OLS 1.8 listener-level rewrites are inert for vhTemplate
|
||||
## members. Don't move it back to this listener.
|
||||
}
|
||||
|
||||
listener HTTPS {
|
||||
address *:443
|
||||
secure 1
|
||||
keyFile /usr/local/lsws/conf/cert/self.key
|
||||
certFile /usr/local/lsws/conf/cert/self.crt
|
||||
sslProtocol 24
|
||||
enableSpdy 15
|
||||
enableQuic 0
|
||||
map siteVH *
|
||||
}
|
||||
|
||||
## --- our vhost via vhTemplate (upstream's working pattern) ---
|
||||
## The template file is /usr/local/lsws/conf/templates/site.conf — written
|
||||
## by create-vhost-litespeed.sh at the same time as this fragment.
|
||||
vhTemplate site {
|
||||
templateFile conf/templates/site.conf
|
||||
listeners HTTP, HTTPS
|
||||
note cac-litespeed per-customer vhost
|
||||
|
||||
## vhDomain: customer's domain + serveralias list + `*` catchall so
|
||||
## ip-only requests (e.g. HAProxy backend health check by container_name)
|
||||
## still resolve. WHP/HAProxy filters hostnames upstream — no risk to
|
||||
## allowing the catchall here.
|
||||
member siteVH {
|
||||
vhDomain ${domain}${vhost_map_aliases}, *
|
||||
}
|
||||
}
|
||||
43
configs/litespeed/lsphp-overrides.ini
Normal file
43
configs/litespeed/lsphp-overrides.ini
Normal file
@@ -0,0 +1,43 @@
|
||||
; Production lsphp overrides — mirrors configs/prod-php.ini for the FPM
|
||||
; image, adapted for LSAPI defaults. Dropped into /usr/local/lsws/lsphpNN/etc/php.d/
|
||||
|
||||
memory_limit = 256M
|
||||
post_max_size = 384M
|
||||
upload_max_filesize = 256M
|
||||
max_input_vars = 2000
|
||||
max_execution_time = 60
|
||||
max_input_time = 120
|
||||
|
||||
expose_php = Off
|
||||
short_open_tag = Off
|
||||
|
||||
display_errors = Off
|
||||
log_errors = On
|
||||
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||
zend.exception_ignore_args = On
|
||||
|
||||
session.save_handler = files
|
||||
session.use_cookies = 1
|
||||
session.use_only_cookies = 1
|
||||
session.use_strict_mode = 0
|
||||
session.gc_probability = 1
|
||||
session.gc_divisor = 1000
|
||||
session.gc_maxlifetime = 1440
|
||||
|
||||
opcache.enable = 1
|
||||
opcache.memory_consumption = 128
|
||||
opcache.interned_strings_buffer = 8
|
||||
opcache.max_accelerated_files = 10000
|
||||
opcache.revalidate_freq = 60
|
||||
opcache.enable_cli = Off
|
||||
|
||||
output_buffering = 4096
|
||||
default_charset = "UTF-8"
|
||||
|
||||
file_uploads = On
|
||||
max_file_uploads = 20
|
||||
|
||||
soap.wsdl_cache_enabled = 1
|
||||
soap.wsdl_cache_dir = "/tmp"
|
||||
soap.wsdl_cache_ttl = 86400
|
||||
soap.wsdl_cache_limit = 5
|
||||
87
configs/litespeed/site-template.tpl
Normal file
87
configs/litespeed/site-template.tpl
Normal file
@@ -0,0 +1,87 @@
|
||||
## OLS vhTemplate for the per-customer vhost. Mirrors the structure of the
|
||||
## upstream docker.conf template but with our paths and LSCache wiring.
|
||||
## Templated vars (envsubst): $user
|
||||
##
|
||||
## $VH_NAME, $VH_ROOT, $DOC_ROOT, $SERVER_ROOT are OLS macros — they MUST
|
||||
## stay literal in the output (not in the envsubst allow-list).
|
||||
|
||||
allowSymbolLink 1
|
||||
enableScript 1
|
||||
restrained 1
|
||||
## setUIDMode 2 = DocRoot UID — lsphp suexec's to the OWNER of vhRoot.
|
||||
## We chown /home/${user} to ${user}:${user} in the entrypoint, so PHP
|
||||
## runs as the customer per request. Container is still the privsep
|
||||
## boundary; this is the clean "scripts run as user" model.
|
||||
setUIDMode 2
|
||||
vhRoot /home/${user}/public_html/
|
||||
configFile $SERVER_ROOT/conf/vhosts/$VH_NAME/vhconf.conf
|
||||
|
||||
virtualHostConfig {
|
||||
docRoot $VH_ROOT
|
||||
|
||||
errorlog /home/${user}/logs/litespeed/error.log {
|
||||
useServer 0
|
||||
logLevel WARN
|
||||
rollingSize 10M
|
||||
keepDays 14
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
accesslog /home/${user}/logs/litespeed/access.log {
|
||||
useServer 0
|
||||
rollingSize 10M
|
||||
keepDays 7
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
index {
|
||||
useServer 0
|
||||
indexFiles index.php, index.html
|
||||
autoIndex 0
|
||||
}
|
||||
|
||||
## LSCache plugin owns Cache-Control / Expires entirely — server-level
|
||||
## expires off so we don't double-emit headers.
|
||||
expires {
|
||||
enableExpires 0
|
||||
}
|
||||
|
||||
accessControl {
|
||||
allow *
|
||||
}
|
||||
|
||||
context / {
|
||||
location $DOC_ROOT/
|
||||
allowBrowse 1
|
||||
rewrite {
|
||||
enable 1
|
||||
inherit 0
|
||||
autoLoadHtaccess 1
|
||||
RewriteFile .htaccess
|
||||
}
|
||||
addDefaultCharset off
|
||||
}
|
||||
|
||||
rewrite {
|
||||
enable 1
|
||||
autoLoadHtaccess 1
|
||||
logLevel 0
|
||||
## Force HTTPS — OLS 1.8 listener-level rewrites don't apply per-vhost,
|
||||
## so the redirect lives here. The RewriteCond guards against an infinite
|
||||
## loop (SERVER_PORT=80 means "this request came in on the HTTP listener,
|
||||
## not HTTPS"). Per-customer .htaccess rules still apply (autoLoadHtaccess).
|
||||
RewriteCond %{SERVER_PORT} 80
|
||||
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [L,R=301]
|
||||
}
|
||||
|
||||
## Per-vhost LSCache storage. The server-level `module cache` block in
|
||||
## stock httpd_config.conf is already enabled (ls_enabled 1); the LSCWP
|
||||
## plugin flips cache on/off per request via X-LiteSpeed-Cache-Control.
|
||||
module cache {
|
||||
storagePath /home/${user}/lscache
|
||||
checkPrivateCache 1
|
||||
checkPublicCache 1
|
||||
enableCache 0
|
||||
enablePrivateCache 0
|
||||
}
|
||||
}
|
||||
77
configs/litespeed/vhconf.tpl
Normal file
77
configs/litespeed/vhconf.tpl
Normal file
@@ -0,0 +1,77 @@
|
||||
## Per-vhost config — rendered at container start.
|
||||
## Templated vars (envsubst allow-list): $user $domain
|
||||
## Anything that looks like $DOC_ROOT, $VH_ROOT, $HTTP_HOST etc. is an OLS
|
||||
## runtime macro — intentionally NOT in the envsubst allow-list so it
|
||||
## passes through unchanged for OLS to expand at request time.
|
||||
|
||||
docRoot /home/${user}/public_html
|
||||
|
||||
enableGzip 1
|
||||
enableBr 1
|
||||
|
||||
errorlog /home/${user}/logs/litespeed/error.log {
|
||||
useServer 0
|
||||
logLevel WARN
|
||||
rollingSize 10M
|
||||
keepDays 14
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
accesslog /home/${user}/logs/litespeed/access.log {
|
||||
useServer 0
|
||||
rollingSize 10M
|
||||
keepDays 7
|
||||
compressArchive 1
|
||||
}
|
||||
|
||||
index {
|
||||
useServer 0
|
||||
indexFiles index.php, index.html
|
||||
autoIndex 0
|
||||
}
|
||||
|
||||
scripthandler {
|
||||
add lsapi:lsphp php
|
||||
}
|
||||
|
||||
## LSCache plugin owns Cache-Control / Expires entirely — keep server-level
|
||||
## expires off so we don't double-emit headers.
|
||||
expires {
|
||||
enableExpires 0
|
||||
}
|
||||
|
||||
accessControl {
|
||||
allow *
|
||||
}
|
||||
|
||||
context / {
|
||||
## $DOC_ROOT is an OLS macro (not a shell var). Don't add it to the
|
||||
## envsubst allow-list in create-vhost-litespeed.sh or it'll expand to
|
||||
## empty and break docroot resolution.
|
||||
location $DOC_ROOT/
|
||||
allowBrowse 1
|
||||
rewrite {
|
||||
enable 1
|
||||
inherit 0
|
||||
autoLoadHtaccess 1
|
||||
RewriteFile .htaccess
|
||||
}
|
||||
addDefaultCharset off
|
||||
}
|
||||
|
||||
rewrite {
|
||||
enable 1
|
||||
autoLoadHtaccess 1
|
||||
logLevel 0
|
||||
}
|
||||
|
||||
## Per-vhost LSCache storage. Server module cache{} block enables the engine;
|
||||
## these lines tell the vhost WHERE to cache. The LSCWP plugin flips the
|
||||
## cache on/off at request time via X-LiteSpeed-Cache-Control headers.
|
||||
module cache {
|
||||
storagePath /home/${user}/lscache
|
||||
checkPrivateCache 1
|
||||
checkPublicCache 1
|
||||
enableCache 0
|
||||
enablePrivateCache 0
|
||||
}
|
||||
78
scripts/create-vhost-litespeed.sh
Normal file
78
scripts/create-vhost-litespeed.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
## create-vhost-litespeed.sh — sets up OLS config for one customer site.
|
||||
##
|
||||
## Approach: keep the stock LiteSpeed-shipped httpd_config.conf VERBATIM
|
||||
## (it has all the cgid/lscgid plumbing that lscgid needs to actually
|
||||
## create its IPC socket), and just APPEND our listeners + vhTemplate.
|
||||
## The custom vhost template lives at conf/templates/site.conf and points
|
||||
## at /home/${user}/public_html. envsubst renders our user/domain into
|
||||
## both files at container start.
|
||||
##
|
||||
## Expects in env: user, domain, serveralias (optional).
|
||||
set -euo pipefail
|
||||
|
||||
TPL_DIR=${TPL_DIR:-/etc/lsws-templates}
|
||||
LSWS_CONF=/usr/local/lsws/conf
|
||||
|
||||
## Ensure the conf dir has stock config to append to. On first boot with
|
||||
## a fresh image this is a no-op (image ships with conf/ populated). With
|
||||
## a future volume mount of conf/, the upstream entrypoint pattern would
|
||||
## copy from .conf/* — keep parity:
|
||||
if [ -z "$(ls -A -- "$LSWS_CONF/" 2>/dev/null)" ]; then
|
||||
cp -R /usr/local/lsws/.conf/* "$LSWS_CONF/"
|
||||
fi
|
||||
|
||||
## Build the serveralias suffix for vhDomain. Empty for none, else
|
||||
## ",alias1,alias2" prepended to the comma list.
|
||||
vhost_map_aliases=""
|
||||
if [ -n "${serveralias:-}" ]; then
|
||||
for alias in $(echo "$serveralias" | tr ',' ' '); do
|
||||
[ -z "$alias" ] && continue
|
||||
vhost_map_aliases="${vhost_map_aliases},${alias}"
|
||||
done
|
||||
fi
|
||||
export vhost_map_aliases user domain
|
||||
|
||||
## --- prep the stock httpd_config.conf before appending ours ---
|
||||
## Stock ships with `listener HTTP {*:80}`, `listener HTTPS {*:443}`, and
|
||||
## a `vhTemplate docker` mapped to /var/www/vhosts/$VH_NAME/html — these
|
||||
## conflict with our ports and would shadow our siteVH vhost. Strip them
|
||||
## and the demo `virtualHost Example`, but KEEP `listener Default` (it's
|
||||
## bound to 8088 — harmless internally, removing risks unrelated breakage).
|
||||
## Always restart from a stock copy so re-runs are idempotent (otherwise
|
||||
## a second sed pass on already-stripped config corrupts it).
|
||||
cp /usr/local/lsws/.conf/httpd_config.conf "$LSWS_CONF/httpd_config.conf"
|
||||
|
||||
## Strip the stock blocks we replace. Use awk: easier than sed range-deletes
|
||||
## to skip a NAMED block of arbitrary length terminated by a top-level `}`.
|
||||
awk '
|
||||
BEGIN { skip = 0 }
|
||||
/^listener HTTP \{/ || /^listener HTTPS \{/ || /^vhTemplate docker \{/ { skip = 1; next }
|
||||
skip && /^\}/ { skip = 0; next }
|
||||
!skip { print }
|
||||
' "$LSWS_CONF/httpd_config.conf" > "$LSWS_CONF/httpd_config.conf.new"
|
||||
mv "$LSWS_CONF/httpd_config.conf.new" "$LSWS_CONF/httpd_config.conf"
|
||||
|
||||
## --- append our listeners + vhTemplate ---
|
||||
SENTINEL="## ---- cac-litespeed append (do not edit below) ----"
|
||||
{
|
||||
echo ""
|
||||
echo "$SENTINEL"
|
||||
envsubst '${user} ${domain} ${vhost_map_aliases}' < "$TPL_DIR/httpd_config.tpl"
|
||||
} >> "$LSWS_CONF/httpd_config.conf"
|
||||
|
||||
## --- write our vhost template to /usr/local/lsws/conf/templates/site.conf ---
|
||||
envsubst '${user}' < "$TPL_DIR/site-template.tpl" \
|
||||
> "$LSWS_CONF/templates/site.conf"
|
||||
|
||||
## --- per-vhost config file the vhTemplate will reference ---
|
||||
## OLS creates conf/vhosts/$VH_NAME/ at template-instantiation time, but
|
||||
## we pre-create it to satisfy the configFile path and write a minimal
|
||||
## vhconf.conf (empty body — all real config is inline in the template's
|
||||
## virtualHostConfig{} block).
|
||||
mkdir -p "$LSWS_CONF/vhosts/siteVH"
|
||||
echo "## auto-generated; real vhost config is in templates/site.conf" \
|
||||
> "$LSWS_CONF/vhosts/siteVH/vhconf.conf"
|
||||
|
||||
## Permissions: OLS reads conf/ as lsadm. Don't break that.
|
||||
chown -R lsadm:nogroup "$LSWS_CONF" 2>/dev/null || true
|
||||
76
scripts/detect-memory-litespeed.sh
Normal file
76
scripts/detect-memory-litespeed.sh
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
## detect-memory-litespeed.sh — sibling to detect-memory.sh.
|
||||
## Computes LSAPI_CHILDREN + extprocessor memSoftLimit/memHardLimit from
|
||||
## container memory cap. Sourced by entrypoint-litespeed.sh.
|
||||
|
||||
## ---- container memory detection (mirrors detect-memory.sh) ----
|
||||
CONTAINER_MEMORY_BYTES=""
|
||||
|
||||
if [ -f /sys/fs/cgroup/memory.max ]; then
|
||||
val=$(cat /sys/fs/cgroup/memory.max 2>/dev/null)
|
||||
if [ "$val" != "max" ] && [ -n "$val" ]; then
|
||||
CONTAINER_MEMORY_BYTES=$val
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CONTAINER_MEMORY_BYTES" ] && [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then
|
||||
val=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null)
|
||||
if [ -n "$val" ] && [ "$val" -lt 8589934592000 ] 2>/dev/null; then
|
||||
CONTAINER_MEMORY_BYTES=$val
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CONTAINER_MEMORY_BYTES" ] && [ -f /proc/meminfo ]; then
|
||||
mem_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
|
||||
if [ -n "$mem_kb" ]; then
|
||||
CONTAINER_MEMORY_BYTES=$((mem_kb * 1024))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CONTAINER_MEMORY_BYTES" ]; then
|
||||
CONTAINER_MEMORY_BYTES=$((512 * 1024 * 1024))
|
||||
fi
|
||||
|
||||
CONTAINER_MEMORY_MB=$((CONTAINER_MEMORY_BYTES / 1024 / 1024))
|
||||
|
||||
## ---- budget split (LSAPI workers get the lion's share) ----
|
||||
OS_RESERVE_MB=50
|
||||
OLS_RESERVE_MB=40 # OpenLiteSpeed daemon footprint
|
||||
DEV_OVERHEAD_MB=0
|
||||
if [ "${environment:-PROD}" = "DEV" ]; then
|
||||
DEV_OVERHEAD_MB=125
|
||||
fi
|
||||
|
||||
AVAILABLE_MB=$((CONTAINER_MEMORY_MB - OS_RESERVE_MB - OLS_RESERVE_MB - DEV_OVERHEAD_MB))
|
||||
if [ "$AVAILABLE_MB" -lt 60 ]; then
|
||||
AVAILABLE_MB=60
|
||||
fi
|
||||
|
||||
## ---- LSAPI children (analogous to PHP_FPM_MAX_CHILDREN) ----
|
||||
## LSAPI is more memory-efficient than FPM; estimate 96 MB / worker
|
||||
## (vs 128 MB for FPM after the 2026-06-01 bump). Floor 2, cap 50.
|
||||
LSPHP_WORKER_ESTIMATE_MB=${LSPHP_WORKER_ESTIMATE_MB:-96}
|
||||
|
||||
calc_lsapi_children=$((AVAILABLE_MB / LSPHP_WORKER_ESTIMATE_MB))
|
||||
if [ "$calc_lsapi_children" -lt 2 ]; then
|
||||
calc_lsapi_children=2
|
||||
fi
|
||||
if [ "$calc_lsapi_children" -gt 50 ]; then
|
||||
calc_lsapi_children=50
|
||||
fi
|
||||
|
||||
## Per-site override knobs — site-pool-env.php still passes FPM_MAX_CHILDREN
|
||||
## for backward compat, so prefer LSAPI_CHILDREN if set, else FPM_MAX_CHILDREN,
|
||||
## else the calculated value.
|
||||
LSAPI_CHILDREN=${LSAPI_CHILDREN:-${FPM_MAX_CHILDREN:-$calc_lsapi_children}}
|
||||
|
||||
## extprocessor mem limits — total LSAPI heap should fit AVAILABLE_MB with
|
||||
## some breathing room. Soft = budget/children, hard = soft * 1.5, both capped
|
||||
## at 2047 (OLS interprets > 2047 oddly in some 1.x builds).
|
||||
LSAPI_MEM_SOFT=$((AVAILABLE_MB / LSAPI_CHILDREN))
|
||||
if [ "$LSAPI_MEM_SOFT" -lt 64 ]; then LSAPI_MEM_SOFT=64; fi
|
||||
if [ "$LSAPI_MEM_SOFT" -gt 2047 ]; then LSAPI_MEM_SOFT=2047; fi
|
||||
LSAPI_MEM_HARD=$((LSAPI_MEM_SOFT * 3 / 2))
|
||||
if [ "$LSAPI_MEM_HARD" -gt 2047 ]; then LSAPI_MEM_HARD=2047; fi
|
||||
|
||||
export CONTAINER_MEMORY_MB LSAPI_CHILDREN LSAPI_MEM_SOFT LSAPI_MEM_HARD
|
||||
128
scripts/entrypoint-litespeed.sh
Normal file
128
scripts/entrypoint-litespeed.sh
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
## entrypoint-litespeed.sh — PID 1 for cac-litespeed:phpNN.
|
||||
## Built on litespeedtech/openlitespeed:1.8.x-lsphp83 prebuilt base. Native
|
||||
## LSAPI (no FPM proxy), one customer per container.
|
||||
##
|
||||
## Process supervision: starts OLS via `openlitespeed -n` (no-daemon +
|
||||
## crash-guard, per OLS source: lshttpdmain.cpp). SIGTERM is forwarded.
|
||||
## crond runs in the background for customer crontabs; OLS itself is the
|
||||
## process we wait on (if OLS dies, the container exits and Docker
|
||||
## restarts it per its restart policy).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${PHPVER:=83}"
|
||||
: "${environment:=PROD}"
|
||||
: "${LSCACHE_AUTOINSTALL:=1}"
|
||||
|
||||
export CONTAINER_ROLE="litespeed_only"
|
||||
export PHPVER environment LSCACHE_AUTOINSTALL
|
||||
|
||||
## ---- env validation ----
|
||||
if [ -z "${uid:-}" ] || [ -z "${user:-}" ]; then
|
||||
echo "FATAL: 'uid' and 'user' env vars are required (panel sets these from WHP_UID/WHP_USER)." >&2
|
||||
exit 1
|
||||
fi
|
||||
: "${domain:=localhost}"
|
||||
export user domain
|
||||
|
||||
## ---- user + directories ----
|
||||
if ! id -u "$user" >/dev/null 2>&1; then
|
||||
## Ubuntu's useradd; mirror what the AL10 entrypoints do with adduser
|
||||
useradd -u "$uid" -m -s /bin/bash "$user"
|
||||
fi
|
||||
|
||||
mkdir -p "/home/$user/public_html"
|
||||
mkdir -p "/home/$user/logs/litespeed"
|
||||
mkdir -p "/home/$user/lscache"
|
||||
|
||||
mkdir -p /tmp/lshttpd/swap
|
||||
chmod 1777 /tmp/lshttpd
|
||||
|
||||
## ---- memory + lsphp pool sizing ----
|
||||
# shellcheck source=/dev/null
|
||||
source /scripts/detect-memory-litespeed.sh
|
||||
echo "Container memory: ${CONTAINER_MEMORY_MB}MB | LSAPI_CHILDREN=${LSAPI_CHILDREN} | memSoft=${LSAPI_MEM_SOFT}M memHard=${LSAPI_MEM_HARD}M | PHPVER=${PHPVER}"
|
||||
|
||||
## ---- self-signed cert (idempotent) ----
|
||||
mkdir -p /usr/local/lsws/conf/cert
|
||||
if [ ! -f /usr/local/lsws/conf/cert/self.crt ]; then
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
||||
-keyout /usr/local/lsws/conf/cert/self.key \
|
||||
-out /usr/local/lsws/conf/cert/self.crt \
|
||||
-subj "/CN=${domain}" 2>/dev/null
|
||||
fi
|
||||
|
||||
## ---- render httpd_config + vhconf from templates ----
|
||||
/scripts/create-vhost-litespeed.sh
|
||||
|
||||
## ---- ownership: OLS master/workers run as nobody; lsphp suexecs to the
|
||||
## customer per request (setUIDMode 2 in httpd_config.tpl). So the customer
|
||||
## owns everything under /home/$user — clean ownership model, no nobody
|
||||
## chowning. OLS's own runtime dirs stay nobody-owned.
|
||||
chown -R nobody:nogroup /usr/local/lsws/logs /usr/local/lsws/conf/cert /tmp/lshttpd 2>/dev/null || true
|
||||
chown -R "$user:$user" "/home/$user"
|
||||
chmod 755 "/home/$user"
|
||||
|
||||
## ---- drop healthz so docker HEALTHCHECK passes before customer files
|
||||
## Always rewrite as customer; suexec lsphp will read it as that uid too.
|
||||
sudo -u "$user" sh -c "echo ok > /home/$user/public_html/healthz"
|
||||
|
||||
## ---- DEV: local mariadb + memcached for parity with cac entrypoints ----
|
||||
if [ "$environment" = "DEV" ]; then
|
||||
echo "Starting Dev Deployment (litespeed)"
|
||||
mkdir -p "/home/$user/_db_backups"
|
||||
## mariadb-server + memcached are already in the image (apt-installed
|
||||
## in the Dockerfile). Just start them.
|
||||
mkdir -p /run/mysqld && chown mysql:mysql /run/mysqld
|
||||
nohup mysqld --user=mysql &>/dev/null &
|
||||
if [ ! -f "/home/$user/mysql_creds" ]; then
|
||||
sleep 10
|
||||
mysql_user=$(openssl rand -hex 7)
|
||||
mysql_password=$(openssl rand -hex 12)
|
||||
mysql_db="devdb_$(openssl rand -hex 3)"
|
||||
mysql -e "CREATE DATABASE $mysql_db;"
|
||||
mysql -e "CREATE USER '$mysql_user'@'localhost' IDENTIFIED BY '$mysql_password';"
|
||||
mysql -e "GRANT ALL PRIVILEGES ON *.* TO '$mysql_user'@'localhost' WITH GRANT OPTION;"
|
||||
mysql -e "FLUSH PRIVILEGES;"
|
||||
{
|
||||
echo "MySQL User: $mysql_user"
|
||||
echo "MySQL Password: $mysql_password"
|
||||
echo "MySQL Database: $mysql_db"
|
||||
} > "/home/$user/mysql_creds"
|
||||
cat "/home/$user/mysql_creds"
|
||||
fi
|
||||
/usr/bin/memcached -d -u "$user"
|
||||
fi
|
||||
|
||||
## ---- user crontab ----
|
||||
if [ ! -f "/home/$user/crontab" ]; then
|
||||
{
|
||||
echo "# User crontab for $user"
|
||||
echo "# Add your cron jobs here"
|
||||
} > "/home/$user/crontab"
|
||||
chown "$user:$user" "/home/$user/crontab"
|
||||
fi
|
||||
crontab -u "$user" "/home/$user/crontab"
|
||||
service cron start >/dev/null 2>&1 || /usr/sbin/cron
|
||||
|
||||
## ---- LSCache plugin (background, non-fatal) ----
|
||||
( /scripts/install-lscache-wp.sh "$user" >>/var/log/lscache-install.log 2>&1 || true ) &
|
||||
|
||||
## ---- start OLS in foreground with crash-guard ----
|
||||
## openlitespeed -n = no-daemon + supervisor. Trap forwards SIGTERM cleanly.
|
||||
OLS_PID=""
|
||||
trap '[ -n "$OLS_PID" ] && kill -TERM "$OLS_PID" 2>/dev/null; wait "$OLS_PID" 2>/dev/null || true' TERM INT
|
||||
|
||||
/usr/local/lsws/bin/openlitespeed -n &
|
||||
OLS_PID=$!
|
||||
|
||||
## Stream OLS + customer logs to PID-1 stdout so `docker logs` works.
|
||||
touch /usr/local/lsws/logs/error.log /usr/local/lsws/logs/access.log
|
||||
touch "/home/$user/logs/litespeed/error.log" "/home/$user/logs/litespeed/access.log"
|
||||
tail -F /usr/local/lsws/logs/error.log \
|
||||
/usr/local/lsws/logs/access.log \
|
||||
"/home/$user/logs/litespeed/error.log" \
|
||||
"/home/$user/logs/litespeed/access.log" 2>/dev/null &
|
||||
|
||||
wait "$OLS_PID"
|
||||
38
scripts/install-lscache-wp.sh
Normal file
38
scripts/install-lscache-wp.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
## install-lscache-wp.sh — auto-install the official litespeed-cache plugin
|
||||
## on first boot if WP is detected and plugin not already managed.
|
||||
## Idempotent: re-runs are no-ops. Honors LSCACHE_AUTOINSTALL=0 escape hatch.
|
||||
##
|
||||
## Args: $1 = $user (customer system user)
|
||||
set -euo pipefail
|
||||
|
||||
user="${1:?usage: install-lscache-wp.sh <user>}"
|
||||
home="/home/${user}"
|
||||
|
||||
if [ "${LSCACHE_AUTOINSTALL:-1}" = "0" ]; then
|
||||
echo "[lscache] LSCACHE_AUTOINSTALL=0 — skipping plugin install."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$home/public_html/wp-config.php" ]; then
|
||||
echo "[lscache] No wp-config.php in $home/public_html — skipping (not a WP site)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
## With setUIDMode 2, lsphp runs as the customer, and customer owns their
|
||||
## home tree — wp-cli also runs as the customer, files end up correctly owned.
|
||||
if ! command -v wp >/dev/null 2>&1; then
|
||||
echo "[lscache] wp-cli not on PATH — skipping (image build issue, not fatal)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if sudo -u "$user" -- wp --path="$home/public_html" plugin is-installed litespeed-cache 2>/dev/null; then
|
||||
echo "[lscache] litespeed-cache already installed — leaving customer's settings alone."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[lscache] Installing litespeed-cache plugin for $user…"
|
||||
sudo -u "$user" -- wp --path="$home/public_html" plugin install litespeed-cache --activate \
|
||||
|| { echo "[lscache] plugin install failed (network? wp-cli? perms?) — non-fatal."; exit 0; }
|
||||
|
||||
echo "[lscache] Done."
|
||||
Reference in New Issue
Block a user