0017: Its own PHP, Ruby and pgvector builds

On this page

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 in apps/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, loads openssl and psych and 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.
  • 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.json listing 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 to tauri.conf.json by a test);
    • each download must match the manifest's SHA-256.
  • 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's url carries 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 with cache-control: public, max-age=31536000, immutable. manifest.json and its .sig stay no-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 under builds/ 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 in builds/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.