Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b9e7f302c | ||
|
|
18f65c6d84 | ||
|
|
b2bec5b3ee | ||
|
|
fa7808897f | ||
|
|
42993f42bd | ||
|
|
83c2ee9c2a | ||
|
|
be1540f559 | ||
|
|
af7fd193e5 | ||
|
|
e8e3bfd490 | ||
|
|
c49f456336 | ||
|
|
bcd737a9e2 | ||
|
|
955aec4573 | ||
|
|
998eb17f39 | ||
|
|
f461d22aae | ||
|
|
cfde82ae44 | ||
|
|
132e591503 | ||
|
|
642b97707d | ||
|
|
71ca114529 | ||
|
|
74e12407f8 | ||
|
|
74648ba34b | ||
|
|
86a88c87db | ||
|
|
02d93f4d1a | ||
|
|
94a73ea8ab | ||
|
|
0ffccac4b8 | ||
|
|
eaf616eb30 | ||
|
|
14761597bb | ||
|
|
db19ee24c3 | ||
|
|
3841b18820 | ||
|
|
48b714447f |
No files matched your search
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- trunk
|
- trunk
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Shell gate. Runs FIRST and costs seconds; the images below do not depend on
|
# Shell gate. Runs FIRST and costs seconds; the images below do not depend on
|
||||||
@@ -50,26 +51,104 @@ jobs:
|
|||||||
scripts/ols-htaccess-watcher.sh \
|
scripts/ols-htaccess-watcher.sh \
|
||||||
scripts/create-vhost-litespeed.sh \
|
scripts/create-vhost-litespeed.sh \
|
||||||
scripts/install-lscache-wp.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/tune-mpm.sh \
|
||||||
scripts/tests/lsphp-info-probe.test.sh
|
scripts/tests/lsphp-info-probe.test.sh \
|
||||||
|
scripts/tests/lsphp-abi-gate.test.sh
|
||||||
|
|
||||||
- name: lsphp probe regression test
|
- name: lsphp probe regression test
|
||||||
run: ./scripts/tests/lsphp-info-probe.test.sh
|
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:
|
Build-and-Push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||||
|
# security builds of the others.
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
phpver: [74, 80, 81, 82, 83, 84, 85]
|
phpver: [74, 80, 81, 82, 83, 84, 85]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||||
|
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||||
|
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||||
|
# exactly as before. The repo cache and the registry mirror are probed
|
||||||
|
# separately so either can fail back on its own.
|
||||||
|
- name: Probe package cache
|
||||||
|
id: pkgcache
|
||||||
|
run: |
|
||||||
|
url=""; mirror=""
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||||
|
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<unreachable: Docker Hub>}"
|
||||||
|
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||||
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
|
# back to Docker Hub if the mirror stops answering mid-run.
|
||||||
|
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||||
|
if: steps.pkgcache.outputs.mirror != ''
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
buildkitd-config-inline: |
|
||||||
|
[registry."docker.io"]
|
||||||
|
mirrors = ["192.168.1.40:5000"]
|
||||||
|
[registry."192.168.1.40:5000"]
|
||||||
|
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
if: steps.pkgcache.outputs.mirror == ''
|
||||||
|
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
|
||||||
|
env:
|
||||||
|
RUN_ID: ${{ gitea.run_id }}
|
||||||
|
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${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
|
||||||
|
# Values reach the script via env, never via ${{ }} inside run: (a
|
||||||
|
# crafted string would otherwise be shell); outputs are regex-checked.
|
||||||
|
env:
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
v="$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
|
||||||
|
[[ "$min" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::php.net returned a malformed version for ${branch}"; exit 1; }
|
||||||
|
echo "php.net current ${branch}: ${min}"
|
||||||
|
echo "min=${min}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Login to Gitea
|
- name: Login to Gitea
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -85,6 +164,9 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
|
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||||
tags: |
|
tags: |
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
|
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
|
||||||
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
|
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
|
||||||
@@ -92,17 +174,79 @@ jobs:
|
|||||||
Build-FPM-Images:
|
Build-FPM-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||||
|
# security builds of the others.
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
phpver: [74, 80, 81, 82, 83, 84, 85]
|
phpver: [74, 80, 81, 82, 83, 84, 85]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||||
|
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||||
|
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||||
|
# exactly as before. The repo cache and the registry mirror are probed
|
||||||
|
# separately so either can fail back on its own.
|
||||||
|
- name: Probe package cache
|
||||||
|
id: pkgcache
|
||||||
|
run: |
|
||||||
|
url=""; mirror=""
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||||
|
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<unreachable: Docker Hub>}"
|
||||||
|
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||||
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
|
# back to Docker Hub if the mirror stops answering mid-run.
|
||||||
|
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||||
|
if: steps.pkgcache.outputs.mirror != ''
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
buildkitd-config-inline: |
|
||||||
|
[registry."docker.io"]
|
||||||
|
mirrors = ["192.168.1.40:5000"]
|
||||||
|
[registry."192.168.1.40:5000"]
|
||||||
|
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
if: steps.pkgcache.outputs.mirror == ''
|
||||||
|
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
|
||||||
|
env:
|
||||||
|
RUN_ID: ${{ gitea.run_id }}
|
||||||
|
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${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
|
||||||
|
# Values reach the script via env, never via ${{ }} inside run: (a
|
||||||
|
# crafted string would otherwise be shell); outputs are regex-checked.
|
||||||
|
env:
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
v="$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
|
||||||
|
[[ "$min" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::php.net returned a malformed version for ${branch}"; exit 1; }
|
||||||
|
echo "php.net current ${branch}: ${min}"
|
||||||
|
echo "min=${min}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Login to Gitea
|
- name: Login to Gitea
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -119,6 +263,9 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
|
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
|
||||||
tags: |
|
tags: |
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
|
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
|
||||||
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
|
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
|
||||||
@@ -126,6 +273,9 @@ jobs:
|
|||||||
Build-LiteSpeed-Images:
|
Build-LiteSpeed-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||||
|
# security builds of the others.
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# PHP 7.4/8.0 deliberately excluded — the LiteSpeed prebuilt base
|
# PHP 7.4/8.0 deliberately excluded — the LiteSpeed prebuilt base
|
||||||
# images stop at older OLS releases for those PHP versions, and the
|
# images stop at older OLS releases for those PHP versions, and the
|
||||||
@@ -142,6 +292,38 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
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
|
||||||
|
env:
|
||||||
|
RUN_ID: ${{ gitea.run_id }}
|
||||||
|
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${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
|
||||||
|
# HTTPS (rpms.litespeedtech.com serves it; measured 2026-10-21), values via
|
||||||
|
# env, and both outputs regex-checked before they reach GITHUB_OUTPUT.
|
||||||
|
env:
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
idx=$(curl -fsS --proto '=https' --retry 3 --max-time 60 https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||||
|
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${PHPVER}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||||
|
if [ -z "$pkg" ]; then echo "::error::lsphp${PHPVER} not found in the LiteSpeed repo index"; exit 1; fi
|
||||||
|
php="${pkg%%-*}"
|
||||||
|
[[ "$pkg" =~ ^[0-9][0-9A-Za-z.+~:-]*$ ]] || { echo "::error::malformed lsphp${PHPVER} package version in the repo index"; exit 1; }
|
||||||
|
[[ "$php" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed PHP version '${php}' derived from lsphp${PHPVER} ${pkg}"; exit 1; }
|
||||||
|
echo "lsphp${PHPVER} in repo: $pkg (PHP ${php})"
|
||||||
|
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "php=${php}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Login to Gitea
|
- name: Login to Gitea
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -149,6 +331,27 @@ jobs:
|
|||||||
username: ${{ secrets.CI_USER }}
|
username: ${{ secrets.CI_USER }}
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
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
|
||||||
|
env:
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
NEW_PHP: ${{ steps.lsphp.outputs.php }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
ref="repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${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="$NEW_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
|
- name: Build and Push LiteSpeed Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -158,6 +361,9 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
OLS_VERSION=1.8.4
|
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
|
# 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
|
# 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
|
# lsphp81 variant on Docker Hub). Bump alongside a local rebuild
|
||||||
@@ -170,6 +376,9 @@ jobs:
|
|||||||
Build-LSPHP-Images:
|
Build-LSPHP-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
# One PHP version failing (e.g. an EOL stream) must not cancel the
|
||||||
|
# security builds of the others.
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Same PHP matrix as cac-litespeed (81–85): cac-lsphp is the detached
|
# Same PHP matrix as cac-litespeed (81–85): cac-lsphp is the detached
|
||||||
# backend for the shared-ols tier and shares the litespeed prebuilt
|
# backend for the shared-ols tier and shares the litespeed prebuilt
|
||||||
@@ -186,6 +395,38 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
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
|
||||||
|
env:
|
||||||
|
RUN_ID: ${{ gitea.run_id }}
|
||||||
|
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${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
|
||||||
|
# HTTPS (rpms.litespeedtech.com serves it; measured 2026-10-21), values via
|
||||||
|
# env, and both outputs regex-checked before they reach GITHUB_OUTPUT.
|
||||||
|
env:
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
idx=$(curl -fsS --proto '=https' --retry 3 --max-time 60 https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||||
|
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${PHPVER}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||||
|
if [ -z "$pkg" ]; then echo "::error::lsphp${PHPVER} not found in the LiteSpeed repo index"; exit 1; fi
|
||||||
|
php="${pkg%%-*}"
|
||||||
|
[[ "$pkg" =~ ^[0-9][0-9A-Za-z.+~:-]*$ ]] || { echo "::error::malformed lsphp${PHPVER} package version in the repo index"; exit 1; }
|
||||||
|
[[ "$php" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed PHP version '${php}' derived from lsphp${PHPVER} ${pkg}"; exit 1; }
|
||||||
|
echo "lsphp${PHPVER} in repo: $pkg (PHP ${php})"
|
||||||
|
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "php=${php}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Login to Gitea
|
- name: Login to Gitea
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -193,6 +434,27 @@ jobs:
|
|||||||
username: ${{ secrets.CI_USER }}
|
username: ${{ secrets.CI_USER }}
|
||||||
password: ${{ secrets.CI_TOKEN }}
|
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
|
||||||
|
env:
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
NEW_PHP: ${{ steps.lsphp.outputs.php }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
ref="repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:php${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="$NEW_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
|
- name: Build and Push lsphp Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -202,6 +464,9 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
OLS_VERSION=1.8.4
|
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
|
# OLS_VERSION pinned to 1.8.4 to match Build-LiteSpeed-Images — same
|
||||||
# prebuilt base, same lsphp binaries. Bump both together.
|
# prebuilt base, same lsphp binaries. Bump both together.
|
||||||
tags: |
|
tags: |
|
||||||
@@ -214,10 +479,40 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||||
|
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||||
|
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||||
|
# exactly as before. The repo cache and the registry mirror are probed
|
||||||
|
# separately so either can fail back on its own.
|
||||||
|
- name: Probe package cache
|
||||||
|
id: pkgcache
|
||||||
|
run: |
|
||||||
|
url=""; mirror=""
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||||
|
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<unreachable: Docker Hub>}"
|
||||||
|
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||||
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
|
# back to Docker Hub if the mirror stops answering mid-run.
|
||||||
|
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||||
|
if: steps.pkgcache.outputs.mirror != ''
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
buildkitd-config-inline: |
|
||||||
|
[registry."docker.io"]
|
||||||
|
mirrors = ["192.168.1.40:5000"]
|
||||||
|
[registry."192.168.1.40:5000"]
|
||||||
|
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
if: steps.pkgcache.outputs.mirror == ''
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Gitea
|
- name: Login to Gitea
|
||||||
@@ -233,6 +528,8 @@ jobs:
|
|||||||
file: ./Dockerfile.shared-httpd
|
file: ./Dockerfile.shared-httpd
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
|
build-args: |
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
tags: |
|
tags: |
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest
|
repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest
|
||||||
|
|
||||||
@@ -242,10 +539,40 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Internal package cache (CT130 pkg-cache, docs/pkg-cache.md). Used ONLY if
|
||||||
|
# it answers over TLS with our pinned CA; otherwise (down, unreachable,
|
||||||
|
# bad/expired cert) this job builds from the upstream repos and Docker Hub
|
||||||
|
# exactly as before. The repo cache and the registry mirror are probed
|
||||||
|
# separately so either can fail back on its own.
|
||||||
|
- name: Probe package cache
|
||||||
|
id: pkgcache
|
||||||
|
run: |
|
||||||
|
url=""; mirror=""
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/; then url="https://192.168.1.40"; fi
|
||||||
|
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40:5000/v2/; then mirror="192.168.1.40:5000"; fi
|
||||||
|
echo "pkg-cache: repos=${url:-<unreachable: upstream>} registry-mirror=${mirror:-<unreachable: Docker Hub>}"
|
||||||
|
[ -n "$url" ] || echo "::warning::pkg-cache unreachable - building from upstream repositories"
|
||||||
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "mirror=${mirror}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
# BuildKit (docker-container driver) ignores dockerd's registry-mirrors,
|
||||||
|
# so the mirror is configured on the builder; BuildKit itself still falls
|
||||||
|
# back to Docker Hub if the mirror stops answering mid-run.
|
||||||
|
- name: Set up Docker Buildx (Docker Hub via pkg-cache mirror)
|
||||||
|
if: steps.pkgcache.outputs.mirror != ''
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
buildkitd-config-inline: |
|
||||||
|
[registry."docker.io"]
|
||||||
|
mirrors = ["192.168.1.40:5000"]
|
||||||
|
[registry."192.168.1.40:5000"]
|
||||||
|
ca = ["${{ github.workspace }}/keys/pkg-cache/ca.crt"]
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
if: steps.pkgcache.outputs.mirror == ''
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Gitea
|
- name: Login to Gitea
|
||||||
@@ -266,5 +593,6 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=83
|
PHPVER=83
|
||||||
OLS_VERSION=1.8.4
|
OLS_VERSION=1.8.4
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
tags: |
|
tags: |
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/shared-ols:latest
|
repo.anhonesthost.net/cloud-hosting-platform/shared-ols:latest
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
name: lsphp from php.net source (EMERGENCY)
|
||||||
|
run-name: EMERGENCY lsphp php.net build by ${{ gitea.actor }}
|
||||||
|
|
||||||
|
## NEVER runs on push. Use only when a PHP security release is out and the
|
||||||
|
## LiteSpeed apt repo (rpms.litespeedtech.com/debian, noble) still ships the
|
||||||
|
## vulnerable lsphp. Runbook + exact trigger command: docs/lsphp-source-emergency.md
|
||||||
|
##
|
||||||
|
## What it builds: the normal Dockerfile.lsphp / Dockerfile.litespeed with
|
||||||
|
## PHP_SRC_VERSION / PHP_SRC_SHA256 / LSPHP_SOURCE=php.net
|
||||||
|
## so lsphp${PHPVER} is compiled from the php.net tarball (sha256 from the
|
||||||
|
## php.net releases JSON + OpenPGP signature against keys/php-src/), configured
|
||||||
|
## from the packaged lsphp's own Configure Command, and the image build FAILS
|
||||||
|
## unless the result has the same extensions / ini / features as the packaged
|
||||||
|
## lsphp (scripts/lsphp-src-overlay.sh).
|
||||||
|
##
|
||||||
|
## Tags: always :phpNN-phpnet-X.Y.Z (immutable, for staging / rollback)
|
||||||
|
## and :phpNN only when push_production_tags = true.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
php_versions:
|
||||||
|
description: "Space-separated PHPVER=X.Y.Z pairs, e.g. '82=8.2.35 83=8.3.36 84=8.4.27 85=8.5.12'"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
images:
|
||||||
|
description: "Which images: both | lsphp | litespeed"
|
||||||
|
required: true
|
||||||
|
default: both
|
||||||
|
type: string
|
||||||
|
push_production_tags:
|
||||||
|
description: "Also push :phpNN (what WHP pulls). false = only :phpNN-phpnet-X.Y.Z for staging."
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build-lsphp-from-source:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
phpver: [81, 82, 83, 84, 85]
|
||||||
|
image: [lsphp, litespeed]
|
||||||
|
steps:
|
||||||
|
- name: Select this job's target (skip if not requested)
|
||||||
|
id: t
|
||||||
|
# inputs via env, never spliced into the script text
|
||||||
|
env:
|
||||||
|
IN_VERSIONS: ${{ inputs.php_versions }}
|
||||||
|
IN_IMAGES: ${{ inputs.images }}
|
||||||
|
IN_PROD: ${{ format('{0}', inputs.push_production_tags) }}
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
IMG: ${{ matrix.image }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
case "$IN_IMAGES" in both|lsphp|litespeed) ;; *) echo "::error::images must be both|lsphp|litespeed, got '$IN_IMAGES'"; exit 1 ;; esac
|
||||||
|
case "$IN_PROD" in true|false) ;; *) echo "::error::push_production_tags must be true|false, got '$IN_PROD'"; exit 1 ;; esac
|
||||||
|
v=""
|
||||||
|
for pair in $IN_VERSIONS; do
|
||||||
|
[[ "$pair" =~ ^(8[1-5])=([0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo "::error::bad php_versions entry '$pair' (want NN=X.Y.Z, NN in 81..85)"; exit 1; }
|
||||||
|
[ "${BASH_REMATCH[1]}" = "$PHPVER" ] && v="${BASH_REMATCH[2]}"
|
||||||
|
done
|
||||||
|
echo "prod=$IN_PROD" >> "$GITHUB_OUTPUT"
|
||||||
|
if [ -z "$v" ] || { [ "$IN_IMAGES" != both ] && [ "$IN_IMAGES" != "$IMG" ]; }; then
|
||||||
|
echo "not requested: php${PHPVER} / ${IMG}"; echo "skip=1" >> "$GITHUB_OUTPUT"; exit 0
|
||||||
|
fi
|
||||||
|
echo "skip=0" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||||
|
case "$IMG" in lsphp|litespeed) ;; *) echo "::error::unexpected matrix image '$IMG'"; exit 1 ;; esac
|
||||||
|
echo "image_name=cac-${IMG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "dockerfile=./Dockerfile.${IMG}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
if: steps.t.outputs.skip == '0'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# sha256 of php-X.Y.Z.tar.xz from php.net's own releases JSON. The build
|
||||||
|
# re-verifies it AND the OpenPGP signature; this step only refuses early
|
||||||
|
# if the release does not exist or is not newer than what LiteSpeed ships.
|
||||||
|
- name: Resolve php.net release + packaged lsphp
|
||||||
|
if: steps.t.outputs.skip == '0'
|
||||||
|
id: v
|
||||||
|
env:
|
||||||
|
V: ${{ steps.t.outputs.version }}
|
||||||
|
PHPVER: ${{ matrix.phpver }}
|
||||||
|
RUN_ID: ${{ gitea.run_id }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
v="$V"
|
||||||
|
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed version '$v'"; exit 1; }
|
||||||
|
json=$(curl -fsS --retry 3 --max-time 60 "https://www.php.net/releases/index.php?json&version=$v")
|
||||||
|
sha=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); v=sys.argv[1]; print(next((s["sha256"] for s in d.get("source", []) if s.get("filename")=="php-%s.tar.xz"%v), ""))' "$v")
|
||||||
|
[ -n "$sha" ] || { echo "::error::php.net has no release php-$v.tar.xz (EOL branch? typo?): $json"; exit 1; }
|
||||||
|
[[ "$sha" =~ ^[0-9a-f]{64}$ ]] || { echo "::error::no sha256 for php-$v.tar.xz in the php.net releases JSON"; exit 1; }
|
||||||
|
idx=$(curl -fsS --proto '=https' --retry 3 --max-time 60 https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
||||||
|
pkg=$(printf '%s\n' "$idx" | awk -v p="lsphp${PHPVER}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
||||||
|
[ -n "$pkg" ] || { echo "::error::lsphp${PHPVER} not in the LiteSpeed repo index"; exit 1; }
|
||||||
|
[[ "$pkg" =~ ^[0-9][0-9A-Za-z.+~:-]*$ ]] || { echo "::error::malformed lsphp${PHPVER} package version in the repo index"; exit 1; }
|
||||||
|
pphp=${pkg%%-*}
|
||||||
|
[[ "$pphp" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::malformed PHP version '$pphp' from lsphp${PHPVER} $pkg"; exit 1; }
|
||||||
|
if [ "$(printf '%s\n%s\n' "$pphp" "$v" | sort -V | tail -n1)" != "$v" ] || [ "$pphp" = "$v" ]; then
|
||||||
|
echo "::error::LiteSpeed already ships lsphp${PHPVER} $pkg (PHP $pphp) >= $v — use the normal build (push to trunk / dispatch build-push.yaml)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "php.net $v sha256=$sha ; LiteSpeed lsphp${PHPVER}=$pkg"
|
||||||
|
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pkgphp=$pphp" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "bust=$(date -u +%Y%m%dT%H%M%SZ)-${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
if: steps.t.outputs.skip == '0'
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Gitea
|
||||||
|
if: steps.t.outputs.skip == '0'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: repo.anhonesthost.net
|
||||||
|
username: ${{ secrets.CI_USER }}
|
||||||
|
password: ${{ secrets.CI_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push (php.net lsphp)
|
||||||
|
if: steps.t.outputs.skip == '0'
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: ${{ steps.t.outputs.dockerfile }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
PHPVER=${{ matrix.phpver }}
|
||||||
|
OLS_VERSION=1.8.4
|
||||||
|
CACHE_BUST=${{ steps.v.outputs.bust }}
|
||||||
|
EXPECT_LSPHP_PKG=${{ steps.v.outputs.pkg }}
|
||||||
|
EXPECT_PHP_VERSION=${{ steps.v.outputs.pkgphp }}
|
||||||
|
PHP_SRC_VERSION=${{ steps.t.outputs.version }}
|
||||||
|
PHP_SRC_SHA256=${{ steps.v.outputs.sha }}
|
||||||
|
LSPHP_SOURCE=php.net
|
||||||
|
tags: |
|
||||||
|
repo.anhonesthost.net/cloud-hosting-platform/${{ steps.t.outputs.image_name }}:php${{ matrix.phpver }}-phpnet-${{ steps.t.outputs.version }}
|
||||||
|
${{ steps.t.outputs.prod == 'true' && format('repo.anhonesthost.net/cloud-hosting-platform/{0}:php{1}', steps.t.outputs.image_name, matrix.phpver) || '' }}
|
||||||
|
${{ steps.t.outputs.prod == 'true' && matrix.phpver == 85 && format('repo.anhonesthost.net/cloud-hosting-platform/{0}:latest', steps.t.outputs.image_name) || '' }}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
cloud-hosting-platform/cloud-apache-container
|
||||||
+42
-4
@@ -1,13 +1,36 @@
|
|||||||
FROM almalinux/10-base
|
FROM almalinux/10-base
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
|
# Per-CI-run value: the first RUN below (dnf update) and everything after it can
|
||||||
|
# never be a layer-cache hit, so every build gets the repos' current packages.
|
||||||
|
# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache.
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
# php.net's current release for this branch (CI); the build fails on older PHP.
|
||||||
|
ARG EXPECT_PHP_MIN=
|
||||||
|
|
||||||
|
# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
# (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||||
|
# GPG-verified against keys/pkg-cache/ either way. Repo files
|
||||||
|
# are restored at the end of every RUN, so no layer keeps the cache address.
|
||||||
|
# See scripts/pkg-cache-repos.sh.
|
||||||
|
ARG PKG_CACHE=
|
||||||
|
COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh
|
||||||
|
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||||
# Install repos, update, install only needed packages, clean up in one layer
|
# Install repos, update, install only needed packages, clean up in one layer
|
||||||
RUN dnf install -y \
|
RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm \
|
/usr/local/sbin/pkg-cache-repos.sh release-install epel remi && \
|
||||||
https://rpms.remirepo.net/enterprise/remi-release-10.rpm && \
|
# Use Remi's origin, not the mirrorlist: during the 2026-10-21 PHP security
|
||||||
|
# release the mirrors lagged hours behind (still 8.2.34 when 8.2.35 was out),
|
||||||
|
# so builds shipped unpatched PHP. HTTPS first; plain http only as a fallback
|
||||||
|
# because the origin's HTTPS dropped/timed out under that day's load. A
|
||||||
|
# replayed (old) http index can't ship old PHP: packages stay GPG-checked and
|
||||||
|
# the EXPECT_PHP_MIN gate below fails the build on anything older than php.net.
|
||||||
|
sed -i -e 's|^#baseurl=http://\(.*\)$|baseurl=https://\1 http://\1|' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
|
printf 'retries=10\ntimeout=60\n' >> /etc/dnf/dnf.conf && \
|
||||||
dnf update -y && \
|
dnf update -y && \
|
||||||
dnf install -y httpd mod_ssl openssl wget procps cronie iproute postgresql-devel microdnf less git \
|
dnf install -y httpd mod_ssl openssl wget procps cronie iproute postgresql-devel microdnf less git \
|
||||||
nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix && \
|
nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
dnf clean all && \
|
dnf clean all && \
|
||||||
rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/*
|
rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/*
|
||||||
|
|
||||||
@@ -16,13 +39,23 @@ COPY ./scripts/ /scripts/
|
|||||||
RUN chmod +x /scripts/*
|
RUN chmod +x /scripts/*
|
||||||
|
|
||||||
# Install ImageMagick from EPEL with HEIC/HEIF/AVIF support
|
# Install ImageMagick from EPEL with HEIC/HEIF/AVIF support
|
||||||
RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \
|
RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
|
dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
dnf clean all
|
dnf clean all
|
||||||
|
|
||||||
# Generate self-signed cert, create needed dirs, install PHP, clean up
|
# Generate self-signed cert, create needed dirs, install PHP, clean up
|
||||||
RUN openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key -x509 -days 3650 -subj "/CN=localhost" -out /etc/pki/tls/certs/localhost.crt && \
|
RUN openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key -x509 -days 3650 -subj "/CN=localhost" -out /etc/pki/tls/certs/localhost.crt && \
|
||||||
mkdir -p /run/php-fpm/ && \
|
mkdir -p /run/php-fpm/ && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
/scripts/install-php$PHPVER.sh && \
|
/scripts/install-php$PHPVER.sh && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
|
# CI passes php.net's current release for the branch; never ship older PHP.
|
||||||
|
if [ -n "$EXPECT_PHP_MIN" ]; then \
|
||||||
|
php -r 'exit(version_compare(PHP_VERSION, getenv("EXPECT_PHP_MIN"), ">=") ? 0 : 1);' || \
|
||||||
|
{ echo "FATAL: built PHP $(php -r 'echo PHP_VERSION;') is older than php.net $EXPECT_PHP_MIN"; exit 1; }; \
|
||||||
|
else echo "WARNING: no EXPECT_PHP_MIN (local build) - PHP version not gated"; fi && \
|
||||||
|
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
# Download and install wp-cli (consider pinning version for reproducibility)
|
# Download and install wp-cli (consider pinning version for reproducibility)
|
||||||
@@ -36,10 +69,15 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
|
|||||||
# Copy configs and web files
|
# Copy configs and web files
|
||||||
COPY ./configs/default-index.conf /etc/httpd/conf.d/
|
COPY ./configs/default-index.conf /etc/httpd/conf.d/
|
||||||
COPY ./configs/prod-php.ini /etc/php.ini
|
COPY ./configs/prod-php.ini /etc/php.ini
|
||||||
|
# php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
|
||||||
|
# every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
|
||||||
|
# all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
|
||||||
|
COPY ./configs/zz-php2shell-mitigation.ini /etc/php.d/zz-php2shell-mitigation.ini
|
||||||
COPY ./configs/phpinfo.php /var/www/html/
|
COPY ./configs/phpinfo.php /var/www/html/
|
||||||
COPY ./configs/mariadb.repo /etc/yum.repos.d/
|
COPY ./configs/mariadb.repo /etc/yum.repos.d/
|
||||||
COPY ./configs/index.php /var/www/html/
|
COPY ./configs/index.php /var/www/html/
|
||||||
COPY ./configs/remote_ip.conf /etc/httpd/conf.d/
|
COPY ./configs/remote_ip.conf /etc/httpd/conf.d/
|
||||||
|
COPY ./configs/whp-security.conf /etc/httpd/conf.d/
|
||||||
|
|
||||||
# Set up cron job in a single layer
|
# Set up cron job in a single layer
|
||||||
RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
||||||
|
|||||||
+41
-4
@@ -1,13 +1,36 @@
|
|||||||
FROM almalinux/10-base
|
FROM almalinux/10-base
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
|
# Per-CI-run value: the first RUN below (dnf update) and everything after it can
|
||||||
|
# never be a layer-cache hit, so every build gets the repos' current packages.
|
||||||
|
# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache.
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
# php.net's current release for this branch (CI); the build fails on older PHP.
|
||||||
|
ARG EXPECT_PHP_MIN=
|
||||||
|
|
||||||
|
# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
# (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||||
|
# GPG-verified against keys/pkg-cache/ either way. Repo files
|
||||||
|
# are restored at the end of every RUN, so no layer keeps the cache address.
|
||||||
|
# See scripts/pkg-cache-repos.sh.
|
||||||
|
ARG PKG_CACHE=
|
||||||
|
COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh
|
||||||
|
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||||
# Install repos, update, install only needed packages (no httpd/mod_ssl), clean up in one layer
|
# Install repos, update, install only needed packages (no httpd/mod_ssl), clean up in one layer
|
||||||
RUN dnf install -y \
|
RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm \
|
/usr/local/sbin/pkg-cache-repos.sh release-install epel remi && \
|
||||||
https://rpms.remirepo.net/enterprise/remi-release-10.rpm && \
|
# Use Remi's origin, not the mirrorlist: during the 2026-10-21 PHP security
|
||||||
|
# release the mirrors lagged hours behind (still 8.2.34 when 8.2.35 was out),
|
||||||
|
# so builds shipped unpatched PHP. HTTPS first; plain http only as a fallback
|
||||||
|
# because the origin's HTTPS dropped/timed out under that day's load. A
|
||||||
|
# replayed (old) http index can't ship old PHP: packages stay GPG-checked and
|
||||||
|
# the EXPECT_PHP_MIN gate below fails the build on anything older than php.net.
|
||||||
|
sed -i -e 's|^#baseurl=http://\(.*\)$|baseurl=https://\1 http://\1|' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
|
printf 'retries=10\ntimeout=60\n' >> /etc/dnf/dnf.conf && \
|
||||||
dnf update -y && \
|
dnf update -y && \
|
||||||
dnf install -y openssl wget procps cronie iproute postgresql-devel microdnf less git \
|
dnf install -y openssl wget procps cronie iproute postgresql-devel microdnf less git \
|
||||||
nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix fcgi && \
|
nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix fcgi && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
dnf clean all && \
|
dnf clean all && \
|
||||||
rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/*
|
rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/*
|
||||||
|
|
||||||
@@ -16,12 +39,22 @@ COPY ./scripts/ /scripts/
|
|||||||
RUN chmod +x /scripts/*
|
RUN chmod +x /scripts/*
|
||||||
|
|
||||||
# Install ImageMagick from EPEL with HEIC/HEIF/AVIF support
|
# Install ImageMagick from EPEL with HEIC/HEIF/AVIF support
|
||||||
RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \
|
RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
|
dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
dnf clean all
|
dnf clean all
|
||||||
|
|
||||||
# Create needed dirs, install PHP, clean up (no SSL cert, no httpd)
|
# Create needed dirs, install PHP, clean up (no SSL cert, no httpd)
|
||||||
RUN mkdir -p /run/php-fpm/ && \
|
RUN mkdir -p /run/php-fpm/ && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
/scripts/install-php$PHPVER.sh && \
|
/scripts/install-php$PHPVER.sh && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
|
# CI passes php.net's current release for the branch; never ship older PHP.
|
||||||
|
if [ -n "$EXPECT_PHP_MIN" ]; then \
|
||||||
|
php -r 'exit(version_compare(PHP_VERSION, getenv("EXPECT_PHP_MIN"), ">=") ? 0 : 1);' || \
|
||||||
|
{ echo "FATAL: built PHP $(php -r 'echo PHP_VERSION;') is older than php.net $EXPECT_PHP_MIN"; exit 1; }; \
|
||||||
|
else echo "WARNING: no EXPECT_PHP_MIN (local build) - PHP version not gated"; fi && \
|
||||||
|
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
# Download and install wp-cli
|
# Download and install wp-cli
|
||||||
@@ -34,6 +67,10 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
|
|||||||
|
|
||||||
# Copy configs (PHP only, no Apache configs)
|
# Copy configs (PHP only, no Apache configs)
|
||||||
COPY ./configs/prod-php.ini /etc/php.ini
|
COPY ./configs/prod-php.ini /etc/php.ini
|
||||||
|
# php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
|
||||||
|
# every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
|
||||||
|
# all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
|
||||||
|
COPY ./configs/zz-php2shell-mitigation.ini /etc/php.d/zz-php2shell-mitigation.ini
|
||||||
COPY ./configs/mariadb.repo /etc/yum.repos.d/
|
COPY ./configs/mariadb.repo /etc/yum.repos.d/
|
||||||
|
|
||||||
# Set up cron job for log rotation
|
# Set up cron job for log rotation
|
||||||
|
|||||||
+63
-2
@@ -22,6 +22,26 @@
|
|||||||
## after FROM for any RUN steps that need it.
|
## after FROM for any RUN steps that need it.
|
||||||
ARG OLS_VERSION=1.8.5
|
ARG OLS_VERSION=1.8.5
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
|
|
||||||
|
## ---- EMERGENCY ONLY: lsphp compiled from php.net source ----------------------
|
||||||
|
## Idle unless PHP_SRC_VERSION is set (then /lsphp-src-out stays empty and the
|
||||||
|
## overlay step below is a no-op). Same stage as Dockerfile.lsphp — see there and
|
||||||
|
## docs/lsphp-source-emergency.md.
|
||||||
|
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} AS lsphp-src
|
||||||
|
ARG PHPVER=83
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
ARG EXPECT_LSPHP_PKG=
|
||||||
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
ARG PHP_SRC_VERSION=
|
||||||
|
ARG PHP_SRC_SHA256=
|
||||||
|
RUN --mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||||
|
--mount=type=bind,source=keys/php-src,target=/usr/local/share/php-src-keys \
|
||||||
|
set -e; \
|
||||||
|
if [ -n "$PHP_SRC_VERSION" ]; then \
|
||||||
|
install -m 0755 /mnt/scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh; \
|
||||||
|
fi; \
|
||||||
|
PHPVER="${PHPVER}" bash /mnt/scripts/lsphp-src-build.sh
|
||||||
|
|
||||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
ENV PHPVER=${PHPVER}
|
ENV PHPVER=${PHPVER}
|
||||||
@@ -45,14 +65,40 @@ ENV PHPVER=${PHPVER}
|
|||||||
## lsphp${PHPVER}-ldap is the only extra ext we add (everything else WP needs
|
## 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
|
## 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.
|
## 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 \
|
## SECURITY UPDATES AT BUILD TIME: lsphp-apt-upgrade.sh also upgrades the OS and
|
||||||
|
## sweeps EVERY installed lsphp${PHPVER}* package to the LiteSpeed repo's current
|
||||||
|
## release, failing the build if the repo is unreachable — so a rebuild ships a
|
||||||
|
## PHP security fix as soon as LiteSpeed publishes the .deb, without waiting for
|
||||||
|
## a new litespeedtech/openlitespeed base tag. See the script header.
|
||||||
|
##
|
||||||
|
## CACHE_BUST: CI passes a per-run value so this layer can never be a cache hit
|
||||||
|
## (an ARG's first use in a RUN is part of that layer's cache key). Local builds:
|
||||||
|
## pass --build-arg CACHE_BUST=$(date +%s) or use --no-cache, or you may get the
|
||||||
|
## lsphp from your last build. EXPECT_* are resolved by CI from the repo index
|
||||||
|
## and asserted here; the same values become the image labels below.
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
ARG EXPECT_LSPHP_PKG=
|
||||||
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||||
|
RUN /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
||||||
gettext-base sudo cron \
|
gettext-base sudo cron \
|
||||||
ca-certificates curl wget \
|
ca-certificates curl wget \
|
||||||
lsphp${PHPVER}-ldap && \
|
lsphp${PHPVER}-ldap && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
|
## EMERGENCY PATH: overlay the php.net lsphp (no-op on normal builds) and prove
|
||||||
|
## it has the same extension/feature surface as the packaged one just installed
|
||||||
|
## — see scripts/lsphp-src-overlay.sh. Bind mounts: no extra image layer content.
|
||||||
|
ARG PHP_SRC_VERSION=
|
||||||
|
ARG LSPHP_SOURCE=litespeed-apt
|
||||||
|
RUN --mount=type=bind,from=lsphp-src,source=/lsphp-src-out,target=/lsphp-src-out,rw \
|
||||||
|
--mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||||
|
install -m 0644 /mnt/scripts/lsphp-src-probe.php /usr/local/share/lsphp-src-probe.php && \
|
||||||
|
bash /mnt/scripts/lsphp-src-overlay.sh runtime "${PHPVER}" && \
|
||||||
|
rm -f /usr/local/share/lsphp-src-probe.php
|
||||||
|
|
||||||
## Composer (matches the wp-cli pattern in the existing CAC Dockerfile —
|
## Composer (matches the wp-cli pattern in the existing CAC Dockerfile —
|
||||||
## phar download, no `php install` pipe since lsphp's CLI mode is fine).
|
## 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 && \
|
RUN curl -fsSL -o /usr/local/bin/composer https://getcomposer.org/download/latest-stable/composer.phar && \
|
||||||
@@ -68,6 +114,10 @@ COPY ./scripts/entrypoint-litespeed.sh \
|
|||||||
/scripts/
|
/scripts/
|
||||||
RUN chmod +x /scripts/*
|
RUN chmod +x /scripts/*
|
||||||
COPY ./configs/litespeed/ /etc/lsws-templates/
|
COPY ./configs/litespeed/ /etc/lsws-templates/
|
||||||
|
## php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
|
||||||
|
## every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
|
||||||
|
## all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
|
||||||
|
COPY ./configs/zz-php2shell-mitigation.ini /etc/lsws-templates/zz-php2shell-mitigation.ini
|
||||||
|
|
||||||
## Apply our production lsphp ini overrides. Ask lsphp for its scan dir
|
## 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
|
## directly (varies by PHP minor version: 8.3/8.4/8.5 each have their own
|
||||||
@@ -77,6 +127,7 @@ 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}"); \
|
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"; \
|
mkdir -p "$SCAN_DIR"; \
|
||||||
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
|
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
|
||||||
|
cp /etc/lsws-templates/zz-php2shell-mitigation.ini "$SCAN_DIR/zz-php2shell-mitigation.ini"; \
|
||||||
echo "wrote overrides to $SCAN_DIR"'
|
echo "wrote overrides to $SCAN_DIR"'
|
||||||
|
|
||||||
## Disable the OLS WebAdmin port for customer-facing containers. Bind admin
|
## Disable the OLS WebAdmin port for customer-facing containers. Bind admin
|
||||||
@@ -87,6 +138,16 @@ RUN sed -i 's|^[[:space:]]*address[[:space:]]\+\*:| address 127.0
|
|||||||
## Cron entry for log rotation (mirrors cac:phpXX).
|
## Cron entry for log rotation (mirrors cac:phpXX).
|
||||||
RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
||||||
|
|
||||||
|
## Inspect without running the image:
|
||||||
|
## docker buildx imagetools inspect <ref> --format '{{json .Image.Config.Labels}}'
|
||||||
|
## Values come from CI's repo-index lookup and were asserted against the
|
||||||
|
## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build).
|
||||||
|
## Emergency (php.net) builds: see the same LABEL block in Dockerfile.lsphp.
|
||||||
|
LABEL org.whp.php-version="${PHP_SRC_VERSION:-${EXPECT_PHP_VERSION:-unverified}}" \
|
||||||
|
org.whp.lsphp-package-version="${EXPECT_LSPHP_PKG:-unverified}" \
|
||||||
|
org.whp.lsphp-source="${LSPHP_SOURCE}" \
|
||||||
|
org.whp.php-flavour="lsphp${PHPVER}"
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
|
|
||||||
## Healthcheck: the entrypoint drops a static /healthz into the customer
|
## Healthcheck: the entrypoint drops a static /healthz into the customer
|
||||||
|
|||||||
+96
-8
@@ -22,6 +22,29 @@
|
|||||||
ARG OLS_VERSION=1.8.4
|
ARG OLS_VERSION=1.8.4
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
|
|
||||||
|
## ---- stage 0 (EMERGENCY ONLY): lsphp compiled from php.net source -----------
|
||||||
|
## Idle unless PHP_SRC_VERSION is set: lsphp-src-build.sh then exits at once and
|
||||||
|
## /lsphp-src-out stays empty, so every normal build is unchanged (the overlay
|
||||||
|
## steps below are no-ops). Set PHP_SRC_VERSION + PHP_SRC_SHA256 + LSPHP_SOURCE=php.net
|
||||||
|
## ONLY when a PHP security release is out and the LiteSpeed apt repo still lags
|
||||||
|
## — CI: .gitea/workflows/lsphp-source-emergency.yaml; runbook:
|
||||||
|
## docs/lsphp-source-emergency.md. The tarball is sha256- AND OpenPGP-verified
|
||||||
|
## against keys/php-src/; configure flags are replayed from the packaged lsphp.
|
||||||
|
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} AS lsphp-src
|
||||||
|
ARG PHPVER=83
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
ARG EXPECT_LSPHP_PKG=
|
||||||
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
ARG PHP_SRC_VERSION=
|
||||||
|
ARG PHP_SRC_SHA256=
|
||||||
|
RUN --mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||||
|
--mount=type=bind,source=keys/php-src,target=/usr/local/share/php-src-keys \
|
||||||
|
set -e; \
|
||||||
|
if [ -n "$PHP_SRC_VERSION" ]; then \
|
||||||
|
install -m 0755 /mnt/scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh; \
|
||||||
|
fi; \
|
||||||
|
PHPVER="${PHPVER}" bash /mnt/scripts/lsphp-src-build.sh
|
||||||
|
|
||||||
## ---- stage 1: build the cac_path_parity extension --------------------------
|
## ---- stage 1: build the cac_path_parity extension --------------------------
|
||||||
## $_SERVER['DOCUMENT_ROOT']/['SCRIPT_FILENAME'] parity with cac-fpm, enforced
|
## $_SERVER['DOCUMENT_ROOT']/['SCRIPT_FILENAME'] parity with cac-fpm, enforced
|
||||||
## from RINIT so a customer's .user.ini cannot displace it — see
|
## from RINIT so a customer's .user.ini cannot displace it — see
|
||||||
@@ -60,9 +83,20 @@ ARG PHPVER=83
|
|||||||
## Toolchain layer, deliberately BEFORE the source COPY so editing the extension
|
## Toolchain layer, deliberately BEFORE the source COPY so editing the extension
|
||||||
## does not re-resolve the PHP version (which is what caused the skew above).
|
## does not re-resolve the PHP version (which is what caused the skew above).
|
||||||
## Records the exact lsphp version the headers belong to; stage 2 pins to it.
|
## Records the exact lsphp version the headers belong to; stage 2 pins to it.
|
||||||
|
##
|
||||||
|
## lsphp-apt-upgrade.sh (see its header) refuses to run on stale/unreachable
|
||||||
|
## repo lists and sweeps every lsphp${PHPVER}* package to the repo's current
|
||||||
|
## release. CACHE_BUST is per-CI-run so this layer — and therefore the recorded
|
||||||
|
## version stage 2 pins to — is never a cache hit; it is re-declared in stage 2.
|
||||||
|
## --no-os-upgrade: this stage is thrown away, OS updates would only cost time.
|
||||||
|
## EXPECT_* (CI's repo-index lookup) are asserted here too, so a mismatch fails
|
||||||
|
## before the compile rather than after it.
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
ARG EXPECT_LSPHP_PKG=
|
||||||
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||||
RUN set -e; \
|
RUN set -e; \
|
||||||
apt-get update; \
|
/usr/local/sbin/lsphp-apt-upgrade.sh --no-os-upgrade "${PHPVER}" \
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
build-essential autoconf pkg-config \
|
build-essential autoconf pkg-config \
|
||||||
lsphp${PHPVER}-dev; \
|
lsphp${PHPVER}-dev; \
|
||||||
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
||||||
@@ -90,6 +124,17 @@ RUN set -e; \
|
|||||||
## - a run that executes ZERO tests also exits 0, so assert the summary shows
|
## - a run that executes ZERO tests also exits 0, so assert the summary shows
|
||||||
## every .phpt in the directory both ran and passed. Same reasoning as the
|
## every .phpt in the directory both ran and passed. Same reasoning as the
|
||||||
## `lsphp -i` probe below: an assertion that cannot fail is worse than none.
|
## `lsphp -i` probe below: an assertion that cannot fail is worse than none.
|
||||||
|
## EMERGENCY PATH: swap the packaged lsphp + its -dev files for the php.net build
|
||||||
|
## (no-op when stage 0 was idle), so cac_path_parity below compiles against, and
|
||||||
|
## its .phpt suite runs on, the PHP this image will actually ship.
|
||||||
|
ARG PHP_SRC_VERSION=
|
||||||
|
ARG LSPHP_SOURCE=litespeed-apt
|
||||||
|
RUN --mount=type=bind,from=lsphp-src,source=/lsphp-src-out,target=/lsphp-src-out,rw \
|
||||||
|
--mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||||
|
install -m 0644 /mnt/scripts/lsphp-src-probe.php /usr/local/share/lsphp-src-probe.php && \
|
||||||
|
bash /mnt/scripts/lsphp-src-overlay.sh dev "${PHPVER}" && \
|
||||||
|
rm -f /usr/local/share/lsphp-src-probe.php
|
||||||
|
|
||||||
COPY ./ext/cac-path-parity /usr/src/cac-path-parity
|
COPY ./ext/cac-path-parity /usr/src/cac-path-parity
|
||||||
RUN set -e; \
|
RUN set -e; \
|
||||||
cd /usr/src/cac-path-parity; \
|
cd /usr/src/cac-path-parity; \
|
||||||
@@ -141,17 +186,29 @@ ENV PHPVER=${PHPVER}
|
|||||||
## cache hit, new version => this layer re-runs and lands on the same one. The
|
## cache hit, new version => this layer re-runs and lands on the same one. The
|
||||||
## explicit `=$V` pins then make a mid-build repo roll a LOUD apt failure instead
|
## explicit `=$V` pins then make a mid-build repo roll a LOUD apt failure instead
|
||||||
## of a silent skew. Verified satisfiable on PHP 8.1/8.3/8.5 (2026-08-05).
|
## of a silent skew. Verified satisfiable on PHP 8.1/8.3/8.5 (2026-08-05).
|
||||||
|
##
|
||||||
|
## SECURITY UPDATES AT BUILD TIME: lsphp-apt-upgrade.sh upgrades the Ubuntu OS
|
||||||
|
## packages and every other lsphp${PHPVER}* package (extensions such as -msgpack
|
||||||
|
## are NOT dragged along by the -common pin) to the repos' current versions, and
|
||||||
|
## fails the build if the repo is unreachable. CACHE_BUST makes this layer a
|
||||||
|
## guaranteed cache miss per CI run; EXPECT_* are CI's repo-index lookup,
|
||||||
|
## asserted by the script and written into the image labels below.
|
||||||
|
ARG CACHE_BUST=
|
||||||
|
ARG EXPECT_LSPHP_PKG=
|
||||||
|
ARG EXPECT_PHP_VERSION=
|
||||||
COPY --from=ext-build /build-out/lsphp.version /etc/cac-lsphp-build.version
|
COPY --from=ext-build /build-out/lsphp.version /etc/cac-lsphp-build.version
|
||||||
|
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||||
RUN set -e; \
|
RUN set -e; \
|
||||||
V=$(cat /etc/cac-lsphp-build.version); \
|
V=$(cat /etc/cac-lsphp-build.version); \
|
||||||
apt-get update; \
|
if ! /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
||||||
if ! DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
lsphp${PHPVER}="$V" lsphp${PHPVER}-common="$V" lsphp${PHPVER}-ldap="$V"; then \
|
lsphp${PHPVER}="$V" lsphp${PHPVER}-common="$V" lsphp${PHPVER}-ldap="$V"; then \
|
||||||
echo "FATAL: lsphp${PHPVER} $V is what cac_path_parity was compiled against," >&2; \
|
echo "FATAL: lsphp-apt-upgrade.sh failed (reason above). If apt said" >&2; \
|
||||||
echo " but the LiteSpeed repo no longer offers it (it keeps only the" >&2; \
|
echo " \"Version '$V' ... was not found\" or refused a downgrade: $V is what" >&2; \
|
||||||
echo " current release). The ext-build stage is almost certainly a stale" >&2; \
|
echo " cac_path_parity was compiled against, but the LiteSpeed repo (which" >&2; \
|
||||||
echo " cache hit — rebuild with --no-cache." >&2; \
|
echo " keeps only the current release) now offers a newer one — either it" >&2; \
|
||||||
|
echo " published mid-build or ext-build was a stale cache hit. Re-run the" >&2; \
|
||||||
|
echo " build (CI) / rebuild with --no-cache (local)." >&2; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
apt-get clean; \
|
apt-get clean; \
|
||||||
@@ -166,6 +223,18 @@ RUN set -e; \
|
|||||||
fi; \
|
fi; \
|
||||||
echo "runtime lsphp${PHPVER} pinned to $V (the version cac_path_parity was built against)"
|
echo "runtime lsphp${PHPVER} pinned to $V (the version cac_path_parity was built against)"
|
||||||
|
|
||||||
|
## EMERGENCY PATH: overlay the php.net lsphp (no-op on normal builds) and prove
|
||||||
|
## it has the same extension/feature surface as the packaged one just installed
|
||||||
|
## — see scripts/lsphp-src-overlay.sh. Bind mounts, so neither the staged build
|
||||||
|
## nor the scripts add a layer to the image.
|
||||||
|
ARG PHP_SRC_VERSION=
|
||||||
|
ARG LSPHP_SOURCE=litespeed-apt
|
||||||
|
RUN --mount=type=bind,from=lsphp-src,source=/lsphp-src-out,target=/lsphp-src-out,rw \
|
||||||
|
--mount=type=bind,source=scripts,target=/mnt/scripts \
|
||||||
|
install -m 0644 /mnt/scripts/lsphp-src-probe.php /usr/local/share/lsphp-src-probe.php && \
|
||||||
|
bash /mnt/scripts/lsphp-src-overlay.sh runtime "${PHPVER}" && \
|
||||||
|
rm -f /usr/local/share/lsphp-src-probe.php
|
||||||
|
|
||||||
## Scripts + the SHARED production lsphp ini (reused verbatim from the litespeed
|
## Scripts + the SHARED production lsphp ini (reused verbatim from the litespeed
|
||||||
## image — same runtime, same tuning). Scripts layer last (they change most).
|
## image — same runtime, same tuning). Scripts layer last (they change most).
|
||||||
COPY ./scripts/entrypoint-lsphp.sh \
|
COPY ./scripts/entrypoint-lsphp.sh \
|
||||||
@@ -178,10 +247,15 @@ RUN chmod +x /scripts/entrypoint-lsphp.sh /scripts/detect-memory-lsphp.sh /scrip
|
|||||||
## Apply production lsphp ini overrides into lsphp's scan dir (path varies by
|
## Apply production lsphp ini overrides into lsphp's scan dir (path varies by
|
||||||
## PHP minor version; ask lsphp directly — same idiom as Dockerfile.litespeed).
|
## PHP minor version; ask lsphp directly — same idiom as Dockerfile.litespeed).
|
||||||
COPY ./configs/litespeed/lsphp-overrides.ini /etc/lsws-templates/lsphp-overrides.ini
|
COPY ./configs/litespeed/lsphp-overrides.ini /etc/lsws-templates/lsphp-overrides.ini
|
||||||
|
## php2shell mitigation (CVE-2026-109618/-109619). Named zz- so it sorts after
|
||||||
|
## every other scan-dir ini (incl. runtime-written 99-*) and wins. Remove once
|
||||||
|
## all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
|
||||||
|
COPY ./configs/zz-php2shell-mitigation.ini /etc/lsws-templates/zz-php2shell-mitigation.ini
|
||||||
RUN bash -c 'set -e; \
|
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}"); \
|
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"; \
|
mkdir -p "$SCAN_DIR"; \
|
||||||
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
|
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
|
||||||
|
cp /etc/lsws-templates/zz-php2shell-mitigation.ini "$SCAN_DIR/zz-php2shell-mitigation.ini"; \
|
||||||
echo "wrote overrides to $SCAN_DIR"'
|
echo "wrote overrides to $SCAN_DIR"'
|
||||||
|
|
||||||
## Install the cac_path_parity extension into lsphp's own extension_dir and load
|
## Install the cac_path_parity extension into lsphp's own extension_dir and load
|
||||||
@@ -218,6 +292,20 @@ RUN bash -c 'set -e; \
|
|||||||
## default so the runuser exec path can't drop it.
|
## default so the runuser exec path can't drop it.
|
||||||
ENV LSPHP_ENABLE_USER_INI=on
|
ENV LSPHP_ENABLE_USER_INI=on
|
||||||
|
|
||||||
|
## Inspect without running the image:
|
||||||
|
## docker buildx imagetools inspect <ref> --format '{{json .Image.Config.Labels}}'
|
||||||
|
## Values come from CI's repo-index lookup and were asserted against the
|
||||||
|
## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build).
|
||||||
|
## Emergency (php.net) builds: org.whp.php-version is the php.net version the
|
||||||
|
## overlay step asserted; org.whp.lsphp-package-version still names the LiteSpeed
|
||||||
|
## packages underneath (their ini/PECL files are what the image keeps);
|
||||||
|
## org.whp.lsphp-source says which one is running. /etc/cac-lsphp-source has the
|
||||||
|
## tarball sha256 + signer.
|
||||||
|
LABEL org.whp.php-version="${PHP_SRC_VERSION:-${EXPECT_PHP_VERSION:-unverified}}" \
|
||||||
|
org.whp.lsphp-package-version="${EXPECT_LSPHP_PKG:-unverified}" \
|
||||||
|
org.whp.lsphp-source="${LSPHP_SOURCE}" \
|
||||||
|
org.whp.php-flavour="lsphp${PHPVER}"
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
## TCP-connect + lsphp-alive check (LSAPI isn't FastCGI, so no cgi-fcgi ping).
|
## TCP-connect + lsphp-alive check (LSAPI isn't FastCGI, so no cgi-fcgi ping).
|
||||||
|
|||||||
+14
-2
@@ -1,10 +1,21 @@
|
|||||||
FROM almalinux/10-base
|
FROM almalinux/10-base
|
||||||
|
|
||||||
|
# OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
# (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||||
|
# GPG-verified against keys/pkg-cache/ either way. Repo files
|
||||||
|
# are restored at the end of every RUN, so no layer keeps the cache address.
|
||||||
|
# See scripts/pkg-cache-repos.sh.
|
||||||
|
ARG PKG_CACHE=
|
||||||
|
COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh
|
||||||
|
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||||
|
|
||||||
# Install Apache and minimal dependencies (no PHP at all)
|
# Install Apache and minimal dependencies (no PHP at all)
|
||||||
RUN dnf install -y \
|
RUN /usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm && \
|
/usr/local/sbin/pkg-cache-repos.sh release-install epel && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh rpm && \
|
||||||
dnf update -y && \
|
dnf update -y && \
|
||||||
dnf install -y httpd mod_ssl openssl iproute cronie procps curl && \
|
dnf install -y httpd mod_ssl openssl iproute cronie procps curl && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
dnf clean all && \
|
dnf clean all && \
|
||||||
rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/*
|
rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/*
|
||||||
|
|
||||||
@@ -13,6 +24,7 @@ COPY ./scripts/detect-memory.sh /scripts/detect-memory.sh
|
|||||||
COPY ./scripts/create-apache-mpm-config.sh /scripts/create-apache-mpm-config.sh
|
COPY ./scripts/create-apache-mpm-config.sh /scripts/create-apache-mpm-config.sh
|
||||||
COPY ./scripts/log-rotate.sh /scripts/log-rotate.sh
|
COPY ./scripts/log-rotate.sh /scripts/log-rotate.sh
|
||||||
COPY ./scripts/entrypoint-shared-httpd.sh /scripts/entrypoint-shared-httpd.sh
|
COPY ./scripts/entrypoint-shared-httpd.sh /scripts/entrypoint-shared-httpd.sh
|
||||||
|
COPY ./scripts/stop-services.sh /scripts/stop-services.sh
|
||||||
COPY ./scripts/tune-mpm.sh /scripts/tune-mpm.sh
|
COPY ./scripts/tune-mpm.sh /scripts/tune-mpm.sh
|
||||||
RUN chmod +x /scripts/*
|
RUN chmod +x /scripts/*
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -19,6 +19,15 @@ ARG OLS_VERSION=1.8.4
|
|||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||||
|
|
||||||
|
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
## (CT130 pkg-cache, https only). Unset = upstream repositories. Release RPMs are
|
||||||
|
## GPG-verified against keys/pkg-cache/ either way. Repo files
|
||||||
|
## are restored at the end of every RUN, so no layer keeps the cache address.
|
||||||
|
## See scripts/pkg-cache-repos.sh.
|
||||||
|
ARG PKG_CACHE=
|
||||||
|
COPY ./scripts/pkg-cache-repos.sh /usr/local/sbin/pkg-cache-repos.sh
|
||||||
|
COPY ./keys/pkg-cache/ /usr/local/share/pkg-cache/
|
||||||
|
|
||||||
## Tooling the shared tier needs on top of the base:
|
## Tooling the shared tier needs on top of the base:
|
||||||
## - inotify-tools: the .htaccess watcher (spec 5.3)
|
## - inotify-tools: the .htaccess watcher (spec 5.3)
|
||||||
## - gettext-base: envsubst for render-shared-ols-config.sh
|
## - gettext-base: envsubst for render-shared-ols-config.sh
|
||||||
@@ -28,9 +37,11 @@ FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
|||||||
## liveness check depends on. Only transitively present via the base image
|
## liveness check depends on. Only transitively present via the base image
|
||||||
## today (Ubuntu 24.04 pulls it in) — pin it explicitly so it can't be
|
## today (Ubuntu 24.04 pulls it in) — pin it explicitly so it can't be
|
||||||
## pruned as "unused" and silently break the supervisor's crash detection.
|
## pruned as "unused" and silently break the supervisor's crash detection.
|
||||||
RUN apt-get update && \
|
RUN /usr/local/sbin/pkg-cache-repos.sh apt && \
|
||||||
|
apt-get update && \
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
inotify-tools gettext-base openssl ca-certificates curl procps && \
|
inotify-tools gettext-base openssl ca-certificates curl procps && \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,18 @@ virtualHostConfig {
|
|||||||
addDefaultCharset off
|
addDefaultCharset off
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## SECURITY: never serve config / VCS dotfiles over HTTP. Same named list as
|
||||||
|
## WHP's shared-ols vhconf template (first `context exp:` there) and
|
||||||
|
## shared-httpd's 000-whp-security.conf. Measured on .148: this tier served
|
||||||
|
## /.env even at mode 0640, because OLS runs as the customer uid here. An
|
||||||
|
## `exp:` regex context wins over the `context /` prefix context regardless
|
||||||
|
## of order (measured on OLS 1.8.4 for shared-ols), so a customer .htaccess
|
||||||
|
## cannot reopen it. Named, not blanket "^\.": /.well-known/ (ACME,
|
||||||
|
## security.txt, app-site-association) must stay reachable.
|
||||||
|
context exp:^/(.*/)?\.(user\.ini|env|git|svn|hg|bzr|ht|whp|site-builder) {
|
||||||
|
allowBrowse 0
|
||||||
|
}
|
||||||
|
|
||||||
rewrite {
|
rewrite {
|
||||||
enable 1
|
enable 1
|
||||||
autoLoadHtaccess 1
|
autoLoadHtaccess 1
|
||||||
|
|||||||
@@ -23,10 +23,22 @@ useIpInProxyHeader 1
|
|||||||
|
|
||||||
## LSCache enabled at MODULE scope for the whole tier (dedicated cache volume,
|
## LSCache enabled at MODULE scope for the whole tier (dedicated cache volume,
|
||||||
## ephemeral across rebuilds; OLS auto-keys a per-vhost subdir under storagePath).
|
## ephemeral across rebuilds; OLS auto-keys a per-vhost subdir under storagePath).
|
||||||
## PUBLIC (anonymous) caching ONLY: enableCache 1 + checkPublicCache 1 let OLS
|
## PUBLIC (anonymous) caching ONLY: checkPublicCache 1 lets OLS serve cacheable,
|
||||||
## serve cacheable, non-logged-in responses marked by the LiteSpeed Cache WP
|
## non-logged-in responses that are EXPLICITLY marked by the LiteSpeed Cache WP
|
||||||
## plugin's X-LiteSpeed-Cache-Control headers (ignoreRespCacheCtrl=0 honors them).
|
## plugin's X-LiteSpeed-Cache-Control headers (ignoreRespCacheCtrl=0 honors them).
|
||||||
##
|
##
|
||||||
|
## enableCache is 0 ON PURPOSE. At module scope enableCache 1 means "cache every
|
||||||
|
## cacheable-looking public response by default", INCLUDING ones that carry no
|
||||||
|
## cache header at all. That bit us on Divi: LSCWP's Divi compat calls
|
||||||
|
## litespeed_disable_all for ?et_fb= / ?et_pb_preview= / ?p=N&preview=true, which
|
||||||
|
## switches the plugin off for the request, so it sends NO header (and its own
|
||||||
|
## "Do Not Cache Query Strings" never runs) — and OLS stored the Visual Builder
|
||||||
|
## page under enableCache 1. With enableCache 0 only responses that opt in via
|
||||||
|
## X-LiteSpeed-Cache-Control: public are stored; a plugin-disabled request is
|
||||||
|
## simply not cached. Measured on OLS 1.8.4 (2026-09-21): headerless response =
|
||||||
|
## miss,hit under 1; not cached under 0; explicit `public` header cached under
|
||||||
|
## both. Matches the dedicated cac-litespeed tier (configs/litespeed/site-template.tpl).
|
||||||
|
##
|
||||||
## PRIVATE caching is intentionally OFF (enablePrivateCache 0 + checkPrivateCache 0).
|
## PRIVATE caching is intentionally OFF (enablePrivateCache 0 + checkPrivateCache 0).
|
||||||
## Logged-in / cookie-bearing pages must NEVER be cached at the tier. We previously
|
## Logged-in / cookie-bearing pages must NEVER be cached at the tier. We previously
|
||||||
## left enablePrivateCache=1 assuming "no plugin -> nothing cached," but that was
|
## left enablePrivateCache=1 assuming "no plugin -> nothing cached," but that was
|
||||||
@@ -45,7 +57,7 @@ module cache {
|
|||||||
respCookieCache 1
|
respCookieCache 1
|
||||||
ignoreReqCacheCtrl 0
|
ignoreReqCacheCtrl 0
|
||||||
ignoreRespCacheCtrl 0
|
ignoreRespCacheCtrl 0
|
||||||
enableCache 1
|
enableCache 0
|
||||||
enablePrivateCache 0
|
enablePrivateCache 0
|
||||||
}
|
}
|
||||||
## ---- end shared-ols server append ----
|
## ---- end shared-ols server append ----
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
## SECURITY: never serve config / VCS dotfiles over HTTP (standalone cac).
|
||||||
|
##
|
||||||
|
## Same named list as WHP's shared-httpd drop-in (web-files/configs/
|
||||||
|
## shared-httpd-security.conf, 000-whp-security.conf on every shared-httpd host),
|
||||||
|
## re-rooted from /mnt/users to /home because here the docroot is
|
||||||
|
## /home/<user>/public_html. Standalone cac never got that drop-in, and measured
|
||||||
|
## on .148 it served /.env with contents.
|
||||||
|
##
|
||||||
|
## <FilesMatch> matches the FILENAME resolved from the filesystem path, not the
|
||||||
|
## request URL, so it cannot be side-stepped by URL-encoding. httpd applies
|
||||||
|
## <Directory> sections (and .htaccess) first and <Files>/<FilesMatch> after
|
||||||
|
## them, so this deny wins over create-vhost.sh's `Require all granted`.
|
||||||
|
##
|
||||||
|
## A NAMED LIST, NOT A BLANKET "^\." DENY: /.well-known/ (ACME, security.txt,
|
||||||
|
## apple-app-site-association, assetlinks.json, payment-domain verification)
|
||||||
|
## must stay reachable. FilesMatch tests the final path component, and for
|
||||||
|
## /.well-known/acme-challenge/<token> that is <token>, not a dotfile.
|
||||||
|
##
|
||||||
|
## HTTP only: PHP still reads .user.ini and .env off the filesystem as before.
|
||||||
|
<Directory "/home">
|
||||||
|
<FilesMatch "^\.(user\.ini|env|git|svn|hg|bzr|ht|whp|site-builder)">
|
||||||
|
Require all denied
|
||||||
|
</FilesMatch>
|
||||||
|
</Directory>
|
||||||
|
## Directory form: reaches the files INSIDE .git/ etc. (e.g. /.git/config,
|
||||||
|
## whose own name is not a dotfile).
|
||||||
|
<DirectoryMatch "^/home/.*/\.(git|svn|hg|bzr|whp|site-builder)(/|$)">
|
||||||
|
Require all denied
|
||||||
|
</DirectoryMatch>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
; php2shell mitigation (CVE-2026-109618 / CVE-2026-109619, GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp)
|
||||||
|
; Remove once all images ship PHP >= 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
|
||||||
|
fatal_error_backtraces = 0
|
||||||
|
session.upload_progress.enabled = 0
|
||||||
|
max_input_nesting_level = 16
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# Emergency lsphp from php.net source
|
||||||
|
|
||||||
|
Use this only when PHP has published a security release and the LiteSpeed apt
|
||||||
|
repo (`rpms.litespeedtech.com/debian`, noble) still ships the vulnerable lsphp.
|
||||||
|
The emergency build replaces the PHP inside `cac-lsphp` and `cac-litespeed` with
|
||||||
|
the same PHP version compiled from the php.net tarball. Everything else in the
|
||||||
|
image stays as LiteSpeed packages it. The build fails unless the result offers
|
||||||
|
the same support as the packaged lsphp.
|
||||||
|
|
||||||
|
This path was first built for php2shell (CVE-2026-109618/-109619, 2026-10-21):
|
||||||
|
php.net released 8.2.35, 8.3.36, 8.4.27 and 8.5.12, and LiteSpeed was still on
|
||||||
|
8.2.34, 8.3.35, 8.4.26 and 8.5.11.
|
||||||
|
|
||||||
|
## Trigger
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Staging tags only (:phpNN-phpnet-X.Y.Z). Test these on .148 first.
|
||||||
|
curl -fsS -X POST -H "Authorization: token $TEA_TOKEN" -H 'Content-Type: application/json' \
|
||||||
|
https://repo.anhonesthost.net/api/v1/repos/cloud-hosting-platform/cloud-apache-container/actions/workflows/lsphp-source-emergency.yaml/dispatches \
|
||||||
|
-d '{"ref":"trunk","inputs":{"php_versions":"82=8.2.35 83=8.3.36 84=8.4.27 85=8.5.12","images":"both","push_production_tags":"false"}}'
|
||||||
|
|
||||||
|
# 2. Production tags (:phpNN, :latest for 85). WHP pulls these on recreate.
|
||||||
|
# The JSON is the same as step 1 except push_production_tags is "true".
|
||||||
|
```
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
|
||||||
|
- `php_versions`: space-separated `NN=X.Y.Z` pairs, with NN between 81 and 85.
|
||||||
|
- `images`: `both`, `lsphp` or `litespeed`.
|
||||||
|
- `push_production_tags`: `true` or `false`.
|
||||||
|
|
||||||
|
The job for a given PHP version refuses to run if php.net has no such release.
|
||||||
|
It also refuses if LiteSpeed already ships that version or a newer one; in that
|
||||||
|
case, run the normal build instead.
|
||||||
|
|
||||||
|
After pushing, check the labels on the pushed image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker buildx imagetools inspect repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:php85 \
|
||||||
|
--format '{{json .Image.Config.Labels}}'
|
||||||
|
# org.whp.lsphp-source = php.net, org.whp.php-version = 8.5.12
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the container, `/etc/cac-lsphp-source` records the tarball sha256, the
|
||||||
|
signer fingerprint and the packaged release that was replaced.
|
||||||
|
|
||||||
|
Running containers do not change until they are recreated. Follow the normal
|
||||||
|
deploy order: test.148, whp02, sdbees, then whp01.
|
||||||
|
|
||||||
|
## Getting back to the packaged lsphp
|
||||||
|
|
||||||
|
When LiteSpeed publishes the fixed lsphp, run the normal `build-push.yaml`
|
||||||
|
build, either with a push to trunk or a dispatch. It overwrites `:phpNN` with
|
||||||
|
the packaged build once the packaged version is the same as or newer than the
|
||||||
|
php.net version.
|
||||||
|
|
||||||
|
Until that happens, the normal build refuses to overwrite a newer php.net image.
|
||||||
|
It fails at the "Do not roll back a newer php.net emergency image" step. This is
|
||||||
|
intentional, so a routine push during the emergency does not put the vulnerable
|
||||||
|
PHP back. That step lets the build through if it cannot inspect the published
|
||||||
|
tag.
|
||||||
|
|
||||||
|
## Local build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile.lsphp --build-arg PHPVER=85 --build-arg OLS_VERSION=1.8.4 \
|
||||||
|
--build-arg CACHE_BUST=$(date +%s) \
|
||||||
|
--build-arg PHP_SRC_VERSION=8.5.12 \
|
||||||
|
--build-arg PHP_SRC_SHA256=<sha256 of php-8.5.12.tar.xz from https://www.php.net/releases/index.php?json&version=8.5.12> \
|
||||||
|
--build-arg LSPHP_SOURCE=php.net -t localhost/cac-lsphp-src:php85-test .
|
||||||
|
```
|
||||||
|
|
||||||
|
The same build args work with `Dockerfile.litespeed`. Without
|
||||||
|
`PHP_SRC_VERSION`, both Dockerfiles build exactly as before: the `lsphp-src`
|
||||||
|
stage and the overlay steps do nothing.
|
||||||
|
|
||||||
|
## What the build does and what makes it fail
|
||||||
|
|
||||||
|
The build has four parts.
|
||||||
|
|
||||||
|
1. The `lsphp-src` stage runs `scripts/lsphp-src-build.sh`, FROM the same
|
||||||
|
`litespeedtech/openlitespeed` base as the shipped image.
|
||||||
|
2. In `Dockerfile.lsphp`, the ext-build stage runs `scripts/lsphp-src-overlay.sh dev`.
|
||||||
|
This builds `cac_path_parity` and runs its `.phpt` suite against the php.net
|
||||||
|
PHP.
|
||||||
|
3. The shipped stage runs `scripts/lsphp-src-overlay.sh runtime`.
|
||||||
|
4. The labels record the source: `org.whp.php-version` is the php.net version
|
||||||
|
and `org.whp.lsphp-source` is `php.net`. `org.whp.lsphp-package-version`
|
||||||
|
still names the LiteSpeed packages underneath.
|
||||||
|
|
||||||
|
Each of the following fails the build:
|
||||||
|
|
||||||
|
| Check | Fails when |
|
||||||
|
|---|---|
|
||||||
|
| Tarball sha256 | It does not match `PHP_SRC_SHA256`, which CI takes from the php.net releases JSON. |
|
||||||
|
| OpenPGP signature | `gpgv` rejects the `.asc` against `keys/php-src/php-keyring.asc`, or the signer's primary fingerprint is not listed for that branch in `keys/php-src/release-managers.txt`. |
|
||||||
|
| Version sanity | The version is not X.Y.Z, is not in the PHPVER branch, or is not newer than the packaged version. |
|
||||||
|
| Configure | Replaying the packaged lsphp's own `Configure Command` fails. The usual cause is a `-dev` package missing from `DEVPKGS`. |
|
||||||
|
| LSAPI SAPI | `php-litespeed-8.3.tgz` does not match its pinned sha256, or the built lsphp's set of `LSAPI_*` names differs from the packaged binary's. |
|
||||||
|
| Compiled-in ini defaults | Any `php -n` ini default differs from the packaged binary. Directives that are new in the release are allowed and printed. |
|
||||||
|
| ABI gate | A struct member's offset or size changed, or a non-`*_globals` struct changed size. See `scripts/lsphp-abi-gate.sh`. |
|
||||||
|
| File replacement | A php-src ELF file owned by a package at the core version has no php.net counterpart. |
|
||||||
|
| Same-support gate (both SAPIs) | Loaded extensions differ, a PECL or Zend extension changes version, a php-src extension is not at the new version, an effective ini value changes or disappears, or a gd/curl/openssl/icu/imagick/redis/memcached/password/hash/stream feature line changes. |
|
||||||
|
| Build-env consistency | `Configure Command` differs beyond CFLAGS, an ELF file has an unresolved library, or the packaged release in the builder stage differs from the one in the shipped stage. |
|
||||||
|
|
||||||
|
## Differences from the packaged build
|
||||||
|
|
||||||
|
These were measured on 2026-10-21 for php85 and php82 (`cac-lsphp`) and php83
|
||||||
|
(`cac-litespeed`):
|
||||||
|
|
||||||
|
- Version strings, build date and build host.
|
||||||
|
- `memory_hard_limit_percent` exists. The php2shell release added this ini
|
||||||
|
directive.
|
||||||
|
- The bundled Olson timezone DB is 2026.5 instead of 2026.3 for 8.5. It comes
|
||||||
|
from the newer tarball.
|
||||||
|
- LDAP "Vendor Version" is 20610 instead of 20607. This is the version of the
|
||||||
|
OpenLDAP headers it was compiled against; noble's libldap-dev is newer now.
|
||||||
|
The runtime library is the same Ubuntu libldap.
|
||||||
|
- CFLAGS has no `-ffile-prefix-map` or `-fdebug-prefix-map` pointing at
|
||||||
|
LiteSpeed's build directory.
|
||||||
|
- One more `_chk` (FORTIFY) symbol is used. Shared extensions get BIND_NOW,
|
||||||
|
which is stricter than the packaged build.
|
||||||
|
- The image is about 100 MB larger. The overlay layer holds a second copy of
|
||||||
|
lsphp, php and php8.M.
|
||||||
|
|
||||||
|
## Maintenance: what must stay in sync
|
||||||
|
|
||||||
|
| Item | When it needs attention | How you find out |
|
||||||
|
|---|---|---|
|
||||||
|
| `keys/php-src/release-managers.txt` and `php-keyring.asc` | A new release manager, or a new PHP branch (8.6). | The build fails because the signer is not pinned or there is no key. Refresh from <https://www.php.net/gpg-keys.php> and <https://www.php.net/distributions/php-keyring.gpg>. |
|
||||||
|
| `keys/php-src/litespeed-sapi.txt` | LiteSpeed publishes a new php-litespeed release and builds lsphp with it. | The build fails on the sha256 mismatch or on the LSAPI name-set difference. Re-measure the packaged binary, then re-pin. |
|
||||||
|
| `DEVPKGS` in `lsphp-src-build.sh` | LiteSpeed adds a configure flag that needs a new `-dev` library. | configure fails and prints the error. |
|
||||||
|
| session.save_path patch (step 2c) | LiteSpeed patches some other compiled-in default. | The compiled-in ini defaults check fails and shows a diff. |
|
||||||
|
| PECL reuse | A PHP patch release changes a struct layout. | The ABI gate fails. PECL extensions would then have to be rebuilt from source, which this path does not do. Without that, the emergency path cannot be used for that release. |
|
||||||
|
| 8.1 | php.net no longer publishes 8.1 (EOL). | The workflow refuses because there is no release. |
|
||||||
|
|
||||||
|
No configure flags, extension lists or PECL versions are kept in this repo.
|
||||||
|
They are read from the packaged lsphp in the same build, so they cannot go out
|
||||||
|
of date unnoticed.
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
# Internal package + image cache (`PKG_CACHE`)
|
||||||
|
|
||||||
|
Opt-in build support for the LAN package cache **CT130 `pkg-cache` at `https://192.168.1.40`** (Proxmox node `pve`, storage `shared-storage-tn`).
|
||||||
|
|
||||||
|
Without `PKG_CACHE`, builds use the upstream repositories. The one change from before is that the EPEL and Remi release RPMs are now **GPG-verified** against `keys/pkg-cache/` before they are installed.
|
||||||
|
|
||||||
|
Why it exists: during the 2026-10-21 PHP security release, builds failed because:
|
||||||
|
- Remi mirrors lagged hours behind origin;
|
||||||
|
- Remi's origin reset or timed out HTTPS under load;
|
||||||
|
- an EPEL mirror had a broken certificate.
|
||||||
|
|
||||||
|
On that day a build without the cache spent **1031 s** in its first dnf step. Through the cache the same step took **40 s cold and 21 s warm**.
|
||||||
|
|
||||||
|
## What the cache serves
|
||||||
|
|
||||||
|
| URL (https, pkg-cache CA) | Upstream tiers, tried in order (stale copy last) |
|
||||||
|
|---|---|
|
||||||
|
| `https://192.168.1.40/remi/` | `https://rpms.remirepo.net`, then `http://rpms.remirepo.net` (origin only, no mirrors) |
|
||||||
|
| `https://192.168.1.40/epel/` | `https://dl.fedoraproject.org/pub/epel`, `https://mirror.math.princeton.edu/pub/epel`, `https://mirrors.kernel.org/fedora-epel` |
|
||||||
|
| `https://192.168.1.40/almalinux/` | `https://repo.almalinux.org/almalinux`, `https://mirror.math.princeton.edu/pub/almalinux` |
|
||||||
|
| `https://192.168.1.40/ubuntu/` | `https://archive.ubuntu.com/ubuntu`, `https://us.archive.ubuntu.com/ubuntu` |
|
||||||
|
| `https://192.168.1.40/ubuntu-security/` | `https://security.ubuntu.com/ubuntu`, `https://archive.ubuntu.com/ubuntu` |
|
||||||
|
| `https://192.168.1.40/litespeed/` | `https://rpms.litespeedtech.com`, then `http://rpms.litespeedtech.com` |
|
||||||
|
| `192.168.1.40:5000` (TLS, pkg-cache CA) | Docker Hub pull-through cache (`registry-1.docker.io`) |
|
||||||
|
|
||||||
|
Plain `http://192.168.1.40/` answers **403** to LAN clients.
|
||||||
|
|
||||||
|
Cache rules:
|
||||||
|
- **Metadata** has a 3-minute TTL. After that it is revalidated upstream (`If-Modified-Since`). Metadata means:
|
||||||
|
- `repomd.xml*`
|
||||||
|
- repodata files whose names don't start with a hash
|
||||||
|
- apt `dists/`, except `by-hash/`
|
||||||
|
- `*-latest*.rpm` and `remi-release-N.rpm`
|
||||||
|
- `RPM-GPG-KEY*`
|
||||||
|
- directory listings
|
||||||
|
- **Packages** (`*.rpm`, `*.deb`, hash-named repodata, `by-hash/`) are cached for 30 days and evicted after 60 days unused. Total size is capped at 110 GB, with 15 GB kept free.
|
||||||
|
- On a connect error, timeout or 5xx, the request falls through to the next tier. Only the **last** tier may answer from a stale copy, so the order is primary, then fallback, then stale.
|
||||||
|
- `proxy_cache_lock` is on.
|
||||||
|
- Responses carry `X-Cache-Status` and `X-Pkgcache-Tier`.
|
||||||
|
|
||||||
|
## Building with the cache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker buildx build --build-arg PKG_CACHE=https://192.168.1.40 --build-arg PHPVER=85 -f Dockerfile .
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/pkg-cache-repos.sh` (installed as `/usr/local/sbin/`) and `keys/pkg-cache/` (installed as `/usr/local/share/pkg-cache/`) are wired into `Dockerfile`, `Dockerfile.fpm`, `Dockerfile.shared-httpd` and `Dockerfile.shared-ols`. The script has four modes.
|
||||||
|
|
||||||
|
**`rpm`** handles AlmaLinux, EPEL and Remi:
|
||||||
|
- Each `baseurl` becomes `<cache>/<prefix>/... <origin>/...`. dnf fails over to the origin if the cache dies.
|
||||||
|
- That section's mirrorlist/metalink is commented out.
|
||||||
|
- The section gets `sslcacert=` pointing at a bundle of the system CAs plus the pkg-cache CA. The bundle lives in the state dir and is deleted by `restore`.
|
||||||
|
- It also gets `gpgcheck=1`.
|
||||||
|
- AlmaLinux and Remi also get `repo_gpgcheck=1`, because both sign `repomd.xml` (measured 2026-10-21: `repomd.xml.asc` returns 200). EPEL publishes no repomd signature (404), so its metadata integrity rests on HTTPS end to end.
|
||||||
|
|
||||||
|
**`apt`** handles Ubuntu archive/security and LiteSpeed:
|
||||||
|
- Their URIs are rewritten to the cache.
|
||||||
|
- `/etc/apt/apt.conf.d/99pkg-cache` sets `Acquire::https::192.168.1.40::CaInfo`, so the CA is trusted for the cache host only.
|
||||||
|
- apt verifies the signed `InRelease`/`Release.gpg` as always. It has no failover, so CI must probe the cache first.
|
||||||
|
|
||||||
|
**`release-install epel [remi]`**:
|
||||||
|
- Downloads the release RPM over HTTPS, from the cache or from the origin.
|
||||||
|
- Checks the package name.
|
||||||
|
- Imports the key committed in `keys/pkg-cache/`.
|
||||||
|
- Requires `rpm -K` to report `digests signatures OK`.
|
||||||
|
- Installs with `--setopt=localpkg_gpgcheck=1`.
|
||||||
|
|
||||||
|
**`restore`**:
|
||||||
|
- Puts rewritten files back.
|
||||||
|
- Deletes files the script created.
|
||||||
|
- Deletes any repo/apt file that **appeared during the RUN** and still references the cache.
|
||||||
|
- Then **fails the build** if any file under `/etc/yum.repos.d`, `/etc/dnf` or `/etc/apt` still mentions the cache.
|
||||||
|
|
||||||
|
Every RUN that rewrites ends with `restore`, so no shipped layer carries the LAN address. This was verified on the built images: 0 references, no state dir, no apt drop-in.
|
||||||
|
|
||||||
|
`PKG_CACHE` must start with `https://`. `http://` is refused.
|
||||||
|
|
||||||
|
`Dockerfile.litespeed` and `Dockerfile.lsphp` are **not wired yet**. Two things are needed first:
|
||||||
|
- the multi-stage `lsphp-apt-upgrade.sh` flow needs a `PKG_CACHE` ARG in each stage;
|
||||||
|
- its madison check must accept the cache URL. It currently requires `*rpms.litespeedtech.com*`, and via the cache madison shows `https://192.168.1.40/litespeed/debian`.
|
||||||
|
|
||||||
|
## Trust model
|
||||||
|
|
||||||
|
`keys/pkg-cache/ca.crt` is the *pkg-cache internal CA*:
|
||||||
|
- EC P-256, valid to 2036.
|
||||||
|
- **Name-constrained** to IP `192.168.1.40` and DNS `pkg-cache.internal.cloud-hosting.io` / `pkg-cache.geekhome.local`. Even its private key, which exists only on CT130 at `/etc/pkgcache-ca/ca.key`, cannot mint a certificate that clients would accept for any other host.
|
||||||
|
- It is trusted only by the rewritten repos, the apt per-host option and the BuildKit/dockerd registry config. It is never added to a system trust store.
|
||||||
|
|
||||||
|
The server certificate `/etc/pkgcache-ca/server.crt` **expires 2029-10-20**. `pkgcache-stats` prints the expiry. Renew it with the same `openssl x509 -req ... -extfile srv.cnf` steps in `/etc/pkgcache-ca/`.
|
||||||
|
|
||||||
|
Remaining risks:
|
||||||
|
- **Replay of old but validly signed metadata.** Signatures stop tampering, not freshness. The cache's own plain-http tiers for Remi and LiteSpeed are the leg where an internet MITM could replay. The php.net gates (`EXPECT_PHP_MIN`, `EXPECT_LSPHP_PKG`/`EXPECT_PHP_VERSION`) fail an image build that gets old PHP. **Josh to decide** whether to drop those http tiers: you gain replay resistance and lose availability when Remi's HTTPS is down, as it was all day on 2026-10-21.
|
||||||
|
- **Proposed, not implemented:** the same kind of minimum-version gate for other security-relevant packages:
|
||||||
|
- CI resolves the newest `openssl`/`openssl-libs` and `httpd` from AlmaLinux BaseOS/AppStream, and `ImageMagick` from EPEL, **directly from the origin over HTTPS** (e.g. `dnf repoquery --repofrompath=o,https://repo.almalinux.org/almalinux/10/BaseOS/x86_64/os/ --repo=o --latest-limit=1 --qf '%{evr}' openssl-libs`).
|
||||||
|
- It passes the results as `EXPECT_OPENSSL_MIN` / `EXPECT_HTTPD_MIN` / `EXPECT_IMAGEMAGICK_MIN`.
|
||||||
|
- The Dockerfile asserts `rpm -q --qf '%{EVR}'` is not older (`rpm --eval "%{lua: print(rpm.vercmp('A','B'))}"`).
|
||||||
|
|
||||||
|
## CI integration (APPLIED 2026-10-21)
|
||||||
|
|
||||||
|
1. **Runner network.** Ephemeral runners are NATed and `iptables FORWARD` drops
|
||||||
|
all RFC1918 destinations:
|
||||||
|
- `eph-pve-*` on `pve`, vmbr2, 10.99.98.0/24;
|
||||||
|
- `eph-pve3-*` on `pve3`, vmbr1, 10.99.99.0/24.
|
||||||
|
|
||||||
|
One rule per node now sits **above** those DROPs. It is persistent through a
|
||||||
|
`post-up`/`post-down` pair placed before the first DROP line in
|
||||||
|
`/etc/network/interfaces.d/vmbr2-ci-isolated` (pve) and
|
||||||
|
`/etc/network/interfaces.d/vmbr1-ci-isolated` (pve3). The Proxmox firewall is
|
||||||
|
disabled on both nodes, so its reloads do not touch these chains. Backups:
|
||||||
|
`/root/vmbr{2,1}-ci-isolated.bak-pkgcache-20261021`.
|
||||||
|
```
|
||||||
|
iptables -I FORWARD 1 -i vmbr2 -o vmbr0 -d 192.168.1.40/32 -p tcp -m multiport --dports 443,5000 \
|
||||||
|
-m comment --comment pkg-cache-ct130 -j ACCEPT
|
||||||
|
```
|
||||||
|
Verified from throwaway CTs on vmbr2 and vmbr1:
|
||||||
|
- :443 and :5000 work with TLS verified;
|
||||||
|
- CT130 :22/:80, pve :8006, .193 :22, the router's :53 and 10.10.14.20 stay **blocked**;
|
||||||
|
- internet access is unchanged.
|
||||||
|
|
||||||
|
The LAN runner VM *Git-Builders* (VM103, 192.168.1.126 on vmbr0) needs no
|
||||||
|
rule. `gitea-runner.internal.cloud-hosting.io` resolves to 10.10.14.20, which
|
||||||
|
is unroutable from both the LAN and the DC (stale record?).
|
||||||
|
2. **Workflow** (`.gitea/workflows/build-push.yaml`). Jobs Build-and-Push,
|
||||||
|
Build-FPM-Images, Build-Shared-httpd and Build-Shared-OLS run a
|
||||||
|
**"Probe package cache"** step. It curls `https://192.168.1.40/` and
|
||||||
|
`https://192.168.1.40:5000/v2/` with `--cacert keys/pkg-cache/ca.crt
|
||||||
|
--max-time 5`.
|
||||||
|
- If both answer: `PKG_CACHE=https://192.168.1.40` is passed as a build arg,
|
||||||
|
and buildx is set up with the Docker Hub mirror (`buildkitd-config-inline`,
|
||||||
|
`ca=`).
|
||||||
|
- If either fails: that output is empty, the build uses upstream repos /
|
||||||
|
Docker Hub, and a `::warning::` is emitted.
|
||||||
|
|
||||||
|
Litespeed and lsphp jobs are unchanged (their Dockerfiles are not wired).
|
||||||
|
**Fail-back measured** with the workflow's own step code, from a runner-bridge CT:
|
||||||
|
|
||||||
|
| cache state | probe result | time |
|
||||||
|
|---|---|---|
|
||||||
|
| up | url + mirror set | <1 s |
|
||||||
|
| nginx + registry stopped | both empty, warning | <1 s (connection refused) |
|
||||||
|
| silently dropped | both empty, warning | 10 s (2 x 5 s) |
|
||||||
|
|
||||||
|
Mid-build cache loss is also covered: dnf has the origin as a 2nd baseurl, and
|
||||||
|
BuildKit falls back from the mirror to Docker Hub (measured with the registry stopped).
|
||||||
|
3. **dockerd clients** (plain `docker pull`). Untested, because it needs a daemon restart.
|
||||||
|
Use TLS, no `insecure-registries`:
|
||||||
|
```
|
||||||
|
install -D -m0644 keys/pkg-cache/ca.crt /etc/docker/certs.d/192.168.1.40:5000/ca.crt
|
||||||
|
# /etc/docker/daemon.json: { "registry-mirrors": ["https://192.168.1.40:5000"] } then restart docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHP hosts (`scripts/host/pkgcache-host-repos.sh`)
|
||||||
|
|
||||||
|
`pkgcache-host-repos.sh apply https://192.168.1.40` does the following:
|
||||||
|
- backs up every `/etc/yum.repos.d/*.repo` to `/root/pkgcache-repo-backup-<UTC>/`;
|
||||||
|
- installs the name-constrained CA as a system anchor;
|
||||||
|
- rewrites the AlmaLinux, EPEL and Remi sections to `baseurl=<cache>/... <origin>/...`;
|
||||||
|
- comments out mirrorlist/metalink in those sections;
|
||||||
|
- sets `gpgcheck=1`, plus `repo_gpgcheck=1` for AlmaLinux and Remi.
|
||||||
|
|
||||||
|
`revert [DIR]` restores byte-identical files (verified on AL9 and AL10) and
|
||||||
|
removes the anchor. `status` shows the current state.
|
||||||
|
|
||||||
|
**Why not "cache first, mirrorlist as fallback":** measured on AL10, with
|
||||||
|
mirrorlist/metalink left in place dnf **ignores `baseurl` completely** (0 cache
|
||||||
|
requests). The origin as second baseurl is the fallback dnf really performs.
|
||||||
|
|
||||||
|
**Measured on 192.168.1.148** (AlmaLinux 10.2), 2026-10-21:
|
||||||
|
|
||||||
|
| test | result |
|
||||||
|
|---|---|
|
||||||
|
| baseline `dnf makecache --refresh` (original mirrorlists) | rc=0, **1209 s** |
|
||||||
|
| via cache `makecache --refresh` | rc=0, **22 s** (2nd run, non-interactive: 2 s) |
|
||||||
|
| via cache `dnf download php-cli` | rc=0, 1 s |
|
||||||
|
| `dnf -y --downloadonly update php-cli` | "Nothing to do" (already 8.5.12) |
|
||||||
|
| cache **down** (nginx stopped): makecache --refresh / download | rc=0 **16 s** / rc=0 24 s, served by origin |
|
||||||
|
| cache **silently dropped** (iptables DROP): makecache --refresh / download | rc=0 **225 s** / rc=0 59 s (dnf's 30 s connect timeout per attempt) |
|
||||||
|
|
||||||
|
**Datacenter hosts** (whp01, whp02, sdbees, TrueSelfCA) cannot reach
|
||||||
|
192.168.1.40 and are **not configured**. That needs CT130 on NetBird (see the
|
||||||
|
report: setup key / route pending).
|
||||||
|
|
||||||
|
## Operating the cache (CT130)
|
||||||
|
|
||||||
|
- **nginx config.**
|
||||||
|
- `/etc/nginx/conf.d/pkgcache-http.conf` holds the cache zone, maps and log format.
|
||||||
|
- `/etc/nginx/pkgcache/{server-common,meta,pkg}.conf` hold the shared settings.
|
||||||
|
- `/etc/nginx/sites-available/pkgcache` is **generated** by `/usr/local/sbin/pkgcache-gen-nginx`. Edit the `UPSTREAMS` table, then run `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx`.
|
||||||
|
- Listeners: :443 TLS for the LAN, `127.0.0.1:80` for local tools, and :80 for the LAN answers 403.
|
||||||
|
- **Registry.** Config is `/etc/docker/registry/config.yml` (Debian `docker-registry` 2.8.3, the same code as `registry:2`, with TLS). Service `docker-registry`.
|
||||||
|
- **TLS.** Everything is in `/etc/pkgcache-ca/`: `ca.key` (0600), `ca.crt`, `server.key`, `server-chain.crt`, and the `ca.cnf`/`srv.cnf` openssl configs.
|
||||||
|
- **Data.** nginx uses `/srv/cache/nginx`, the registry uses `/srv/cache/registry`. Both live on the 150 GB mount point `mp0` (`backup=0`).
|
||||||
|
- **Security-release day.** `pkgcache-refresh` re-fetches every cached metadata entry right away. Variants:
|
||||||
|
- `pkgcache-refresh remi` refreshes one prefix;
|
||||||
|
- `--list` lists the entries;
|
||||||
|
- `--purge` deletes them instead.
|
||||||
|
|
||||||
|
A refresh bypasses the cache from localhost only and replaces each entry in place. If upstream is down, the old copy stays. Metadata is at most 3 minutes old anyway.
|
||||||
|
- **Monitoring.** `pkgcache-stats` shows:
|
||||||
|
- disk usage;
|
||||||
|
- hit ratio;
|
||||||
|
- fallback-tier use;
|
||||||
|
- the STALE count;
|
||||||
|
- upstream errors;
|
||||||
|
- certificate expiry.
|
||||||
|
|
||||||
|
Logs are `/var/log/nginx/pkgcache.access.log` (fields `cache=`, `tier=`, `up=`) and `journalctl -u docker-registry`. Suggested Uptime Kuma checks are `https://192.168.1.40/` and `https://192.168.1.40:5000/v2/`, with certificate checking against the CA.
|
||||||
|
- **Docker Hub rate limits.** Anonymous pulls are limited per egress IP, and all clients share it. Add `username`/`password` (a Hub access token) under `proxy:` if needed.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# LiteSpeed's own LSAPI SAPI source, which the LiteSpeed lsphp packages compile
|
||||||
|
# INSTEAD of php-src's bundled sapi/litespeed (measured 2026-10-21 on packaged
|
||||||
|
# lsphp81..85: the binaries carry LSAPI_MAX_CMD_SCRIPT_PATH_LEN, report
|
||||||
|
# "Copyright (c) 1997-2016" and phpversion('litespeed') = '' — all three are
|
||||||
|
# php-litespeed-8.3, none is php-src 8.5.12's sapi/litespeed).
|
||||||
|
# LiteSpeed publishes no signature for it; this sha256 was taken on 2026-10-21
|
||||||
|
# (Last-Modified: Sun, 07 Sep 2025 20:35:32 GMT) and lsphp-src-build.sh refuses
|
||||||
|
# any other file. lsphp-src-build.sh additionally checks that the resulting
|
||||||
|
# binary has the same LSAPI_* feature/env-name set as the packaged lsphp, so a
|
||||||
|
# LiteSpeed SAPI upgrade we have not pinned yet fails the build instead of
|
||||||
|
# shipping an older SAPI.
|
||||||
|
# <url> <sha256>
|
||||||
|
https://www.litespeedtech.com/packages/lsapi/php-litespeed-8.3.tgz cd616b82cc853e5e3187fccd060228b9cd1a2009ce72d8da5950eef950ac29b1
|
||||||
@@ -0,0 +1,804 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=SWGM
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# PHP release-manager signing keys, per branch: <branch> <PRIMARY fingerprint> <who>
|
||||||
|
# Transcribed from https://www.php.net/gpg-keys.php (2026-10-21). The key material
|
||||||
|
# is php-keyring.asc in this directory, the official keyring from
|
||||||
|
# https://www.php.net/distributions/php-keyring.gpg (saved unmodified; it is ASCII-armoured despite its .gpg name).
|
||||||
|
# lsphp-src-build.sh accepts a tarball only if gpgv validates its .asc against
|
||||||
|
# php-keyring.asc AND the signing key's PRIMARY fingerprint is listed here for
|
||||||
|
# the tarball's branch. A new release manager = a reviewed commit to both files.
|
||||||
|
8.1 528995BFEDFBA7191D46839EF9BA0ADA31CBD89E Joe Watkins
|
||||||
|
8.1 39B641343D8C104B2B146DC3F9C39DC0B9698544 Ben Ramsey
|
||||||
|
8.1 F1F692238FBC1666E5A5CCD4199F9DFEF6FFBAFD Patrick Allaert
|
||||||
|
8.2 1198C0117593497A5EC5C199286AF1F9897469DC Pierrick Charron
|
||||||
|
8.2 39B641343D8C104B2B146DC3F9C39DC0B9698544 Ben Ramsey
|
||||||
|
8.2 E60913E4DF209907D8E30D96659A97C9CF2A795A Sergey Panteleev
|
||||||
|
8.3 1198C0117593497A5EC5C199286AF1F9897469DC Pierrick Charron
|
||||||
|
8.3 AFD8691FDAEDF03BDF6E460563F15A9B715376CA Eric A Mann
|
||||||
|
8.3 C28D937575603EB4ABB725861C0779DC5C0A9DE4 Jakub Zelenka
|
||||||
|
8.4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA Eric A Mann
|
||||||
|
8.4 9D7F99A0CB8F05C8A6958D6256A97AF7600A39A6 Calvin Buckley
|
||||||
|
8.4 0616E93D95AF471243E26761770426E17EBBB3DD Saki Takamachi
|
||||||
|
8.5 1198C0117593497A5EC5C199286AF1F9897469DC Pierrick Charron
|
||||||
|
8.5 49D9AF6BC72A80D6691719C8AA23F5BE9C7097D4 Volker Dusch
|
||||||
|
8.5 D95C03BC702BE9515344AE3374E44BC9067701A5 Daniel Scherzer
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
Trust material for the OPT-IN internal package cache (scripts/pkg-cache-repos.sh,
|
||||||
|
docs/pkg-cache.md). Copied into images at /usr/local/share/pkg-cache/; nothing
|
||||||
|
here is added to the system trust store.
|
||||||
|
|
||||||
|
ca.crt "pkg-cache internal CA (CT130)", EC P-256, valid to 2036-10-18.
|
||||||
|
NAME-CONSTRAINED to IP 192.168.1.40 and DNS
|
||||||
|
pkg-cache.internal.cloud-hosting.io / pkg-cache.geekhome.local.
|
||||||
|
SHA-256 fingerprint BB:5B:9A:08:05:ED:2F:58:83:92:D0:99:9D:11:0A:30:14:88:68:F0:F0:88:65:5E:94:0B:75:EB:C2:A2:BD:F3
|
||||||
|
(private key lives only on CT130: /etc/pkgcache-ca/ca.key)
|
||||||
|
RPM-GPG-KEY-EPEL-10 Fedora (epel10) 7D8D 15CB FC4E 6268 8591 FB26 33D9 8517 E37E D158
|
||||||
|
fetched 2026-10-21 over HTTPS from dl.fedoraproject.org and
|
||||||
|
mirror.math.princeton.edu (identical); fingerprint matches
|
||||||
|
https://fedoraproject.org/security/
|
||||||
|
RPM-GPG-KEY-remi2024 Remi's RPM repository 2024 CF1D F005 7CE8 5DFF 5B2F 2A37 C2FD 3B2C 2A09 48E4
|
||||||
|
fetched 2026-10-21 over HTTPS from rpms.remirepo.net; byte-identical
|
||||||
|
to the key shipped in remi-release-10.2-1.el10.remi. Not found on
|
||||||
|
keys.openpgp.org / keyserver.ubuntu.com - confirm out of band.
|
||||||
|
Remi rotates keys (remi2025, ...): when remi-release starts being
|
||||||
|
signed by a new key, `release-install remi` fails loudly - add the
|
||||||
|
new key here (and to pkg-cache-repos.sh).
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBGV4X6kBEAC3eQxgiWuo08uc3mHo4ELux++uqTnYz/tJzEf9Ou3h36WnhumA
|
||||||
|
Nvs+Ts5h8PBx879Y9/aIX1Z20p1kf6tBCinZnEJu59n+TAAsph0+XQlV1l5YkleK
|
||||||
|
Z2ff/Fg65k8QcLXWaIGykA/FaKznRiSurGuD6tRGhJw7DawEwBJr8QZSkRUpnH1L
|
||||||
|
URW97Q/iKrRPiE5VEayE0y8eAL28jIIiFvR+4oJMzvCsRRB/2wYZ2MlJOW91hcYf
|
||||||
|
mbUoXKOBD5UzsJylu7kj25K/ge8rEJ7KicOOwcdYddxsU3DxGSSfwF8AMagENcm2
|
||||||
|
XROeXknjm84A8sNlUkFZBJwfuc7eRTiZGJrnQQVYLrkKj8Mxpq9Ts7hU51TqAWNI
|
||||||
|
uvGDlJdYNE3D2RMqjMEsZ8ej08Thrib6xslu4NzTBkt+6QNnXL4E3hEgYtoyio60
|
||||||
|
GswSz2ulogKg7X4JrNdJYE8/qNowyF3hoVgj5TG1/wQRq+5HlMMOLjgGu9wzLUix
|
||||||
|
fnVfEUnzaofbrUf4/GabCaeY8xRe4tFQrvzigQ4g+kgwKKnfAeqBmPov0yljkw9z
|
||||||
|
BYJWR5zvaw0ffg9Ing00KUSaXBXA5jSlgk1603Y+LefY1SlXsTyqohiRvGH6FI77
|
||||||
|
HNMo72DwoJfFcYjncZUzKgXWJECR4nhVsdj6pKoOjcQ4aSuyVxtsR86ASQARAQAB
|
||||||
|
tChGZWRvcmEgKGVwZWwxMCkgPGVwZWxAZmVkb3JhcHJvamVjdC5vcmc+iQJOBBMB
|
||||||
|
CAA4FiEEfY0Vy/xOYmiFkfsmM9mFF+N+0VgFAmV4X6kCGw8FCwkIBwIGFQoJCAsC
|
||||||
|
BBYCAwECHgECF4AACgkQM9mFF+N+0Vhv/A/+PlhPLSctGRCUEahE+cN4764Acc3p
|
||||||
|
l40ZYzXRhqR0/Tc1/cSDjlA3qVTc8SPohi5OJXwCyr9EiMqKoyoDN097euqbYpyp
|
||||||
|
yN/Pj0lBjsXwcpdDtZ21WGeQU0Khb04N68bMtJbDaxeBciTvDDQravZuPPh0m4Rg
|
||||||
|
Z6myEoa6Aa6EK0hI1Qwi1qIWeRiuEkVT671IaKVETBW5XiUpNBXDAB/L+6DzUF9u
|
||||||
|
scBzfsUDiPO6NrpYDtV3jwq22y6gWluIct/Ka8brwPbqK2sBfFzrHboRhfqlTGjs
|
||||||
|
7F9qUGwIQZn/A8iozXZYQ0+JG1bhQyvjA8eN1GOcRpT+O7H7JXN49o6IG2As4+iK
|
||||||
|
F04+qjqAu2sVfpD8mzM2VubFNllcKKiyCzRYHhSbObRCPzsudDL9GPiXeGGaCuWg
|
||||||
|
sDkiA1MESvf2tLETAGBs/TziO4GwmXUtlKbRiq1FYm90mVq9mBxPZ/Idn+yZusNB
|
||||||
|
0O5SXIbI8lYZw5n4XTK4b+byHRBYsOTHiTsGvjTF2Y7oSwW2CVUmL6RZ23mI4qoY
|
||||||
|
1p5kzRS+GjT1acnTei/FTsOlIKCsjfeHx7uxCkX6xpAD8P3UtLQqfsgH0CL4vSZt
|
||||||
|
TGO6L1InQlp4ZG3OYIomTKbD3/R0wod3U3dTqdulQMXL895u6OLTY3spY2m2MO2k
|
||||||
|
p9Dfd2pKuxK9Mys=
|
||||||
|
=mhQZ
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=meaU
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICPzCCAeagAwIBAgIUJI0QJZ+PCrSQ/nKZFENwwcp8Zr0wCgYIKoZIzj0EAwIw
|
||||||
|
TDEmMCQGA1UEAwwdcGtnLWNhY2hlIGludGVybmFsIENBIChDVDEzMCkxIjAgBgNV
|
||||||
|
BAoMGWNsb3VkLWhvc3RpbmcuaW8gaW50ZXJuYWwwHhcNMjYxMDIxMTkzNjIyWhcN
|
||||||
|
MzYxMDE4MTkzNjIyWjBMMSYwJAYDVQQDDB1wa2ctY2FjaGUgaW50ZXJuYWwgQ0Eg
|
||||||
|
KENUMTMwKTEiMCAGA1UECgwZY2xvdWQtaG9zdGluZy5pbyBpbnRlcm5hbDBZMBMG
|
||||||
|
ByqGSM49AgEGCCqGSM49AwEHA0IABBJh75sC5vSzTnkYl0CYYEA/TKUHEXhbtanV
|
||||||
|
OVNW4NlD3za5+bu5YuKjpbFAva4TCZqWsaS9IMd/JfsaFbf5XVejgaUwgaIwEgYD
|
||||||
|
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP7UCTf/
|
||||||
|
v7KU1KBrs1vqa71/TZl0MF0GA1UdHgEB/wRTMFGgTzAKhwjAqAEo/////zAlgiNw
|
||||||
|
a2ctY2FjaGUuaW50ZXJuYWwuY2xvdWQtaG9zdGluZy5pbzAaghhwa2ctY2FjaGUu
|
||||||
|
Z2Vla2hvbWUubG9jYWwwCgYIKoZIzj0EAwIDRwAwRAIgOod4wPHu0kAzYAw8KeGg
|
||||||
|
cIrFMwQ4ATKVTkDioiSFOqICIEhY+sGj/YZ4oBMst3b/AAz9zMEJAXpnCkSTI6tM
|
||||||
|
YAIl
|
||||||
|
-----END CERTIFICATE-----
|
||||||
+58
-24
@@ -14,8 +14,18 @@ export FPM_LISTEN=${FPM_LISTEN:-9000}
|
|||||||
|
|
||||||
adduser -u $uid $user
|
adduser -u $uid $user
|
||||||
|
|
||||||
mkdir -p /home/$user/public_html
|
# Root never follows customer symlinks under /home/$user: see safe-paths.sh.
|
||||||
mkdir -p /home/$user/logs/php-fpm
|
source /scripts/safe-paths.sh
|
||||||
|
secure_home
|
||||||
|
|
||||||
|
user_mkdir /home/$user/public_html /home/$user/logs/php-fpm
|
||||||
|
# Create the pool's error_log (php_admin_value[error_log] in
|
||||||
|
# create-php-config.sh) AS THE CUSTOMER, before php-fpm starts. It used to be
|
||||||
|
# `touch`ed as root at the very end, after the chown -R, so on a site's first
|
||||||
|
# start the pool (customer uid) could not open it and PHP errors fell back to
|
||||||
|
# FastCGI stderr -> shared-httpd's error log until the next restart's chown -R
|
||||||
|
# fixed the owner. user_touch never writes or chowns through a symlink.
|
||||||
|
user_touch /home/$user/logs/php-fpm/error.log
|
||||||
|
|
||||||
ln -sf /home/$user/logs/php-fpm /var/log/php-fpm
|
ln -sf /home/$user/logs/php-fpm /var/log/php-fpm
|
||||||
|
|
||||||
@@ -25,13 +35,27 @@ echo "Container memory: ${CONTAINER_MEMORY_MB}MB | PHP-FPM pm=${PHP_FPM_PM} max_
|
|||||||
/scripts/create-php-config.sh
|
/scripts/create-php-config.sh
|
||||||
|
|
||||||
mkdir -p /run/php-fpm/
|
mkdir -p /run/php-fpm/
|
||||||
|
# php-fpm opens its slowlog under /home/$user/logs as root: last fail-closed
|
||||||
|
# check immediately before it starts (safe-paths.sh).
|
||||||
|
require_safe_logs /home/$user/logs/php-fpm
|
||||||
/usr/sbin/php-fpm -y /etc/php-fpm.conf
|
/usr/sbin/php-fpm -y /etc/php-fpm.conf
|
||||||
|
# GNU chown -R defaults to -P: symlinks are lchown()ed, never followed.
|
||||||
chown -R $user:$user /home/$user
|
chown -R $user:$user /home/$user
|
||||||
chmod -R 755 /home/$user
|
# Do NOT `chmod -R 755` the docroot here. /home/$user is the customer's
|
||||||
|
# bind-mounted site directory; a recursive 755 on every start made every file
|
||||||
|
# (wp-config.php included) world-readable AND executable, and reverted any
|
||||||
|
# tighter permissions the customer had set. Apache (shared-httpd, or the
|
||||||
|
# bundled httpd) runs as `apache`, not the customer, so it only needs to
|
||||||
|
# traverse directories to serve static files. Normalise that and nothing else:
|
||||||
|
# open the docroot itself, and add o+x to directories that lack it. Regular
|
||||||
|
# files are never touched, so execute is never added and wp-config.php is
|
||||||
|
# never loosened. Runs as the customer (safe-paths.sh) so a symlink can only
|
||||||
|
# reach what the customer already owns.
|
||||||
|
open_dir_traversal
|
||||||
|
|
||||||
if [[ $environment == 'DEV' ]]; then
|
if [[ $environment == 'DEV' ]]; then
|
||||||
echo "Starting Dev Deployment (FPM-only mode)"
|
echo "Starting Dev Deployment (FPM-only mode)"
|
||||||
mkdir -p /home/$user/_db_backups
|
user_mkdir /home/$user/_db_backups
|
||||||
if ! command -v microdnf &> /dev/null; then
|
if ! command -v microdnf &> /dev/null; then
|
||||||
echo "microdnf not found, installing with dnf..."
|
echo "microdnf not found, installing with dnf..."
|
||||||
dnf install -y microdnf && dnf clean all
|
dnf install -y microdnf && dnf clean all
|
||||||
@@ -49,13 +73,12 @@ if [[ $environment == 'DEV' ]]; then
|
|||||||
mysql -e "CREATE USER '"$mysql_user"'@'localhost' IDENTIFIED BY '"$mysql_password"';"
|
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 "GRANT ALL PRIVILEGES ON *.* TO '"$mysql_user"'@'localhost' WITH GRANT OPTION;"
|
||||||
mysql -e "FLUSH PRIVILEGES;"
|
mysql -e "FLUSH PRIVILEGES;"
|
||||||
echo "# User crontab for $user" > /home/$user/crontab
|
# Written AS THE CUSTOMER (as_user tee), never by root through a customer path.
|
||||||
echo "*/15 * * * * /scripts/mysql-backup.sh $user devdb_$mysql_db" >> /home/$user/crontab
|
drop_symlink /home/$user/crontab
|
||||||
chown $user:$user /home/$user/crontab
|
printf '%s\n' "# User crontab for $user" "*/15 * * * * /scripts/mysql-backup.sh $user devdb_$mysql_db" \
|
||||||
echo "MySQL User: "$mysql_user > /home/$user/mysql_creds
|
| as_user tee /home/$user/crontab >/dev/null
|
||||||
echo "MySQL Password: "$mysql_password >> /home/$user/mysql_creds
|
printf '%s\n' "MySQL User: $mysql_user" "MySQL Password: $mysql_password" "MySQL Database: devdb_$mysql_db" \
|
||||||
echo "MySQL Database: devdb_"$mysql_db >> /home/$user/mysql_creds
|
| as_user tee /home/$user/mysql_creds
|
||||||
cat /home/$user/mysql_creds
|
|
||||||
fi
|
fi
|
||||||
/usr/bin/memcached -d -u $user
|
/usr/bin/memcached -d -u $user
|
||||||
fi
|
fi
|
||||||
@@ -66,21 +89,32 @@ if [[ $environment == 'PROD' ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up user crontab
|
# Set up + load the user crontab, created and read as the customer
|
||||||
if [ ! -f /home/$user/crontab ]; then
|
# (safe-paths.sh). Root reading/writing/chowning it followed symlinks.
|
||||||
echo "# User crontab for $user" > /home/$user/crontab
|
load_user_crontab
|
||||||
echo "# Add your cron jobs here" >> /home/$user/crontab
|
|
||||||
echo "# Example: */5 * * * * /home/$user/scripts/my-script.sh" >> /home/$user/crontab
|
|
||||||
chown $user:$user /home/$user/crontab
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load user crontab
|
|
||||||
crontab -u $user /home/$user/crontab
|
|
||||||
|
|
||||||
/usr/sbin/crond
|
/usr/sbin/crond
|
||||||
|
|
||||||
# Tail PHP-FPM logs (becomes PID 1 process)
|
# Graceful shutdown. bash is PID 1 here, and PID 1 only receives signals it
|
||||||
touch /home/$user/logs/php-fpm/error.log
|
# has a handler for, so without this trap `docker stop`'s SIGTERM was dropped:
|
||||||
tail -f /home/$user/logs/php-fpm/*
|
# docker waited the full 10s and SIGKILLed php-fpm mid-request (exit 137).
|
||||||
|
# php-fpm QUIT = graceful (workers finish their current request), then crond.
|
||||||
|
source /scripts/stop-services.sh
|
||||||
|
TAIL_PID=""
|
||||||
|
shutdown() {
|
||||||
|
trap - TERM INT
|
||||||
|
echo "entrypoint: received stop signal, shutting down"
|
||||||
|
stop_service php-fpm /run/php-fpm/php-fpm.pid QUIT 6 'php-fpm: master'
|
||||||
|
stop_service crond /run/crond.pid TERM 1 '^/usr/sbin/crond'
|
||||||
|
[ -n "$TAIL_PID" ] && kill "$TAIL_PID" 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
trap shutdown TERM INT
|
||||||
|
|
||||||
|
# Tail PHP-FPM logs to docker logs. Backgrounded + `wait` (instead of a
|
||||||
|
# foreground tail) so bash can run the trap as soon as a signal arrives.
|
||||||
|
tail -f /home/$user/logs/php-fpm/* &
|
||||||
|
TAIL_PID=$!
|
||||||
|
wait "$TAIL_PID"
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
@@ -59,11 +59,30 @@ fi
|
|||||||
# Start cron for log rotation
|
# Start cron for log rotation
|
||||||
/usr/sbin/crond
|
/usr/sbin/crond
|
||||||
|
|
||||||
# Tail Apache logs (becomes PID 1 process)
|
# Graceful shutdown. bash is PID 1 here, and PID 1 only receives signals it
|
||||||
|
# has a handler for, so without this trap `docker stop`'s SIGTERM was dropped:
|
||||||
|
# docker waited the full 10s and SIGKILLed apache mid-request (exit 137), for
|
||||||
|
# every site on the host at once. graceful-stop (WINCH) lets in-flight requests
|
||||||
|
# finish; after 7s it falls back to TERM. Timeouts sum to < 10s so this
|
||||||
|
# completes inside docker's default stop timeout.
|
||||||
|
source /scripts/stop-services.sh
|
||||||
|
TAIL_PID=""
|
||||||
|
SLEEP_PID=""
|
||||||
|
shutdown() {
|
||||||
|
trap - TERM INT
|
||||||
|
echo "entrypoint-shared-httpd: received stop signal, shutting down"
|
||||||
|
stop_service httpd /etc/httpd/run/httpd.pid WINCH 7 '^/usr/sbin/httpd'
|
||||||
|
stop_service crond /run/crond.pid TERM 1 '^/usr/sbin/crond'
|
||||||
|
[ -n "$SLEEP_PID" ] && kill "$SLEEP_PID" 2>/dev/null
|
||||||
|
[ -n "$TAIL_PID" ] && kill "$TAIL_PID" 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
trap shutdown TERM INT
|
||||||
|
|
||||||
|
# Tail Apache logs to docker logs.
|
||||||
# Use a loop to pick up new log files as vhosts are added.
|
# Use a loop to pick up new log files as vhosts are added.
|
||||||
# tail -f only watches files that exist at start time.
|
# tail -f only watches files that exist at start time.
|
||||||
touch /var/log/httpd/error_log
|
touch /var/log/httpd/error_log
|
||||||
TAIL_PID=""
|
|
||||||
while true; do
|
while true; do
|
||||||
LOG_FILES=$(find /var/log/httpd/ -name '*.log' -o -name '*_log' 2>/dev/null | sort)
|
LOG_FILES=$(find /var/log/httpd/ -name '*.log' -o -name '*_log' 2>/dev/null | sort)
|
||||||
if [ -n "$TAIL_PID" ]; then
|
if [ -n "$TAIL_PID" ]; then
|
||||||
@@ -72,6 +91,10 @@ while true; do
|
|||||||
fi
|
fi
|
||||||
tail -f $LOG_FILES &
|
tail -f $LOG_FILES &
|
||||||
TAIL_PID=$!
|
TAIL_PID=$!
|
||||||
# Re-check for new log files every 60 seconds
|
# Re-check for new log files every 60 seconds. Background + `wait` (not a
|
||||||
sleep 60
|
# foreground sleep) so a stop signal runs the trap immediately instead of
|
||||||
|
# after up to 60s.
|
||||||
|
sleep 60 &
|
||||||
|
SLEEP_PID=$!
|
||||||
|
wait "$SLEEP_PID"
|
||||||
done
|
done
|
||||||
+57
-23
@@ -10,8 +10,11 @@ fi
|
|||||||
|
|
||||||
adduser -u $uid $user
|
adduser -u $uid $user
|
||||||
|
|
||||||
mkdir -p /home/$user/public_html
|
# Root never follows customer symlinks under /home/$user: see safe-paths.sh.
|
||||||
mkdir -p /home/$user/logs/{apache,php-fpm}
|
source /scripts/safe-paths.sh
|
||||||
|
secure_home
|
||||||
|
|
||||||
|
user_mkdir /home/$user/public_html /home/$user/logs/apache /home/$user/logs/php-fpm
|
||||||
|
|
||||||
mv /var/log/httpd /var/log/httpd.bak
|
mv /var/log/httpd /var/log/httpd.bak
|
||||||
|
|
||||||
@@ -34,14 +37,28 @@ if [ -f /etc/httpd/conf.d/ssl.conf ]; then
|
|||||||
mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.bak
|
mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.bak
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# httpd and php-fpm open their logs under /home/$user/logs as root: last
|
||||||
|
# fail-closed check immediately before they start (safe-paths.sh).
|
||||||
|
require_safe_logs /home/$user/logs/apache /home/$user/logs/php-fpm
|
||||||
/usr/sbin/httpd -k start
|
/usr/sbin/httpd -k start
|
||||||
/usr/sbin/php-fpm -y /etc/php-fpm.conf
|
/usr/sbin/php-fpm -y /etc/php-fpm.conf
|
||||||
|
# GNU chown -R defaults to -P: symlinks are lchown()ed, never followed.
|
||||||
chown -R $user:$user /home/$user
|
chown -R $user:$user /home/$user
|
||||||
chmod -R 755 /home/$user
|
# Do NOT `chmod -R 755` the docroot here. /home/$user is the customer's
|
||||||
|
# bind-mounted site directory; a recursive 755 on every start made every file
|
||||||
|
# (wp-config.php included) world-readable AND executable, and reverted any
|
||||||
|
# tighter permissions the customer had set. Apache (shared-httpd, or the
|
||||||
|
# bundled httpd) runs as `apache`, not the customer, so it only needs to
|
||||||
|
# traverse directories to serve static files. Normalise that and nothing else:
|
||||||
|
# open the docroot itself, and add o+x to directories that lack it. Regular
|
||||||
|
# files are never touched, so execute is never added and wp-config.php is
|
||||||
|
# never loosened. Runs as the customer (safe-paths.sh) so a symlink can only
|
||||||
|
# reach what the customer already owns.
|
||||||
|
open_dir_traversal
|
||||||
|
|
||||||
if [[ $environment == 'DEV' ]]; then
|
if [[ $environment == 'DEV' ]]; then
|
||||||
echo "Starting Dev Deployment"
|
echo "Starting Dev Deployment"
|
||||||
mkdir -p /home/$user/_db_backups
|
user_mkdir /home/$user/_db_backups
|
||||||
# Ensure microdnf is available for installing MariaDB and memcached in DEV mode
|
# Ensure microdnf is available for installing MariaDB and memcached in DEV mode
|
||||||
if ! command -v microdnf &> /dev/null; then
|
if ! command -v microdnf &> /dev/null; then
|
||||||
echo "microdnf not found, installing with dnf..."
|
echo "microdnf not found, installing with dnf..."
|
||||||
@@ -60,14 +77,13 @@ if [[ $environment == 'DEV' ]]; then
|
|||||||
mysql -e "CREATE USER '"$mysql_user"'@'localhost' IDENTIFIED BY '"$mysql_password"';"
|
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 "GRANT ALL PRIVILEGES ON *.* TO '"$mysql_user"'@'localhost' WITH GRANT OPTION;"
|
||||||
mysql -e "FLUSH PRIVILEGES;"
|
mysql -e "FLUSH PRIVILEGES;"
|
||||||
# Create user crontab with MySQL backup job
|
# Create user crontab with MySQL backup job. Written AS THE CUSTOMER
|
||||||
echo "# User crontab for $user" > /home/$user/crontab
|
# (as_user tee), never by root through a customer path.
|
||||||
echo "*/15 * * * * /scripts/mysql-backup.sh $user devdb_$mysql_db" >> /home/$user/crontab
|
drop_symlink /home/$user/crontab
|
||||||
chown $user:$user /home/$user/crontab
|
printf '%s\n' "# User crontab for $user" "*/15 * * * * /scripts/mysql-backup.sh $user devdb_$mysql_db" \
|
||||||
echo "MySQL User: "$mysql_user > /home/$user/mysql_creds
|
| as_user tee /home/$user/crontab >/dev/null
|
||||||
echo "MySQL Password: "$mysql_password >> /home/$user/mysql_creds
|
printf '%s\n' "MySQL User: $mysql_user" "MySQL Password: $mysql_password" "MySQL Database: devdb_$mysql_db" \
|
||||||
echo "MySQL Database: devdb_"$mysql_db >> /home/$user/mysql_creds
|
| as_user tee /home/$user/mysql_creds
|
||||||
cat /home/$user/mysql_creds
|
|
||||||
|
|
||||||
fi
|
fi
|
||||||
/usr/bin/memcached -d -u $user
|
/usr/bin/memcached -d -u $user
|
||||||
@@ -78,19 +94,37 @@ if [[ $environment == 'PROD' ]]; then
|
|||||||
sed -r -i 's/;session.save_path="localhost:11211/session.save_path="memcache:11211/' /etc/php.d/50-memcached.ini
|
sed -r -i 's/;session.save_path="localhost:11211/session.save_path="memcache:11211/' /etc/php.d/50-memcached.ini
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up user crontab
|
# Set up + load the user crontab, created and read as the customer
|
||||||
if [ ! -f /home/$user/crontab ]; then
|
# (safe-paths.sh). Root reading/writing/chowning it followed symlinks.
|
||||||
echo "# User crontab for $user" > /home/$user/crontab
|
load_user_crontab
|
||||||
echo "# Add your cron jobs here" >> /home/$user/crontab
|
|
||||||
echo "# Example: */5 * * * * /home/$user/scripts/my-script.sh" >> /home/$user/crontab
|
|
||||||
chown $user:$user /home/$user/crontab
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load user crontab
|
|
||||||
crontab -u $user /home/$user/crontab
|
|
||||||
|
|
||||||
/usr/sbin/crond
|
/usr/sbin/crond
|
||||||
tail -f /var/log/httpd/*
|
|
||||||
|
# Graceful shutdown. bash is PID 1 here, and PID 1 only receives signals it
|
||||||
|
# has a handler for, so without this trap `docker stop`'s SIGTERM was dropped:
|
||||||
|
# docker waited the full 10s and SIGKILLed apache and php-fpm mid-request
|
||||||
|
# (exit 137). Stop apache first (graceful-stop: finish in-flight requests,
|
||||||
|
# accept no new ones), then php-fpm (QUIT: workers finish their current
|
||||||
|
# request), then crond. Timeouts sum to < 10s so this completes inside
|
||||||
|
# docker's default stop timeout.
|
||||||
|
source /scripts/stop-services.sh
|
||||||
|
TAIL_PID=""
|
||||||
|
shutdown() {
|
||||||
|
trap - TERM INT
|
||||||
|
echo "entrypoint: received stop signal, shutting down"
|
||||||
|
stop_service httpd /etc/httpd/run/httpd.pid WINCH 4 '^/usr/sbin/httpd'
|
||||||
|
stop_service php-fpm /run/php-fpm/php-fpm.pid QUIT 3 'php-fpm: master'
|
||||||
|
stop_service crond /run/crond.pid TERM 1 '^/usr/sbin/crond'
|
||||||
|
[ -n "$TAIL_PID" ] && kill "$TAIL_PID" 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
trap shutdown TERM INT
|
||||||
|
|
||||||
|
# Stream apache logs to docker logs. Backgrounded + `wait` (instead of a
|
||||||
|
# foreground tail) so bash can run the trap as soon as a signal arrives.
|
||||||
|
tail -f /var/log/httpd/* &
|
||||||
|
TAIL_PID=$!
|
||||||
|
wait "$TAIL_PID"
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## pkgcache-host-repos.sh — point a WHP HOST's dnf repos (AlmaLinux 9/10 BaseOS/
|
||||||
|
## AppStream/CRB/extras/..., EPEL, Remi) at the internal package cache, with the
|
||||||
|
## ORIGIN as automatic fallback. Not used by image builds (see pkg-cache-repos.sh).
|
||||||
|
##
|
||||||
|
## pkgcache-host-repos.sh apply https://192.168.1.40 # backs up, then rewrites
|
||||||
|
## pkgcache-host-repos.sh status
|
||||||
|
## pkgcache-host-repos.sh revert [BACKUP_DIR] # default: newest backup
|
||||||
|
##
|
||||||
|
## Fallback design (measured 2026-10-21 on AlmaLinux 10.2):
|
||||||
|
## * each rewritten section gets baseurl=<cache>/<prefix>/<path> <origin>/<path>
|
||||||
|
## — dnf/librepo treats the second URL as a failover mirror.
|
||||||
|
## * the section's mirrorlist=/metalink= is COMMENTED OUT. Measured: with a
|
||||||
|
## mirrorlist/metalink present dnf ignores baseurl entirely (0 cache requests),
|
||||||
|
## so "cache first, mirrorlist as fallback" is not something dnf can do.
|
||||||
|
## * gpgcheck=1 everywhere; repo_gpgcheck=1 for AlmaLinux + Remi (both sign
|
||||||
|
## repomd.xml); EPEL has no repomd signature and relies on HTTPS end to end.
|
||||||
|
## * trust: the name-constrained pkg-cache CA is installed as a system anchor
|
||||||
|
## (/etc/pki/ca-trust/source/anchors/pkg-cache-ca.crt). Its nameConstraints
|
||||||
|
## (IP 192.168.1.40, pkg-cache.* DNS) are enforced by OpenSSL — a cert it
|
||||||
|
## signs for any other name fails ("permitted subtree violation").
|
||||||
|
## Every changed file is copied to /root/pkgcache-repo-backup-<UTC>/ first.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODE="${1:-status}"
|
||||||
|
CA_SRC="$(dirname "$0")/pkg-cache-ca.crt"
|
||||||
|
ANCHOR=/etc/pki/ca-trust/source/anchors/pkg-cache-ca.crt
|
||||||
|
die() { echo "FATAL(pkgcache-host-repos): $*" >&2; exit 1; }
|
||||||
|
same() { [ "$(sha256sum < "$1")" = "$(sha256sum < "$2")" ]; } # cmp/diffutils is not on minimal hosts
|
||||||
|
|
||||||
|
rewrite() { # $1 file $2 cache url
|
||||||
|
python3 - "$1" "$2" <<'PY'
|
||||||
|
import re, sys
|
||||||
|
f, C = sys.argv[1], sys.argv[2].rstrip('/')
|
||||||
|
maps = [ # regex on a (commented or live) baseurl, cache prefix, origin base, repomd signed?
|
||||||
|
(r'^#?\s*baseurl\s*=\s*https?://repo\.almalinux\.org/almalinux/(\S+)', 'almalinux', 'https://repo.almalinux.org/almalinux/', True),
|
||||||
|
(r'^#?\s*baseurl\s*=\s*https?://download\.example/pub/epel/(\S+)', 'epel', 'https://dl.fedoraproject.org/pub/epel/', False),
|
||||||
|
(r'^#?\s*baseurl\s*=\s*https?://rpms\.remirepo\.net/(\S+)', 'remi', 'https://rpms.remirepo.net/', True),
|
||||||
|
]
|
||||||
|
out, sec = [], []
|
||||||
|
state = {'hit': False, 'sig': False}
|
||||||
|
def flush():
|
||||||
|
for x in sec:
|
||||||
|
if state['hit']:
|
||||||
|
if re.match(r'^(mirrorlist|metalink)\s*=', x):
|
||||||
|
x = '#' + x
|
||||||
|
elif re.match(r'^(gpgcheck|repo_gpgcheck)\s*=', x):
|
||||||
|
continue
|
||||||
|
out.append(x)
|
||||||
|
if state['hit']:
|
||||||
|
# keep blank-line layout: insert before trailing blanks
|
||||||
|
tail = []
|
||||||
|
while out and out[-1].strip() == '':
|
||||||
|
tail.append(out.pop())
|
||||||
|
out.append('gpgcheck=1')
|
||||||
|
if state['sig']:
|
||||||
|
out.append('repo_gpgcheck=1')
|
||||||
|
out.extend(tail)
|
||||||
|
sec.clear(); state['hit'] = state['sig'] = False
|
||||||
|
for line in open(f).read().split('\n'):
|
||||||
|
if line.startswith('['):
|
||||||
|
flush()
|
||||||
|
for rx, prefix, origin, sig in maps:
|
||||||
|
m = re.match(rx, line)
|
||||||
|
if m:
|
||||||
|
p = m.group(1)
|
||||||
|
line = f'baseurl={C}/{prefix}/{p} {origin}{p}'
|
||||||
|
state['hit'] = True; state['sig'] = state['sig'] or sig
|
||||||
|
break
|
||||||
|
sec.append(line)
|
||||||
|
flush()
|
||||||
|
open(f, 'w').write('\n'.join(out))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
apply)
|
||||||
|
C="${2:?usage: apply https://<cache>}"; C="${C%/}"
|
||||||
|
case "$C" in https://*) ;; *) die "cache URL must be https://" ;; esac
|
||||||
|
[ -s "$CA_SRC" ] || die "missing $CA_SRC (copy keys/pkg-cache/ca.crt next to this script)"
|
||||||
|
B=/root/pkgcache-repo-backup-$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
mkdir -p "$B/yum.repos.d"
|
||||||
|
cp -a /etc/yum.repos.d/*.repo "$B/yum.repos.d/"
|
||||||
|
[ -e "$ANCHOR" ] && cp -a "$ANCHOR" "$B/" || touch "$B/.no-anchor-before"
|
||||||
|
install -m 0644 "$CA_SRC" "$ANCHOR" && update-ca-trust
|
||||||
|
for f in /etc/yum.repos.d/almalinux*.repo /etc/yum.repos.d/epel*.repo /etc/yum.repos.d/remi*.repo; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
grep -q -E '^#?\s*baseurl\s*=\s*https?://(repo\.almalinux\.org|download\.example|rpms\.remirepo\.net)/' "$f" && rewrite "$f" "$C"
|
||||||
|
done
|
||||||
|
echo "backup: $B"
|
||||||
|
echo "changed files:"; for f in "$B"/yum.repos.d/*.repo; do same "$f" "/etc/yum.repos.d/$(basename "$f")" || echo " /etc/yum.repos.d/$(basename "$f")"; done
|
||||||
|
;;
|
||||||
|
revert)
|
||||||
|
B="${2:-$(ls -d /root/pkgcache-repo-backup-* 2>/dev/null | sort | tail -1)}"
|
||||||
|
[ -d "$B/yum.repos.d" ] || die "no backup dir ($B)"
|
||||||
|
for f in "$B"/yum.repos.d/*.repo; do same "$f" "/etc/yum.repos.d/$(basename "$f")" || { cp -a "$f" /etc/yum.repos.d/; echo "restored /etc/yum.repos.d/$(basename "$f")"; }; done
|
||||||
|
if [ -e "$B/.no-anchor-before" ]; then rm -f "$ANCHOR"; update-ca-trust; echo "removed $ANCHOR"; fi
|
||||||
|
dnf clean metadata -q || true
|
||||||
|
echo "reverted from $B"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
grep -l -E '^baseurl=https://[^ ]+/(almalinux|epel|remi)/' /etc/yum.repos.d/*.repo 2>/dev/null | sed 's/^/via cache: /' || echo "no repo uses the cache"
|
||||||
|
ls -d /root/pkgcache-repo-backup-* 2>/dev/null | sed 's/^/backup: /' || true
|
||||||
|
[ -e "$ANCHOR" ] && echo "CA anchor: $ANCHOR" || echo "CA anchor: absent"
|
||||||
|
;;
|
||||||
|
*) die "usage: apply URL | status | revert [DIR]" ;;
|
||||||
|
esac
|
||||||
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## lsphp-abi-gate.sh — may PHP extensions compiled against OLD_INC be loaded into
|
||||||
|
## a PHP built with NEW_INC? Used by lsphp-src-build.sh (emergency php.net build)
|
||||||
|
## before it keeps LiteSpeed's packaged PECL .so files (redis, imagick,
|
||||||
|
## memcached, igbinary, msgpack) next to a newer PHP patch release.
|
||||||
|
##
|
||||||
|
## WHY: PHP refuses a module whose ZEND_MODULE_API_NO / build-id differ (loud),
|
||||||
|
## but a changed struct layout under the SAME API number loads fine and corrupts
|
||||||
|
## memory (silent). Patch releases are not supposed to change layouts, yet the
|
||||||
|
## php2shell fix (8.x.y, 2026-10-21) did add fields — so this is measured, not
|
||||||
|
## assumed.
|
||||||
|
##
|
||||||
|
## HOW: compile one TU including the public PHP headers (each header in the list
|
||||||
|
## below that exists in BOTH trees) with -g against each tree, dump every struct/
|
||||||
|
## union with pahole (incl. anonymous typedef'd ones), and compare:
|
||||||
|
## * every pre-existing member keeps its offset and size (else FAIL)
|
||||||
|
## * a new member may only use bytes no old member used (else FAIL)
|
||||||
|
## (padding, or appended at the end)
|
||||||
|
## * a struct may change size only if it is *_globals — engine/module globals
|
||||||
|
## are singletons behind one exported symbol (executor_globals, ps_globals);
|
||||||
|
## no extension embeds or allocates them (else FAIL)
|
||||||
|
## A member that is renamed or retyped counts as removed: a false alarm that fails
|
||||||
|
## loud, never a silent pass.
|
||||||
|
##
|
||||||
|
## Usage: lsphp-abi-gate.sh OLD_INC NEW_INC WORKDIR (each *_INC contains php/)
|
||||||
|
## Env: ABI_MIN_MEMBERS (default 1000) — fewer members extracted from OLD_INC
|
||||||
|
## means the probe itself is broken; refuse rather than pass vacuously.
|
||||||
|
## Exit 0 = compatible (report on stdout), 1 = incompatible (report on stderr).
|
||||||
|
## Tested by scripts/tests/lsphp-abi-gate.test.sh.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OLD_INC="${1:?usage: lsphp-abi-gate.sh OLD_INC NEW_INC WORKDIR}"
|
||||||
|
NEW_INC="${2:?usage: lsphp-abi-gate.sh OLD_INC NEW_INC WORKDIR}"
|
||||||
|
WORK="${3:?usage: lsphp-abi-gate.sh OLD_INC NEW_INC WORKDIR}"
|
||||||
|
MIN="${ABI_MIN_MEMBERS:-1000}"
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
|
||||||
|
HDRS=(php.h php_ini.h SAPI.h ext/standard/info.h ext/standard/php_var.h
|
||||||
|
ext/standard/php_string.h ext/standard/basic_functions.h ext/standard/url.h
|
||||||
|
ext/standard/php_incomplete_class.h ext/standard/php_smart_string.h
|
||||||
|
ext/session/php_session.h ext/json/php_json.h ext/date/php_date.h
|
||||||
|
ext/hash/php_hash.h ext/random/php_random.h ext/spl/spl_exceptions.h
|
||||||
|
ext/pcre/php_pcre.h zend_exceptions.h zend_interfaces.h zend_smart_str.h
|
||||||
|
zend_closures.h zend_generators.h zend_observer.h zend_attributes.h
|
||||||
|
zend_enum.h zend_weakrefs.h zend_fibers.h zend_extensions.h)
|
||||||
|
|
||||||
|
: >"$WORK/abi.c"
|
||||||
|
for h in "${HDRS[@]}"; do
|
||||||
|
for t in "$OLD_INC/php" "$NEW_INC/php"; do
|
||||||
|
[ -e "$t/$h" ] || [ -e "$t/main/$h" ] || [ -e "$t/Zend/$h" ] || continue 2
|
||||||
|
done
|
||||||
|
printf '#include "%s"\n' "$h" >>"$WORK/abi.c"
|
||||||
|
done
|
||||||
|
[ -s "$WORK/abi.c" ] || { echo "ABI gate: none of the probe headers exist in both trees" >&2; exit 1; }
|
||||||
|
|
||||||
|
dump() { # $1 = include root, $2 = tag
|
||||||
|
local inc="$1/php" o="$WORK/abi-$2.o"
|
||||||
|
gcc -g -O0 -c -fno-eliminate-unused-debug-types \
|
||||||
|
-I"$inc" -I"$inc/main" -I"$inc/TSRM" -I"$inc/Zend" -I"$inc/ext" -I"$inc/ext/date/lib" \
|
||||||
|
-o "$o" "$WORK/abi.c"
|
||||||
|
## M <struct> <decl>#<n> <offset[:bit]> <size> and S <struct> <size>
|
||||||
|
## Named: "struct _zend_object {" ... "};" /* size: N ... */
|
||||||
|
## Anonymous: "typedef struct {" ... "} zend_fcall_info_cache;"
|
||||||
|
pahole -a -F dwarf "$o" | awk '
|
||||||
|
/^(typedef )?(struct|union) ([A-Za-z_0-9]+ )?\{/ {
|
||||||
|
depth = 1; nm = 0; sz = ""; delete seen
|
||||||
|
name = ($1 == "typedef") ? "" : $2; next }
|
||||||
|
depth == 0 { next }
|
||||||
|
depth == 1 && /^\}/ {
|
||||||
|
if (name == "") { n = $0; sub(/^\}[ \t]*/, "", n); sub(/;.*$/, "", n); name = n }
|
||||||
|
for (i = 1; i <= nm; i++) print "M", name, mem[i]
|
||||||
|
if (sz != "") print "S", name, sz
|
||||||
|
depth = 0; next }
|
||||||
|
depth == 1 && /\/\* size: [0-9]+/ { t = $0; sub(/^.*size: /, "", t); sub(/[^0-9].*$/, "", t); sz = t; next }
|
||||||
|
/\{[ \t]*$/ { depth++; next }
|
||||||
|
/^[ \t]+\}/ { depth-- }
|
||||||
|
/;[ \t]*\/\*[ \t]+[0-9]+(:[ \t]*[0-9]+)?[ \t]+[0-9]+[ \t]+\*\// {
|
||||||
|
d = $0; sub(/;.*$/, "", d); gsub(/[ \t]+/, " ", d); sub(/^ /, "", d); gsub(/ /, "_", d)
|
||||||
|
c = $0; sub(/^.*\/\*[ \t]+/, "", c); sub(/[ \t]+\*\/.*$/, "", c); gsub(/:[ \t]+/, ":", c)
|
||||||
|
k = split(c, w, /[ \t]+/)
|
||||||
|
seen[d]++
|
||||||
|
mem[++nm] = d "#" seen[d] " " w[1] " " w[k] }
|
||||||
|
' >"$WORK/abi-$2.txt"
|
||||||
|
}
|
||||||
|
dump "$OLD_INC" old
|
||||||
|
dump "$NEW_INC" new
|
||||||
|
|
||||||
|
NOLD=$(awk '$1 == "M"' "$WORK/abi-old.txt" | wc -l)
|
||||||
|
NSTRUCT=$(awk '$1 == "S"' "$WORK/abi-old.txt" | wc -l)
|
||||||
|
if [ "$NOLD" -lt "$MIN" ]; then
|
||||||
|
echo "ABI gate: only $NOLD struct members extracted from $OLD_INC (< $MIN) — the probe is broken; refusing to call that compatible." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPORT=$(awk '
|
||||||
|
FNR == NR { if ($1 == "M") { old[$2 " " $3] = $4 " " $5; ost[$2] = 1 } else if ($1 == "S") osz[$2] = $3; next }
|
||||||
|
$1 == "M" { new[$2 " " $3] = $4 " " $5; nst[$2] = 1 }
|
||||||
|
$1 == "S" { nsz[$2] = $3 }
|
||||||
|
END {
|
||||||
|
for (k in old) { split(k, p, " "); if (!(p[1] in nst)) continue
|
||||||
|
if (!(k in new)) print "BAD removed/renamed member " k " (was " old[k] ")"
|
||||||
|
else if (old[k] != new[k]) print "BAD moved member " k ": " old[k] " -> " new[k] }
|
||||||
|
for (k in new) { if (k in old) continue; split(k, p, " "); if (!(p[1] in ost)) continue
|
||||||
|
split(new[k], q, " "); s = q[1] + 0; e = s + q[2]
|
||||||
|
hit = ""
|
||||||
|
for (o in old) { split(o, r, " "); if (r[1] != p[1]) continue; split(old[o], z, " ")
|
||||||
|
if (s < z[1] + z[2] && z[1] + 0 < e) { hit = o; break } }
|
||||||
|
if (hit != "") print "BAD new member " k " at " new[k] " overlaps old " hit
|
||||||
|
else print "OK new member " k " at " new[k] " (padding/appended)" }
|
||||||
|
for (st in osz) { if (!(st in nsz) || osz[st] == nsz[st]) continue
|
||||||
|
if (st ~ /_globals$/) print "OK size " st " " osz[st] " -> " nsz[st] " (*_globals singleton)"
|
||||||
|
else print "BAD size " st " " osz[st] " -> " nsz[st] }
|
||||||
|
}' "$WORK/abi-old.txt" "$WORK/abi-new.txt" | sort)
|
||||||
|
|
||||||
|
if [[ $'\n'"$REPORT" == *$'\nBAD '* ]]; then
|
||||||
|
printf '%s\n' "$REPORT" >&2
|
||||||
|
echo "ABI gate: INCOMPATIBLE ($NOLD members / $NSTRUCT structs compared)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
[ -z "$REPORT" ] || printf '%s\n' "$REPORT"
|
||||||
|
echo "ABI gate: compatible ($NOLD members / $NSTRUCT structs compared)"
|
||||||
Executable
+102
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## lsphp-apt-upgrade.sh — BUILD-TIME ONLY. Brings every installed lsphp${PHPVER}*
|
||||||
|
## package (and, by default, the Ubuntu OS packages) up to what the repos offer
|
||||||
|
## RIGHT NOW, then proves it.
|
||||||
|
##
|
||||||
|
## Why this exists: cac-litespeed / cac-lsphp start FROM the LiteSpeed prebuilt
|
||||||
|
## image litespeedtech/openlitespeed:<OLS>-lsphpNN, which LiteSpeed republishes
|
||||||
|
## rarely (the 1.8.4 tags were built 2025-12-16 and ship lsphp85 8.5.0 /
|
||||||
|
## lsphp84 8.4.15, measured 2026-10-21, while rpms.litespeedtech.com/debian
|
||||||
|
## offers 8.5.11 / 8.4.26). The base image already carries that apt repo, so a
|
||||||
|
## PHP security release reaches us the moment LiteSpeed publishes the .deb —
|
||||||
|
## provided every build actually asks for it. Before this script the lsphp core
|
||||||
|
## only moved forward as a SIDE EFFECT of `apt-get install lsphpNN-ldap` (whose
|
||||||
|
## exact-version dependency on lsphpNN-common drags the family along); lsphp
|
||||||
|
## extension packages without that coupling (e.g. lsphpNN-msgpack) and every
|
||||||
|
## Ubuntu package stayed at the base image's December versions.
|
||||||
|
##
|
||||||
|
## Fails LOUDLY — never ships the base image's PHP by accident:
|
||||||
|
## * `apt-get update --error-on=any`: plain `apt-get update` only WARNS when a
|
||||||
|
## repo is unreachable and carries on with no/old lists, exit 0.
|
||||||
|
## * the LiteSpeed repo must be the candidate source for lsphp${PHPVER}.
|
||||||
|
## * after upgrading, no lsphp${PHPVER}* package may remain upgradable.
|
||||||
|
## * if CI passed the version it resolved from the repo index (EXPECT_LSPHP_PKG,
|
||||||
|
## EXPECT_PHP_VERSION), the installed package / running PHP must match it;
|
||||||
|
## that same value becomes the image LABEL, so the label cannot lie.
|
||||||
|
##
|
||||||
|
## Usage: lsphp-apt-upgrade.sh [--no-os-upgrade] PHPVER [extra apt packages / pkg=ver pins...]
|
||||||
|
## Extra packages are installed in the same transaction set (after the OS
|
||||||
|
## upgrade, before the lsphp sweep). Does NOT clean apt lists — the caller
|
||||||
|
## decides (multi-stage builders want them; shipped layers delete them).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OS_UPGRADE=1
|
||||||
|
if [ "${1:-}" = "--no-os-upgrade" ]; then OS_UPGRADE=0; shift; fi
|
||||||
|
PHPVER="${1:?usage: lsphp-apt-upgrade.sh [--no-os-upgrade] PHPVER [pkgs...]}"
|
||||||
|
shift
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
APT=(apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold)
|
||||||
|
LSPHP="/usr/local/lsws/lsphp${PHPVER}/bin/lsphp"
|
||||||
|
PHPCLI="/usr/local/lsws/lsphp${PHPVER}/bin/php"
|
||||||
|
|
||||||
|
die() { echo "FATAL(lsphp-apt-upgrade): $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "lsphp-apt-upgrade: PHPVER=${PHPVER} os_upgrade=${OS_UPGRADE} cache_bust=${CACHE_BUST:-<none>}"
|
||||||
|
echo "lsphp-apt-upgrade: base image lsphp${PHPVER}=$(dpkg-query -W -f='${Version}' "lsphp${PHPVER}" 2>/dev/null || echo none)"
|
||||||
|
|
||||||
|
"${APT[@]}" update --error-on=any || die "apt-get update failed (repo unreachable?). Refusing to build with stale package lists."
|
||||||
|
|
||||||
|
# The candidate for lsphpNN must come from the LiteSpeed repo — not "none", and
|
||||||
|
# not merely whatever is already installed.
|
||||||
|
# Read each output into a variable and match in the shell: piping into an
|
||||||
|
# early-exit reader (grep -q, awk ... exit) under pipefail can SIGPIPE the writer
|
||||||
|
# and report "missing" for a package that is there (scripts/tests/lsphp-info-probe.test.sh).
|
||||||
|
MADISON=$(apt-cache madison "lsphp${PHPVER}")
|
||||||
|
if [[ "$MADISON" != *rpms.litespeedtech.com* ]]; then
|
||||||
|
apt-cache policy "lsphp${PHPVER}" >&2 || true
|
||||||
|
die "the LiteSpeed apt repo does not offer lsphp${PHPVER} (repo missing from sources, or its index is empty)."
|
||||||
|
fi
|
||||||
|
POLICY=$(apt-cache policy "lsphp${PHPVER}")
|
||||||
|
CAND=$(awk '/Candidate:/ {print $2; exit}' <<<"$POLICY")
|
||||||
|
echo "lsphp-apt-upgrade: repo candidate lsphp${PHPVER}=${CAND}"
|
||||||
|
|
||||||
|
if [ "$OS_UPGRADE" = 1 ]; then
|
||||||
|
# --with-new-pkgs: let an upgrade pull in a new dependency instead of being
|
||||||
|
# silently "kept back". Never removes packages (that would be dist-upgrade).
|
||||||
|
"${APT[@]}" upgrade --with-new-pkgs
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
"${APT[@]}" install --no-install-recommends "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sweep EVERY installed lsphpNN* package (core + all extensions) to the candidate.
|
||||||
|
mapfile -t PKGS < <(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' "lsphp${PHPVER}*" 2>/dev/null | awk '$1 == "ii" {print $2}')
|
||||||
|
[ "${#PKGS[@]}" -gt 0 ] || die "no lsphp${PHPVER}* packages installed — wrong base image?"
|
||||||
|
"${APT[@]}" install --only-upgrade --no-install-recommends "${PKGS[@]}"
|
||||||
|
|
||||||
|
LEFT=$(apt list --upgradable 2>/dev/null | grep "^lsphp${PHPVER}" || true)
|
||||||
|
[ -z "$LEFT" ] || die "lsphp packages still upgradable after the sweep:
|
||||||
|
$LEFT"
|
||||||
|
|
||||||
|
INST=$(dpkg-query -W -f='${Version}' "lsphp${PHPVER}")
|
||||||
|
RUNV=$("$PHPCLI" -r 'echo PHP_VERSION;') || die "lsphp${PHPVER} CLI does not run"
|
||||||
|
|
||||||
|
if [ -n "${EXPECT_LSPHP_PKG:-}" ] && [ "$INST" != "$EXPECT_LSPHP_PKG" ]; then
|
||||||
|
die "installed lsphp${PHPVER}=$INST but CI resolved $EXPECT_LSPHP_PKG from the repo index (repo changed mid-build? re-run)."
|
||||||
|
fi
|
||||||
|
if [ -n "${EXPECT_PHP_VERSION:-}" ] && [ "$RUNV" != "$EXPECT_PHP_VERSION" ]; then
|
||||||
|
die "lsphp${PHPVER} reports PHP $RUNV but the image will be labelled $EXPECT_PHP_VERSION."
|
||||||
|
fi
|
||||||
|
if [ -z "${EXPECT_PHP_VERSION:-}" ]; then
|
||||||
|
echo "lsphp-apt-upgrade: WARNING no EXPECT_PHP_VERSION build-arg — image label will read 'unverified' (fine for local builds; CI always sets it)." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$RUNV" > /etc/cac-php-version
|
||||||
|
|
||||||
|
echo "lsphp-apt-upgrade: ===== version report ====="
|
||||||
|
dpkg-query -W -f=' ${Package} ${Version}\n' "lsphp${PHPVER}*"
|
||||||
|
echo " lsphp -v: $("$LSPHP" -v | head -n1)"
|
||||||
|
echo " OS packages still upgradable: $(apt list --upgradable 2>/dev/null | grep -vc '^Listing' || true)"
|
||||||
|
echo "lsphp-apt-upgrade: OK lsphp${PHPVER}=${INST} (PHP ${RUNV})"
|
||||||
Executable
+304
@@ -0,0 +1,304 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## lsphp-src-build.sh — BUILD-TIME ONLY, EMERGENCY PATH. Compiles lsphp from the
|
||||||
|
## official php.net release tarball, configured EXACTLY like the LiteSpeed-packaged
|
||||||
|
## lsphp${PHPVER} it will replace, for the window in which a PHP security release
|
||||||
|
## is out but rpms.litespeedtech.com/debian has not published it yet.
|
||||||
|
##
|
||||||
|
## Runs in the throwaway `lsphp-src` stage of Dockerfile.lsphp / Dockerfile.litespeed,
|
||||||
|
## FROM the same litespeedtech/openlitespeed base as the shipped stage. The output
|
||||||
|
## (/lsphp-src-out) is applied to the shipped stage by lsphp-src-overlay.sh, which
|
||||||
|
## is where the "same support as the packaged lsphp" assertions live.
|
||||||
|
##
|
||||||
|
## PHP_SRC_VERSION empty (every normal build) => does nothing but create an empty
|
||||||
|
## /lsphp-src-out, in well under a second. Nothing is downloaded or installed.
|
||||||
|
##
|
||||||
|
## WHAT "CONFIGURED EXACTLY LIKE THE PACKAGE" MEANS, and why it cannot drift:
|
||||||
|
## the configure arguments are NOT kept in this repo. They are read, at build
|
||||||
|
## time, from the `Configure Command` line of the packaged lsphp that the same
|
||||||
|
## build just installed from the LiteSpeed repo (php -i), and replayed verbatim —
|
||||||
|
## only CFLAGS is edited, to drop the two -f*-prefix-map flags that encode
|
||||||
|
## LiteSpeed's build directory. When LiteSpeed changes their flags, this follows
|
||||||
|
## automatically; if a new flag needs a -dev package we do not install, configure
|
||||||
|
## FAILS (loud), it does not silently drop the feature. CPPFLAGS/LDFLAGS are not
|
||||||
|
## recorded by PHP; they come from Ubuntu noble's dpkg-buildflags with
|
||||||
|
## hardening=+all, which reproduces the packaged binary's measured hardening
|
||||||
|
## (PIE, full RELRO, BIND_NOW, FORTIFY _chk symbols) — checked again below.
|
||||||
|
##
|
||||||
|
## TRUST: the tarball must (1) match PHP_SRC_SHA256 (CI takes it from
|
||||||
|
## https://www.php.net/releases/index.php?json&version=X.Y; the build fails if it
|
||||||
|
## is not supplied) AND (2) carry a valid OpenPGP signature (.asc from php.net)
|
||||||
|
## by a key in keys/php-src/php-keyring.asc (the official keyring from
|
||||||
|
## https://www.php.net/distributions/php-keyring.gpg, committed
|
||||||
|
## unmodified as php-keyring.asc — it is ASCII-armoured despite the name) whose PRIMARY
|
||||||
|
## fingerprint is listed for this branch in keys/php-src/release-managers.txt
|
||||||
|
## (transcribed from https://www.php.net/gpg-keys.php). Either check failing
|
||||||
|
## aborts the build. A new release manager key therefore needs a reviewed commit.
|
||||||
|
##
|
||||||
|
## The LSAPI SAPI itself is LiteSpeed's php-litespeed release (pinned by sha256
|
||||||
|
## in keys/php-src/litespeed-sapi.txt), NOT php-src's bundled sapi/litespeed —
|
||||||
|
## that is what LiteSpeed's packages compile (measured, see that file).
|
||||||
|
##
|
||||||
|
## PECL extensions (igbinary, imagick, memcached, msgpack, redis) are NOT built
|
||||||
|
## here: the shipped stage keeps LiteSpeed's own packaged .so files — the same
|
||||||
|
## binaries the primary image ships, which LiteSpeed itself does not rebuild per
|
||||||
|
## PHP patch release (e.g. lsphp85-msgpack 3.0.1-1 is from June, core 8.5.11 from
|
||||||
|
## September). That is only safe while the Zend ABI is unchanged, so this script
|
||||||
|
## also runs an ABI gate (scripts/lsphp-abi-gate.sh: pahole over every struct the
|
||||||
|
## public PHP headers define, packaged -dev headers vs the new headers); any
|
||||||
|
## changed offset/size of an existing member fails the build.
|
||||||
|
##
|
||||||
|
## Usage (env): PHPVER=85 PHP_SRC_VERSION=8.5.12 PHP_SRC_SHA256=<64 hex> lsphp-src-build.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HERE=$(cd "$(dirname "$0")" && pwd) # sibling scripts (lsphp-abi-gate.sh)
|
||||||
|
OUT=/lsphp-src-out
|
||||||
|
KEYDIR=${KEYDIR:-/usr/local/share/php-src-keys}
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
die() { echo "FATAL(lsphp-src-build): $*" >&2; exit 1; }
|
||||||
|
log() { echo "lsphp-src-build: $*"; }
|
||||||
|
|
||||||
|
PHPVER="${PHPVER:?PHPVER required}"
|
||||||
|
[[ "$PHPVER" =~ ^[0-9]{2}$ ]] || die "PHPVER='$PHPVER' is not NN."
|
||||||
|
if [ -z "${PHP_SRC_VERSION:-}" ]; then
|
||||||
|
[ -z "${PHP_SRC_SHA256:-}" ] || die "PHP_SRC_SHA256 given without PHP_SRC_VERSION."
|
||||||
|
log "PHP_SRC_VERSION empty — packaged lsphp build, nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
BRANCH="${PHPVER:0:1}.${PHPVER:1}" # 85 -> 8.5
|
||||||
|
[[ "$PHP_SRC_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "PHP_SRC_VERSION='$PHP_SRC_VERSION' is not X.Y.Z (RCs/alphas are refused)."
|
||||||
|
[ "${PHP_SRC_VERSION%.*}" = "$BRANCH" ] || die "PHP_SRC_VERSION=$PHP_SRC_VERSION does not belong to PHPVER=$PHPVER (branch $BRANCH)."
|
||||||
|
[[ "${PHP_SRC_SHA256:-}" =~ ^[0-9a-f]{64}$ ]] || die "PHP_SRC_SHA256 must be the 64-hex sha256 of php-${PHP_SRC_VERSION}.tar.xz from the php.net releases JSON."
|
||||||
|
|
||||||
|
P="/usr/local/lsws/lsphp${PHPVER}"
|
||||||
|
START=$(date +%s)
|
||||||
|
|
||||||
|
## ---- 1. toolchain + every -dev library the packaged configure line needs -----
|
||||||
|
## Same script as the shipped stage: refuses stale/unreachable repo lists and
|
||||||
|
## brings lsphp${PHPVER}* (core + -dev) to the repo's current release, so the
|
||||||
|
## Configure Command we read is the one the shipped stage's packages carry
|
||||||
|
## (lsphp-src-overlay.sh re-checks that the versions match).
|
||||||
|
## -dev packages: one per library in the packaged Configure Command. Extras are
|
||||||
|
## harmless; a missing one makes configure fail, which is the loud outcome.
|
||||||
|
DEVPKGS=(
|
||||||
|
build-essential autoconf pkg-config file dwarves
|
||||||
|
curl ca-certificates gpg gpgv xz-utils
|
||||||
|
libxml2-dev libssl-dev libcurl4-openssl-dev libenchant-2-dev libgmp-dev
|
||||||
|
libonig-dev libedit-dev libbz2-dev libxslt1-dev libzip-dev libsodium-dev
|
||||||
|
libargon2-dev libjpeg-dev libxpm-dev libpng-dev libwebp-dev libfreetype-dev
|
||||||
|
libldap-dev libsasl2-dev libpq-dev libsnmp-dev libsqlite3-dev freetds-dev
|
||||||
|
libtidy-dev libicu-dev zlib1g-dev libkrb5-dev
|
||||||
|
)
|
||||||
|
case "$PHPVER" in
|
||||||
|
81) DEVPKGS+=(libc-client2007e-dev libpspell-dev) ;; # --with-imap, --with-pspell
|
||||||
|
82|83) DEVPKGS+=(libpspell-dev) ;; # --with-pspell (gone in 8.4)
|
||||||
|
esac
|
||||||
|
/usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" "${DEVPKGS[@]}" "lsphp${PHPVER}-dev"
|
||||||
|
PKGV=$(dpkg-query -W -f='${Version}' "lsphp${PHPVER}")
|
||||||
|
DEVV=$(dpkg-query -W -f='${Version}' "lsphp${PHPVER}-dev")
|
||||||
|
[ "$PKGV" = "$DEVV" ] || die "lsphp${PHPVER}=$PKGV but lsphp${PHPVER}-dev=$DEVV — packaged headers do not match the packaged binary."
|
||||||
|
PKG_PHPV=$("$P/bin/php" -r 'echo PHP_VERSION;')
|
||||||
|
log "packaged reference: lsphp${PHPVER} ${PKGV} (PHP ${PKG_PHPV}); building php.net ${PHP_SRC_VERSION}"
|
||||||
|
if [ "$(printf '%s\n%s\n' "$PKG_PHPV" "$PHP_SRC_VERSION" | sort -V | tail -n1)" != "$PHP_SRC_VERSION" ] \
|
||||||
|
|| [ "$PKG_PHPV" = "$PHP_SRC_VERSION" ]; then
|
||||||
|
die "PHP_SRC_VERSION=$PHP_SRC_VERSION is not NEWER than the packaged $PKG_PHPV — the emergency path is pointless (or a downgrade). Use the normal build."
|
||||||
|
fi
|
||||||
|
|
||||||
|
## ---- 2. fetch + verify -------------------------------------------------------
|
||||||
|
WORK=/usr/src/php-src
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
cd "$WORK"
|
||||||
|
TARBALL="php-${PHP_SRC_VERSION}.tar.xz"
|
||||||
|
for f in "$TARBALL" "$TARBALL.asc"; do
|
||||||
|
curl -fsSL --retry 3 --max-time 600 -o "$f" "https://www.php.net/distributions/$f" \
|
||||||
|
|| die "download of https://www.php.net/distributions/$f failed."
|
||||||
|
done
|
||||||
|
|
||||||
|
GOT=$(sha256sum "$TARBALL" | awk '{print $1}')
|
||||||
|
[ "$GOT" = "$PHP_SRC_SHA256" ] || die "sha256 mismatch for $TARBALL: got $GOT, expected $PHP_SRC_SHA256 (php.net releases JSON)."
|
||||||
|
log "sha256 OK $GOT"
|
||||||
|
|
||||||
|
[ -s "$KEYDIR/php-keyring.asc" ] || die "pinned keyring $KEYDIR/php-keyring.asc missing."
|
||||||
|
## The committed file is php.net's keyring byte-for-byte (ASCII-armoured); gpgv
|
||||||
|
## only reads binary keyrings.
|
||||||
|
gpg --batch --yes --dearmor -o "$WORK/php-keyring.gpg" "$KEYDIR/php-keyring.asc" || die "could not dearmor $KEYDIR/php-keyring.asc."
|
||||||
|
[ -s "$KEYDIR/release-managers.txt" ] || die "pinned fingerprint list $KEYDIR/release-managers.txt missing."
|
||||||
|
STATUS=$(gpgv --status-fd 1 --keyring "$WORK/php-keyring.gpg" "$TARBALL.asc" "$TARBALL" 2>"$WORK/gpgv.err") || {
|
||||||
|
cat "$WORK/gpgv.err" >&2; die "OpenPGP signature verification of $TARBALL FAILED."; }
|
||||||
|
cat "$WORK/gpgv.err"
|
||||||
|
## [GNUPG:] VALIDSIG <signing-key-fpr> <date> <ts> <expire> <ver> <rsv> <pk-algo> <hash> <class> <PRIMARY-fpr>
|
||||||
|
PRIMARY=$(awk '$1 == "[GNUPG:]" && $2 == "VALIDSIG" {print $NF}' <<<"$STATUS")
|
||||||
|
[ -n "$PRIMARY" ] || die "gpgv reported no VALIDSIG for $TARBALL."
|
||||||
|
ALLOWED=$(awk -v b="$BRANCH" '$1 == b {print $2}' "$KEYDIR/release-managers.txt")
|
||||||
|
[ -n "$ALLOWED" ] || die "no release-manager fingerprints pinned for PHP $BRANCH in release-managers.txt."
|
||||||
|
OK=0
|
||||||
|
while read -r fp; do [ "$fp" = "$PRIMARY" ] && OK=1; done <<<"$ALLOWED"
|
||||||
|
[ "$OK" = 1 ] || die "$TARBALL is validly signed by $PRIMARY, which is NOT a pinned PHP $BRANCH release manager (release-managers.txt)."
|
||||||
|
log "signature OK: primary key $PRIMARY is a pinned PHP $BRANCH release manager"
|
||||||
|
|
||||||
|
tar -xJf "$TARBALL"
|
||||||
|
SRC="$WORK/php-${PHP_SRC_VERSION}"
|
||||||
|
grep -F "PHP ${PHP_SRC_VERSION}" "$SRC/NEWS" >/dev/null || die "NEWS in $TARBALL has no 'PHP ${PHP_SRC_VERSION}' entry."
|
||||||
|
|
||||||
|
## ---- 2b. LiteSpeed's LSAPI SAPI, as the packages use (keys/php-src/litespeed-sapi.txt)
|
||||||
|
read -r SAPI_URL SAPI_SHA < <(awk '!/^#/ && NF == 2 {print; exit}' "$KEYDIR/litespeed-sapi.txt")
|
||||||
|
[ -n "${SAPI_URL:-}" ] && [[ "${SAPI_SHA:-}" =~ ^[0-9a-f]{64}$ ]] || die "no pinned LiteSpeed SAPI in $KEYDIR/litespeed-sapi.txt."
|
||||||
|
curl -fsSL --retry 3 --max-time 300 -o "$WORK/lsapi.tgz" "$SAPI_URL" || die "download of $SAPI_URL failed."
|
||||||
|
GOT=$(sha256sum "$WORK/lsapi.tgz" | awk '{print $1}')
|
||||||
|
[ "$GOT" = "$SAPI_SHA" ] || die "LiteSpeed SAPI $SAPI_URL sha256 $GOT != pinned $SAPI_SHA (LiteSpeed republished it? re-measure against the packaged lsphp before re-pinning)."
|
||||||
|
mkdir -p "$WORK/lsapi"
|
||||||
|
tar -xzf "$WORK/lsapi.tgz" -C "$WORK/lsapi"
|
||||||
|
## Per its README: copy only the *.c/*.h over php-src's sapi/litespeed and keep
|
||||||
|
## php-src's config.m4 / Makefile.frag / arginfo.
|
||||||
|
N=0
|
||||||
|
for f in "$WORK"/lsapi/*/*.c "$WORK"/lsapi/*/*.h; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
install -m 0644 "$f" "$SRC/sapi/litespeed/$(basename "$f")"; N=$((N + 1))
|
||||||
|
done
|
||||||
|
[ "$N" -ge 4 ] || die "LiteSpeed SAPI archive had only $N .c/.h files."
|
||||||
|
log "LiteSpeed SAPI $(basename "$SAPI_URL") (sha256 OK) replaces php-src sapi/litespeed sources ($N files)"
|
||||||
|
|
||||||
|
## ---- 2c. compiled-in ini defaults LiteSpeed patched ----------------------------
|
||||||
|
## Measured 2026-10-21 with `php -n`: LiteSpeed's lsphp82/83 carry Debian's
|
||||||
|
## session.save_path patch (default '/var/lib/php/sessions' instead of php-src's
|
||||||
|
## ''); lsphp85 does not. Reproduce whatever the packaged binary reports. Any
|
||||||
|
## OTHER compiled-in default that differs is caught by the gate in step 4b.
|
||||||
|
PKG_SAVE_PATH=$("$P/bin/php" -n -r 'echo ini_get("session.save_path");')
|
||||||
|
if [ -n "$PKG_SAVE_PATH" ]; then
|
||||||
|
[[ "$PKG_SAVE_PATH" =~ ^[A-Za-z0-9/._-]+$ ]] || die "unexpected packaged session.save_path default '$PKG_SAVE_PATH'."
|
||||||
|
sed -i -E 's|(STD_PHP_INI_ENTRY\("session\.save_path",[[:space:]]*)""|\1"'"$PKG_SAVE_PATH"'"|' "$SRC/ext/session/session.c"
|
||||||
|
grep -F "\"session.save_path\"" "$SRC/ext/session/session.c" | grep -F "\"$PKG_SAVE_PATH\"" >/dev/null \
|
||||||
|
|| die "could not apply the packaged session.save_path default '$PKG_SAVE_PATH' to ext/session/session.c."
|
||||||
|
log "compiled-in session.save_path default set to '$PKG_SAVE_PATH' (as the packaged lsphp${PHPVER})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## ---- 3. replay the packaged configure line -----------------------------------
|
||||||
|
CFG_LINE=$("$P/bin/php" -i | awk -F' => ' '$1 == "Configure Command " || $1 == "Configure Command" {sub(/^[^=]*=> /, ""); print}')
|
||||||
|
[ -n "$CFG_LINE" ] || die "could not read 'Configure Command' from the packaged $P/bin/php -i."
|
||||||
|
## xargs does the shell-style single-quote splitting without an eval.
|
||||||
|
mapfile -t CFG_ARGS < <(printf '%s\n' "$CFG_LINE" | xargs -n1 printf '%s\n')
|
||||||
|
[ "${CFG_ARGS[0]}" = "./configure" ] || die "unexpected Configure Command shape (first word '${CFG_ARGS[0]}')."
|
||||||
|
CFG_ARGS=("${CFG_ARGS[@]:1}")
|
||||||
|
|
||||||
|
ARGS=()
|
||||||
|
PKG_CFLAGS=""
|
||||||
|
for a in "${CFG_ARGS[@]}"; do
|
||||||
|
case "$a" in
|
||||||
|
CFLAGS=*) PKG_CFLAGS="${a#CFLAGS=}" ;;
|
||||||
|
*) ARGS+=("$a") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -n "$PKG_CFLAGS" ] || die "packaged Configure Command carries no CFLAGS — refusing to guess."
|
||||||
|
CFLAGS=""
|
||||||
|
for w in $PKG_CFLAGS; do
|
||||||
|
case "$w" in -ffile-prefix-map=*|-fdebug-prefix-map=*) continue ;; esac
|
||||||
|
CFLAGS+="${CFLAGS:+ }$w"
|
||||||
|
done
|
||||||
|
CFLAGS+=" -ffile-prefix-map=${SRC}=."
|
||||||
|
export DEB_BUILD_MAINT_OPTIONS=hardening=+all
|
||||||
|
CPPFLAGS=$(dpkg-buildflags --get CPPFLAGS)
|
||||||
|
LDFLAGS=""
|
||||||
|
for w in $(dpkg-buildflags --get LDFLAGS); do
|
||||||
|
## CFLAGS ends in -fno-lto (LiteSpeed's choice); keep the link step consistent.
|
||||||
|
case "$w" in -flto*|-ffat-lto-objects) continue ;; esac
|
||||||
|
LDFLAGS+="${LDFLAGS:+ }$w"
|
||||||
|
done
|
||||||
|
CXXFLAGS="$CFLAGS"
|
||||||
|
export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
|
||||||
|
log "configure args (from packaged lsphp${PHPVER} ${PKGV}): ${ARGS[*]}"
|
||||||
|
log "CFLAGS=$CFLAGS"
|
||||||
|
log "CPPFLAGS=$CPPFLAGS"
|
||||||
|
log "LDFLAGS=$LDFLAGS"
|
||||||
|
|
||||||
|
## Build-environment shim (builder stage only, never shipped): ext/pdo_dblib's
|
||||||
|
## config.m4 looks for libsybdb in $prefix/lib, not the multiarch dir Ubuntu's
|
||||||
|
## freetds-dev uses. pdo_dblib is built but not shipped (the packaged image has
|
||||||
|
## no lsphpNN-dblib), exactly as in the packaged build.
|
||||||
|
MA=$(dpkg-architecture -qDEB_HOST_MULTIARCH)
|
||||||
|
[ -e /usr/lib/libsybdb.so ] || ln -s "/usr/lib/$MA/libsybdb.so" /usr/lib/libsybdb.so
|
||||||
|
|
||||||
|
cd "$SRC"
|
||||||
|
if ! ./configure "${ARGS[@]}" "CFLAGS=$CFLAGS" >"$WORK/configure.log" 2>&1; then
|
||||||
|
tail -n 60 "$WORK/configure.log" >&2
|
||||||
|
die "configure FAILED with the packaged flags — most likely a -dev package missing from DEVPKGS in this script (see the error above)."
|
||||||
|
fi
|
||||||
|
## Unknown options are expected for a handful of historic flags LiteSpeed still
|
||||||
|
## passes (--enable-gd-native-ttf, --with-vpx-dir, ...); they are just as unknown
|
||||||
|
## to the packaged build. Print them so a NEW unknown flag is visible in the log.
|
||||||
|
grep -E "^configure: WARNING: unrecognized options" "$WORK/configure.log" || true
|
||||||
|
|
||||||
|
T0=$(date +%s)
|
||||||
|
make -j"$(nproc)" >"$WORK/make.log" 2>&1 || { tail -n 80 "$WORK/make.log" >&2; die "make FAILED."; }
|
||||||
|
log "make took $(( $(date +%s) - T0 ))s on $(nproc) CPUs"
|
||||||
|
|
||||||
|
STAGE="$WORK/stage"
|
||||||
|
make install INSTALL_ROOT="$STAGE" >"$WORK/install.log" 2>&1 || { tail -n 40 "$WORK/install.log" >&2; die "make install FAILED."; }
|
||||||
|
SP="$STAGE$P"
|
||||||
|
[ -x "$SP/bin/lsphp${BRANCH}" ] || die "make install produced no $P/bin/lsphp${BRANCH} (did --enable-litespeed vanish from the packaged flags?)."
|
||||||
|
[ -x "$SP/bin/php${BRANCH}" ] || die "make install produced no $P/bin/php${BRANCH}."
|
||||||
|
## --program-suffix names everything <tool>8.M; LiteSpeed's packages ship the
|
||||||
|
## SAPI as bin/lsphp and the CLI as both bin/php and bin/php8.M (two identical
|
||||||
|
## regular files, measured). Reproduce those names; the overlay copies only the
|
||||||
|
## files the packaged image actually has.
|
||||||
|
mv "$SP/bin/lsphp${BRANCH}" "$SP/bin/lsphp"
|
||||||
|
cp -p "$SP/bin/php${BRANCH}" "$SP/bin/php"
|
||||||
|
|
||||||
|
## Strip like dh_strip does for the packaged binaries (measured: no .comment /
|
||||||
|
## debug sections in the packaged lsphp).
|
||||||
|
find "$SP" -type f \( -name '*.so' -o -path "*/bin/*" \) -print0 | while IFS= read -r -d '' f; do
|
||||||
|
case "$(file -b "$f")" in
|
||||||
|
ELF*shared\ object*|ELF*pie\ executable*|ELF*executable*)
|
||||||
|
strip --remove-section=.comment --remove-section=.note --strip-unneeded "$f" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
NEWV=$("$SP/bin/php${BRANCH}" -n -r 'echo PHP_VERSION;' 2>/dev/null || true)
|
||||||
|
[ "$NEWV" = "$PHP_SRC_VERSION" ] || die "built php reports '$NEWV', expected $PHP_SRC_VERSION."
|
||||||
|
|
||||||
|
## ---- 4b. compiled-in ini defaults must match the packaged binary ---------------
|
||||||
|
## `php -n` = no php.ini, no scan dir, no shared extensions: what is left is
|
||||||
|
## what the binary itself defaults to. New directives (a security release may
|
||||||
|
## add one, e.g. memory_hard_limit_percent in 8.x.y php2shell fix) are allowed
|
||||||
|
## and printed; a removed or changed default fails.
|
||||||
|
ini_defaults() { "$1" -n -r 'foreach (ini_get_all(null, false) as $k => $v) echo $k, "=", var_export($v, true), "\n";' | sort; }
|
||||||
|
INI_PKG=$(ini_defaults "$P/bin/php")
|
||||||
|
INI_NEW=$(ini_defaults "$SP/bin/php${BRANCH}")
|
||||||
|
INI_BAD=$(diff <(printf '%s\n' "$INI_PKG") <(printf '%s\n' "$INI_NEW") | grep '^<' || true)
|
||||||
|
INI_ADD=$(diff <(printf '%s\n' "$INI_PKG") <(printf '%s\n' "$INI_NEW") | grep '^>' || true)
|
||||||
|
[ -z "$INI_BAD" ] || die "compiled-in ini defaults differ from the packaged lsphp${PHPVER} (< packaged, > php.net build):
|
||||||
|
$(diff <(printf '%s\n' "$INI_PKG") <(printf '%s\n' "$INI_NEW") || true)
|
||||||
|
LiteSpeed patched a default this script does not reproduce (see step 2c)."
|
||||||
|
log "compiled-in ini defaults identical to packaged ($(printf '%s\n' "$INI_PKG" | wc -l) directives)${INI_ADD:+; new in $PHP_SRC_VERSION: $(printf '%s' "$INI_ADD" | tr '\n' ' ')}"
|
||||||
|
|
||||||
|
## Same LSAPI SAPI as the packaged binary? Compare the LSAPI_*/LSCAPI_* names
|
||||||
|
## (its env knobs + feature switches) compiled into each. A difference means
|
||||||
|
## LiteSpeed moved to a SAPI release we have not pinned (or the copy failed).
|
||||||
|
lsapi_names() { grep -aoE 'LS(C)?API_[A-Z0-9_]+' "$1" | sort -u; }
|
||||||
|
LSAPI_DIFF=$(diff <(lsapi_names "$P/bin/lsphp") <(lsapi_names "$SP/bin/lsphp") || true)
|
||||||
|
[ -z "$LSAPI_DIFF" ] || die "LSAPI SAPI differs from the packaged lsphp (< packaged, > php.net build):
|
||||||
|
$LSAPI_DIFF"
|
||||||
|
log "LSAPI SAPI matches the packaged lsphp ($(lsapi_names "$SP/bin/lsphp" | wc -l) LSAPI_* names identical)"
|
||||||
|
|
||||||
|
## ---- 4. ABI gate: may LiteSpeed's packaged PECL .so files load into this? -----
|
||||||
|
## See scripts/lsphp-abi-gate.sh (and its test). Measured for 8.5.11 -> 8.5.12:
|
||||||
|
## the php2shell fix adds CG(interned_strings_fallible) in padding and appends
|
||||||
|
## EG(delayed_oom) / PS(in_rfc1867_update); no existing member moved.
|
||||||
|
ABI_OUT=$(bash "$HERE/lsphp-abi-gate.sh" "$P/include" "$SP/include" "$WORK/abi" 2>&1) || {
|
||||||
|
printf '%s\n' "$ABI_OUT" >&2
|
||||||
|
die "ABI gate: PHP $PHP_SRC_VERSION changed struct layouts relative to the packaged $PKG_PHPV headers. LiteSpeed's packaged PECL .so files (redis, imagick, memcached, igbinary, msgpack) were compiled against the old layout and must NOT be loaded into this build — they would have to be rebuilt from source, which this emergency path does not do (docs/lsphp-source-emergency.md)."
|
||||||
|
}
|
||||||
|
printf '%s\n' "$ABI_OUT" | sed 's/^/lsphp-src-build: /'
|
||||||
|
|
||||||
|
## ---- 5. hand-off ----------------------------------------------------------------
|
||||||
|
mkdir -p "$OUT/root"
|
||||||
|
cp -a "$STAGE/." "$OUT/root/"
|
||||||
|
cat >"$OUT/meta.env" <<EOF
|
||||||
|
LSPHP_SRC_PHP_VERSION=$PHP_SRC_VERSION
|
||||||
|
LSPHP_SRC_SHA256=$PHP_SRC_SHA256
|
||||||
|
LSPHP_SRC_SIGNER=$PRIMARY
|
||||||
|
LSPHP_SRC_PKG_REFERENCE=$PKGV
|
||||||
|
LSPHP_SRC_PKG_PHP_VERSION=$PKG_PHPV
|
||||||
|
EOF
|
||||||
|
printf '%s\n' "${ARGS[@]}" >"$OUT/configure-args.txt"
|
||||||
|
log "done in $(( $(date +%s) - START ))s: php.net ${PHP_SRC_VERSION} staged at $OUT/root (reference lsphp${PHPVER} ${PKGV})"
|
||||||
Executable
+203
@@ -0,0 +1,203 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## lsphp-src-overlay.sh — BUILD-TIME ONLY, EMERGENCY PATH. Applies the php.net
|
||||||
|
## build produced by lsphp-src-build.sh (staged in /lsphp-src-out by the
|
||||||
|
## `lsphp-src` stage) over the LiteSpeed-packaged lsphp${PHPVER} that this stage
|
||||||
|
## has just installed, then PROVES the result offers the same support.
|
||||||
|
##
|
||||||
|
## Usage: lsphp-src-overlay.sh runtime|dev PHPVER [OUT_DIR=/lsphp-src-out]
|
||||||
|
## runtime — the shipped stage: replace the php-src binaries (lsphp, php,
|
||||||
|
## php8.M, phar) and every php-src shared extension the packages
|
||||||
|
## installed. Everything else stays exactly as packaged: php.ini,
|
||||||
|
## mods-available/*.ini, the LiteSpeed PECL .so files (see
|
||||||
|
## lsphp-src-build.sh for why), helper scripts, man pages.
|
||||||
|
## dev — Dockerfile.lsphp's ext-build stage: runtime + the -dev files
|
||||||
|
## (headers, phpize, php-config, lib/php/build) so cac_path_parity is
|
||||||
|
## compiled against, and its .phpt suite run on, the php.net build.
|
||||||
|
##
|
||||||
|
## PHP_SRC_VERSION empty and no staged build => no-op (normal builds).
|
||||||
|
##
|
||||||
|
## WHAT IS REPLACED is decided by dpkg, not by a list kept here: a file is
|
||||||
|
## php-src output iff it is owned by an installed lsphp${PHPVER}* package whose
|
||||||
|
## version equals lsphp${PHPVER}'s (core, -common, -curl, -intl, -ldap, -mysql,
|
||||||
|
## -opcache, -dev ...; the PECL packages carry their own versions). Every ELF
|
||||||
|
## file among those MUST have a php.net counterpart — a missing one fails the
|
||||||
|
## build, so a new LiteSpeed sub-package can never keep an old binary silently.
|
||||||
|
##
|
||||||
|
## THE "SAME SUPPORT" GATE (all fatal): lsphp-src-probe.php is run under both
|
||||||
|
## SAPIs before and after; the loaded extension set (incl. Zend extensions) must
|
||||||
|
## be identical, every PECL extension must report its same version, every
|
||||||
|
## php-src extension the new version, no effective ini value may change or
|
||||||
|
## disappear (new directives are allowed and printed), and the
|
||||||
|
## gd/curl/openssl/icu/imagick/redis/memcached/password/hash/stream feature
|
||||||
|
## lines must be unchanged. `php -i` Configure Command must match the packaged
|
||||||
|
## one except CFLAGS. No ELF under the prefix may have an unresolved library.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODE="${1:?usage: lsphp-src-overlay.sh runtime|dev PHPVER}"
|
||||||
|
PHPVER="${2:?usage: lsphp-src-overlay.sh runtime|dev PHPVER}"
|
||||||
|
OUT="${3:-/lsphp-src-out}"
|
||||||
|
case "$MODE" in runtime|dev) ;; *) echo "FATAL(lsphp-src-overlay): mode must be runtime|dev" >&2; exit 1 ;; esac
|
||||||
|
[[ "$PHPVER" =~ ^[0-9]{2}$ ]] || { echo "FATAL(lsphp-src-overlay): PHPVER='$PHPVER' is not NN" >&2; exit 1; }
|
||||||
|
|
||||||
|
die() { echo "FATAL(lsphp-src-overlay): $*" >&2; exit 1; }
|
||||||
|
log() { echo "lsphp-src-overlay[$MODE]: $*"; }
|
||||||
|
|
||||||
|
LSPHP_SOURCE="${LSPHP_SOURCE:-litespeed-apt}"
|
||||||
|
if [ ! -f "$OUT/meta.env" ]; then
|
||||||
|
[ -z "${PHP_SRC_VERSION:-}" ] || die "PHP_SRC_VERSION=$PHP_SRC_VERSION but no staged php.net build in $OUT (lsphp-src stage did not run?)."
|
||||||
|
[ "$LSPHP_SOURCE" = "litespeed-apt" ] || die "LSPHP_SOURCE=$LSPHP_SOURCE requires PHP_SRC_VERSION."
|
||||||
|
log "no php.net build staged — packaged lsphp${PHPVER} kept as is."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
[ "$LSPHP_SOURCE" = "php.net" ] || die "a php.net build is staged but LSPHP_SOURCE=$LSPHP_SOURCE (must be 'php.net' so the image label tells the truth)."
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. "$OUT/meta.env"
|
||||||
|
[ "${PHP_SRC_VERSION:-}" = "$LSPHP_SRC_PHP_VERSION" ] || die "staged build is $LSPHP_SRC_PHP_VERSION but PHP_SRC_VERSION=${PHP_SRC_VERSION:-<empty>}."
|
||||||
|
|
||||||
|
BRANCH="${PHPVER:0:1}.${PHPVER:1}"
|
||||||
|
P="/usr/local/lsws/lsphp${PHPVER}"
|
||||||
|
SRCROOT="$OUT/root"
|
||||||
|
PROBE=/usr/local/share/lsphp-src-probe.php
|
||||||
|
[ -f "$PROBE" ] || die "$PROBE missing."
|
||||||
|
|
||||||
|
CORE="lsphp${PHPVER}"
|
||||||
|
COREV=$(dpkg-query -W -f='${Version}' "$CORE")
|
||||||
|
[ "$COREV" = "$LSPHP_SRC_PKG_REFERENCE" ] || die "this stage has $CORE $COREV but the php.net build was configured from $LSPHP_SRC_PKG_REFERENCE (the LiteSpeed repo moved between stages? rebuild)."
|
||||||
|
|
||||||
|
probe() { # $1 = tag; writes $OUT/probe-<tag>-{cli,lsapi}.txt
|
||||||
|
"$P/bin/php" "$PROBE" >"$OUT/probe-$1-cli.txt" || die "php probe failed ($1)."
|
||||||
|
"$P/bin/lsphp" "$PROBE" >"$OUT/probe-$1-lsapi.txt" || die "lsphp probe failed ($1)."
|
||||||
|
local first
|
||||||
|
first=$(head -n1 "$OUT/probe-$1-lsapi.txt")
|
||||||
|
[ "$first" = "SAPI litespeed" ] || die "lsphp did not run the probe under the litespeed SAPI ($1): '$first'."
|
||||||
|
}
|
||||||
|
## Configure Command minus CFLAGS, whitespace-normalised (the CFLAGS argument
|
||||||
|
## sits in a different position in our ./configure invocation).
|
||||||
|
cfgcmd() { "$P/bin/php" -i | awk -F' => ' '$1 ~ /^Configure Command/ {print $2}' | sed -E "s/'CFLAGS=[^']*'//; s/[[:space:]]+/ /g; s/^ //; s/ $//"; }
|
||||||
|
|
||||||
|
probe before
|
||||||
|
CFG_BEFORE=$(cfgcmd)
|
||||||
|
OLDV=$(awk '$1 == "VERSION" {print $2}' "$OUT/probe-before-cli.txt")
|
||||||
|
[ "$OLDV" = "$LSPHP_SRC_PKG_PHP_VERSION" ] || die "packaged PHP is $OLDV, build meta says $LSPHP_SRC_PKG_PHP_VERSION."
|
||||||
|
|
||||||
|
## ---- which files are php-src output? (dpkg decides) ---------------------------
|
||||||
|
mapfile -t SRC_PKGS < <(dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' "lsphp${PHPVER}*" \
|
||||||
|
| awk -v v="$COREV" '$1 == "ii" && $3 == v {print $2}')
|
||||||
|
mapfile -t PECL_PKGS < <(dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' "lsphp${PHPVER}*" \
|
||||||
|
| awk -v v="$COREV" '$1 == "ii" && $3 != v {print $2 "=" $3}')
|
||||||
|
if [ "$MODE" = runtime ]; then
|
||||||
|
for p in "${SRC_PKGS[@]}"; do
|
||||||
|
[ "$p" != "lsphp${PHPVER}-dev" ] || die "runtime mode but lsphp${PHPVER}-dev is installed in this stage."
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
log "php-src packages at $COREV: ${SRC_PKGS[*]}"
|
||||||
|
log "kept as packaged (own versions): ${PECL_PKGS[*]:-none}"
|
||||||
|
|
||||||
|
REPLACED=0
|
||||||
|
KEPT=()
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -f "$f" ] && [ ! -L "$f" ] || continue
|
||||||
|
rel="${f#/}"
|
||||||
|
is_elf=0
|
||||||
|
if [ "$(head -c4 "$f" | od -An -c | tr -d ' ')" = '177ELF' ]; then is_elf=1; fi
|
||||||
|
case "$f" in
|
||||||
|
"$P/bin/phar${BRANCH}.phar"|"$P"/include/php/*|"$P"/lib/php/build/*|"$P/bin/php-config"|"$P/bin/php-config${BRANCH}"|"$P/bin/phpize"|"$P/bin/phpize${BRANCH}")
|
||||||
|
## text artefacts generated by the php-src build
|
||||||
|
[ "$MODE" = dev ] || [[ "$f" == "$P/bin/phar${BRANCH}.phar" ]] || { KEPT+=("$f"); continue; }
|
||||||
|
src="$SRCROOT/$rel"
|
||||||
|
case "$f" in
|
||||||
|
"$P/bin/php-config") src="$SRCROOT$P/bin/php-config${BRANCH}" ;;
|
||||||
|
"$P/bin/phpize") src="$SRCROOT$P/bin/phpize${BRANCH}" ;;
|
||||||
|
esac
|
||||||
|
if [ -f "$src" ]; then
|
||||||
|
install -m "$(stat -c %a "$f")" "$src" "$f"; REPLACED=$((REPLACED + 1))
|
||||||
|
elif [[ "$f" == "$P"/include/php/* ]]; then
|
||||||
|
log "header $f no longer exists in php.net $LSPHP_SRC_PHP_VERSION — removing the stale copy"
|
||||||
|
rm -f "$f"
|
||||||
|
else
|
||||||
|
die "php.net build has no counterpart for packaged $f."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ "$is_elf" = 1 ]; then
|
||||||
|
src="$SRCROOT/$rel"
|
||||||
|
[ "$f" = "$P/bin/php" ] && src="$SRCROOT$P/bin/php${BRANCH}"
|
||||||
|
[ -f "$src" ] || die "packaged ELF $f (php-src output, owned by a $COREV package) has NO php.net counterpart at $src — refusing to ship an old binary next to the new PHP."
|
||||||
|
install -m "$(stat -c %a "$f")" "$src" "$f"; REPLACED=$((REPLACED + 1))
|
||||||
|
else
|
||||||
|
KEPT+=("$f")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(for p in "${SRC_PKGS[@]}"; do dpkg -L "$p"; done | sort -u)
|
||||||
|
|
||||||
|
if [ "$MODE" = dev ]; then
|
||||||
|
## headers that are NEW in the php.net release (none for most patch releases)
|
||||||
|
while IFS= read -r -d '' h; do
|
||||||
|
dst="${h#"$SRCROOT"}"
|
||||||
|
[ -e "$dst" ] || { mkdir -p "$(dirname "$dst")"; install -m 0644 "$h" "$dst"; log "added new header $dst"; }
|
||||||
|
done < <(find "$SRCROOT$P/include/php" -type f -print0)
|
||||||
|
CFV=$("$P/bin/php-config" --version)
|
||||||
|
[ "$CFV" = "$LSPHP_SRC_PHP_VERSION" ] || die "php-config reports $CFV after overlay."
|
||||||
|
fi
|
||||||
|
log "replaced $REPLACED php-src files; kept ${#KEPT[@]} packaging files (ini templates, helper scripts, docs, man pages)"
|
||||||
|
|
||||||
|
## ---- no unresolved shared libraries -------------------------------------------
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
if [ "$(head -c4 "$f" | od -An -c | tr -d ' ')" = '177ELF' ]; then
|
||||||
|
LDD=$(ldd "$f" 2>&1 || true)
|
||||||
|
[[ "$LDD" != *"not found"* ]] || die "$f has unresolved libraries:
|
||||||
|
$LDD"
|
||||||
|
fi
|
||||||
|
done < <(find "$P/bin" "$P/lib" -type f -print0)
|
||||||
|
|
||||||
|
## ---- the "same support" gate ----------------------------------------------------
|
||||||
|
probe after
|
||||||
|
CFG_AFTER=$(cfgcmd)
|
||||||
|
[ "$CFG_BEFORE" = "$CFG_AFTER" ] || die "Configure Command differs from the packaged build beyond CFLAGS:
|
||||||
|
packaged: $CFG_BEFORE
|
||||||
|
php.net : $CFG_AFTER"
|
||||||
|
|
||||||
|
FAIL=0
|
||||||
|
for sapi in cli lsapi; do
|
||||||
|
B="$OUT/probe-before-$sapi.txt"; A="$OUT/probe-after-$sapi.txt"
|
||||||
|
NEWV=$(awk '$1 == "VERSION" {print $2}' "$A")
|
||||||
|
[ "$NEWV" = "$LSPHP_SRC_PHP_VERSION" ] || { echo "[$sapi] reports PHP $NEWV, expected $LSPHP_SRC_PHP_VERSION" >&2; FAIL=1; }
|
||||||
|
## extension sets + versions
|
||||||
|
R=$(awk -v o="$OLDV" -v n="$LSPHP_SRC_PHP_VERSION" '
|
||||||
|
FNR == NR { if ($1 == "EXT" || $1 == "ZEXT") b[$1" "$2] = $3; next }
|
||||||
|
($1 == "EXT" || $1 == "ZEXT") { a[$1" "$2] = $3 }
|
||||||
|
END {
|
||||||
|
for (k in b) {
|
||||||
|
if (!(k in a)) { print "MISSING " k; continue }
|
||||||
|
want = (b[k] == o) ? n : b[k]
|
||||||
|
if (a[k] != want) print "VERSION " k " " b[k] " -> " a[k] " (want " want ")"
|
||||||
|
}
|
||||||
|
for (k in a) if (!(k in b)) print "EXTRA " k " " a[k]
|
||||||
|
}' "$B" "$A" | sort)
|
||||||
|
if [ -n "$R" ]; then echo "[$sapi] extension surface changed:"$'\n'"$R" >&2; FAIL=1; fi
|
||||||
|
## feature lines must be byte-identical
|
||||||
|
R=$(diff <(grep '^FEAT ' "$B") <(grep '^FEAT ' "$A") || true)
|
||||||
|
if [ -n "$R" ]; then echo "[$sapi] feature surface changed:"$'\n'"$R" >&2; FAIL=1; fi
|
||||||
|
## effective ini (packaged php.ini + mods-available/*.ini): no directive may
|
||||||
|
## vanish or change value; directives NEW in the release are printed.
|
||||||
|
R=$(awk 'FNR == NR { if ($1 == "INI") b[$2] = $0; next } $1 == "INI" { a[$2] = $0 }
|
||||||
|
END { for (k in b) { if (!(k in a)) print "REMOVED " b[k]; else if (a[k] != b[k]) print "CHANGED " b[k] " -> " a[k] } }' "$B" "$A" | sort)
|
||||||
|
if [ -n "$R" ]; then echo "[$sapi] effective ini changed:"$'\n'"$R" >&2; FAIL=1; fi
|
||||||
|
R=$(awk 'FNR == NR { if ($1 == "INI") b[$2] = 1; next } $1 == "INI" && !($2 in b) { print " " $0 }' "$B" "$A")
|
||||||
|
[ -z "$R" ] || log "[$sapi] ini directives new in $LSPHP_SRC_PHP_VERSION (informational):"$'\n'"$R"
|
||||||
|
log "[$sapi] $(grep -c '^EXT ' "$A") extensions + $(grep -c '^ZEXT ' "$A") zend extensions, identical to packaged; $(grep -c '^FEAT ' "$A") feature lines identical"
|
||||||
|
done
|
||||||
|
[ "$FAIL" = 0 ] || die "the php.net build does NOT offer the same support as packaged lsphp${PHPVER} $COREV (details above)."
|
||||||
|
|
||||||
|
printf '%s\n' "$LSPHP_SRC_PHP_VERSION" >/etc/cac-php-version
|
||||||
|
cat >/etc/cac-lsphp-source <<EOF
|
||||||
|
source=php.net
|
||||||
|
php_version=$LSPHP_SRC_PHP_VERSION
|
||||||
|
tarball_sha256=$LSPHP_SRC_SHA256
|
||||||
|
signer_primary_fpr=$LSPHP_SRC_SIGNER
|
||||||
|
replaces_packaged=$CORE=$COREV (PHP $OLDV)
|
||||||
|
pecl_kept=${PECL_PKGS[*]:-none}
|
||||||
|
EOF
|
||||||
|
log "OK: lsphp${PHPVER} is now php.net $LSPHP_SRC_PHP_VERSION (was packaged $OLDV); /etc/cac-lsphp-source written"
|
||||||
|
## $OUT is a BuildKit bind mount (rw = writes discarded); nothing to clean up.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
// lsphp-src-probe.php — prints the runtime "support surface" of the PHP that
|
||||||
|
// runs it, one fact per line, for lsphp-src-overlay.sh to diff before/after the
|
||||||
|
// php.net overlay. Run under BOTH SAPIs: `bin/php probe.php` and
|
||||||
|
// `bin/lsphp probe.php` (lsphp executes a script argument under the litespeed
|
||||||
|
// SAPI — measured; it is only `-m`/`-r` it does not understand).
|
||||||
|
// Line kinds: SAPI <name> | VERSION <v> | EXT <name> <version> | ZEXT <name> <version>
|
||||||
|
// INI <name> <value> | FEAT <key> <value>
|
||||||
|
// The overlay script relies on the stable "<KIND> <key> <rest>" shape; keep it.
|
||||||
|
echo 'SAPI ', PHP_SAPI, "\n";
|
||||||
|
echo 'VERSION ', PHP_VERSION, "\n";
|
||||||
|
echo 'FEAT ZTS ', PHP_ZTS, "\n";
|
||||||
|
echo 'FEAT DEBUG ', PHP_DEBUG, "\n";
|
||||||
|
echo 'FEAT INT_SIZE ', PHP_INT_SIZE, "\n";
|
||||||
|
foreach (get_loaded_extensions() as $e) {
|
||||||
|
echo 'EXT ', str_replace(' ', '_', $e), ' ', phpversion($e), "\n";
|
||||||
|
}
|
||||||
|
foreach (get_loaded_extensions(true) as $e) {
|
||||||
|
echo 'ZEXT ', str_replace(' ', '_', $e), ' ', phpversion($e), "\n";
|
||||||
|
}
|
||||||
|
foreach (ini_get_all(null, false) as $k => $v) {
|
||||||
|
echo 'INI ', $k, ' ', var_export($v, true), "\n";
|
||||||
|
}
|
||||||
|
if (function_exists('gd_info')) {
|
||||||
|
foreach (gd_info() as $k => $v) {
|
||||||
|
echo 'FEAT gd:', str_replace(' ', '_', $k), ' ', var_export($v, true), "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (function_exists('curl_version')) {
|
||||||
|
$c = curl_version();
|
||||||
|
echo 'FEAT curl:version ', $c['version'], "\n";
|
||||||
|
echo 'FEAT curl:ssl ', $c['ssl_version'], "\n";
|
||||||
|
echo 'FEAT curl:protocols ', implode(',', $c['protocols']), "\n";
|
||||||
|
}
|
||||||
|
echo 'FEAT openssl ', defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'none', "\n";
|
||||||
|
if (defined('INTL_ICU_VERSION')) echo 'FEAT icu ', INTL_ICU_VERSION, "\n";
|
||||||
|
if (class_exists('Imagick')) echo 'FEAT imagick:formats ', count(Imagick::queryFormats()), "\n";
|
||||||
|
if (function_exists('sodium_crypto_aead_aes256gcm_is_available')) echo 'FEAT sodium:aes256gcm ', var_export(sodium_crypto_aead_aes256gcm_is_available(), true), "\n";
|
||||||
|
echo 'FEAT password_algos ', implode(',', password_algos()), "\n";
|
||||||
|
echo 'FEAT hash_algos ', count(hash_algos()), "\n";
|
||||||
|
echo 'FEAT stream_wrappers ', implode(',', stream_get_wrappers()), "\n";
|
||||||
|
echo 'FEAT stream_filters ', implode(',', stream_get_filters()), "\n";
|
||||||
|
if (function_exists('mb_list_encodings')) echo 'FEAT mb_encodings ', count(mb_list_encodings()), "\n";
|
||||||
|
if (function_exists('opcache_get_status')) echo 'FEAT opcache:loaded 1', "\n";
|
||||||
|
if (class_exists('Redis')) echo 'FEAT redis:serializers ', implode(',', array_filter(['IGBINARY', 'MSGPACK', 'JSON'], fn($s) => defined("Redis::SERIALIZER_$s"))), "\n";
|
||||||
|
if (class_exists('Redis')) echo 'FEAT redis:compression ', implode(',', array_filter(['LZF', 'ZSTD', 'LZ4'], fn($s) => defined("Redis::COMPRESSION_$s"))), "\n";
|
||||||
|
if (class_exists('Memcached')) echo 'FEAT memcached:igbinary ', var_export(Memcached::HAVE_IGBINARY, true), ' msgpack ', var_export(Memcached::HAVE_MSGPACK, true), ' sasl ', var_export(Memcached::HAVE_SASL, true), "\n";
|
||||||
Executable
+223
@@ -0,0 +1,223 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## pkg-cache-repos.sh — BUILD-TIME ONLY, OPT-IN. Points dnf/apt at the internal
|
||||||
|
## package cache (nginx proxy_cache, CT130 "pkg-cache" on the Proxmox cluster)
|
||||||
|
## when PKG_CACHE is set; without it the repo files are left alone and builds
|
||||||
|
## use the upstream repositories as before.
|
||||||
|
##
|
||||||
|
## PKG_CACHE=https://192.168.1.40 pkg-cache-repos.sh rpm # AlmaLinux/EPEL/Remi
|
||||||
|
## PKG_CACHE=https://192.168.1.40 pkg-cache-repos.sh apt # Ubuntu + LiteSpeed
|
||||||
|
## pkg-cache-repos.sh release-install epel [remi] # GPG-verified release RPMs
|
||||||
|
## pkg-cache-repos.sh restore # undo everything above
|
||||||
|
##
|
||||||
|
## Every RUN that rewrites must `restore` before it ends: it puts rewritten files
|
||||||
|
## back, deletes files this script created, deletes any NEW repo/apt file that
|
||||||
|
## still references the cache, then FAILS if any reference is left — so no shipped
|
||||||
|
## layer carries the cache's LAN address (images run on hosts that can't reach it).
|
||||||
|
##
|
||||||
|
## Why: on 2026-10-21 (PHP security release) builds failed on Remi mirrors lagging
|
||||||
|
## hours behind origin, Remi's origin timing out over HTTPS, and an EPEL mirror
|
||||||
|
## with a broken certificate. The cache fetches from the ORIGINS over HTTPS (with
|
||||||
|
## origin/mirror fallbacks and stale-on-error), keeps metadata ~3 minutes and
|
||||||
|
## immutable .rpm/.deb files long-term.
|
||||||
|
##
|
||||||
|
## Integrity (security review 2026-10-21):
|
||||||
|
## * PKG_CACHE must be https://. The cache's certificate is issued by the
|
||||||
|
## name-constrained "pkg-cache internal CA" (keys/pkg-cache/ca.crt, valid only
|
||||||
|
## for 192.168.1.40 / pkg-cache.*). That CA is trusted ONLY by the rewritten
|
||||||
|
## repos (dnf sslcacert= bundle, apt per-host CaInfo) and only during the RUN —
|
||||||
|
## never added to the system trust store.
|
||||||
|
## * gpgcheck=1 is forced on every rewritten repo; repo_gpgcheck=1 on Remi and
|
||||||
|
## AlmaLinux, which sign repomd.xml (measured: repomd.xml.asc 200). EPEL
|
||||||
|
## publishes no repomd signature (404), so its metadata relies on HTTPS end
|
||||||
|
## to end (client->cache->dl.fedoraproject.org/mirrors, all verified).
|
||||||
|
## * release-install downloads epel-release / remi-release (from the cache, or
|
||||||
|
## from the HTTPS origin without PKG_CACHE), checks the package name, imports
|
||||||
|
## the signing keys committed in keys/pkg-cache/ and requires
|
||||||
|
## `rpm -K` "signatures OK" before installing with localpkg_gpgcheck=1.
|
||||||
|
## * The php.net version gates (EXPECT_PHP_MIN / EXPECT_PHP_VERSION) still fail
|
||||||
|
## a build that gets old PHP (replay of old-but-signed metadata).
|
||||||
|
##
|
||||||
|
## rpm: each rewritten baseurl keeps the upstream origin as a SECOND url, so if
|
||||||
|
## the cache dies mid-build dnf fails over to the origin by itself.
|
||||||
|
## apt: apt has no per-source failover; CI only sets PKG_CACHE after probing the
|
||||||
|
## cache (with the CA), and a build without PKG_CACHE uses upstream sources.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MODE="${1:?usage: pkg-cache-repos.sh rpm|apt|release-install NAME...|restore}"
|
||||||
|
shift || true
|
||||||
|
STATE=/var/lib/pkg-cache-repos # saved originals + manifest + created files
|
||||||
|
SHARE="${PKG_CACHE_SHARE:-/usr/local/share/pkg-cache}" # ca.crt + RPM-GPG-KEY-*
|
||||||
|
C="${PKG_CACHE:-}"
|
||||||
|
C="${C%/}"
|
||||||
|
|
||||||
|
die() { echo "FATAL(pkg-cache-repos): $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [ -n "$C" ]; then
|
||||||
|
case "$C" in
|
||||||
|
https://*) ;;
|
||||||
|
*) die "PKG_CACHE must be an https:// URL (plain http is refused), got '$C'" ;;
|
||||||
|
esac
|
||||||
|
HOSTPORT="${C#https://}"; HOSTPORT="${HOSTPORT%%/*}"
|
||||||
|
[ -s "$SHARE/ca.crt" ] || die "PKG_CACHE set but $SHARE/ca.crt (pkg-cache CA) is missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Files that may hold repo configuration; restore re-checks all of them.
|
||||||
|
repo_files() {
|
||||||
|
find /etc/yum.repos.d /etc/dnf /etc/apt -type f 2>/dev/null | sort || true
|
||||||
|
}
|
||||||
|
|
||||||
|
start_session() {
|
||||||
|
[ -d "$STATE" ] && return 0
|
||||||
|
mkdir -p "$STATE/orig"
|
||||||
|
repo_files > "$STATE/manifest" # what existed before this RUN touched anything
|
||||||
|
: > "$STATE/created"
|
||||||
|
printf '%s\n' "$HOSTPORT" > "$STATE/host"
|
||||||
|
}
|
||||||
|
save() { # keep the first-seen (pre-rewrite) copy
|
||||||
|
[ -e "$STATE/orig$1" ] && return 0
|
||||||
|
mkdir -p "$STATE/orig$(dirname "$1")"
|
||||||
|
cp -p "$1" "$STATE/orig$1"
|
||||||
|
}
|
||||||
|
created() { printf '%s\n' "$1" >> "$STATE/created"; }
|
||||||
|
|
||||||
|
if [ "$MODE" = restore ]; then
|
||||||
|
[ -d "$STATE" ] || exit 0
|
||||||
|
host=$(cat "$STATE/host")
|
||||||
|
(cd "$STATE/orig" && find . -type f) | while read -r rel; do
|
||||||
|
cat "$STATE/orig/$rel" > "/${rel#./}"
|
||||||
|
done
|
||||||
|
while read -r f; do [ -n "$f" ] && rm -f -- "$f"; done < "$STATE/created"
|
||||||
|
# A repo/apt file that appeared during this RUN and still names the cache
|
||||||
|
# (e.g. written by a package from the rewritten repos) is removed too.
|
||||||
|
repo_files | while read -r f; do
|
||||||
|
if ! grep -qxF -- "$f" "$STATE/manifest" && grep -qF -- "$host" "$f"; then
|
||||||
|
echo "pkg-cache-repos: removing new file that references the cache: $f"
|
||||||
|
rm -f -- "$f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
left=$(repo_files | xargs -r grep -lF -- "$host" 2>/dev/null || true)
|
||||||
|
rm -rf "$STATE"
|
||||||
|
[ -z "$left" ] || die "cache address $host still present after restore in: $left"
|
||||||
|
echo "pkg-cache-repos: original repository files restored (no reference to $host left)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$MODE" = release-install ]; then
|
||||||
|
[ $# -gt 0 ] || die "release-install needs: epel and/or remi"
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
curl_opts=(-fsS --proto '=https' --retry 5 --retry-all-errors --max-time 120)
|
||||||
|
if [ -n "$C" ]; then
|
||||||
|
start_session
|
||||||
|
cat /etc/pki/tls/certs/ca-bundle.crt "$SHARE/ca.crt" > "$STATE/ca-bundle.pem"
|
||||||
|
curl_opts+=(--cacert "$STATE/ca-bundle.pem")
|
||||||
|
fi
|
||||||
|
files=()
|
||||||
|
for name in "$@"; do
|
||||||
|
case "$name" in
|
||||||
|
epel) pkg=epel-release key=RPM-GPG-KEY-EPEL-10
|
||||||
|
path=/epel/epel-release-latest-10.noarch.rpm
|
||||||
|
origin=https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm ;;
|
||||||
|
remi) pkg=remi-release key=RPM-GPG-KEY-remi2024
|
||||||
|
path=/remi/enterprise/remi-release-10.rpm
|
||||||
|
origin=https://rpms.remirepo.net/enterprise/remi-release-10.rpm ;;
|
||||||
|
*) die "unknown release package '$name'" ;;
|
||||||
|
esac
|
||||||
|
url="$origin"; [ -n "$C" ] && url="$C$path"
|
||||||
|
f="$tmp/$pkg.rpm"
|
||||||
|
curl "${curl_opts[@]}" -o "$f" "$url" || die "download failed: $url"
|
||||||
|
rpm --import "$SHARE/$key" || die "cannot import $SHARE/$key"
|
||||||
|
got=$(rpm -qp --nosignature --qf '%{NAME}' "$f" 2>/dev/null || true)
|
||||||
|
[ "$got" = "$pkg" ] || die "$url is '$got', expected $pkg"
|
||||||
|
chk=$(rpm -K "$f" 2>&1 || true)
|
||||||
|
[[ "$chk" == *": digests signatures OK" ]] || die "signature check failed for $url: $chk"
|
||||||
|
echo "pkg-cache-repos: $pkg verified ($chk) from $url"
|
||||||
|
files+=("$f")
|
||||||
|
done
|
||||||
|
dnf install -y --setopt=localpkg_gpgcheck=1 "${files[@]}"
|
||||||
|
rm -rf "$tmp"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$C" ]; then
|
||||||
|
echo "pkg-cache-repos($MODE): PKG_CACHE not set - using upstream repositories"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
rewrite_rpm() {
|
||||||
|
local f="$1"
|
||||||
|
grep -q -E '^#? ?baseurl=https?://(repo\.almalinux\.org|download\.example|rpms\.remirepo\.net)/' "$f" || return 0
|
||||||
|
save "$f"
|
||||||
|
# Per [section]: a (commented or live) baseurl on a known origin becomes
|
||||||
|
# "baseurl=<cache>/<prefix>/<path> <origin>/<path>"; that section's
|
||||||
|
# mirrorlist/metalink is commented out (dnf prefers them over baseurl) and it
|
||||||
|
# gets sslcacert=<system bundle + pkg-cache CA>, gpgcheck=1 and, for repos
|
||||||
|
# that sign repomd.xml, repo_gpgcheck=1. Other sections are untouched.
|
||||||
|
awk -v C="$C" -v BUNDLE="$STATE/ca-bundle.pem" '
|
||||||
|
function flush( i) {
|
||||||
|
for (i = 1; i <= n; i++) {
|
||||||
|
if (hit && buf[i] ~ /^(mirrorlist|metalink)=/) buf[i] = "#" buf[i]
|
||||||
|
if (hit && buf[i] ~ /^(sslcacert|gpgcheck|repo_gpgcheck)[ \t]*=/) continue
|
||||||
|
print buf[i]
|
||||||
|
}
|
||||||
|
if (hit) {
|
||||||
|
print "sslcacert=" BUNDLE
|
||||||
|
print "gpgcheck=1"
|
||||||
|
if (signed) print "repo_gpgcheck=1"
|
||||||
|
}
|
||||||
|
n = 0; hit = 0; signed = 0
|
||||||
|
}
|
||||||
|
function rw(line, re, prefix, origin, sig, p) {
|
||||||
|
if (match(line, re)) {
|
||||||
|
p = substr(line, RSTART + RLENGTH); sub(/[ \t].*$/, "", p)
|
||||||
|
hit = 1; if (sig) signed = 1
|
||||||
|
return "baseurl=" C "/" prefix "/" p " " origin "/" p
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
/^\[/ { flush() }
|
||||||
|
{
|
||||||
|
l = $0
|
||||||
|
if (l ~ /^#? ?baseurl=/) {
|
||||||
|
l = rw(l, "^#? ?baseurl=https?://repo\\.almalinux\\.org/almalinux/", "almalinux", "https://repo.almalinux.org/almalinux", 1)
|
||||||
|
l = rw(l, "^#? ?baseurl=https?://download\\.example/pub/epel/", "epel", "https://dl.fedoraproject.org/pub/epel", 0)
|
||||||
|
l = rw(l, "^#? ?baseurl=https?://rpms\\.remirepo\\.net/", "remi", "https://rpms.remirepo.net", 1)
|
||||||
|
}
|
||||||
|
buf[++n] = l
|
||||||
|
}
|
||||||
|
END { flush() }
|
||||||
|
' "$f" > "$f.pkgcache" && cat "$f.pkgcache" > "$f" && rm -f "$f.pkgcache"
|
||||||
|
}
|
||||||
|
|
||||||
|
rewrite_apt() {
|
||||||
|
local f="$1"
|
||||||
|
grep -q -E 'https?://([a-z]{2}\.)?(archive|security)\.ubuntu\.com/|https?://rpms\.litespeedtech\.com/' "$f" || return 0
|
||||||
|
save "$f"
|
||||||
|
sed -i -E \
|
||||||
|
-e "s%https?://(archive|[a-z]{2}\.archive)\.ubuntu\.com/ubuntu/?%$C/ubuntu/%g" \
|
||||||
|
-e "s%https?://security\.ubuntu\.com/ubuntu/?%$C/ubuntu-security/%g" \
|
||||||
|
-e "s%https?://rpms\.litespeedtech\.com/%$C/litespeed/%g" \
|
||||||
|
"$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
rpm)
|
||||||
|
start_session
|
||||||
|
cat /etc/pki/tls/certs/ca-bundle.crt "$SHARE/ca.crt" > "$STATE/ca-bundle.pem"
|
||||||
|
for f in /etc/yum.repos.d/*.repo; do [ -f "$f" ] && rewrite_rpm "$f"; done
|
||||||
|
echo "pkg-cache-repos(rpm): repos now via $C:"
|
||||||
|
grep -h '^baseurl=' /etc/yum.repos.d/*.repo | grep -F "$C/" | sort -u | sed 's/^/ /'
|
||||||
|
;;
|
||||||
|
apt)
|
||||||
|
start_session
|
||||||
|
conf=/etc/apt/apt.conf.d/99pkg-cache
|
||||||
|
# Trust the pkg-cache CA for the cache host ONLY (apt per-host option).
|
||||||
|
printf 'Acquire::https::%s::CaInfo "%s";\n' "${HOSTPORT%%:*}" "$SHARE/ca.crt" > "$conf"
|
||||||
|
created "$conf"
|
||||||
|
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
|
||||||
|
[ -f "$f" ] && rewrite_apt "$f"
|
||||||
|
done
|
||||||
|
echo "pkg-cache-repos(apt): sources now via $C:"
|
||||||
|
grep -h -E '^(deb|URIs:)' /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null | grep -F "$C/" | sort -u | sed 's/^/ /'
|
||||||
|
;;
|
||||||
|
*) die "unknown mode '$MODE'" ;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC2154 # $uid / $user come from the container environment
|
||||||
|
## safe-paths.sh — sourced by entrypoint.sh and entrypoint-fpm.sh. Not executed directly.
|
||||||
|
##
|
||||||
|
## /home/$user is the customer's bind-mounted site directory: the customer can
|
||||||
|
## create anything in it, including symlinks, from SFTP/FTP, cron, or PHP. The
|
||||||
|
## entrypoints run as root, and root following a customer symlink is a
|
||||||
|
## privilege escalation: `touch`/`chown`/`>` on logs/php-fpm/error.log ->
|
||||||
|
## /etc/shadow hands the customer /etc/shadow (container root).
|
||||||
|
##
|
||||||
|
## Rules the entrypoints follow:
|
||||||
|
## * /home/$user itself is the bind-mount point and must be a real directory.
|
||||||
|
## * logs/ is WHP-owned and apache / php-fpm open files in it as root, so it
|
||||||
|
## must be a real directory and every symlink under it is removed + logged.
|
||||||
|
## * Files and dirs are CREATED as the customer (as_user), so a symlink or a
|
||||||
|
## swapped path can only reach what the customer could reach anyway.
|
||||||
|
## * Ownership changes use `chown -h` / `chown -R` (GNU default -P: never
|
||||||
|
## follows symlinks, lchown()s them), never a dereferencing chown.
|
||||||
|
## * Customer files are READ as the customer (crontab), never as root.
|
||||||
|
|
||||||
|
# as_user CMD... : run CMD as the customer uid/gid (no root, no caps).
|
||||||
|
as_user() {
|
||||||
|
if command -v setpriv >/dev/null 2>&1; then
|
||||||
|
setpriv --reuid="$uid" --regid="$uid" --init-groups -- "$@"
|
||||||
|
else
|
||||||
|
runuser -u "$user" -- "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# die MSG : refuse to start. Only the customer who planted the path is hurt,
|
||||||
|
# which is preferred to root following it.
|
||||||
|
die() {
|
||||||
|
echo "entrypoint: FATAL: $* - refusing to start" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# drop_symlink PATH : if PATH is a symlink, unlink it AS THE CUSTOMER (rm on
|
||||||
|
# the final component never touches the link target, and if a parent dir is
|
||||||
|
# swapped for a symlink mid-way the customer can only delete what they already
|
||||||
|
# could) and log it. Fails closed: still a symlink afterwards -> refuse to start.
|
||||||
|
drop_symlink() {
|
||||||
|
if [ -L "$1" ]; then
|
||||||
|
echo "entrypoint: SECURITY: $1 was a symlink to '$(readlink "$1")' - removing" >&2
|
||||||
|
as_user rm -f -- "$1"
|
||||||
|
[ -L "$1" ] && die "could not remove symlink $1"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# secure_home : run once, right after adduser, before anything else touches
|
||||||
|
# /home/$user.
|
||||||
|
secure_home() {
|
||||||
|
local h="/home/$user"
|
||||||
|
if [ -L "$h" ] || [ ! -d "$h" ]; then
|
||||||
|
die "$h is not a real directory (symlink or missing)"
|
||||||
|
fi
|
||||||
|
# The mount point itself: owned by the customer so as_user can create in it.
|
||||||
|
chown -h "$user:$user" "$h"
|
||||||
|
drop_symlink "$h/logs"
|
||||||
|
# public_html may legitimately be a symlink (release-dir deploys); it is left
|
||||||
|
# alone and every root-side operation on it runs as the customer instead.
|
||||||
|
if [ -d "$h/logs" ]; then
|
||||||
|
# As the customer: find without -L never follows symlinks, -delete unlinks
|
||||||
|
# the link itself, and a raced parent swap can only reach what the customer
|
||||||
|
# could already delete. Errors are not fatal HERE: require_safe_logs
|
||||||
|
# re-checks immediately before the daemons start and fails closed.
|
||||||
|
as_user find "$h/logs" -xdev -type l \
|
||||||
|
-printf 'entrypoint: SECURITY: %p was a symlink to %l - removed\n' -delete >&2 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# user_mkdir DIR... : create directories as the customer.
|
||||||
|
user_mkdir() {
|
||||||
|
as_user mkdir -p -- "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# user_touch FILE : create FILE as the customer (a symlink there is dropped
|
||||||
|
# first, fail-closed). Root never touches/chowns it: if it already exists
|
||||||
|
# root-owned (left by an older image) the touch fails quietly and the
|
||||||
|
# entrypoint's chown -R, which never follows symlinks, fixes the owner. Must end
|
||||||
|
# up a regular file, or refuse to start.
|
||||||
|
user_touch() {
|
||||||
|
drop_symlink "$1"
|
||||||
|
as_user touch -- "$1" 2>/dev/null || true
|
||||||
|
[ -f "$1" ] && [ ! -L "$1" ] || die "$1 is not a regular file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# require_safe_logs DIR... : fail-closed gate, run immediately before starting
|
||||||
|
# the daemons that open files under /home/$user/logs AS ROOT (httpd, php-fpm).
|
||||||
|
# Each DIR must be a real directory, and nothing under logs/ may be anything
|
||||||
|
# but a regular file or directory (no symlink, fifo, socket, device).
|
||||||
|
require_safe_logs() {
|
||||||
|
local d bad
|
||||||
|
for d in "/home/$user/logs" "$@"; do
|
||||||
|
[ -d "$d" ] && [ ! -L "$d" ] || die "$d is not a real directory"
|
||||||
|
done
|
||||||
|
bad=$(find "/home/$user/logs" -xdev ! -type f ! -type d -print -quit 2>&1)
|
||||||
|
[ -z "$bad" ] || die "unsafe entry under /home/$user/logs: $bad"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# load_user_crontab : install /home/$user/crontab, read AS THE CUSTOMER. Root
|
||||||
|
# reading it (`crontab -u $user /home/$user/crontab`) would follow a symlink to
|
||||||
|
# any root-readable file and install it where the customer can `crontab -l` it.
|
||||||
|
load_user_crontab() {
|
||||||
|
local f="/home/$user/crontab"
|
||||||
|
if [ ! -e "$f" ] && [ ! -L "$f" ]; then
|
||||||
|
as_user sh -c 'printf "%s\n" "# User crontab for $1" "# Add your cron jobs here" "# Example: */5 * * * * /home/$1/scripts/my-script.sh" > "$2"' sh "$user" "$f"
|
||||||
|
fi
|
||||||
|
# No temp file: root writing a predictable /tmp path is the same bug class.
|
||||||
|
local content
|
||||||
|
if content=$(as_user cat -- "$f" 2>/dev/null); then
|
||||||
|
printf '%s\n' "$content" | crontab -u "$user" -
|
||||||
|
else
|
||||||
|
echo "entrypoint: WARNING: $f not readable as $user - user crontab not loaded" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# open_dir_traversal : chmod 755 the home dir, then add o+x to every directory
|
||||||
|
# lacking it (apache/nginx run as their own user and must traverse). Run AS THE
|
||||||
|
# CUSTOMER: `find -type d` without -L skips symlinks, but chmod would follow a
|
||||||
|
# dir swapped for a symlink between find and chmod; as the customer that can
|
||||||
|
# only reach what the customer already owns.
|
||||||
|
open_dir_traversal() {
|
||||||
|
chmod 755 "/home/$user"
|
||||||
|
as_user find "/home/$user" -type d ! -perm -o=x -exec chmod o+x {} + 2>/dev/null || true
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
## stop-services.sh — sourced by the bash PID-1 entrypoints (entrypoint.sh,
|
||||||
|
## entrypoint-fpm.sh, entrypoint-shared-httpd.sh). Not executed directly.
|
||||||
|
##
|
||||||
|
## Why this exists: those entrypoints end with bash as PID 1 tailing logs.
|
||||||
|
## The kernel never delivers a signal to PID 1 unless PID 1 installed a handler
|
||||||
|
## for it, so `docker stop`'s SIGTERM was silently dropped. Docker waited the
|
||||||
|
## full stop timeout (10s) and then SIGKILLed the whole container, killing
|
||||||
|
## apache / php-fpm mid-request (exit 137). The entrypoints now trap TERM/INT
|
||||||
|
## and call stop_service for each daemon they started, then exit 0.
|
||||||
|
##
|
||||||
|
## Budget: docker's default stop timeout is 10s. Callers pass per-service
|
||||||
|
## timeouts that sum to < 10s so the graceful path always completes before
|
||||||
|
## docker gives up and SIGKILLs.
|
||||||
|
|
||||||
|
# A pid counts as alive if it exists and is not a zombie. bash (PID 1) reaps
|
||||||
|
# the daemons it adopted, but between exit and reap a zombie still answers
|
||||||
|
# `kill -0`, so check /proc state too.
|
||||||
|
_svc_alive() {
|
||||||
|
[ -n "$1" ] || return 1
|
||||||
|
kill -0 "$1" 2>/dev/null || return 1
|
||||||
|
! grep -qs '^State:[[:space:]]*Z' "/proc/$1/status"
|
||||||
|
}
|
||||||
|
|
||||||
|
# stop_service NAME PIDFILE GRACEFUL_SIGNAL GRACE_SECONDS [PGREP_PATTERN]
|
||||||
|
# Sends GRACEFUL_SIGNAL, waits up to GRACE_SECONDS for the process to exit,
|
||||||
|
# then sends TERM and waits 1s more, then KILL. Pid comes from PIDFILE, or
|
||||||
|
# from `pgrep -o -f PGREP_PATTERN` when the pidfile is missing/stale.
|
||||||
|
stop_service() {
|
||||||
|
local name=$1 pidfile=$2 sig=$3 grace=$4 pat=${5:-} pid="" i
|
||||||
|
[ -r "$pidfile" ] && pid=$(tr -dc '0-9' < "$pidfile")
|
||||||
|
if ! _svc_alive "$pid" && [ -n "$pat" ]; then
|
||||||
|
pid=$(pgrep -o -f "$pat" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
if ! _svc_alive "$pid"; then
|
||||||
|
echo "entrypoint: $name not running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "entrypoint: stopping $name (pid $pid) with SIG$sig"
|
||||||
|
kill -s "$sig" "$pid" 2>/dev/null
|
||||||
|
for ((i = 0; i < grace * 10; i++)); do
|
||||||
|
_svc_alive "$pid" || { echo "entrypoint: $name stopped"; return 0; }
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
echo "entrypoint: $name still running after ${grace}s, sending SIGTERM"
|
||||||
|
kill -TERM "$pid" 2>/dev/null
|
||||||
|
for ((i = 0; i < 10; i++)); do
|
||||||
|
_svc_alive "$pid" || { echo "entrypoint: $name stopped"; return 0; }
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
echo "entrypoint: $name still running, sending SIGKILL"
|
||||||
|
kill -KILL "$pid" 2>/dev/null
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Executable
+99
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
## lsphp-abi-gate.test.sh — proves scripts/lsphp-abi-gate.sh can FAIL.
|
||||||
|
##
|
||||||
|
## The gate is what lets the emergency php.net lsphp build keep LiteSpeed's
|
||||||
|
## packaged PECL .so files. A gate that always says "compatible" would be worse
|
||||||
|
## than none, so every rule is exercised against synthetic header trees whose
|
||||||
|
## outcome is known: three layouts that must pass (unchanged; new member in
|
||||||
|
## padding; member appended to a *_globals struct) and five that must fail
|
||||||
|
## (member inserted mid-struct; member removed; member retyped/resized; a
|
||||||
|
## non-globals struct grown at the end; a struct embedded in another growing).
|
||||||
|
## Plus: a probe that extracts too few members must refuse, not pass.
|
||||||
|
##
|
||||||
|
## Needs gcc + pahole (dwarves). Exits 0 only if every case behaves.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HERE=$(cd "$(dirname "$0")/.." && pwd)
|
||||||
|
GATE="$HERE/lsphp-abi-gate.sh"
|
||||||
|
for t in gcc pahole; do
|
||||||
|
command -v "$t" >/dev/null 2>&1 || { echo "SKIP-FAIL: $t not installed (apt-get install gcc dwarves)" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
T=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$T"' EXIT
|
||||||
|
|
||||||
|
## $1 = dir, $2 = body of struct _zend_object, $3 = body of _zend_executor_globals,
|
||||||
|
## $4 = extra declarations
|
||||||
|
mk() {
|
||||||
|
mkdir -p "$1/php/main"
|
||||||
|
cat >"$1/php/main/php.h" <<EOF
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
struct _zend_object { $2 };
|
||||||
|
struct _zend_executor_globals { $3 };
|
||||||
|
struct _holder { struct _zend_object obj; int after; };
|
||||||
|
typedef struct { int a; void *b; } anon_cache;
|
||||||
|
$4
|
||||||
|
struct _zend_executor_globals executor_globals;
|
||||||
|
struct _holder holder_sym;
|
||||||
|
anon_cache anon_sym;
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
BASE_OBJ='uint32_t gc; uint32_t handle; bool flag; void *ce; void *handlers;'
|
||||||
|
BASE_EG='void *a; int b; void *reserved[4];'
|
||||||
|
mk "$T/old" "$BASE_OBJ" "$BASE_EG" ''
|
||||||
|
|
||||||
|
PASS=0; FAILN=0
|
||||||
|
expect() { # $1 = pass|fail, $2 = name, $3 = new dir
|
||||||
|
local rc=0
|
||||||
|
ABI_MIN_MEMBERS=5 bash "$GATE" "$T/old" "$3" "$T/w-$2" >"$T/out-$2" 2>&1 || rc=$?
|
||||||
|
if { [ "$1" = pass ] && [ "$rc" = 0 ]; } || { [ "$1" = fail ] && [ "$rc" != 0 ]; }; then
|
||||||
|
PASS=$((PASS + 1)); echo "ok - $2 ($1, rc=$rc)"
|
||||||
|
else
|
||||||
|
FAILN=$((FAILN + 1)); echo "FAIL - $2: expected $1, got rc=$rc"; sed 's/^/ /' "$T/out-$2"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mk "$T/same" "$BASE_OBJ" "$BASE_EG" ''
|
||||||
|
expect pass unchanged "$T/same"
|
||||||
|
|
||||||
|
## bool after `flag` lands in the 3 padding bytes before the pointer `ce`
|
||||||
|
mk "$T/pad" 'uint32_t gc; uint32_t handle; bool flag; bool newflag; void *ce; void *handlers;' "$BASE_EG" ''
|
||||||
|
expect pass new-member-in-padding "$T/pad"
|
||||||
|
|
||||||
|
mk "$T/eg" "$BASE_OBJ" 'void *a; int b; void *reserved[4]; void *delayed_oom;' ''
|
||||||
|
expect pass globals-appended "$T/eg"
|
||||||
|
|
||||||
|
mk "$T/mid" 'uint32_t gc; void *inserted; uint32_t handle; bool flag; void *ce; void *handlers;' "$BASE_EG" ''
|
||||||
|
expect fail member-inserted-mid-struct "$T/mid"
|
||||||
|
|
||||||
|
mk "$T/rm" 'uint32_t gc; uint32_t handle; bool flag; void *handlers;' "$BASE_EG" ''
|
||||||
|
expect fail member-removed "$T/rm"
|
||||||
|
|
||||||
|
mk "$T/retype" 'uint32_t gc; uint64_t handle; bool flag; void *ce; void *handlers;' "$BASE_EG" ''
|
||||||
|
expect fail member-resized "$T/retype"
|
||||||
|
|
||||||
|
## appended to a NON-globals struct: no old member moves, but sizeof grows —
|
||||||
|
## extensions that allocate sizeof(zend_object)+x would under-allocate
|
||||||
|
mk "$T/grow" 'uint32_t gc; uint32_t handle; bool flag; void *ce; void *handlers; void *extra;' "$BASE_EG" ''
|
||||||
|
expect fail non-globals-struct-grew "$T/grow"
|
||||||
|
|
||||||
|
## same change seen from the embedding struct: the report must name _holder.after
|
||||||
|
## as MOVED, i.e. the container check works on its own, not only via the size rule
|
||||||
|
if grep -E '^BAD moved member _holder int_after#1' "$T/out-non-globals-struct-grew" >/dev/null; then
|
||||||
|
PASS=$((PASS + 1)); echo "ok - embedded-struct-shifts-container (reported _holder.after moved)"
|
||||||
|
else
|
||||||
|
FAILN=$((FAILN + 1)); echo "FAIL - embedded-struct-shifts-container: _holder.after not reported"; sed 's/^/ /' "$T/out-non-globals-struct-grew"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## the anonymous typedef is tracked too
|
||||||
|
mkdir -p "$T/anon/php/main"
|
||||||
|
sed 's/typedef struct { int a; void \*b; } anon_cache;/typedef struct { int a; int z; void *b; long c; } anon_cache;/' "$T/old/php/main/php.h" >"$T/anon/php/main/php.h"
|
||||||
|
expect fail anonymous-typedef-changed "$T/anon"
|
||||||
|
|
||||||
|
## a probe that finds (almost) nothing must refuse
|
||||||
|
rc=0; ABI_MIN_MEMBERS=1000 bash "$GATE" "$T/old" "$T/same" "$T/w-min" >"$T/out-min" 2>&1 || rc=$?
|
||||||
|
if [ "$rc" != 0 ] && grep 'probe is broken' "$T/out-min" >/dev/null; then PASS=$((PASS + 1)); echo "ok - too-few-members-refused (rc=$rc)"
|
||||||
|
else FAILN=$((FAILN + 1)); echo "FAIL - too-few-members-refused: rc=$rc"; cat "$T/out-min"; fi
|
||||||
|
|
||||||
|
echo "lsphp-abi-gate: $PASS passed, $FAILN failed"
|
||||||
|
[ "$FAILN" = 0 ]
|
||||||
Reference in new issue
Block a user