0017: Its own PHP, Ruby and pgvector builds
Status: accepted. Where the builds run is superseded by 0019: they now run in the public ShortLeash/runtimes repository. Windows PHP's extensions, its last "still to come", are settled in 0076.
Context
Hosted Laravel and Symfony apps rely on PHP extensions the static-php-cli bulk builds don't include: igbinary, msgpack, mongodb, ldap, gettext, excimer, grpc, ev, sqlsrv and pdo_sqlsrv. Ruby has no official prebuilt binaries for macOS. The PostgreSQL builds Short Leash downloads don't include pgvector. Compiling these on each person's Mac would need the Xcode command line tools and take minutes on first use.
Decision
- One GitHub Actions workflow, Builds (
.github/workflows/builds.yml, run by hand), uses the scripts inapps/desktop/builds/on macOS arm64 and Intel, and on Windows for pgvector:- PHP: static-php-cli builds of the CLI and php-fpm for each supported minor version, with the extensions in
php-extensions.txt. The build fails if one is missing. - Ruby: the newest patch release of each supported minor from ruby-lang.org, with OpenSSL and libyaml linked in statically and
--enable-load-relative. The build fails unless the result, moved to another path, loadsopensslandpsychand links nothing outside the system's libraries. Windows will use RubyInstaller's releases, checked against GitHub's digests. - pgvector: each pgvector release built against every PostgreSQL version in the service catalog, from the same theseus-rs binaries Short Leash installs. The build fails unless the extension loads and answers a query. A test keeps the workflow's Postgres list in step with the catalog.
- PHP: static-php-cli builds of the CLI and php-fpm for each supported minor version, with the extensions in
- Every input is checked:
- GitHub release digests for static-php-cli, OpenSSL and Postgres;
- ruby-lang.org's SHA-256;
- a pinned SHA-256 for libyaml, which publishes none;
- a pinned commit for pgvector's tag.
- The workflow writes
builds/manifest.jsonlisting every file with its URL, SHA-256 and size, merged over the published one. It signs the manifest with the updater key (builds/manifest.json.sig), then uploads the files first and the manifest last. - Short Leash trusts a build only through that chain:
- the manifest's signature must verify against the updater public key built into Short Leash (
config::defaults::SIGNING_PUBLIC_KEY, kept equal totauri.conf.jsonby a test); - each download must match the manifest's SHA-256.
- the manifest's signature must verify against the updater public key built into Short Leash (
- PHP always prefers Short Leash's newest build of a version, even when static-php-cli has a newer patch release, so a site's extensions never disappear. To keep patch releases from lagging, the workflow runs daily and builds each PHP release php.net has published since the last run. Without a manifest, everything works as before.
Consequences
- People get the extra extensions, Ruby and pgvector as quick downloads, with no compiler.
- We own those builds. PHP patch releases reach Short Leash within a day through the scheduled run; a security release of Ruby, OpenSSL or pgvector needs a run by hand. The signing key now vouches for more than app updates.
- A PHP patch release that fails to build keeps Short Leash on the previous patch until the build is fixed, and the daily run retries it.
- Windows PHP keeps coming from windows.php.net, and its extra extensions are still to come.
Addresses that hold the file's SHA-256 (2026-10-20)
Publishing used to replace a rebuilt file at the same address, builds/<name>/<version>/<file>. The CDN caches /builds/* for four hours, so after a rebuild people got the old file while the signed manifest listed the new SHA-256, and Short Leash refused the download until the cache expired.
- Every file published from now on goes to
builds/<name>/<version>/<first 16 hex of its SHA-256>/<file>, and the manifest'surlcarries that address. A GPL build's sources go beside its files the same way and stay a build no platform installs. The content at an address never changes, so files are uploaded withcache-control: public, max-age=31536000, immutable.manifest.jsonand its.sigstayno-cache. - Entries published before keep their URLs until they're next rebuilt. Short Leash reads every URL from the manifest, so released versions download both forms.
- The checks don't change: the manifest's signature against the updater key, then each file against its SHA-256 in the manifest.
- After each publish on
main, the workflow deletes objects underbuilds/that neither the manifest it just signed (and checks again) nor the published one it merged over has listed for 7 days, and that weren't uploaded in the last 7 days, and records when each was first unlisted inbuilds/unreferenced.json. It never deletes the manifest, a signature, that list or a model file, and deletes nothing in a run where R2's listing or the list can't be read.