Builds c-ares-free, static PHP binaries (CLI + FPM) for
yerd and publishes them — with a signed,
machine-readable manifest — as a single rolling GitHub Release that yerdd
consumes at runtime.
Why this repo exists and every normative rule live in
yerd-php-AGENTS.md — the authoritative brief. This
README only maps that brief onto the files here. If the two disagree, the brief
wins.
Windows works differently. The static CLI + FPM model below is macOS and Linux only. Windows has no static build and no FPM, so its leg repackages the official windows.php.net build into a directory bundle (php.exe + php-cgi.exe + DLLs). See
README-WINDOWS.md.
.github/workflows/release.yml is triggered
daily (schedule) or manually (workflow_dispatch). It is a thin driver that
calls the reusable
.github/workflows/release-channel.yml
once per channel (see Channels), serialized against other
releases by a single concurrency group so revisions can't collide (§9). Each
channel run has three jobs:
- resolve — fetch the channel's previously-published manifest, resolve the
latest upstream patch per minor, and compute the build matrix + each build's
revisionfrom the previous manifest (§7 increment algorithm). - build (matrix, native runner per target) — build with static-php-cli, apply the c-ares patch, run the §5 gate, package the two single-member tarballs. macOS binaries are ad-hoc signed.
- publish — regenerate the manifest, minisign-sign it (prehashed), upload assets → manifest → signature, then prune last; finish with a signed round-trip sanity check.
Two channels publish into the same rolling release (tag php), each with
its own signed manifest, so yerdd can offer (or hide) the EOL tier
independently:
| Channel | Minors | Manifest | Extensions | ext partner |
|---|---|---|---|---|
stable |
8.2 – 8.5 | php.json |
full set | yerd-dump / pcov |
legacy |
7.4 / 8.0 / 8.1 | php-legacy.json |
trimmed (no opentelemetry, no swoole) |
none (EOL) |
The channel is a single switch in scripts/config.sh (CHANNEL=stable|legacy)
that swaps the minors, extension set, and manifest name; every other script is
channel-agnostic. publish.sh prunes against the union of both manifests so
one channel never deletes the other's tarballs.
Legacy also runs one extra build step: apply-legacy-cflags-patch.sh forces
-std=gnu17 for the php-src compile, because EOL minors bundle old K&R-style
libbcmath that the compiler's new C23 default rejects (stable pins its own std
and is unaffected). The legacy extension set otherwise stays provisional until
the first CI build confirms what spc accepts on EOL PHP — the authoritative list
is php -m on a shipped binary, not the requested set.
| Script | Brief | Role |
|---|---|---|
scripts/config.sh |
§1,§2,§3 | Single source of truth: minors, extension set, spc pin, targets, asset-naming |
scripts/latest-patches.sh |
§9 | Latest upstream patch per minor (php.net) |
scripts/targets-json.sh |
§1 | Target table → JSON |
scripts/resolve-builds.php |
§7 | Decide what to build + each revision (reads previous manifest) |
scripts/build-target.sh |
§4 | Clone pinned spc → patch → download → build → sign |
scripts/apply-curl-patch.sh |
§3 | Force ENABLE_ARES=OFF; fail if it no-ops |
scripts/apply-legacy-cflags-patch.sh |
legacy | Force -std=gnu17 for EOL php-src (K&R libbcmath vs C23); fail if it no-ops |
scripts/sign-macos.sh |
§4 | Ad-hoc sign both macOS binaries (mandatory on arm64) |
scripts/verify-artifact.sh |
§5 | No-c-ares gate, -m/-fpm -t, real cross-repo ext load |
scripts/package-artifacts.sh |
§1 | The two single-member .tar.gz with exact names |
scripts/generate-manifest.php |
§7 | Merge carried-over + rebuilt entries; validate the contract |
scripts/sign-manifest.sh |
§6 | minisign -H (prehashed) with the dedicated key |
scripts/publish.sh |
§6 | Upload assets → manifest → sig, prune last |
scripts/sanity-check.sh |
§10.8 | Verify live signature + every asset sha256 |
scripts/keygen.sh |
§6 | One-time dedicated keypair generation |
scripts/third-party-notices.sh |
§8 | Assemble THIRD-PARTY-NOTICES |
- Generate this repo's dedicated minisign key (do not reuse yerd's
app-update key):
bash scripts/keygen.sh
- Store the halves:
gh secret set MINISIGN_SECRET_KEY < keys/yerd-php-minisign.keygh secret set MINISIGN_PASSWORD(if you set one)gh variable set MINISIGN_PUBLIC_KEY --body "$(tail -1 keys/yerd-php-minisign.pub)"
- Embed the public key in
yerddasPHP_LISTING_PUBLIC_KEY. Rotating it later is a coordinated yerd release (§6 key rotation).
The rolling release (tag php) is created automatically on the first publish.
- Newer patch lands → the daily schedule builds it at
revision 1. - Rebuild an unchanged patch (c-ares cutover, spc-ref bump, security) →
workflow_dispatchwithforce: true(optionallyonly_minor: 8.4). This bumps the-Nrevision so existing installs actually receive it (auto-heal, §7). - Target one channel →
workflow_dispatchwithchannel: legacy(orstable). Defaultboth(scheduled runs always do both).only_minorapplies within the chosen channel(s), e.g.channel: legacy,only_minor: 8.1. - Bump static-php-cli → change
SPC_REFinconfig.shand re-verify the §3 curl.php patch still matches (it's under upstream refactor). The build fails loudly if the patch no-ops.
The pure-logic pieces run without a build (set CHANNEL to pick the channel's
knobs; defaults to stable):
bash scripts/targets-json.sh
CHANNEL=stable bash -c 'source scripts/config.sh; echo "$SUPPORTED_MINORS -> $MANIFEST_NAME"'
CHANNEL=legacy bash -c 'source scripts/config.sh; echo "$SUPPORTED_MINORS -> $MANIFEST_NAME"'
php scripts/resolve-builds.php --latest=latest.json --targets=targets.json --minors="8.2 8.3 8.4 8.5"
php scripts/generate-manifest.php --built=matrix.json --assets-dir=dist --minors="7.4 8.0 8.1"