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) || '' }}