Files
cloud-apache-container/.gitea/workflows/build-push.yaml
T
shadowdaoandClaude Opus 5.5 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>
2026-10-21 09:46:22 -07:00

436 lines
19 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Cloud Apache Container
run-name: ${{ gitea.actor }} pushed a change to trunk
on:
push:
branches:
- trunk
workflow_dispatch:
jobs:
# Shell gate. Runs FIRST and costs seconds; the images below do not depend on
# it (a red job here does not block a push that is otherwise fine), but it is
# the only place the ENTRYPOINT logic is executed at all. The .phpt suite and
# the Dockerfile's `lsphp -i` probe both test the extension, and both were
# green for the release whose entrypoint declared that same extension
# missing — see scripts/tests/lsphp-info-probe.test.sh for what went wrong
# and why it needed a test outside the image build to catch it.
Shell-Checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Runner images differ on whether they are root and whether sudo exists.
- name: Install shellcheck
run: |
if ! command -v shellcheck >/dev/null 2>&1; then
(apt-get update && apt-get install -y shellcheck) ||
(sudo apt-get update && sudo apt-get install -y shellcheck)
fi
shellcheck --version
- name: Syntax check every shell script
run: |
set -euo pipefail
find scripts ext -name '*.sh' -print0 | xargs -0 -n1 bash -n
# Deliberately NOT repo-wide. The older scripts (entrypoint.sh,
# entrypoint-fpm.sh, create-vhost.sh, create-php-config.sh,
# detect-memory.sh) carry pre-existing SC2154/SC2027 findings that predate
# this job; listing them here would make the gate red on arrival and
# therefore ignored. This is the set that is clean today — the scripts
# that run `set -o pipefail` plus the tests. Add files as they are fixed;
# do not add one that is not yet clean.
- name: shellcheck (warnings and above, on the clean set)
run: |
shellcheck -S warning \
scripts/entrypoint-lsphp.sh \
scripts/entrypoint-litespeed.sh \
scripts/entrypoint-shared-ols.sh \
scripts/render-shared-ols-config.sh \
scripts/ols-htaccess-watcher.sh \
scripts/create-vhost-litespeed.sh \
scripts/install-lscache-wp.sh \
scripts/lsphp-apt-upgrade.sh \
scripts/lsphp-src-build.sh \
scripts/lsphp-src-overlay.sh \
scripts/lsphp-abi-gate.sh \
scripts/tune-mpm.sh \
scripts/tests/lsphp-info-probe.test.sh \
scripts/tests/lsphp-abi-gate.test.sh
- name: lsphp probe regression test
run: ./scripts/tests/lsphp-info-probe.test.sh
# The emergency php.net lsphp path (lsphp-source-emergency.yaml) keeps
# LiteSpeed's PECL .so files only if this gate says the Zend ABI is
# unchanged; prove here, on every push, that the gate can still FAIL.
- name: lsphp ABI gate regression test
run: |
if ! command -v pahole >/dev/null 2>&1 || ! command -v gcc >/dev/null 2>&1; then
(apt-get update && apt-get install -y gcc dwarves) ||
(sudo apt-get update && sudo apt-get install -y gcc dwarves)
fi
./scripts/tests/lsphp-abi-gate.test.sh
Build-and-Push:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
phpver: [74, 80, 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
# Per-run cache-bust value. Each job's buildx builder is created fresh by
# setup-buildx-action (no cache-from/cache-to here), so today nothing is
# cached between runs — this keeps it that way if a cache is ever added:
# the package-upgrade layers consume CACHE_BUST and can never be reused.
- name: Cache-bust value
id: bust
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
# The minimum PHP version this build must ship: php.net's current release for
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
- name: Resolve current PHP release from php.net
id: phpmin
run: |
set -euo pipefail
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
echo "php.net current ${branch}: ${min}"
echo "min=${min}" >> "$GITHUB_OUTPUT"
- 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 Image
uses: docker/build-push-action@v6
with:
platforms: linux/amd64
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
Build-FPM-Images:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
phpver: [74, 80, 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
# Per-run cache-bust value. Each job's buildx builder is created fresh by
# setup-buildx-action (no cache-from/cache-to here), so today nothing is
# cached between runs — this keeps it that way if a cache is ever added:
# the package-upgrade layers consume CACHE_BUST and can never be reused.
- name: Cache-bust value
id: bust
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
# The minimum PHP version this build must ship: php.net's current release for
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
- name: Resolve current PHP release from php.net
id: phpmin
run: |
set -euo pipefail
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
echo "php.net current ${branch}: ${min}"
echo "min=${min}" >> "$GITHUB_OUTPUT"
- 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 FPM Image
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.fpm
platforms: linux/amd64
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
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:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
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
# Per-run cache-bust value. Each job's buildx builder is created fresh by
# setup-buildx-action (no cache-from/cache-to here), so today nothing is
# cached between runs — this keeps it that way if a cache is ever added:
# the package-upgrade layers consume CACHE_BUST and can never be reused.
- name: Cache-bust value
id: bust
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
# Ask the LiteSpeed apt repo (the same one the base image's apt uses) for the
# CURRENT lsphp version. The Dockerfile asserts the installed package equals
# this (build fails if the repo changed mid-build — just re-run) and writes
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
id: lsphp
run: |
set -euo pipefail
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
if [ -z "$pkg" ]; then echo "::error::lsphp${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi
echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})"
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
echo "php=${pkg%%-*}" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
registry: repo.anhonesthost.net
username: ${{ secrets.CI_USER }}
password: ${{ secrets.CI_TOKEN }}
# A php.net emergency build (lsphp-source-emergency.yaml) may be what
# :phpNN points at right now. While LiteSpeed still lags, a normal build
# would silently put the VULNERABLE packaged lsphp back. Refuse instead;
# the job goes green again by itself once the LiteSpeed repo catches up.
# Fail-open if the tag cannot be inspected (first build, registry blip).
- name: Do not roll back a newer php.net emergency image
run: |
set -euo pipefail
ref=repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${{ matrix.phpver }}
src=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.lsphp-source" }}' 2>/dev/null || true)
cur=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.php-version" }}' 2>/dev/null || true)
new='${{ steps.lsphp.outputs.php }}'
echo "published $ref: source=${src:-?} php=${cur:-?}; this build: packaged $new"
if [ "$src" = "php.net" ] && [ "$cur" != "$new" ] && [ "$(printf '%s\n%s\n' "$cur" "$new" | sort -V | tail -n1)" = "$cur" ]; then
echo "::error::$ref is a php.net emergency build of PHP $cur; LiteSpeed still ships $new. Not overwriting it with an older PHP. Re-run lsphp-source-emergency.yaml for a newer fix, or wait for LiteSpeed."
exit 1
fi
- 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
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
# 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-LSPHP-Images:
runs-on: ubuntu-latest
strategy:
# One PHP version failing (e.g. an EOL stream) must not cancel the
# security builds of the others.
fail-fast: false
matrix:
# Same PHP matrix as cac-litespeed (81–85): cac-lsphp is the detached
# backend for the shared-ols tier and shares the litespeed prebuilt
# base, which only ships lsphp for 8.1+. Keep this matrix in lockstep
# with Build-LiteSpeed-Images.
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
# Per-run cache-bust value. Each job's buildx builder is created fresh by
# setup-buildx-action (no cache-from/cache-to here), so today nothing is
# cached between runs — this keeps it that way if a cache is ever added:
# the package-upgrade layers consume CACHE_BUST and can never be reused.
- name: Cache-bust value
id: bust
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
# Ask the LiteSpeed apt repo (the same one the base image's apt uses) for the
# CURRENT lsphp version. The Dockerfile asserts the installed package equals
# this (build fails if the repo changed mid-build — just re-run) and writes
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
id: lsphp
run: |
set -euo pipefail
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
if [ -z "$pkg" ]; then echo "::error::lsphp${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi
echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})"
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
echo "php=${pkg%%-*}" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
registry: repo.anhonesthost.net
username: ${{ secrets.CI_USER }}
password: ${{ secrets.CI_TOKEN }}
# A php.net emergency build (lsphp-source-emergency.yaml) may be what
# :phpNN points at right now. While LiteSpeed still lags, a normal build
# would silently put the VULNERABLE packaged lsphp back. Refuse instead;
# the job goes green again by itself once the LiteSpeed repo catches up.
# Fail-open if the tag cannot be inspected (first build, registry blip).
- name: Do not roll back a newer php.net emergency image
run: |
set -euo pipefail
ref=repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:php${{ matrix.phpver }}
src=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.lsphp-source" }}' 2>/dev/null || true)
cur=$(docker buildx imagetools inspect "$ref" --format '{{ index .Image.Config.Labels "org.whp.php-version" }}' 2>/dev/null || true)
new='${{ steps.lsphp.outputs.php }}'
echo "published $ref: source=${src:-?} php=${cur:-?}; this build: packaged $new"
if [ "$src" = "php.net" ] && [ "$cur" != "$new" ] && [ "$(printf '%s\n%s\n' "$cur" "$new" | sort -V | tail -n1)" = "$cur" ]; then
echo "::error::$ref is a php.net emergency build of PHP $cur; LiteSpeed still ships $new. Not overwriting it with an older PHP. Re-run lsphp-source-emergency.yaml for a newer fix, or wait for LiteSpeed."
exit 1
fi
- name: Build and Push lsphp Image
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.lsphp
platforms: linux/amd64
push: true
build-args: |
PHPVER=${{ matrix.phpver }}
OLS_VERSION=1.8.4
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
# OLS_VERSION pinned to 1.8.4 to match Build-LiteSpeed-Images — same
# prebuilt base, same lsphp binaries. Bump both together.
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:latest' || '' }}
Build-Shared-httpd:
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 httpd Image
uses: docker/build-push-action@v6
with:
file: ./Dockerfile.shared-httpd
platforms: linux/amd64
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