Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7d4db5bd3 | ||
|
|
0fee1b54c4 | ||
|
|
9baa7a6029 | ||
|
|
3a6cab19ee | ||
|
|
6a11fc9894 | ||
|
|
05ad47a833 | ||
|
|
d1547f3418 | ||
|
|
a00a3c7fc3 | ||
|
|
3334f94cd5 | ||
|
|
76fc53358c | ||
|
|
fc60ac5ef3 | ||
|
|
472fdcbc4d | ||
|
|
cb0cecb557 | ||
|
|
2b9e7f302c | ||
|
|
18f65c6d84 | ||
|
|
b2bec5b3ee | ||
|
|
fa7808897f | ||
|
|
42993f42bd | ||
|
|
83c2ee9c2a | ||
|
|
be1540f559 | ||
|
|
af7fd193e5 | ||
|
|
e8e3bfd490 | ||
|
|
c49f456336 | ||
|
|
bcd737a9e2 | ||
|
|
955aec4573 | ||
|
|
998eb17f39 |
No files matched your search
Executable
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## resolve-base-image.sh IMAGE:TAG — CI only (not copied into images).
|
||||||
|
## Prints IMAGE@sha256:<digest> for a Docker Hub tag, looked up DIRECTLY at
|
||||||
|
## registry-1.docker.io over HTTPS (never via the pkg-cache mirror), so the
|
||||||
|
## build can say `FROM IMAGE@sha256:...` and the mirror can only serve
|
||||||
|
## content-addressed blobs that BuildKit verifies. Exits non-zero (prints
|
||||||
|
## nothing on stdout) if the lookup fails; the workflow then skips the mirror
|
||||||
|
## and pulls the tag directly from Docker Hub. HEAD requests do not count
|
||||||
|
## against Docker Hub pull rate limits.
|
||||||
|
## Canonical copy: cloud-apache-container/.gitea/scripts/resolve-base-image.sh
|
||||||
|
## (docs/pkg-cache.md there); other repos carry identical copies.
|
||||||
|
set -euo pipefail
|
||||||
|
ref="${1:?usage: resolve-base-image.sh IMAGE:TAG}"
|
||||||
|
name_re='^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)?$'
|
||||||
|
tag_re='^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$'
|
||||||
|
name="${ref%:*}"; tag="${ref##*:}"
|
||||||
|
[ "$name" != "$ref" ] || { echo "resolve-base-image: '$ref' has no :tag" >&2; exit 2; }
|
||||||
|
[[ "$name" =~ $name_re ]] || { echo "resolve-base-image: bad image name '$name'" >&2; exit 2; }
|
||||||
|
[[ "$tag" =~ $tag_re ]] || { echo "resolve-base-image: bad tag '$tag'" >&2; exit 2; }
|
||||||
|
repo="$name"; [[ "$repo" == */* ]] || repo="library/$repo" # official images
|
||||||
|
|
||||||
|
curl_opts=(-fsS --proto '=https' --retry 3 --retry-all-errors --max-time 20)
|
||||||
|
token=$(curl "${curl_opts[@]}" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" |
|
||||||
|
python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
|
||||||
|
[ -n "$token" ] || { echo "resolve-base-image: no Docker Hub token for $repo" >&2; exit 1; }
|
||||||
|
hdrs=$(curl "${curl_opts[@]}" -I \
|
||||||
|
-H "Authorization: Bearer ${token}" \
|
||||||
|
-H 'Accept: application/vnd.oci.image.index.v1+json' \
|
||||||
|
-H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
|
||||||
|
-H 'Accept: application/vnd.oci.image.manifest.v1+json' \
|
||||||
|
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
|
||||||
|
"https://registry-1.docker.io/v2/${repo}/manifests/${tag}")
|
||||||
|
digest=$(printf '%s\n' "$hdrs" | tr -d '\r' | awk 'tolower($1)=="docker-content-digest:" {print $2}' | tail -n1)
|
||||||
|
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "resolve-base-image: no valid digest for $ref (got '${digest}')" >&2; exit 1; }
|
||||||
|
printf '%s@%s\n' "$name" "$digest"
|
||||||
@@ -52,25 +52,97 @@ jobs:
|
|||||||
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-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 \
|
||||||
|
.gitea/scripts/resolve-base-image.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: [82, 83, 84, 85]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||||
|
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||||
|
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||||
|
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||||
|
- name: Resolve base image digest (Docker Hub, direct)
|
||||||
|
id: base
|
||||||
|
env:
|
||||||
|
BASE_TAG: almalinux/10-base:latest
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||||
|
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||||
|
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# The registry mirror is only used for a digest-pinned base image.
|
||||||
|
env:
|
||||||
|
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||||
|
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 [ "$BASE_PINNED" = true ] && 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:-<not used: Docker Hub direct>}"
|
||||||
|
[ -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
|
||||||
|
|
||||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||||
@@ -79,7 +151,28 @@ jobs:
|
|||||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||||
- name: Cache-bust value
|
- name: Cache-bust value
|
||||||
id: bust
|
id: bust
|
||||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
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
|
||||||
@@ -96,6 +189,9 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
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' || '' }}
|
||||||
@@ -103,16 +199,72 @@ 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: [82, 83, 84, 85]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||||
|
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||||
|
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||||
|
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||||
|
- name: Resolve base image digest (Docker Hub, direct)
|
||||||
|
id: base
|
||||||
|
env:
|
||||||
|
BASE_TAG: almalinux/10-base:latest
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||||
|
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||||
|
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# The registry mirror is only used for a digest-pinned base image.
|
||||||
|
env:
|
||||||
|
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||||
|
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 [ "$BASE_PINNED" = true ] && 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:-<not used: Docker Hub direct>}"
|
||||||
|
[ -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
|
||||||
|
|
||||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||||
@@ -121,7 +273,28 @@ jobs:
|
|||||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||||
- name: Cache-bust value
|
- name: Cache-bust value
|
||||||
id: bust
|
id: bust
|
||||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
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
|
||||||
@@ -139,27 +312,90 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
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' || '' }}
|
||||||
|
|
||||||
Build-LiteSpeed-Images:
|
Build-LiteSpeed-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# One place for the OLS base tag: digest lookup and build-args both read it.
|
||||||
|
# OLS_VERSION pinned to 1.8.4 (see the build step); Dockerfile default differs.
|
||||||
|
env:
|
||||||
|
OLS_VERSION: "1.8.4"
|
||||||
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
|
||||||
# cac-litespeed tier is a paid premium offering: 8.1+ is the
|
# cac-litespeed tier is a paid premium offering: 8.1+ is the
|
||||||
# modernization story we're selling.
|
# modernization story we're selling.
|
||||||
phpver: [81, 82, 83, 84, 85]
|
phpver: [82, 83, 84, 85]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||||
|
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||||
|
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||||
|
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||||
|
- name: Resolve base image digest (Docker Hub, direct)
|
||||||
|
id: base
|
||||||
|
env:
|
||||||
|
BASE_TAG: litespeedtech/openlitespeed:${{ env.OLS_VERSION }}-lsphp${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||||
|
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||||
|
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# The registry mirror is only used for a digest-pinned base image.
|
||||||
|
env:
|
||||||
|
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||||
|
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 [ "$BASE_PINNED" = true ] && 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:-<not used: Docker Hub direct>}"
|
||||||
|
[ -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
|
||||||
|
|
||||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||||
@@ -168,7 +404,9 @@ jobs:
|
|||||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||||
- name: Cache-bust value
|
- name: Cache-bust value
|
||||||
id: bust
|
id: bust
|
||||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
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
|
# 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
|
# CURRENT lsphp version. The Dockerfile asserts the installed package equals
|
||||||
@@ -176,14 +414,21 @@ jobs:
|
|||||||
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
|
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
|
||||||
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
|
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
|
||||||
id: lsphp
|
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: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
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${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
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${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi
|
if [ -z "$pkg" ]; then echo "::error::lsphp${PHPVER} not found in the LiteSpeed repo index"; exit 1; fi
|
||||||
echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})"
|
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 "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||||
echo "php=${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
|
||||||
@@ -192,6 +437,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:
|
||||||
@@ -200,14 +466,15 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
OLS_VERSION=1.8.4
|
OLS_VERSION=${{ env.OLS_VERSION }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
|
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
|
||||||
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
|
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. It was the only release with an lsphp81
|
||||||
# for every PHP version we ship (1.8.5 and 1.9.0 don't have an
|
# variant; 8.1 and older are no longer built (2026-10-21), so the pin
|
||||||
# lsphp81 variant on Docker Hub). Bump alongside a local rebuild
|
# can move to a newer OLS release after a local rebuild test.
|
||||||
# test when LiteSpeed publishes lsphp81 on a newer OLS release.
|
|
||||||
# See spec: docs/superpowers/specs/2026-06-01-cac-litespeed-design.md
|
# See spec: docs/superpowers/specs/2026-06-01-cac-litespeed-design.md
|
||||||
tags: |
|
tags: |
|
||||||
repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${{ matrix.phpver }}
|
repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:php${{ matrix.phpver }}
|
||||||
@@ -215,21 +482,81 @@ jobs:
|
|||||||
|
|
||||||
Build-LSPHP-Images:
|
Build-LSPHP-Images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# One place for the OLS base tag: digest lookup and build-args both read it.
|
||||||
|
# OLS_VERSION pinned to 1.8.4 (see the build step); Dockerfile default differs.
|
||||||
|
env:
|
||||||
|
OLS_VERSION: "1.8.4"
|
||||||
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
|
||||||
# base, which only ships lsphp for 8.1+. Keep this matrix in lockstep
|
# base, which only ships lsphp for 8.1+. Keep this matrix in lockstep
|
||||||
# with Build-LiteSpeed-Images.
|
# with Build-LiteSpeed-Images.
|
||||||
phpver: [81, 82, 83, 84, 85]
|
phpver: [82, 83, 84, 85]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||||
|
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||||
|
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||||
|
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||||
|
- name: Resolve base image digest (Docker Hub, direct)
|
||||||
|
id: base
|
||||||
|
env:
|
||||||
|
BASE_TAG: litespeedtech/openlitespeed:${{ env.OLS_VERSION }}-lsphp${{ matrix.phpver }}
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||||
|
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||||
|
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# The registry mirror is only used for a digest-pinned base image.
|
||||||
|
env:
|
||||||
|
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||||
|
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 [ "$BASE_PINNED" = true ] && 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:-<not used: Docker Hub direct>}"
|
||||||
|
[ -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
|
||||||
|
|
||||||
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
# Per-run cache-bust value. Each job's buildx builder is created fresh by
|
||||||
@@ -238,7 +565,9 @@ jobs:
|
|||||||
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
# the package-upgrade layers consume CACHE_BUST and can never be reused.
|
||||||
- name: Cache-bust value
|
- name: Cache-bust value
|
||||||
id: bust
|
id: bust
|
||||||
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
|
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
|
# 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
|
# CURRENT lsphp version. The Dockerfile asserts the installed package equals
|
||||||
@@ -246,14 +575,21 @@ jobs:
|
|||||||
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
|
# it into the image labels org.whp.php-version / org.whp.lsphp-package-version.
|
||||||
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
|
- name: Resolve current lsphp${{ matrix.phpver }} from the LiteSpeed repo
|
||||||
id: lsphp
|
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: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
idx=$(curl -fsS --retry 3 --max-time 60 http://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages)
|
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${{ matrix.phpver }}" '/^Package: /{n=$2} /^Version: / && n==p {print $2}' | sort -V | tail -n1)
|
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${{ matrix.phpver }} not found in the LiteSpeed repo index"; exit 1; fi
|
if [ -z "$pkg" ]; then echo "::error::lsphp${PHPVER} not found in the LiteSpeed repo index"; exit 1; fi
|
||||||
echo "lsphp${{ matrix.phpver }} in repo: $pkg (PHP ${pkg%%-*})"
|
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 "pkg=$pkg" >> "$GITHUB_OUTPUT"
|
||||||
echo "php=${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
|
||||||
@@ -262,6 +598,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:
|
||||||
@@ -270,7 +627,9 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=${{ matrix.phpver }}
|
PHPVER=${{ matrix.phpver }}
|
||||||
OLS_VERSION=1.8.4
|
OLS_VERSION=${{ env.OLS_VERSION }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||||
CACHE_BUST=${{ steps.bust.outputs.value }}
|
CACHE_BUST=${{ steps.bust.outputs.value }}
|
||||||
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
|
EXPECT_LSPHP_PKG=${{ steps.lsphp.outputs.pkg }}
|
||||||
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
|
EXPECT_PHP_VERSION=${{ steps.lsphp.outputs.php }}
|
||||||
@@ -286,10 +645,63 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||||
|
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||||
|
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||||
|
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||||
|
- name: Resolve base image digest (Docker Hub, direct)
|
||||||
|
id: base
|
||||||
|
env:
|
||||||
|
BASE_TAG: almalinux/10-base:latest
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||||
|
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||||
|
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# The registry mirror is only used for a digest-pinned base image.
|
||||||
|
env:
|
||||||
|
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||||
|
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 [ "$BASE_PINNED" = true ] && 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:-<not used: Docker Hub direct>}"
|
||||||
|
[ -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
|
||||||
@@ -305,19 +717,79 @@ jobs:
|
|||||||
file: ./Dockerfile.shared-httpd
|
file: ./Dockerfile.shared-httpd
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
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
|
||||||
|
|
||||||
Build-Shared-OLS:
|
Build-Shared-OLS:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# One place for the OLS base tag: digest lookup and build-args both read it.
|
||||||
|
env:
|
||||||
|
OLS_VERSION: "1.8.4"
|
||||||
|
PHPVER: "83"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Base image pinned by DIGEST, looked up directly at Docker Hub (never via
|
||||||
|
# the pkg-cache mirror), so the mirror can only serve content-addressed
|
||||||
|
# blobs that BuildKit verifies. If the lookup fails this job does NOT use
|
||||||
|
# the mirror and pulls the tag straight from Docker Hub. docs/pkg-cache.md.
|
||||||
|
- name: Resolve base image digest (Docker Hub, direct)
|
||||||
|
id: base
|
||||||
|
env:
|
||||||
|
BASE_TAG: litespeedtech/openlitespeed:${{ env.OLS_VERSION }}-lsphp${{ env.PHPVER }}
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
if ref=$(.gitea/scripts/resolve-base-image.sh "$BASE_TAG") && [[ "$ref" =~ ^[a-z0-9][a-z0-9._/-]*@sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "base-image: ${BASE_TAG} -> ${ref} (digest from registry-1.docker.io)"
|
||||||
|
echo "image=${ref}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::warning::base image ${BASE_TAG}: Docker Hub digest lookup failed - pulling the tag directly from Docker Hub, pkg-cache registry mirror NOT used"
|
||||||
|
echo "image=${BASE_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "pinned=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# The registry mirror is only used for a digest-pinned base image.
|
||||||
|
env:
|
||||||
|
BASE_PINNED: ${{ steps.base.outputs.pinned }}
|
||||||
|
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 [ "$BASE_PINNED" = true ] && 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:-<not used: Docker Hub direct>}"
|
||||||
|
[ -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
|
||||||
@@ -336,7 +808,9 @@ jobs:
|
|||||||
# Single image (runs no PHP). PHPVER just selects the OLS base tag;
|
# Single image (runs no PHP). PHPVER just selects the OLS base tag;
|
||||||
# pinned to 83 / OLS 1.8.4 to match the rest of the litespeed family.
|
# pinned to 83 / OLS 1.8.4 to match the rest of the litespeed family.
|
||||||
build-args: |
|
build-args: |
|
||||||
PHPVER=83
|
PHPVER=${{ env.PHPVER }}
|
||||||
OLS_VERSION=1.8.4
|
OLS_VERSION=${{ env.OLS_VERSION }}
|
||||||
|
BASE_IMAGE=${{ steps.base.outputs.image }}
|
||||||
|
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: [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[2-5])=([0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo "::error::bad php_versions entry '$pair' (want NN=X.Y.Z, NN in 82..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) || '' }}
|
||||||
+44
-6
@@ -1,17 +1,41 @@
|
|||||||
FROM almalinux/10-base
|
# Base image. CI passes BASE_IMAGE=almalinux/10-base@sha256:<digest>, resolved
|
||||||
|
# DIRECTLY from Docker Hub in the same run (.gitea/scripts/resolve-base-image.sh),
|
||||||
|
# so the pkg-cache registry mirror can only serve content-addressed blobs.
|
||||||
|
# Local builds use the tag. docs/pkg-cache.md "Base-image digest pinning".
|
||||||
|
ARG BASE_IMAGE=almalinux/10-base
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
# Per-CI-run value: the first RUN below (dnf update) and everything after it can
|
# 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.
|
# 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.
|
# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache.
|
||||||
ARG CACHE_BUST=
|
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/*
|
||||||
|
|
||||||
@@ -20,13 +44,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
|
||||||
|
|
||||||
# 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 && \
|
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
@@ -54,7 +87,12 @@ 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
|
||||||
|
|
||||||
|
## Healthy = Apache answered with anything but a 5xx. `curl -f` also failed on
|
||||||
|
## 4xx, so a new site with an empty docroot (403) or an app with no route for /
|
||||||
|
## (404) showed unhealthy forever, and WHP's monitor restarted it as "PHP
|
||||||
|
## wedged". 000 (no answer) and 5xx (PHP/app error) still fail.
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
||||||
CMD curl -f http://localhost/ || exit 1
|
CMD code=$(curl -s -o /dev/null -m 4 -w '%{http_code}' http://localhost/); \
|
||||||
|
case "$code" in 000|5*) echo "http $code"; exit 1;; esac
|
||||||
|
|
||||||
ENTRYPOINT [ "/scripts/entrypoint.sh" ]
|
ENTRYPOINT [ "/scripts/entrypoint.sh" ]
|
||||||
+38
-5
@@ -1,17 +1,41 @@
|
|||||||
FROM almalinux/10-base
|
# Base image. CI passes BASE_IMAGE=almalinux/10-base@sha256:<digest>, resolved
|
||||||
|
# DIRECTLY from Docker Hub in the same run (.gitea/scripts/resolve-base-image.sh),
|
||||||
|
# so the pkg-cache registry mirror can only serve content-addressed blobs.
|
||||||
|
# Local builds use the tag. docs/pkg-cache.md "Base-image digest pinning".
|
||||||
|
ARG BASE_IMAGE=almalinux/10-base
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
# Per-CI-run value: the first RUN below (dnf update) and everything after it can
|
# 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.
|
# 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.
|
# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache.
|
||||||
ARG CACHE_BUST=
|
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/*
|
||||||
|
|
||||||
@@ -20,12 +44,21 @@ 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 && \
|
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
|
|||||||
+51
-3
@@ -22,7 +22,33 @@
|
|||||||
## 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
|
||||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256:<digest> of the
|
||||||
|
## ${OLS_VERSION}-lsphp${PHPVER} tag, resolved DIRECTLY from Docker Hub in the same
|
||||||
|
## run (.gitea/scripts/resolve-base-image.sh), so the pkg-cache registry mirror can
|
||||||
|
## only serve content-addressed blobs. Local builds use the tag. Every stage below
|
||||||
|
## (incl. the emergency lsphp-src stage) starts from this one image.
|
||||||
|
ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||||
|
|
||||||
|
## ---- 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 ${BASE_IMAGE} 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 ${BASE_IMAGE}
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
ENV PHPVER=${PHPVER}
|
ENV PHPVER=${PHPVER}
|
||||||
|
|
||||||
@@ -60,14 +86,34 @@ ENV PHPVER=${PHPVER}
|
|||||||
ARG CACHE_BUST=
|
ARG CACHE_BUST=
|
||||||
ARG EXPECT_LSPHP_PKG=
|
ARG EXPECT_LSPHP_PKG=
|
||||||
ARG EXPECT_PHP_VERSION=
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
## (CT130 pkg-cache, https only). Unset = upstream repositories (Ubuntu +
|
||||||
|
## rpms.litespeedtech.com). Repo files are restored at the end of the 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/
|
||||||
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||||
RUN /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
RUN /usr/local/sbin/pkg-cache-repos.sh apt && \
|
||||||
|
/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 && \
|
||||||
|
/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/*
|
||||||
|
|
||||||
|
## 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 && \
|
||||||
@@ -111,8 +157,10 @@ RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab
|
|||||||
## docker buildx imagetools inspect <ref> --format '{{json .Image.Config.Labels}}'
|
## docker buildx imagetools inspect <ref> --format '{{json .Image.Config.Labels}}'
|
||||||
## Values come from CI's repo-index lookup and were asserted against the
|
## Values come from CI's repo-index lookup and were asserted against the
|
||||||
## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build).
|
## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build).
|
||||||
LABEL org.whp.php-version="${EXPECT_PHP_VERSION:-unverified}" \
|
## 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-package-version="${EXPECT_LSPHP_PKG:-unverified}" \
|
||||||
|
org.whp.lsphp-source="${LSPHP_SOURCE}" \
|
||||||
org.whp.php-flavour="lsphp${PHPVER}"
|
org.whp.php-flavour="lsphp${PHPVER}"
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
|
|||||||
+79
-3
@@ -21,6 +21,35 @@
|
|||||||
|
|
||||||
ARG OLS_VERSION=1.8.4
|
ARG OLS_VERSION=1.8.4
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
|
## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256:<digest> of the
|
||||||
|
## ${OLS_VERSION}-lsphp${PHPVER} tag, resolved DIRECTLY from Docker Hub in the same
|
||||||
|
## run (.gitea/scripts/resolve-base-image.sh), so the pkg-cache registry mirror can
|
||||||
|
## only serve content-addressed blobs. Local builds use the tag. Every stage below
|
||||||
|
## (incl. the emergency lsphp-src stage) starts from this one image.
|
||||||
|
ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||||
|
|
||||||
|
## ---- 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 ${BASE_IMAGE} 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
|
||||||
@@ -54,7 +83,7 @@ ARG PHPVER=83
|
|||||||
## Separate stage on purpose: the compiler + headers (~400MB) stay out of the
|
## Separate stage on purpose: the compiler + headers (~400MB) stay out of the
|
||||||
## shipped image, which gains only the ~40KB .so. Costs ~1-2 min of CI per PHP
|
## shipped image, which gains only the ~40KB .so. Costs ~1-2 min of CI per PHP
|
||||||
## version; both stages share the same base layer, so no extra pull.
|
## version; both stages share the same base layer, so no extra pull.
|
||||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER} AS ext-build
|
FROM ${BASE_IMAGE} AS ext-build
|
||||||
ARG PHPVER=83
|
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
|
||||||
@@ -71,11 +100,20 @@ ARG PHPVER=83
|
|||||||
ARG CACHE_BUST=
|
ARG CACHE_BUST=
|
||||||
ARG EXPECT_LSPHP_PKG=
|
ARG EXPECT_LSPHP_PKG=
|
||||||
ARG EXPECT_PHP_VERSION=
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
## (CT130 pkg-cache, https only). Unset = upstream repositories (Ubuntu +
|
||||||
|
## rpms.litespeedtech.com). Repo files are restored at the end of the 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/
|
||||||
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
COPY ./scripts/lsphp-apt-upgrade.sh /usr/local/sbin/lsphp-apt-upgrade.sh
|
||||||
RUN set -e; \
|
RUN set -e; \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh apt; \
|
||||||
/usr/local/sbin/lsphp-apt-upgrade.sh --no-os-upgrade "${PHPVER}" \
|
/usr/local/sbin/lsphp-apt-upgrade.sh --no-os-upgrade "${PHPVER}" \
|
||||||
build-essential autoconf pkg-config \
|
build-essential autoconf pkg-config \
|
||||||
lsphp${PHPVER}-dev; \
|
lsphp${PHPVER}-dev; \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh restore; \
|
||||||
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
||||||
DEVV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}-dev); \
|
DEVV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}-dev); \
|
||||||
if [ "$RTV" != "$DEVV" ]; then \
|
if [ "$RTV" != "$DEVV" ]; then \
|
||||||
@@ -101,6 +139,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; \
|
||||||
@@ -134,7 +183,7 @@ RUN set -e; \
|
|||||||
cp modules/cac_path_parity.so /build-out/
|
cp modules/cac_path_parity.so /build-out/
|
||||||
|
|
||||||
## ---- stage 2: the shipped sidecar image ------------------------------------
|
## ---- stage 2: the shipped sidecar image ------------------------------------
|
||||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
FROM ${BASE_IMAGE}
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
ENV PHPVER=${PHPVER}
|
ENV PHPVER=${PHPVER}
|
||||||
|
|
||||||
@@ -162,10 +211,18 @@ ENV PHPVER=${PHPVER}
|
|||||||
ARG CACHE_BUST=
|
ARG CACHE_BUST=
|
||||||
ARG EXPECT_LSPHP_PKG=
|
ARG EXPECT_LSPHP_PKG=
|
||||||
ARG EXPECT_PHP_VERSION=
|
ARG EXPECT_PHP_VERSION=
|
||||||
|
## OPTIONAL internal package cache, e.g. --build-arg PKG_CACHE=https://192.168.1.40
|
||||||
|
## (CT130 pkg-cache, https only). Unset = upstream repositories (Ubuntu +
|
||||||
|
## rpms.litespeedtech.com). Repo files are restored at the end of the 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/
|
||||||
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
|
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); \
|
||||||
|
/usr/local/sbin/pkg-cache-repos.sh apt; \
|
||||||
if ! /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
if ! /usr/local/sbin/lsphp-apt-upgrade.sh "${PHPVER}" \
|
||||||
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 \
|
||||||
@@ -177,6 +234,7 @@ RUN set -e; \
|
|||||||
echo " build (CI) / rebuild with --no-cache (local)." >&2; \
|
echo " build (CI) / rebuild with --no-cache (local)." >&2; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
|
/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/*; \
|
||||||
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
RTV=$(dpkg-query -W -f='${Version}' lsphp${PHPVER}); \
|
||||||
@@ -189,6 +247,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 \
|
||||||
@@ -250,8 +320,14 @@ ENV LSPHP_ENABLE_USER_INI=on
|
|||||||
## docker buildx imagetools inspect <ref> --format '{{json .Image.Config.Labels}}'
|
## docker buildx imagetools inspect <ref> --format '{{json .Image.Config.Labels}}'
|
||||||
## Values come from CI's repo-index lookup and were asserted against the
|
## Values come from CI's repo-index lookup and were asserted against the
|
||||||
## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build).
|
## installed packages by lsphp-apt-upgrade.sh above ("unverified" = local build).
|
||||||
LABEL org.whp.php-version="${EXPECT_PHP_VERSION:-unverified}" \
|
## 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-package-version="${EXPECT_LSPHP_PKG:-unverified}" \
|
||||||
|
org.whp.lsphp-source="${LSPHP_SOURCE}" \
|
||||||
org.whp.php-flavour="lsphp${PHPVER}"
|
org.whp.php-flavour="lsphp${PHPVER}"
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|||||||
+19
-3
@@ -1,10 +1,26 @@
|
|||||||
FROM almalinux/10-base
|
# Base image. CI passes BASE_IMAGE=almalinux/10-base@sha256:<digest>, resolved
|
||||||
|
# DIRECTLY from Docker Hub in the same run (.gitea/scripts/resolve-base-image.sh),
|
||||||
|
# so the pkg-cache registry mirror can only serve content-addressed blobs.
|
||||||
|
# Local builds use the tag. docs/pkg-cache.md "Base-image digest pinning".
|
||||||
|
ARG BASE_IMAGE=almalinux/10-base
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
|
# 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/*
|
||||||
|
|
||||||
|
|||||||
+18
-2
@@ -17,7 +17,21 @@
|
|||||||
|
|
||||||
ARG OLS_VERSION=1.8.4
|
ARG OLS_VERSION=1.8.4
|
||||||
ARG PHPVER=83
|
ARG PHPVER=83
|
||||||
FROM litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
## CI passes BASE_IMAGE=litespeedtech/openlitespeed@sha256:<digest> of the
|
||||||
|
## ${OLS_VERSION}-lsphp${PHPVER} tag, resolved DIRECTLY from Docker Hub in the same
|
||||||
|
## run (.gitea/scripts/resolve-base-image.sh), so the pkg-cache registry mirror can
|
||||||
|
## only serve content-addressed blobs. Local builds use the tag.
|
||||||
|
ARG BASE_IMAGE=litespeedtech/openlitespeed:${OLS_VERSION}-lsphp${PHPVER}
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
|
## 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)
|
||||||
@@ -28,9 +42,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/*
|
||||||
|
|
||||||
|
|||||||
@@ -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,276 @@
|
|||||||
|
# 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` |
|
||||||
|
| `https://192.168.1.40/nodesource/` | `https://rpm.nodesource.com` (origin only; RPMs signed, repomd not) — added 2026-10-22 |
|
||||||
|
| `https://192.168.1.40/debian/` | `https://deb.debian.org/debian` (CDN origin; princeton was 6 h behind on trixie-updates) — added 2026-10-22 |
|
||||||
|
| `https://192.168.1.40/debian-security/` | `https://security.debian.org/debian-security`, `https://deb.debian.org/debian-security` — added 2026-10-22 |
|
||||||
|
| `https://192.168.1.40/goproxy/` | `https://proxy.golang.org` (Go module proxy; `/sumdb/` is 404 there, so `go` checks sum.golang.org directly) — added 2026-10-22 |
|
||||||
|
| `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*`
|
||||||
|
- Go proxy `/@v/list` and `/@latest`
|
||||||
|
- 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`.
|
||||||
|
|
||||||
|
**`nodesource MAJOR`** (added 2026-10-22) replaces `curl https://rpm.nodesource.com/setup_N.x | bash`:
|
||||||
|
- writes `/etc/yum.repos.d/nodesource-nodejs.repo` with the ORIGIN baseurl, `gpgcheck=1` and `gpgkey=file:///usr/local/share/pkg-cache/RPM-GPG-KEY-nodesource` (key committed in `keys/pkg-cache/`);
|
||||||
|
- with `PKG_CACHE` it is then rewritten like the other repos; `restore` puts the origin version back, so the shipped image keeps a normal NodeSource repo.
|
||||||
|
|
||||||
|
**`go-env`** (added 2026-10-22): `eval "$(pkg-cache-repos.sh go-env)"` inside the RUN that runs `go mod download`/`go build`. Prints `GOPROXY=<cache>/goproxy|https://proxy.golang.org|direct` (`|` = fall through on ANY error) and `SSL_CERT_FILE=<state>/ca-bundle.pem`; nothing without `PKG_CACHE`. Modules stay verified by `go.sum` + sum.golang.org. End the RUN with `restore` (removes the bundle).
|
||||||
|
|
||||||
|
**EL9**: `release-install epel|remi` picks the EL major from `rpm -E %rhel` (9 or 10); `RPM-GPG-KEY-EPEL-9` is committed.
|
||||||
|
|
||||||
|
**Debian** (`apt` mode): `deb.debian.org/debian`, `deb.debian.org/debian-security` and `security.debian.org` (classic and deb822 `.sources`) are rewritten to `<cache>/debian/` and `<cache>/debian-security/`.
|
||||||
|
|
||||||
|
**Not proxied, on purpose:** PyPI and the npm registry. Our pip/npm installs are not hash-locked (`pip install -r` without `--require-hashes`, `npm install -g pm2@latest`), so the client would trust whatever the cache served — a compromised cache would mean code execution in shipped images. Proxy them only after the installs are hash-pinned. GitHub-release downloads are likewise left direct.
|
||||||
|
|
||||||
|
**`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 wired since 2026-10-22 (normal apt path; the php.net EMERGENCY stage `lsphp-src` stays on upstream). `lsphp-apt-upgrade.sh`'s madison gate accepts `<PKG_CACHE>/litespeed/` only when `PKG_CACHE` is set.
|
||||||
|
|
||||||
|
## Repositories wired (2026-10-22)
|
||||||
|
|
||||||
|
The script and `keys/pkg-cache/` are **canonical here**; every other repo carries byte-identical copies (`sha256sum scripts/pkg-cache-repos.sh` to compare). Change it here first, then sync. Same for `.gitea/scripts/resolve-base-image.sh`.
|
||||||
|
|
||||||
|
| Repo | Image(s) | Bases | Through the cache | Base digest-pinned |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| cloud-apache-container | cac, cac-fpm, shared-httpd (almalinux/10-base); shared-ols, cac-litespeed, cac-lsphp (litespeedtech/openlitespeed) | Docker Hub | AlmaLinux/EPEL/Remi (rpm); Ubuntu + LiteSpeed (apt) | yes, all 6 jobs |
|
||||||
|
| cloud-node-container | cnoc:node18..26 | almalinux/10-base | AlmaLinux/EPEL + NodeSource (`nodesource N`) | yes |
|
||||||
|
| cloud-memcached-container | cmc | almalinux/9-base | AlmaLinux 9 + EPEL 9 | yes |
|
||||||
|
| cloud-haproxy-container | chc | almalinux/9-base | AlmaLinux 9 + EPEL 9 (inotify-tools GitHub tarball direct) | yes |
|
||||||
|
| whp-default-container | whp-default | httpd:alpine | nothing to install (mirror only) | yes |
|
||||||
|
| versitygw-container | versitygw | ubuntu:latest | Ubuntu (versitygw .deb from GitHub direct) | yes |
|
||||||
|
| haproxy-manager-base | haproxy-manager-base, coraza-spoa | repo.anhonesthost.net python:3.12-slim / golang:1.25 (not Docker Hub: no mirror); gcr.io distroless | Debian (apt) + Go modules (`go-env`); pip direct | n/a; `mirror-base-image.yaml` now copies Docker Hub images by the digest resolved directly from Docker Hub |
|
||||||
|
| cpanel-importer | cpanel-importer | almalinux:10-minimal | AlmaLinux/EPEL/Remi — **PR #1 open, not merged** (pending rebase onto the PHP 8.5 work) | in the PR |
|
||||||
|
|
||||||
|
Not image builds (nothing to wire): kb-anhonesthost, anhonesthost-blog, dao-mail-signup, renovate-runner. ols-mail-client builds images in CI only for scanning/smoke tests (never pushed), its bases are already digest-pinned, and they install no OS packages.
|
||||||
|
|
||||||
|
## Adding a new upstream
|
||||||
|
|
||||||
|
1. On CT130: back up, then add the prefix to `UPSTREAMS` in `/usr/local/sbin/pkgcache-gen-nginx` (origin first; add a mirror tier only if you measured it is as fresh as the origin). If its mutable metadata isn't covered by `META_RE`, extend it there AND in `/usr/local/sbin/pkgcache-refresh`. Add the prefix to the `location = /` text in `/etc/nginx/pkgcache/server-common.conf`.
|
||||||
|
2. `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx` (`pct exec` has no /usr/local/sbin in PATH: use full paths). Verify old and new prefixes with `curl --cacert keys/pkg-cache/ca.crt https://192.168.1.40/<prefix>/...` (look at `X-Cache-Status`/`X-Pkgcache-Tier`). CT130 has no egress filter, so no firewall change is needed for new origins.
|
||||||
|
3. Only proxy upstreams whose content the CLIENT verifies end to end (GPG-signed packages/metadata, Go sumdb, content digests). Commit any signing key under `keys/pkg-cache/` with its fingerprint and provenance in the README.
|
||||||
|
4. Teach `scripts/pkg-cache-repos.sh` the rewrite (keep the origin as a fallback where the client supports it), test through the cache from a runner-network host, then sync the copies.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Base-image digest pinning (2026-10-22)
|
||||||
|
|
||||||
|
Gap closed (security review): with the BuildKit mirror on, `FROM almalinux/10-base`
|
||||||
|
was resolved **by tag through the cache**, so a compromised cache could hand out a
|
||||||
|
different image for that tag.
|
||||||
|
|
||||||
|
Now every job that uses the mirror first runs **"Resolve base image digest (Docker
|
||||||
|
Hub, direct)"**:
|
||||||
|
- `.gitea/scripts/resolve-base-image.sh IMAGE:TAG` gets an anonymous pull token from
|
||||||
|
`auth.docker.io` and sends a `HEAD /v2/<repo>/manifests/<tag>` to
|
||||||
|
`registry-1.docker.io` over HTTPS (never the mirror; HEADs don't count against the
|
||||||
|
pull rate limit). It prints `IMAGE@sha256:<index digest>`. Input and output are
|
||||||
|
regex-validated; values reach `run:` only via `env:`.
|
||||||
|
- The job passes `BASE_IMAGE=<that>` as a build arg; the Dockerfiles say
|
||||||
|
`ARG BASE_IMAGE=<tag>` / `FROM ${BASE_IMAGE}`. The mirror can then only serve
|
||||||
|
content-addressed manifests/blobs, which BuildKit verifies against the digest.
|
||||||
|
- **Lookup fails:** `BASE_IMAGE` falls back to the tag, the probe step does **not**
|
||||||
|
enable the registry mirror (`registry-mirror=<not used: Docker Hub direct>`), and
|
||||||
|
the tag is pulled straight from Docker Hub, as before the cache existed. The
|
||||||
|
package-repo cache (`PKG_CACHE`) is unaffected, since its content is GPG-checked.
|
||||||
|
- Freshness is automatic: the digest is looked up on every run, nothing is
|
||||||
|
hard-coded. Local builds without the arg use the tag.
|
||||||
|
- Log lines to look for: `base-image: almalinux/10-base:latest -> almalinux/10-base@sha256:...`
|
||||||
|
and `pkg-cache: repos=https://192.168.1.40 registry-mirror=192.168.1.40:5000`.
|
||||||
|
|
||||||
|
Applied to every job here (Build-and-Push, FPM, Shared-httpd, Shared-OLS, LiteSpeed, LSPHP) and to every other repo with a Docker Hub base (see "Repositories wired").
|
||||||
|
The script is canonical here (`.gitea/scripts/resolve-base-image.sh`); other repos
|
||||||
|
carry byte-identical copies.
|
||||||
|
|
||||||
|
## 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,32 @@
|
|||||||
|
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).
|
||||||
|
RPM-GPG-KEY-EPEL-9 Fedora (epel9) FF8A D134 4597 106E CE81 3B91 8A38 72BF 3228 467C
|
||||||
|
fetched 2026-10-22 over HTTPS from dl.fedoraproject.org and
|
||||||
|
mirror.math.princeton.edu (identical); used by `release-install epel`
|
||||||
|
on EL9 images (cloud-memcached-container, cloud-haproxy-container).
|
||||||
|
RPM-GPG-KEY-nodesource Nodesource Operations <operations@nodesource.com>
|
||||||
|
242B 8138 31AF 0956 2B6C 46F7 6B88 DA4E 3AF2 8A14 (rsa4096, 2023-12-22)
|
||||||
|
fetched 2026-10-22 over HTTPS from
|
||||||
|
https://rpm.nodesource.com/gpgkey/ns-operations-public.key (the key
|
||||||
|
NodeSource's own setup_N.x scripts reference); verified by dnf
|
||||||
|
gpgcheck against the nodejs RPMs. Not on keys.openpgp.org /
|
||||||
|
keyserver.ubuntu.com - confirm out of band. Used by
|
||||||
|
`pkg-cache-repos.sh nodesource MAJOR` (cloud-node-container).
|
||||||
@@ -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,29 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBGE3mOsBEACsU+XwJWDJVkItBaugXhXIIkb9oe+7aadELuVo0kBmc3HXt/Yp
|
||||||
|
CJW9hHEiGZ6z2jwgPqyJjZhCvcAWvgzKcvqE+9i0NItV1rzfxrBe2BtUtZmVcuE6
|
||||||
|
2b+SPfxQ2Hr8llaawRjt8BCFX/ZzM4/1Qk+EzlfTcEcpkMf6wdO7kD6ulBk/tbsW
|
||||||
|
DHX2lNcxszTf+XP9HXHWJlA2xBfP+Dk4gl4DnO2Y1xR0OSywE/QtvEbN5cY94ieu
|
||||||
|
n7CBy29AleMhmbnx9pw3NyxcFIAsEZHJoU4ZW9ulAJ/ogttSyAWeacW7eJGW31/Z
|
||||||
|
39cS+I4KXJgeGRI20RmpqfH0tuT+X5Da59YpjYxkbhSK3HYBVnNPhoJFUc2j5iKy
|
||||||
|
XLgkapu1xRnEJhw05kr4LCbud0NTvfecqSqa+59kuVc+zWmfTnGTYc0PXZ6Oa3rK
|
||||||
|
44UOmE6eAT5zd/ToleDO0VesN+EO7CXfRsm7HWGpABF5wNK3vIEF2uRr2VJMvgqS
|
||||||
|
9eNwhJyOzoca4xFSwCkc6dACGGkV+CqhufdFBhmcAsUotSxe3zmrBjqA0B/nxIvH
|
||||||
|
DVgOAMnVCe+Lmv8T0mFgqZSJdIUdKjnOLu/GRFhjDKIak4jeMBMTYpVnU+HhMHLq
|
||||||
|
uDiZkNEvEEGhBQmZuI8J55F/a6UURnxUwT3piyi3Pmr2IFD7ahBxPzOBCQARAQAB
|
||||||
|
tCdGZWRvcmEgKGVwZWw5KSA8ZXBlbEBmZWRvcmFwcm9qZWN0Lm9yZz6JAk4EEwEI
|
||||||
|
ADgWIQT/itE0RZcQbs6BO5GKOHK/MihGfAUCYTeY6wIbDwULCQgHAgYVCgkICwIE
|
||||||
|
FgIDAQIeAQIXgAAKCRCKOHK/MihGfFX/EACBPWv20+ttYu1A5WvtHJPzwbj0U4yF
|
||||||
|
3zTQpBglQ2UfkRpYdipTlT3Ih6j5h2VmgRPtINCc/ZE28adrWpBoeFIS2YAKOCLC
|
||||||
|
nZYtHl2nCoLq1U7FSttUGsZ/t8uGCBgnugTfnIYcmlP1jKKA6RJAclK89evDQX5n
|
||||||
|
R9ZD+Cq3CBMlttvSTCht0qQVlwycedH8iWyYgP/mF0W35BIn7NuuZwWhgR00n/VG
|
||||||
|
4nbKPOzTWbsP45awcmivdrS74P6mL84WfkghipdmcoyVb1B8ZP4Y/Ke0RXOnLhNe
|
||||||
|
CfrXXvuW+Pvg2RTfwRDtehGQPAgXbmLmz2ZkV69RGIr54HJv84NDbqZovRTMr7gL
|
||||||
|
9k3ciCzXCiYQgM8yAyGHV0KEhFSQ1HV7gMnt9UmxbxBE2pGU7vu3CwjYga5DpwU7
|
||||||
|
w5wu1TmM5KgZtZvuWOTDnqDLf0cKoIbW8FeeCOn24elcj32bnQDuF9DPey1mqcvT
|
||||||
|
/yEo/Ushyz6CVYxN8DGgcy2M9JOsnmjDx02h6qgWGWDuKgb9jZrvRedpAQCeemEd
|
||||||
|
fhEs6ihqVxRFl16HxC4EVijybhAL76SsM2nbtIqW1apBQJQpXWtQwwdvgTVpdEtE
|
||||||
|
r4ArVJYX5LrswnWEQMOelugUG6S3ZjMfcyOa/O0364iY73vyVgaYK+2XtT2usMux
|
||||||
|
VL469Kj5m13T6w==
|
||||||
|
=Mjs/
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
oSVkzT/Mwp9zFVQu5w==
|
||||||
|
=drMP
|
||||||
|
-----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-----
|
||||||
@@ -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)"
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
## Fails LOUDLY — never ships the base image's PHP by accident:
|
## 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
|
## * `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.
|
## repo is unreachable and carries on with no/old lists, exit 0.
|
||||||
## * the LiteSpeed repo must be the candidate source for lsphp${PHPVER}.
|
## * the LiteSpeed repo must be the candidate source for lsphp${PHPVER}
|
||||||
|
## (rpms.litespeedtech.com, or ${PKG_CACHE}/litespeed/ when PKG_CACHE is set).
|
||||||
## * after upgrading, no lsphp${PHPVER}* package may remain upgradable.
|
## * after upgrading, no lsphp${PHPVER}* package may remain upgradable.
|
||||||
## * if CI passed the version it resolved from the repo index (EXPECT_LSPHP_PKG,
|
## * 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;
|
## EXPECT_PHP_VERSION), the installed package / running PHP must match it;
|
||||||
@@ -52,8 +53,18 @@ echo "lsphp-apt-upgrade: base image lsphp${PHPVER}=$(dpkg-query -W -f='${Version
|
|||||||
# Read each output into a variable and match in the shell: piping into an
|
# 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
|
# 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).
|
# and report "missing" for a package that is there (scripts/tests/lsphp-info-probe.test.sh).
|
||||||
|
# With the internal package cache (PKG_CACHE build-arg, scripts/pkg-cache-repos.sh
|
||||||
|
# apt) the LiteSpeed source reads ${PKG_CACHE}/litespeed/... instead; that URL is
|
||||||
|
# accepted ONLY when PKG_CACHE is set, and only under its /litespeed/ path.
|
||||||
MADISON=$(apt-cache madison "lsphp${PHPVER}")
|
MADISON=$(apt-cache madison "lsphp${PHPVER}")
|
||||||
if [[ "$MADISON" != *rpms.litespeedtech.com* ]]; then
|
LS_REPO_OK=0
|
||||||
|
if [[ "$MADISON" == *rpms.litespeedtech.com* ]]; then
|
||||||
|
LS_REPO_OK=1
|
||||||
|
elif [ -n "${PKG_CACHE:-}" ] && [[ "$MADISON" == *"${PKG_CACHE%/}/litespeed/"* ]]; then
|
||||||
|
LS_REPO_OK=1
|
||||||
|
echo "lsphp-apt-upgrade: LiteSpeed repo via package cache ${PKG_CACHE%/}/litespeed/"
|
||||||
|
fi
|
||||||
|
if [ "$LS_REPO_OK" != 1 ]; then
|
||||||
apt-cache policy "lsphp${PHPVER}" >&2 || true
|
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)."
|
die "the LiteSpeed apt repo does not offer lsphp${PHPVER} (repo missing from sources, or its index is empty)."
|
||||||
fi
|
fi
|
||||||
|
|||||||
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
+296
@@ -0,0 +1,296 @@
|
|||||||
|
#!/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/NodeSource
|
||||||
|
## PKG_CACHE=https://192.168.1.40 pkg-cache-repos.sh apt # Ubuntu, Debian, LiteSpeed
|
||||||
|
## pkg-cache-repos.sh release-install epel [remi] # GPG-verified release RPMs (EL9/EL10)
|
||||||
|
## pkg-cache-repos.sh nodesource MAJOR # NodeSource repo file + pinned key
|
||||||
|
## eval "$(pkg-cache-repos.sh go-env)" # GOPROXY via the cache (go.sum/sumdb verified)
|
||||||
|
## pkg-cache-repos.sh restore # undo everything above
|
||||||
|
##
|
||||||
|
## CANONICAL copy: cloud-apache-container/scripts/pkg-cache-repos.sh (docs/pkg-cache.md
|
||||||
|
## there). Other repos carry byte-identical copies - change it there first.
|
||||||
|
##
|
||||||
|
## 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...|nodesource MAJOR|go-env|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
|
||||||
|
[[ "$C" =~ ^https://[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._/-]*)?$ ]] || die "PKG_CACHE has unexpected characters: '$C'"
|
||||||
|
HOSTPORT="${C#https://}"; HOSTPORT="${HOSTPORT%%/*}"
|
||||||
|
[ -s "$SHARE/ca.crt" ] || die "PKG_CACHE set but $SHARE/ca.crt (pkg-cache CA) is missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# System CA bundle (EL vs Debian/Ubuntu path); the pkg-cache CA is appended to a
|
||||||
|
# copy in the state dir, never to the system store.
|
||||||
|
sys_bundle() {
|
||||||
|
local b
|
||||||
|
for b in /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt; do
|
||||||
|
[ -s "$b" ] && { echo "$b"; return 0; }
|
||||||
|
done
|
||||||
|
die "no system CA bundle found"
|
||||||
|
}
|
||||||
|
make_bundle() { cat "$(sys_bundle)" "$SHARE/ca.crt" > "$STATE/ca-bundle.pem"; }
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
# mapfile from a process substitution, not `cmd | while read`: no pipeline,
|
||||||
|
# so nothing for pipefail to misreport (scripts/tests/lsphp-info-probe.test.sh).
|
||||||
|
mapfile -t saved < <(cd "$STATE/orig" && find . -type f)
|
||||||
|
for rel in "${saved[@]}"; 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.
|
||||||
|
mapfile -t current < <(repo_files)
|
||||||
|
for f in "${current[@]}"; 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
|
||||||
|
make_bundle
|
||||||
|
curl_opts+=(--cacert "$STATE/ca-bundle.pem")
|
||||||
|
fi
|
||||||
|
files=()
|
||||||
|
el=$(rpm -E '%{?rhel}' 2>/dev/null || true)
|
||||||
|
case "$el" in 9|10) ;; *) die "release-install supports EL9/EL10 only (rpm -E %rhel = '$el')" ;; esac
|
||||||
|
for name in "$@"; do
|
||||||
|
case "$name" in
|
||||||
|
epel) pkg=epel-release key=RPM-GPG-KEY-EPEL-$el
|
||||||
|
path=/epel/epel-release-latest-$el.noarch.rpm
|
||||||
|
origin=https://dl.fedoraproject.org/pub/epel/epel-release-latest-$el.noarch.rpm ;;
|
||||||
|
remi) pkg=remi-release key=RPM-GPG-KEY-remi2024
|
||||||
|
path=/remi/enterprise/remi-release-$el.rpm
|
||||||
|
origin=https://rpms.remirepo.net/enterprise/remi-release-$el.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" ] && [ "$MODE" != nodesource ] && [ "$MODE" != go-env ]; 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|rpm\.nodesource\.com)/' "$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)
|
||||||
|
# NodeSource signs its RPMs but not repomd.xml (404): gpgcheck only.
|
||||||
|
l = rw(l, "^#? ?baseurl=https?://rpm\\.nodesource\\.com/", "nodesource", "https://rpm.nodesource.com", 0)
|
||||||
|
}
|
||||||
|
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/|https?://(deb|security)\.debian\.org/' "$f" || return 0
|
||||||
|
save "$f"
|
||||||
|
# Debian: debian-security first, and "debian" only as a whole path segment,
|
||||||
|
# so .../debian-security is never rewritten as /debian/.
|
||||||
|
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" \
|
||||||
|
-e "s%https?://(deb|security)\.debian\.org/debian-security/?([[:space:]]|\$)%$C/debian-security/\\2%g" \
|
||||||
|
-e "s%https?://security\.debian\.org/?([[:space:]]|\$)%$C/debian-security/\\1%g" \
|
||||||
|
-e "s%https?://deb\.debian\.org/debian/?([[:space:]]|\$)%$C/debian/\\1%g" \
|
||||||
|
"$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$MODE" = go-env ]; then
|
||||||
|
# Prints shell exports for `eval`. Without PKG_CACHE: nothing (Go defaults).
|
||||||
|
# GOPROXY "a|b" falls through on ANY error, so a dead cache costs one
|
||||||
|
# failed request, not the build. GOSUMDB stays the default sum.golang.org,
|
||||||
|
# reached directly: modules are still verified against go.sum + the sumdb.
|
||||||
|
[ -n "$C" ] || { echo "pkg-cache-repos(go-env): PKG_CACHE not set - default GOPROXY" >&2; exit 0; }
|
||||||
|
start_session
|
||||||
|
make_bundle
|
||||||
|
printf "export GOPROXY='%s/goproxy|https://proxy.golang.org|direct' SSL_CERT_FILE='%s'\n" "$C" "$STATE/ca-bundle.pem"
|
||||||
|
echo "pkg-cache-repos(go-env): GOPROXY via $C/goproxy (fallback proxy.golang.org)" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$MODE" = nodesource ]; then
|
||||||
|
# Replaces `curl https://rpm.nodesource.com/setup_N.x | bash` (unverified
|
||||||
|
# remote script): writes the same nodesource-nodejs repo (origin URL, so the
|
||||||
|
# shipped image keeps a normal repo file) with gpgcheck=1 and the signing key
|
||||||
|
# committed in keys/pkg-cache/; with PKG_CACHE it is then rewritten like any
|
||||||
|
# other repo and `restore` puts the origin version back.
|
||||||
|
major="${1:?usage: pkg-cache-repos.sh nodesource MAJOR}"
|
||||||
|
[[ "$major" =~ ^[0-9]{1,3}$ ]] || die "bad Node major '$major'"
|
||||||
|
key="$SHARE/RPM-GPG-KEY-nodesource"
|
||||||
|
[ -s "$key" ] || die "missing $key"
|
||||||
|
rpm --import "$key" || die "cannot import $key"
|
||||||
|
rm -f /etc/yum.repos.d/nodesource*.repo
|
||||||
|
f=/etc/yum.repos.d/nodesource-nodejs.repo
|
||||||
|
printf '%s\n' "[nodesource-nodejs]" \
|
||||||
|
"name=Node.js Packages for Linux RPM based distros - \$basearch" \
|
||||||
|
"baseurl=https://rpm.nodesource.com/pub_${major}.x/nodistro/nodejs/\$basearch" \
|
||||||
|
"priority=9" "enabled=1" "gpgcheck=1" \
|
||||||
|
"gpgkey=file://$key" "module_hotfixes=1" > "$f"
|
||||||
|
echo "pkg-cache-repos: wrote $f (Node ${major}.x, key $(basename "$key"))"
|
||||||
|
if [ -n "$C" ]; then
|
||||||
|
start_session
|
||||||
|
make_bundle
|
||||||
|
rewrite_rpm "$f"
|
||||||
|
grep -h '^baseurl=' "$f" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
rpm)
|
||||||
|
start_session
|
||||||
|
make_bundle
|
||||||
|
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:"
|
||||||
|
# (|| true: Debian 13 images have no /etc/apt/sources.list; grep's exit 2
|
||||||
|
# for the missing file must not fail the build under pipefail)
|
||||||
|
{ grep -h -E '^(deb|URIs:)' /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null || true; } | grep -F "$C/" | sort -u | sed 's/^/ /' || true
|
||||||
|
;;
|
||||||
|
*) die "unknown mode '$MODE'" ;;
|
||||||
|
esac
|
||||||
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