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:
@@ -185,3 +185,36 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest
|
||||
|
||||
Build-Shared-OLS:
|
||||
runs-on: ubuntu-latest
|
||||
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 Shared OLS Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: ./Dockerfile.shared-ols
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
# Single image (runs no PHP). PHPVER just selects the OLS base tag;
|
||||
# pinned to 83 / OLS 1.8.4 to match the rest of the litespeed family.
|
||||
build-args: |
|
||||
PHPVER=83
|
||||
OLS_VERSION=1.8.4
|
||||
tags: |
|
||||
repo.anhonesthost.net/cloud-hosting-platform/shared-ols:latest
|
||||
|
||||
57
Dockerfile.shared-ols
Normal file
57
Dockerfile.shared-ols
Normal file
@@ -0,0 +1,57 @@
|
||||
## shared-ols — the shared OpenLiteSpeed webserver tier.
|
||||
##
|
||||
## One OLS container fronting MANY tenants' detached cac-lsphp sidecars — the
|
||||
## OLS analogue of the shared-httpd container. Runs NO PHP locally: every site's
|
||||
## PHP goes to its own cac-lsphp:phpNN sidecar over LSAPI (extProcessor type
|
||||
## lsapi, address <sidecar>:9000). HAProxy stays the TLS/WAF/SNI edge and routes
|
||||
## OLS-type hostnames here on :443.
|
||||
##
|
||||
## Built on the SAME litespeedtech prebuilt base as cac-litespeed / cac-lsphp so
|
||||
## the OLS build + plumbing (lscgid, cgid socket — see feedback_ols_packaging_landmines)
|
||||
## are the proven ones. The base is lsphp-tagged but we never run that lsphp;
|
||||
## the tag just selects the OLS build. Pinned to lsphp83 / OLS 1.8.4.
|
||||
##
|
||||
## Config model (established by PoC 2026-06-10): OLS has NO top-level `include`,
|
||||
## so render-shared-ols-config.sh assembles httpd_config.conf from the panel's
|
||||
## per-site files at boot + on every change. See that script + the plan.
|
||||
|
||||
ARG OLS_VERSION=1.8.4
|
||||
ARG PHPVER=83
|
||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||
|
||||
## Tooling the shared tier needs on top of the base:
|
||||
## - inotify-tools: the .htaccess watcher (spec 5.3)
|
||||
## - gettext-base: envsubst for render-shared-ols-config.sh
|
||||
## - openssl: self-signed cert for the :443 listener (HAProxy verifies none)
|
||||
## - curl/ca-certificates: HEALTHCHECK
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
inotify-tools gettext-base openssl ca-certificates curl && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
## Snapshot the stock httpd_config.conf so render-shared-ols-config.sh always has
|
||||
## a pristine base to strip-and-rebuild from (the base image keeps it at conf/).
|
||||
RUN mkdir -p /usr/local/lsws/.conf && \
|
||||
cp /usr/local/lsws/conf/httpd_config.conf /usr/local/lsws/.conf/httpd_config.conf
|
||||
|
||||
COPY ./scripts/entrypoint-shared-ols.sh \
|
||||
./scripts/render-shared-ols-config.sh \
|
||||
./scripts/ols-htaccess-watcher.sh \
|
||||
/scripts/
|
||||
RUN chmod +x /scripts/entrypoint-shared-ols.sh /scripts/render-shared-ols-config.sh /scripts/ols-htaccess-watcher.sh
|
||||
COPY ./configs/shared-ols/ /etc/shared-ols-templates/
|
||||
|
||||
## Admin console unreachable from tenant/edge networks (spec 5.2): bind the
|
||||
## WebAdmin listener to loopback. Same sed as Dockerfile.litespeed.
|
||||
RUN sed -i 's|^[[:space:]]*address[[:space:]]\+\*:| address 127.0.0.1:|' \
|
||||
/usr/local/lsws/admin/conf/admin_config.conf 2>/dev/null || true
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
## Health: the entrypoint renders a catch-all _health vhost serving /healthz, so
|
||||
## this passes from boot (zero customer sites) onward. Self-signed :443.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD curl -fsSk https://127.0.0.1/healthz || exit 1
|
||||
|
||||
ENTRYPOINT ["/scripts/entrypoint-shared-ols.sh"]
|
||||
38
configs/shared-ols/httpd_config_base.tpl
Normal file
38
configs/shared-ols/httpd_config_base.tpl
Normal 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 ----
|
||||
70
configs/shared-ols/vhconf.tpl
Normal file
70
configs/shared-ols/vhconf.tpl
Normal 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
|
||||
}
|
||||
120
scripts/entrypoint-shared-ols.sh
Normal file
120
scripts/entrypoint-shared-ols.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
## entrypoint-shared-ols.sh — PID 1 for the shared-ols tier.
|
||||
##
|
||||
## One OpenLiteSpeed container fronting MANY tenants' detached cac-lsphp
|
||||
## sidecars (the OLS analogue of the shared-httpd container). Webserver ONLY —
|
||||
## it runs NO PHP locally (render-shared-ols-config.sh strips the stock local
|
||||
## lsphp; every site's PHP goes to its own sidecar over LSAPI). HAProxy stays
|
||||
## the TLS/WAF/SNI edge and routes OLS-type hostnames here on :443.
|
||||
##
|
||||
## Reuses cac-litespeed's hard-won DAEMON-MODE supervision (NOT `openlitespeed
|
||||
## -n` + wait): OLS self-restarts on QUIC.cloud IP refresh would otherwise exit
|
||||
## PID 1 cleanly and tear the container down. See entrypoint-litespeed.sh and
|
||||
## feedback_ols_quiccloud_restart_kills_container.
|
||||
set -euo pipefail
|
||||
|
||||
: "${environment:=PROD}"
|
||||
export CONTAINER_ROLE="shared_ols"
|
||||
|
||||
LSWS_CONF=/usr/local/lsws/conf
|
||||
CERT_DIR="$LSWS_CONF/cert"
|
||||
HEALTH_DIR=/usr/local/lsws/shared-ols-health
|
||||
export SITES_ROOT="${SITES_ROOT:-$LSWS_CONF/shared-sites}"
|
||||
export LSCACHE_ROOT="${LSCACHE_ROOT:-/var/lscache}"
|
||||
export CERT_FILE="$CERT_DIR/shared-ols.crt"
|
||||
export KEY_FILE="$CERT_DIR/shared-ols.key"
|
||||
|
||||
mkdir -p "$SITES_ROOT" "$LSCACHE_ROOT" "$CERT_DIR" "$HEALTH_DIR/html"
|
||||
|
||||
## ---- self-signed cert for the :443 listener (HAProxy verifies none) ----
|
||||
if [ ! -f "$CERT_FILE" ]; then
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
||||
-keyout "$KEY_FILE" -out "$CERT_FILE" -subj "/CN=shared-ols" 2>/dev/null
|
||||
fi
|
||||
|
||||
## ---- health vhost (catch-all): valid server with zero customer sites +
|
||||
## answers HAProxy health checks that hit by IP / unknown Host with a 200 ----
|
||||
cat > "$HEALTH_DIR/vhconf.conf" <<'EOF'
|
||||
docRoot $VH_ROOT/html
|
||||
enableScript 0
|
||||
context / {
|
||||
allowBrowse 1
|
||||
location $DOC_ROOT/
|
||||
}
|
||||
EOF
|
||||
printf 'ok\n' > "$HEALTH_DIR/html/healthz"
|
||||
printf 'shared-ols\n' > "$HEALTH_DIR/html/index.html"
|
||||
|
||||
## ---- assemble httpd_config.conf from the panel's per-site files ----
|
||||
/scripts/render-shared-ols-config.sh
|
||||
|
||||
chown -R lsadm:nogroup "$LSWS_CONF" "$HEALTH_DIR" 2>/dev/null || true
|
||||
|
||||
## ---- stream OLS logs to PID-1 stdout (follows across restarts) ----
|
||||
mkdir -p /usr/local/lsws/logs
|
||||
touch /usr/local/lsws/logs/error.log /usr/local/lsws/logs/access.log
|
||||
tail -F /usr/local/lsws/logs/error.log /usr/local/lsws/logs/access.log 2>/dev/null &
|
||||
|
||||
## ---- .htaccess watcher (required; spec 5.3). Background; the panel monitors
|
||||
## that it stays alive (its death silently stops rewrite changes applying). ----
|
||||
/scripts/ols-htaccess-watcher.sh &
|
||||
WATCHER_PID=$!
|
||||
|
||||
## ---- supervise OLS in DAEMON mode (verbatim model from entrypoint-litespeed.sh) ----
|
||||
STOP_REQUESTED=0
|
||||
term_handler() {
|
||||
STOP_REQUESTED=1
|
||||
kill "$WATCHER_PID" 2>/dev/null || true
|
||||
/usr/local/lsws/bin/lswsctrl stop >/dev/null 2>&1 || true
|
||||
}
|
||||
trap term_handler TERM INT
|
||||
|
||||
ols_running() { /usr/local/lsws/bin/lswsctrl status 2>/dev/null | grep -qi 'running with pid'; }
|
||||
|
||||
MAX_STARTS=5
|
||||
WINDOW=60
|
||||
starts=""
|
||||
|
||||
start_ols() {
|
||||
/usr/local/lsws/bin/lswsctrl start >/dev/null 2>&1 || true
|
||||
for _ in $(seq 1 20); do
|
||||
ols_running && return 0
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! start_ols; then
|
||||
echo "entrypoint-shared-ols: OLS failed to start (not running after 10s)." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "entrypoint-shared-ols: OLS started in daemon mode — $(/usr/local/lsws/bin/lswsctrl status 2>/dev/null || true)"
|
||||
|
||||
while true; do
|
||||
if ols_running; then
|
||||
sleep 3
|
||||
continue
|
||||
fi
|
||||
sleep 2
|
||||
if [ "$STOP_REQUESTED" -eq 0 ] && ols_running; then
|
||||
continue
|
||||
fi
|
||||
if [ "$STOP_REQUESTED" -eq 1 ]; then
|
||||
echo "entrypoint-shared-ols: SIGTERM received, OLS stopped — exiting."
|
||||
exit 0
|
||||
fi
|
||||
now=$(date +%s)
|
||||
starts="$starts $now"
|
||||
pruned=""
|
||||
for t in $starts; do
|
||||
[ $((now - t)) -lt "$WINDOW" ] && pruned="$pruned $t"
|
||||
done
|
||||
starts="$pruned"
|
||||
n=$(echo $starts | wc -w)
|
||||
echo "entrypoint-shared-ols: OLS not running — relaunching (attempt $n/$MAX_STARTS within ${WINDOW}s)." >&2
|
||||
if [ "$n" -ge "$MAX_STARTS" ]; then
|
||||
echo "entrypoint-shared-ols: OLS crash-looping — bailing for Docker restart policy / monitor." >&2
|
||||
exit 1
|
||||
fi
|
||||
start_ols || true
|
||||
done
|
||||
58
scripts/ols-htaccess-watcher.sh
Normal file
58
scripts/ols-htaccess-watcher.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
## ols-htaccess-watcher.sh — graceful-restart the shared OLS when any tenant's
|
||||
## .htaccess changes. OLS reads .htaccess (RewriteFile) at (re)start, NOT per
|
||||
## request, so without this a WordPress permalink/LiteSpeed-Cache change would
|
||||
## silently not take effect. Required by spec 5.3.
|
||||
##
|
||||
## Watches all docroots for .htaccess writes, COALESCES bursts (a WP plugin save
|
||||
## touches the file several times) within a debounce window, and RATE-LIMITS to
|
||||
## a floor (one restart per FLOOR seconds) so many tenants saving at once can't
|
||||
## trigger a restart storm. Debounce/floor are env-tunable (panel discloses the
|
||||
## resulting "~60s" window to customers).
|
||||
##
|
||||
## Failure of THIS process is the silent-ticket failure mode (spec 7): if it
|
||||
## dies, tenants' rewrite changes stop applying with no error. The entrypoint
|
||||
## runs it and the panel monitors it (check-ols-htaccess-watcher.php).
|
||||
set -uo pipefail
|
||||
|
||||
WATCH_ROOT="${OLS_WATCH_ROOT:-/mnt/users}"
|
||||
DEBOUNCE="${OLS_HTACCESS_DEBOUNCE:-15}" # coalesce window (s)
|
||||
FLOOR="${OLS_HTACCESS_FLOOR:-60}" # min seconds between restarts
|
||||
LSWSCTRL=/usr/local/lsws/bin/lswsctrl
|
||||
last_restart=0
|
||||
|
||||
log() { echo "ols-htaccess-watcher: $*" >&2; }
|
||||
|
||||
do_restart() {
|
||||
now=$(date +%s)
|
||||
if [ $((now - last_restart)) -lt "$FLOOR" ]; then
|
||||
log "within ${FLOOR}s floor — coalescing, skipping restart"
|
||||
return
|
||||
fi
|
||||
if "$LSWSCTRL" restart >/dev/null 2>&1; then
|
||||
last_restart=$now
|
||||
log "graceful restart issued (.htaccess change)"
|
||||
else
|
||||
log "WARNING: lswsctrl restart failed"
|
||||
fi
|
||||
}
|
||||
|
||||
if ! command -v inotifywait >/dev/null 2>&1; then
|
||||
log "FATAL: inotifywait not installed (inotify-tools)"; exit 1
|
||||
fi
|
||||
mkdir -p "$WATCH_ROOT"
|
||||
log "watching $WATCH_ROOT for .htaccess changes (debounce=${DEBOUNCE}s floor=${FLOOR}s)"
|
||||
|
||||
## -m monitor, -r recursive. We filter to .htaccess in the read loop rather than
|
||||
## --include so this works on older inotify-tools too. modify/create/delete/move
|
||||
## all matter (delete of .htaccess also changes rewrite behavior).
|
||||
inotifywait -m -r -e modify,create,delete,move "$WATCH_ROOT" --format '%f' 2>/dev/null |
|
||||
while read -r fname; do
|
||||
case "$fname" in
|
||||
.htaccess) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
## Drain further events for DEBOUNCE seconds (coalesce the burst), then act.
|
||||
while read -r -t "$DEBOUNCE" _; do :; done
|
||||
do_restart
|
||||
done
|
||||
125
scripts/render-shared-ols-config.sh
Normal file
125
scripts/render-shared-ols-config.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
## render-shared-ols-config.sh — assemble httpd_config.conf for the shared-ols
|
||||
## tier from the per-site files the WHP panel drops into $SITES_ROOT.
|
||||
##
|
||||
## WHY THIS EXISTS: OpenLiteSpeed has NO top-level `include` directive (unlike
|
||||
## Apache's IncludeOptional that shared-httpd relies on). So we cannot just drop
|
||||
## per-vhost files in a dir and have OLS pick them up — the listener `map` lines
|
||||
## and the vhost stanzas must live IN httpd_config.conf. This script is the
|
||||
## "include" OLS lacks: it concatenates the panel's per-site pieces into one
|
||||
## valid httpd_config.conf, then the caller issues `lswsctrl restart`.
|
||||
## (Empirically established 2026-06-10 — see the OLS-tier PoC.)
|
||||
##
|
||||
## Per-site contract — the panel writes, for each site, a directory:
|
||||
## $SITES_ROOT/<vhname>/vhconf.conf (rendered from configs/shared-ols/vhconf.tpl)
|
||||
## $SITES_ROOT/<vhname>/site.meta (shell: VHNAME, VHROOT, DOMAINS="a.com,www.a.com")
|
||||
## This script turns each into a `virtualhost {configFile}` stanza + a listener
|
||||
## `map` line. A site dir missing either file is skipped (logged).
|
||||
##
|
||||
## Idempotent: always rebuilds from the stock config, so re-runs never compound.
|
||||
set -euo pipefail
|
||||
|
||||
LSWS_CONF=/usr/local/lsws/conf
|
||||
TPL_DIR=${TPL_DIR:-/etc/shared-ols-templates}
|
||||
SITES_ROOT=${SITES_ROOT:-$LSWS_CONF/shared-sites}
|
||||
LSCACHE_ROOT=${LSCACHE_ROOT:-/var/lscache}
|
||||
CERT_FILE=${CERT_FILE:-$LSWS_CONF/cert/shared-ols.crt}
|
||||
KEY_FILE=${KEY_FILE:-$LSWS_CONF/cert/shared-ols.key}
|
||||
export LSCACHE_ROOT
|
||||
|
||||
OUT="$LSWS_CONF/httpd_config.conf"
|
||||
STOCK="/usr/local/lsws/.conf/httpd_config.conf"
|
||||
|
||||
mkdir -p "$SITES_ROOT" "$LSCACHE_ROOT"
|
||||
|
||||
## --- 1. start from a pristine stock config (idempotent) ---
|
||||
if [ ! -f "$STOCK" ]; then
|
||||
## Some image builds keep the only copy at conf/; snapshot it once so future
|
||||
## renders have a clean base to strip.
|
||||
mkdir -p "$(dirname "$STOCK")"
|
||||
cp "$OUT" "$STOCK"
|
||||
fi
|
||||
|
||||
## --- 2. strip stock blocks that conflict or would run PHP LOCALLY ---
|
||||
## extProcessor lsphp (autoStart 1, uds) + the server scriptHandler are removed
|
||||
## so this server NEVER executes PHP itself — all PHP goes to remote sidecars.
|
||||
## listener HTTP/HTTPS + vhTemplate docker are removed (we add our own).
|
||||
awk '
|
||||
/^listener HTTP \{/ { skip=1; next }
|
||||
/^listener HTTPS \{/ { skip=1; next }
|
||||
/^vhTemplate docker ?\{/ { skip=1; next }
|
||||
/^extProcessor lsphp ?\{/{ skip=1; next }
|
||||
/^scriptHandler ?\{/ { skip=1; next }
|
||||
skip && /^\}/ { skip=0; next }
|
||||
!skip { print }
|
||||
' "$STOCK" > "$OUT"
|
||||
|
||||
## --- 3. append our server-level base (real-IP, cache module, no local PHP) ---
|
||||
{
|
||||
echo ""
|
||||
envsubst '${LSCACHE_ROOT}' < "$TPL_DIR/httpd_config_base.tpl"
|
||||
} >> "$OUT"
|
||||
|
||||
## --- 4. emit per-site vhost stanzas + collect listener map lines ---
|
||||
maps=""
|
||||
site_count=0
|
||||
for meta in "$SITES_ROOT"/*/site.meta; do
|
||||
[ -e "$meta" ] || continue
|
||||
sdir=$(dirname "$meta")
|
||||
VHNAME=""; VHROOT=""; DOMAINS=""
|
||||
# shellcheck source=/dev/null
|
||||
. "$meta"
|
||||
if [ -z "$VHNAME" ] || [ -z "$VHROOT" ] || [ -z "$DOMAINS" ] || [ ! -f "$sdir/vhconf.conf" ]; then
|
||||
echo "render-shared-ols: skipping $sdir (incomplete: VHNAME/VHROOT/DOMAINS/vhconf.conf)" >&2
|
||||
continue
|
||||
fi
|
||||
{
|
||||
echo ""
|
||||
echo "virtualhost ${VHNAME} {"
|
||||
echo " vhRoot ${VHROOT}"
|
||||
echo " configFile ${sdir}/vhconf.conf"
|
||||
echo " allowSymbolLink 1"
|
||||
echo " enableScript 1"
|
||||
echo " restrained 1"
|
||||
echo "}"
|
||||
} >> "$OUT"
|
||||
maps="${maps} map ${VHNAME} ${DOMAINS}"$'\n'
|
||||
site_count=$((site_count + 1))
|
||||
done
|
||||
|
||||
## --- 5. ALWAYS add a health vhost mapped to the catch-all so the server is
|
||||
## valid with zero customer sites and HAProxy health checks (which hit by IP /
|
||||
## unknown Host) get a 200. Exact-domain maps above win over this '*'. ---
|
||||
{
|
||||
echo ""
|
||||
echo "virtualhost _health {"
|
||||
echo " vhRoot /usr/local/lsws/shared-ols-health"
|
||||
echo " configFile /usr/local/lsws/shared-ols-health/vhconf.conf"
|
||||
echo " allowSymbolLink 1"
|
||||
echo " enableScript 0"
|
||||
echo "}"
|
||||
} >> "$OUT"
|
||||
maps="${maps} map _health *"$'\n'
|
||||
|
||||
## --- 6. listeners (HTTP :80 + HTTPS :443 self-signed) carrying ALL maps.
|
||||
## HAProxy terminates real TLS and connects to this tier on :443 ssl verify
|
||||
## none (same as shared-httpd), so :443 needs a cert — self-signed is fine. ---
|
||||
{
|
||||
echo ""
|
||||
echo "listener shared_http {"
|
||||
echo " address *:80"
|
||||
echo " secure 0"
|
||||
printf '%s' "$maps"
|
||||
echo "}"
|
||||
echo ""
|
||||
echo "listener shared_https {"
|
||||
echo " address *:443"
|
||||
echo " secure 1"
|
||||
echo " keyFile ${KEY_FILE}"
|
||||
echo " certFile ${CERT_FILE}"
|
||||
printf '%s' "$maps"
|
||||
echo "}"
|
||||
} >> "$OUT"
|
||||
|
||||
chown -R lsadm:nogroup "$LSWS_CONF" 2>/dev/null || true
|
||||
echo "render-shared-ols: wrote $OUT ($site_count customer vhost(s) + health)"
|
||||
Reference in New Issue
Block a user