Concepts#
The terms used on the other pages, each with the behaviour the code gives it.
Connections and keys#
Every site has one key: 32 random bytes, base64url-encoded, generated the first time the settings are read and
stored in the bb_migrate_settings option. The key is the only credential. There are no user accounts in the protocol.
A connection string is <endpoint> <key>, where the endpoint is the site's REST URL for the bb-migrate/v1
namespace:
https://staging.example.com/wp-json/bb-migrate/v1 <key>
The site that drives a transfer holds the connection string of the site it calls and signs each request with that key. The called site verifies the signature with its own key. See Protocol.
- The key identifies the receiver, not the caller. Anyone holding it is treated the same.
- Regenerating the key (
wp bb-migrate key --regenerate, or the checkbox on the Settings tab) invalidates every connection string for that site at once. - Saved connections on the Settings tab keep other sites' connection strings, keys included, in this site's
bb_migrate_settingsoption so they can be picked from a list. Options namedbb_migrate_…are never written into an export, a backup or a push, so saved connections and the site's own key do not travel with the database. - A job record holds the connection string while the job runs. Finished and failed jobs are deleted at once.
Switches#
Three per-site settings decide what a valid signature may do. All default to off, and each is checked on the receiving site for every request.
| Setting | Label on the Settings tab | Effect |
|---|---|---|
allow_pull |
Allow other sites to pull from this one | Read routes answer: database export, file listing and download, post search and package export |
allow_push |
Accept pushes from other sites | Write routes answer: database import, file upload and deletion, package import, site actions, the pre-push backup |
allow_code_files |
Allow theme and plugin files to be transferred | File routes also accept the themes, theme:<slug>, plugins and plugin:<slug> scopes; plugins may be activated and deactivated remotely; a push that asks to take the sender's plugin list may replace active_plugins |
Points that follow from the route table in Protocol:
- The handshake needs no switch. A valid key always gets the site's URLs, paths, versions, prefix, table names and switch values.
allow_code_filesadds to the other two; alone it allows nothing.- A file push calls the remote's file listing to work out what differs. The listing is a read route, so a file
push needs
allow_pullas well asallow_pushon the remote. - Listing remote plugins and showing remote BB Cache settings are served by write routes, so they need
allow_push.
A database push cannot change the receiver's switches or key: bb_migrate_settings is always restored after the
swap.
Scopes#
File sync never works on arbitrary paths. A request names a scope, and every path is relative to that scope's root folder.
| Scope | Root folder | Switches on the remote |
|---|---|---|
uploads |
The uploads base directory (wp-content/uploads) |
allow_pull to read, allow_push to write |
themes |
The theme root (all themes) | the same, plus allow_code_files |
theme:<slug> |
One theme folder | the same, plus allow_code_files |
plugins |
The plugin directory (all plugins) | the same, plus allow_code_files |
plugin:<slug> |
One plugin folder | the same, plus allow_code_files |
- A slug scope addresses the same folder name on both sites. The slug may contain letters, digits,
_,-and., and may not contain... themeon its own means the active theme of whichever site resolves it. Prefer the explicittheme:<slug>form.- The plugin's own folder is refused as a scope.
plugin:bb-migrate(or whatever folder the plugin runs from) returns "Unknown scope", and inside thepluginsscope any folder namedbb-migrateis skipped. A transfer cannot overwrite the code that is running it; new versions of BB Migrate are installed by zip upload. - In every scope these names are skipped:
node_modules,.git,.svn,.DS_Store,bb-migrate, and partial upload files ending in.bbpart. Inuploads, entries namedcacheare skipped too.
More in Files.
Preserved options on a database import#
An import replaces whole tables, including wp_options. So that the destination keeps working as itself, a short
list of its option values is captured before the import and written back after the swap:
| Option | Why |
|---|---|
active_plugins |
The destination keeps its own set of active plugins |
bb_migrate_settings |
Its own key, switches, limits and saved connections |
bb_cache_settings |
Its own BB Cache configuration |
blog_public |
Its own search engine visibility |
wp_environment_type |
Its own environment type |
The list can be changed with the bb_migrate_preserved_options filter.
"Skip preserve" (--skip-preserve, or the "also overwrite … active plugins and BB settings" checkbox) takes the
source's values instead, with two exceptions that hold whatever the caller asks:
bb_migrate_settingsis always preserved.- On a push, the receiver also keeps its
active_pluginsunless itsallow_code_filesswitch is on, because changing the active plugin list changes which code runs.
Temp tables and the atomic rename#
Imported SQL is never executed against the live tables.
- Every statement in the incoming SQL that names a table (
DROP TABLE IF EXISTS,CREATE TABLE,INSERT INTOand the like) has the table name rewritten to_bbmig_<table>before it runs._bbmig_is the constantBB_MIGRATE_TMP_PREFIX. -
When everything has arrived, one statement renames each live table to
_bbold_<table>and each_bbmig_<table>to<table>:RENAME TABLE `wp_posts` TO `_bbold_wp_posts`, `_bbmig_wp_posts` TO `wp_posts`, … -
The
_bbold_tables are dropped. - Find and replace runs on the swapped tables, preserved options are written back, caches are flushed and the
rewrite_rulesoption is deleted so WordPress rebuilds it on the next request.
If the transfer fails or is aborted before step 2, the temp tables are dropped and the live tables are untouched.
Only tables that appear in the incoming SQL are swapped; other tables stay as they are. _bbmig_ tables are never
listed, exported or offered to a remote.
The compatibility mu-plugin#
mu-plugins/bb-migrate-compat.php solves one problem: while a theme or plugin is being copied onto a site, that
site's own code may be half-written and fatal on load, which would break the very request that is delivering the
rest of it.
What it does. For a qualifying request it filters the active plugin list down to BB Migrate alone and points
WordPress at a theme that does not exist, so no other plugin and no functions.php loads. It remembers the real
theme so the handshake can still report it.
When it acts. Only when all of these hold:
- The request carries an
X-BB-Migrate-Signatureheader. - The request path is the plugin's own REST route,
/wp-json/bb-migrate/v1/<leaf>, or therest_routequery variable is/bb-migrate/v1/<leaf>. A URL that merely contains those words elsewhere does not qualify. - The route is not
content-*orsite-*. Page packages and site actions need the full site: post types, taxonomies, ACF, the plugin list. - The signature is valid.
It verifies the signature itself. Before it switches anything off, the mu-plugin checks the header format, the 300 second window and the HMAC against the site's stored key, using the same inputs as the plugin. A request with a bad signature gets the normal, full site, and then a 403 from the plugin. Replay protection and the switches remain the plugin's job.
Lifecycle. Copied into place on activation, re-copied whenever its content differs from the bundled file (checked
on admin and WP-CLI requests, so a plugin that arrived by file copy heals itself), removed on deactivation. It is safe
to delete by hand. If a site still has an old copy that also strips the site for site-* routes, site actions fail
with a message that asks you to refresh it: open the plugin's settings page on that site, or deactivate and
reactivate the plugin.
Jobs#
Every operation that can take long is a job: export, import, replace, pull, push, files or content
(page packages). A job is an array stored as one option, bb_migrate_job_<id>, and advanced in bounded steps.
- In the admin screen a script calls
admin-ajax.phprepeatedly. Each call works for about 12 seconds, saves the job and returns progress and the last log lines. - In WP-CLI the same step function is called in a loop with no time budget until the job is done.
- On the receiver, a push creates a
receivejob (the preserved option values, the tables seen so far, a statement count) and, for the backup, anexportjob stepped by the sender.
A job's log keeps at most 400 lines. A job is deleted as soon as it finishes or fails; records that were left behind are removed by the hourly cleanup once they have not been updated for a day.
The storage folder#
wp-content/uploads/bb-migrate/ holds exports, pre-import backups, pulled dumps, uploaded .sql/.sql.gz/.zip
files, page package zips, and the replay markers in nonces/.
- File names are
<host>-<label>-<date>-<32 random characters>.<ext>. - The folder gets a
.htaccessthat denies all access, anindex.phpand an emptyindex.html. - The hourly cleanup deletes
.sql,.sql.gz,.zipand.tmpfiles older thankeep_files_hours(24 by default) and replay markers older than 15 minutes. - The folder is never part of a file sync, in any scope.
- File names in it are never reported to a remote site. On servers that serve uploads statically without reading
.htaccess, the random name is what protects a dump. See Hosting notes.