Repository navigation
Conversation
…0.2.2)
Premium (licensed) plugins cannot be updated via WordPress.org, so they are
installed from version-pinned zips in the private store
codetot-web/premium-plugins. Those packages often declare a PHP floor the
site's WEB php does not meet, and installing them anyway is a documented way
to white-screen a site:
PHP Fatal error: Uncaught RuntimeException: Composer detected issues in
your platform: Your Composer dependencies require a PHP version >= 8.1.0
(thrown by vendor/composer/platform_check.php at autoload time)
PHP Parse error: syntax error, unexpected '|' (PHP 8.0+ union types)
Real incident: perfmatters 2.6.7 requires PHP 8.1 and auto-deactivated itself
on five PHP 7.4 sites, killing their custom /quantri/ login URL.
This script makes the PHP requirement a HARD GATE instead of a warning. Per
(site, package):
1. floor = max(Requires PHP header, vendor/composer/platform_check.php) --
composer throws before any plugin code runs, so it wins when stricter;
2. site PHP is detected from the stack actually serving the site:
RunCloud nginx fpm.d, RunCloud OLS handler.conf, or litesoup/Apache
proxy:fcgi socket, matched by vhost name -> ServerName/ServerAlias ->
DocumentRoot (webapp dichvu-website lives in dichvu.website.conf, so
name matching alone misses real sites);
never php -v -- the CLI binary is routinely a different major;
3. site PHP < floor => BLOCKED, nothing written, required version reported.
No automatic override; --allow-php-gate-bypass is for staging ahead of a
planned PHP upgrade;
4. package's top-level PHP files are linted with the SITE's own php binary
before install, catching packages that use newer syntax than advertised;
5. PHP undetectable => site blocked, never guessed.
Apply (dry-run by default): keeps <slug>.bak-<ts>, moves the old folder aside
and extracts fresh (never on top), chowns to the site owner, re-reads the
on-disk version, re-activates a previously-active plugin and VERIFIES it is
still active (auto-deactivate => rollback), checks HTTP and auto-rolls-back on
non-200/301/302, flushes WP + LiteSpeed caches, commits per plugin.
Exit code 2 = something failed and was rolled back.
Also fixes three latent bugs found while testing:
- swallowed 'git add' failure: when .git/objects/<xx> is root-owned (a root
git fetch leaves it that way) the add fails and a bare
'git diff --cached --quiet' then reports NO diff -- indistinguishable from
'nothing to commit'. The update landed on disk with no commit and the
per-plugin revert safety net silently did not exist. Any output from git add
is now treated as a failure, printed with the exact chown to run.
- version_cmp compared only 3 components, so Gravity Forms 3.1.3.4 vs 3.1.3.10
compared EQUAL and a real update would have been skipped. Now compares 4.
- zip_entry_php had false negatives under set -o pipefail:
'unzip -p | grep -q' fails when grep exits early and unzip takes SIGPIPE, so
packages whose entry file is not first alphabetically (Gravity Forms'
gravityforms.php) read as having no version header.
Verified live: dry-run on sg10 (Apache/litesoup, 4 sites) and jp1; block path
proven with a synthetic Requires-PHP-9.9 package on a PHP 8.2 site; apply path
proven on msn.chuyen.dev (wpml-string-translation 3.5.2 -> 5.1.0, plugin stayed
active, HTTP 200, ownership litesoup:litesoup).
Bookkeeping: VERSION 0.0.2.0 -> 0.0.2.2, CHANGELOG [v0.0.2.2], README feature
bullet + full section, docs/premium-plugin-update.md runbook.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
wp-premium-update.sh— an updater for premium (licensed) plugins that refuses to install a package the site's PHP cannot run.Why
Premium plugins aren't on WordPress.org, so they're installed from version-pinned zips in the private store
codetot-web/premium-plugins. Those packages declare PHP floors the web PHP often doesn't meet, and installing them anyway white-screens the site. Two signatures, both real:The incident this is built around: perfmatters 2.6.7 declares
Requires PHP: 8.1and auto-deactivated itself on five PHP 7.4 sites, killing their custom/quantri/login URL. The plugin stays installed but inactive — nothing looks broken until someone needs the feature.The gate
For every (site, package) pair:
Package floor = the higher of the
Requires PHP:header and the version pinned invendor/composer/platform_check.php. Composer throws at autoload, before any plugin code runs, so it wins when stricter.Site PHP = the version actually serving the site, detected per stack:
/etc/php<XY>rc/fpm.d/<app>.conf/etc/lsws-rc/conf.d/<app>.d/handler.conf→lsphpNNSetHandler proxy:unix:/run/php/…-php<X.Y>-fpm.sock, matched by vhost filename →ServerName/ServerAlias→DocumentRoot(webappdichvu-websitelives indichvu.website.conf, so name matching alone misses real sites — found and fixed during testing)It never uses
php -v: the CLI binary is routinely a different major than the web PHP, and trusting it is the original incident.Site PHP < floor → BLOCKED. Nothing is written, the required version is named. No automatic override —
--allow-php-gate-bypassexists only for staging ahead of a planned PHP upgrade.Lint gate. The package's top-level PHP files are linted with the site's own php binary before anything is installed, so a package using newer syntax than it advertises is caught pre-install rather than by a 500.
PHP undetectable → site blocked, never guessed. A wrong PHP assumption is exactly what takes a site down.
Apply behaviour
Dry-run by default.
--apply: keeps<slug>.bak-<ts>, moves the old folder aside and extracts fresh (never extracts on top — that leaves files from the previous version behind), chowns to the site owner (root:rootfrom a root extract is unreadable by the PHP worker), re-reads the on-disk version, re-activates a previously-active plugin and verifies it stayed active (an auto-deactivate means it fatalled → rollback), HTTP-checkssiteurland auto-rolls-back on non-200/301/302, flushes WP + LiteSpeed caches, commits per plugin. Exit 2 = something failed and was rolled back.Three latent bugs fixed along the way
git addfailure. When.git/objects/<xx>is root-owned (a rootgit fetchleaves it that way),git addfails withinsufficient permission for adding an object to repository database— and a baregit diff --cached --quietthen reports no diff, indistinguishable from "nothing to commit". The plugin landed on disk with no commit and the per-plugin revert safety net silently did not exist. Any output fromgit addis now a failure, printed with the exactchown.version_cmpcompared only 3 components — Gravity Forms3.1.3.4vs3.1.3.10compared equal, so a real update would have been skipped. Now compares 4.zip_entry_phpfalse negatives underpipefail—unzip -p … | grep -qfails when grep exits early and unzip takes SIGPIPE, so packages whose entry file isn't first alphabetically (Gravity Forms'gravityforms.php) read as having no version header. Body is now captured before matching.Test plan
bash -nclean;shellcheck -S warningclean.version_cmp(4-part versions),required_php(header + composer floor),zip_entry_php, andlint_zipagainst the real store packages.Requires PHP: 9.9package against a PHP 8.2 site on sg10 →⊘ BLOCKED site PHP 8.2 < package requires 9.9, nothing written.msn.chuyen.devwpml-string-translation 3.5.2 → 5.1.0— plugin stayedactive, HTTP 200, ownershiplitesoup:litesoup, backup created. Re-tested after a manual revert; the git-add failure surfaced with the actionable message instead of being swallowed./root/premium-plugins/(those boxes clone over HTTPS with no credentials and the store is private, so a git submodule can't be pulled there).Bookkeeping
VERSION0.0.2.0 → 0.0.2.2 (0.0.2.1 was already in CHANGELOG but never bumped in VERSION), CHANGELOG[v0.0.2.2], README feature bullet + full section,docs/premium-plugin-update.mdrunbook.Reviewer note
plugins/in this repo is currently just a.gitkeep. The zips are deployed by rsync to/root/premium-plugins/, which the script auto-detects; committing them into the repo would make every clone 47 MB heavier and duplicate the private store.