Must-use plugin for the Vector hosting platform.
- PHP 8.2+
- WordPress 6.0+
- Composer
Install a release artifact: extract it into wp-content/mu-plugins/, which gives
mu-plugins/vector-mu/, and copy the loader up a level (WordPress only loads
top-level files there):
tar -xzf vector-mu-X.Y.Z.tar.gz -C wp-content/mu-plugins
cp wp-content/mu-plugins/vector-mu/mu-loader.php wp-content/mu-plugins/vector-mu.phpFrom a checkout instead, place this repository at wp-content/mu-plugins/vector-mu/,
run composer install --no-dev --optimize-autoloader, and copy the loader the same way.
VECTOR_RUNTIME selects what vector-mu turns on. It's read from the environment once
per request and exposed as the VECTOR_MU_RUNTIME constant.
| Value | Runtime | Bundled plugins |
|---|---|---|
lambda, unset, or empty |
lambda (production) |
all, including vector-wordpress-plugin |
dev |
dev (Vector dev hosts) |
all except vector-wordpress-plugin, so uploads are served from disk |
| anything else | logged, then dev |
as dev |
Unset means lambda because WP-CLI on a dev host doesn't see the site's FPM pool
environment; without YMIR_ENVIRONMENT the fork stays inert there anyway. The Ymir
conflict guard runs in both runtimes.
Dev sites sit behind an access gate. So the site's requests to itself (Site Health,
cron spawning, async and REST self-calls) get through, the LoopbackHeader module
sends X-Vector-Loopback: <secret> on WP HTTP API requests whose host is the site's
own (the host of home_url() or site_url(), any scheme, case-insensitive), and on
no others. The header is removed before a redirect to another host, and a redirect
chain that has left the site's host never gets it back.
The secret is read, at most once per request, from
/etc/vector/sites/<user>/loopback, where <user> is the effective POSIX user. It
must be 64 lowercase hex characters (a trailing newline is fine). The module keys off
this file rather than VECTOR_RUNTIME, since WP-CLI and cron on a dev host don't see
the FPM pool's environment; where the file is missing, unreadable, or malformed, or
the posix extension is unavailable, it does nothing. The secret is never logged.
On dev hosts, mail is captured instead of sent. PHP's sendmail_path there points
at the host's capture binary, /opt/vector/bin/vector-sendmail, which catches
mail() and core's default transport, but a mailer plugin (SMTP or an API) would
still send. So the DevMailCapture module hooks pre_wp_mail at the earliest
priority and stops wp_mail() before any mailer plugin's transport:
- It builds the message as core's
wp_mail()would, with WordPress's PHPMailer: the same handling of recipients, headers, From, Content-Type and charset, attachments, and embeds, the samewp_mail_*filters, andphpmailer_init. It uses a fresh PHPMailer, not the global one a mailer plugin may have replaced, and sets the transport back to sendmail afterphpmailer_init, so SMTP or API settings made there are never used. The headers include Bcc, assendmail -treads them. - It pipes the raw message to the capture binary through
proc_openwith an argv array (no shell).wp_mail()returns true when the binary exits 0, afterwp_mail_succeeded; otherwise it fireswp_mail_failedwith aWP_Errorand returns false.
It's on in the dev runtime and wherever the capture binary exists, since WP-CLI
and cron on a dev host don't see the FPM pool's environment and resolve to lambda.
In production neither holds and it does nothing. A filter that runs earlier and
returns a value is passed through.
Each release vX.Y.Z on GitHub has two assets:
vector-mu-X.Y.Z.tar.gz: a singlevector-mu/directory withvector-mu.php,mu-loader.php,LICENSE,README.md,src/,vendor/(the Composer autoloader, no dev dependencies), andplugins/without the bundled plugins' dev files. It holds only regular files (0644) and directories (0755), owned by root.vector-mu-X.Y.Z.tar.gz.sha256, insha256sumformat.
bin/build-release <version> <out-dir> builds both from the committed HEAD (paths
marked export-ignore in .gitattributes are left out) and checks the tree. It needs
GNU tar (gtar on macOS: brew install gnu-tar). The build is reproducible for a
given commit and Composer version; CI pins Composer, so a local build with another
version may differ.
tests/integration/wordpress.sh [artifact] installs the artifact (or a fresh build)
into a pinned WordPress against a MySQL server and checks that it loads with no
YMIR_* or AWS_* variables, as dev (no fork, uploads served from disk) and with
VECTOR_RUNTIME unset (lambda), and that dev captures wp_mail() through a
stand-in capture binary even with SMTP configured. See the script for its settings;
CI runs it against MySQL 8.4.
-
Bump
Plugin::VERSIONinsrc/Plugin.phpand theVersion:header invector-mu.phptoX.Y.Z, and merge tomain. -
Tag the merge commit and push the tag:
git tag vX.Y.Z && git push origin vX.Y.Z.github/workflows/release.ymlruns CI, fails unless the tag matches both versions, builds and tests the artifact, and publishes the GitHub release. -
Update the version and sha256 pinned by the hosting platform; sites get the new vector-mu with its next release.
Plugins under plugins/ are loaded as must-use plugins by the VendorPlugins module
(src/Modules/VendorPlugins.php), each only in the runtimes it lists.
Vector's fork of the Ymir WordPress plugin
(GPL-3.0). It loads only in the lambda runtime. It provides cloud storage, page caching, email, image processing and the
wp vector … WP-CLI commands. See
plugins/vector-wordpress-plugin/README.md
for the upstream base version and the list of changes.
bin/update-plugin <slug> [version]Then add '<slug>/<slug>.php' => [ Runtime::LAMBDA, Runtime::DEV ] to
VendorPlugins::PLUGINS (the runtimes it loads in) and commit the plugin files. If it
has dev files, mark them export-ignore in .gitattributes.
composer install
composer test # Pest
composer lint # PHPCS
composer analyze # PHPStan
# vector-wordpress-plugin unit tests (needs WordPress core, default /tmp/wordpress)
composer install --working-dir=plugins/vector-wordpress-plugin
plugins/vector-wordpress-plugin/bin/install-wp.sh
composer test:fork