#!/usr/bin/env bash ## entrypoint-lsphp.sh — PID 1 for cac-lsphp:phpNN. ## ## The per-site PHP backend for the SHARED OpenLiteSpeed tier. Runs lsphp in ## DETACHED LSAPI mode (`lsphp -b `) and nothing else — no ## webserver. The shared-ols container connects to this over the docker ## network (extProcessor type lsapi, address :9000) exactly ## like the shared httpd connects to a cac-fpm container's php-fpm on :9000. ## ## Structurally identical to cac-fpm/cac-litespeed: same `uid`/`user` contract, ## the customer docroot mounted at /home/$user (so PHP sees /home/$user/public_html ## EXACTLY like the standalone tiers — true 1:1 drop-in for WordPress ABSPATH, ## config paths, and DB-stored absolute paths). The only difference is OLS lives ## in a separate container, so this PID 1 is lsphp itself. ## ## THE SYMLINK (see feedback_ols_lsapi_no_script_filename_remap): OLS has no ## ProxyFCGISetEnvIf-style remap — it hands lsphp exactly its vhost docRoot path. ## The shared-ols container serves from its bulk /docker/users->/mnt/users mount, ## so its docRoot (and the SCRIPT_FILENAME it sends us) is ## /mnt/users///public_html. We create a symlink ## /mnt/users// -> /home/$user so that path resolves to the real ## /home/$user/public_html files. PHP canonicalises the symlink, so ## __FILE__/__DIR__/realpath all report /home/$user/public_html (verified ## 2026-06-10) — the customer never sees the /mnt/users path. set -euo pipefail : "${PHPVER:=83}" : "${environment:=PROD}" export CONTAINER_ROLE="lsphp_only" export PHPVER environment ## ---- env validation (same contract as entrypoint-fpm / entrypoint-litespeed) ---- 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 LSPHP_BIN="/usr/local/lsws/lsphp${PHPVER}/bin/lsphp" if [ ! -x "$LSPHP_BIN" ]; then echo "FATAL: lsphp binary not found at $LSPHP_BIN (PHPVER=$PHPVER)." >&2 exit 1 fi ## ---- user + directories (identical to entrypoint-litespeed.sh: docroot at ## /home/$user, the customer's bind-mounted domain dir) ---- if ! id -u "$user" >/dev/null 2>&1; then useradd -u "$uid" -m -s /bin/bash "$user" fi mkdir -p "/home/$user/public_html" "/home/$user/logs/php-fpm" ## ---- compatibility symlink for the OLS-sent path ---- ## OLS sends SCRIPT_FILENAME under /mnt/users///public_html ## (the shared-ols container's view). Point that at our real /home/$user mount so ## the path resolves. matches the on-disk convention: wildcard ## `*.foo.com` is stored as `wildcard.foo.com`. SAFE_DOMAIN="$domain" case "$domain" in \*.*) SAFE_DOMAIN="wildcard.${domain#\*.}" ;; esac mkdir -p "/mnt/users/$user" ln -sfn "/home/$user" "/mnt/users/$user/$SAFE_DOMAIN" ## ---- detached-lsphp pool sizing ---- # shellcheck source=/dev/null source /scripts/detect-memory-lsphp.sh ## LSAPI tuning (spec §5.1). PHP_LSAPI_CHILDREN MUST equal the shared-ols vhost ## maxConns — the WHP panel writes both from the single fpm_max_children value, ## so they can't drift. LSAPI_MAX_IDLE is THE RAM win: idle children exit, so an ## idle site's footprint collapses toward baseline (ondemand-like). export PHP_LSAPI_CHILDREN="${PHP_LSAPI_CHILDREN:-$LSAPI_CHILDREN}" export PHP_LSAPI_MAX_REQUESTS="${PHP_LSAPI_MAX_REQUESTS:-500}" export LSAPI_MAX_IDLE="${LSAPI_MAX_IDLE:-30}" export LSAPI_EXTRA_CHILDREN="${LSAPI_EXTRA_CHILDREN:-5}" export LSAPI_AVOID_FORK="${LSAPI_AVOID_FORK:-0}" LSPHP_BIND="${LSPHP_BIND:-0.0.0.0:9000}" echo "Container memory: ${CONTAINER_MEMORY_MB}MB | PHP_LSAPI_CHILDREN=${PHP_LSAPI_CHILDREN} | LSAPI_MAX_IDLE=${LSAPI_MAX_IDLE} | PHPVER=${PHPVER} | bind=${LSPHP_BIND}" ## ---- per-site ini drop-ins (identical mechanism to entrypoint-litespeed.sh) ---- ## error_log → the same customer-visible path cac:phpNN / cac-litespeed use, so ## "where's my PHP error log?" is answered identically across all site types. SCAN_DIR=$("$LSPHP_BIN" -i 2>/dev/null | awk -F'=> ' '/^Scan this dir/ {print $2; exit}') if [ -n "$SCAN_DIR" ]; then mkdir -p "$SCAN_DIR" cat > "$SCAN_DIR/99-user-error-log.ini" < "$SCAN_DIR/99-user-opcache.ini" fi fi ## ---- ownership ---- ## Ensure the dirs we created + the log file are customer-owned so lsphp (running ## as $user) can read code and write logs. Customer content is already ## customer-owned from the host side, so we don't recurse the whole (potentially ## large) tree on every boot. touch "/home/$user/logs/php-fpm/error.log" chown "$uid:$uid" "/home/$user" "/home/$user/public_html" "/home/$user/logs" "/home/$user/logs/php-fpm" "/home/$user/logs/php-fpm/error.log" 2>/dev/null || true ## ---- exec lsphp -b as the customer user (PID 1) ---- ## Bind port is unprivileged (9000), so no root port-bind step is needed — start ## directly as $user. Prefer setpriv (util-linux, on the Ubuntu base); fall back ## to runuser. exec so lsphp becomes PID 1 and receives Docker's signals ## directly (clean stop/restart, matches the php-fpm container's lifecycle). echo "entrypoint-lsphp: exec $LSPHP_BIN -b $LSPHP_BIND as $user (uid=$uid)" if command -v setpriv >/dev/null 2>&1; then exec setpriv --reuid "$uid" --regid "$uid" --init-groups "$LSPHP_BIN" -b "$LSPHP_BIND" elif command -v runuser >/dev/null 2>&1; then exec runuser -u "$user" -- "$LSPHP_BIN" -b "$LSPHP_BIND" else exec sudo -u "$user" -E "$LSPHP_BIN" -b "$LSPHP_BIND" fi