Cross-host migration — xoct, xcopy, xmass
Three root-side tools move accounts between BOA hosts. All three are fetched and
auto-updated by BOA.sh.txt via _fetch_versioned serials (plus a chmod 700
sweep), so the binaries live at /opt/local/bin/{xoct,xcopy,xmass} and all three
— xcopy included — self-refresh on every BOA update.
| Tool | Moves | DB transport | Source after | Write-freeze |
|---|---|---|---|---|
xoct |
one Octopus account | mydumper / myloader |
converted to proxy | http-off 503 |
xcopy |
one Octopus account (duplicate) | mydumper / myloader |
stays live | none |
xmass |
the whole host (all accounts + Solr) | xtrabackup snapshot + GTID replication | converted to proxy | http-off 503 |
xoctis a move (the source account is decommissioned to a proxy).xcopyis a copy (the source account keeps serving — nohttp-off, no source proxy conversion).xmassis a whole-server move for evacuating a box.
Account identity is preserved across the hop: BOA does not support cross-account
migration in the general case (o1 lands on o1, o2 on o2). The one rename axis
is xoct/xcopy's optional fourth argument, which renames the moved account on the
target (see below).
xoct — single-account move
xoct is a multi-step dispatcher, not a two-verb prep/import model. Almost every
step runs on the source with the target IP as the argument — including create,
which SSHes to the target to provision the new Octopus account there (boa in-octopus).
Only import and post-mig are run on the target; pre-mig is run on both. The
optional fourth argument renames the account on the target.
pre-mig (run on the source first, then the target) freezes the task queue for the move:
it parks the BOA background runners — runner.sh, owl.sh, usage.sh, graceful.sh,
manage_ltd_users.sh — as .off and kills any running copy, then exchanges root SSH keys
so root can SSH freely between the two hosts. post-mig restores those runners afterwards.
# Phase 0 — pre-mig, run on BOTH source then target
xoct pre-mig <source-fqdn>
# On SOURCE — provision target shared infra + first-pass transfer.
# transfer/pretransfer default to a read-only DRY run: review the
# [DRY-PLAN] output, then re-run the same command with --live.
xoct transfer shared <target-ip> # DRY plan
xoct transfer shared <target-ip> --live
xoct create o1 <target-ip> [o2]
xoct pretransfer o1 <target-ip> [o2] # DRY plan
xoct pretransfer o1 <target-ip> [o2] --live
# On SOURCE — export (writes http-off 503 + dumps) then transfer
xoct export o1 <target-ip>
xoct transfer o1 <target-ip> [o2] # DRY plan
xoct transfer o1 <target-ip> [o2] --live
# On TARGET — import, then post-mig (restores runner scripts on target)
xoct import o1 <target-ip> [o2]
xoct post-mig <source-fqdn>
# On SOURCE — convert old host to proxy, then post-mig
xoct proxy o1 <target-ip> [o2]
xoct post-mig <source-fqdn>
# Then update DNS A records to <target-ip>; the proxy hop drops away
# once DNS propagates.
The dispatch verbs are: export, create, import, pretransfer, transfer,
proxy, pre-mig, post-mig, and ssl-gen (re-issue proxy SSL, gated on
/root/.ssl.proxy.cnf).
transfer shared provisions the account-independent shared infrastructure on the
target — the shared codebases (/data/all, /data/disk/all), archives and SQL dumps
(/data/disk/arch), the Solr cores (/opt/solr4, /var/solr7/data, /var/solr9/data),
/var/www/static, /etc/bind, and the usage logs under /var/log/boa/usage. The
per-account verbs take the Octopus username (o1, o2, …) as their second argument and
the target IP as their third.
When the optional fourth argument differs from the source username, xoct enters
rename mode on import. renameaegirhost rewrites every reference from the source to
the target account name via its --old-account/--new-account axis: the control-panel
identity becomes o2.<target-fqdn> (adopting the fresh install's panel site dir and its
valid credentials), and /data/disk/o1 paths in platform, git, backup and package records
are rewritten to /data/disk/o2 — otherwise the first DB-driven regeneration would
re-stamp the stale source paths over the transfer-time fixes. Customer-site URIs are
never touched: a site on the account subdomain such as shop.o1.<fqdn> keeps its name.
The rewrite also covers the per-site FPM $user_socket account token, so the renamed site
is served by its own account's pool rather than the target's install-time account that
still carries the old name.
The target account o2 must not already exist before create — create provisions
it fresh. After a rename, verify the site returns a real 200 direct to the target
(not a proxy or catch-all): a site-wide 403 immediately after a rename historically meant
a socket token still pointed at the wrong account's pool.
The transfer/pretransfer verbs are gated by the DRY/--live model — see
Storage-aware transfers and the DRY/--live gate below.
The export dump
export dumps the entire hostmaster database (${_HDB}, derived from the
hostmaster site's drushrc.php) into /data/disk/<oN>/src/prev_hostmaster.sql,
using the same mysqldump option set renameaegirhost uses (--single-transaction --quick --no-autocommit --skip-add-locks --no-tablespaces --hex-blob).
The previous hand-curated table allowlist — tied to the removed direct-SQL import
surgery below — silently dropped any hostmaster table not on the list, so newly added
module/field schema tables simply vanished from the migrated instance; the full dump
gives the import target a faithful copy for renameaegirhost's rewrite and 5-pass
queue.
The same applies to xcopy export; xmass is unaffected (full-server replication, no
hostmaster mysqldump).
Source disk space: export writes the per-site mydumper output and the hostmaster
dump under /data/disk/<oct>/src/, so keep at least 1× the account's total database
size free there before you start.
Chained migrations — a former target moving onward: a box that was itself a migration
target earlier still holds src/prev_hostmaster.sql and log/imported.pid from that
transfer. xoct export will not write a fresh dump while prev_hostmaster.sql is
present (it protects a dump a pending import may still need), so a chained move would ship
the stale dump; imported.pid also blocks the account from being an import target again.
Before migrating a former target onward, remove both — rm -f /data/disk/o1/src/prev_hostmaster.sql /data/disk/o1/log/imported.pid. (log/sourcefqdn.txt
is rewritten with every dump and needs no cleanup.)
On-target import
Before importing, on the target: pause the BOA task runner so no background job fires
mid-import — run service cron stop, then chmod 644 /data/all/cpuinfo, and wait about
five minutes for any in-progress tasks to settle. Run the import under websh: point
/bin/sh and /usr/bin/sh at it first — ln -sfn $(which websh) /bin/sh; ln -sfn $(which websh) /usr/bin/sh — and confirm with ls -la /bin/sh.
On the target, import reduces to:
- re-import of the full hostmaster DB dump,
- a couple of frontend
variable-setcalls via drush (site_frontpage, plushosting_client_send_welcomein the fix path — Ægir API, not direct SQL), then renameaegirhost --aegir-root.
renameaegirhost handles the in-place hostname rename of the moved account — a full
5-pass Ægir task queue that rewrites Drush aliases, nginx vhost files under
config/server_master/, and the Ægir DB content as its atomic rename step.
This supersedes the legacy "migrated/merged instance" fixup layers, which were removed
from import:
- the direct-SQL surgery (in-place
sedof hostmaster drush aliases plus hardcoded-niddrush sqlqREPLACE/UPDATE/DELETE againsthosting_context/hosting_package/node/hosting_site/hosting_platform/users_roles, guarded by a one-shotpost-merge-fix.pid), and - the ghost/empty-platform delete loop, whose missing-
sites/alltest was true for every valid Composer D8+ platform and so deleted valid platforms from Ægir during import.
xcopy import follows the same model; the intended remaining xoct/xcopy divergence
is that xcopy does not enable the migration proxy.
Operational notes
- Re-running after a failure — most
xoctsteps are pid-gated and safe to repeat after a failed run. To force a step to run again, remove its marker under/data/disk/o1/log/(for exampleexported.pid,transferred.pid,imported.pidorproxied.pid). - Drupal 6 IP blocking — Drupal 6 sites that block by IP can lock out the migration.
Before migrating such a site, whitelist the source IP at
/admin/user/rules(a Host rule of type Allow), or flush the site's{access}table via Chive afterwards.
No automatic fixups on upgrade or nightly runs
Migrated/merged instances get no automatic surgery elsewhere either. The legacy fixups that used to rewrite imported instances on upgrade and nightly runs were all removed:
- the hardcoded-node-ID SQL rewrites against hostmaster's
hosting_context/node/hosting_sitetables, - the greedy
sed"Pre-Fix" rewrites ofhostmaster.alias.drushrc.php, - the nightly one-shot
UPDATE hosting_context SET name=hostmasterSQL, and - the nightly
_fix_site_readonlymodereset.
They predate renameaegirhost and, on long-lived instances whose node IDs no longer
matched the hardcoded assumptions, caused hostmaster drift rather than fixing it.
After a cross-host migration, hostname/topology fixups are exclusively the job of
renameaegirhost and the normal hosting machinery — do not expect BOA to auto-rewrite
hostmaster contexts or aliases on the next upgrade or nightly run.
The retired internal markers (post-merge-fix.pid, hmpathfix.pid,
hosting_context.pid, readonlymode_fix.info) need no operator action.
DNS proxy conversion
DNS-proxy setup is not automatic: it is the explicit xoct proxy step run on the
source, which converts the account's nginx vhosts to proxy templates forwarding to the
target IP, removes the migration http-off, and emails the account owner. After DNS is
repointed the proxy hop is no longer used.
Downtime and cross-version safety
Downtime per account: minutes to a few hours depending on site sizes. Sequential — one
account at a time. xoct uses mydumper/myloader for the DB phase and is
cross-version safe — it works between hosts running different Percona versions.
xboa is gone
xboa was the predecessor name. It is fully removed — there is no xboa binary and
no xboa fetch entry (BOA fetches xoct, xcopy, and xmass; nothing under the
old name).
xoct is its functional successor with two improvements:
- the Ægir DB hostname replacement and post-import task queue are delegated to
renameaegirhost(more thorough, 5-pass), and - the hardcoded internal-account email exclusion was removed so
xoctworks correctly when driven byxmass.
If muscle-memory still types xboa, an operator-created compatibility symlink is the
documented workaround (BOA does not ship one):
ln -sfn /opt/local/bin/xoct /opt/local/bin/xboa
The symlink target must be the real binary at /opt/local/bin/xoct (xoct is fetched
to /opt/local/bin only — it is not mirrored into /usr/local/bin); both directories
are on PATH, so a bare xboa then resolves.
xcopy — duplicate an account, source stays live
xcopy shares xoct's dispatcher shape but is a non-destructive copy: it does
not write http-off on the source, and it does not convert the source to a
proxy. Use it to stand up a duplicate of an account on a second host (staging clone of a
whole instance, pre-cutover rehearsal) while the original keeps serving.
xcopy pre-mig <source-fqdn> # on SOURCE + TARGET
xcopy transfer shared <target-ip> # on SOURCE — DRY plan
xcopy transfer shared <target-ip> --live # on SOURCE
xcopy create o1 <target-ip> [o2] # on SOURCE
xcopy pretransfer o1 <target-ip> [o2] # on SOURCE — DRY plan
xcopy pretransfer o1 <target-ip> [o2] --live # on SOURCE
xcopy export o1 <target-ip> # on SOURCE (dumps, no http-off)
xcopy transfer o1 <target-ip> [o2] # on SOURCE — DRY plan
xcopy transfer o1 <target-ip> [o2] --live # on SOURCE
xcopy import o1 <target-ip> [o2] # on TARGET
xcopy noproxy o1 <target-ip> [o2] # on SOURCE (skip proxy conversion)
xcopy post-mig <source-fqdn> # on SOURCE + TARGET
xcopy ssl-gen # on TARGET (re-issue certs)
Dispatch verbs: pre-mig, export, create, import, pretransfer, transfer,
noproxy, post-mig, ssl-gen.
The noproxy verb is the deliberate counterpart to xoct proxy — it finalises the copy
without proxying the source.
ssl-gen re-issues Encryption on the target for the (now duplicate) account, which is
required because the copied account, like any name-changed account, lands with
Encryption disabled.
Because the source is never frozen, an xcopy produces a point-in-time duplicate that
diverges from the still-live source the moment it completes — it is a copy, not a
synchronised replica.
xmass — whole-server move
xmass moves an entire BOA host — every Octopus account, every Solr index, every
config — from source to target. The DB transport is xtrabackup snapshot +
MySQL GTID replication, not per-account mydumper cycles, so it scales to fleets of
large databases where per-account dump/restore would be impractical.
A common reason to reach for xmass is an OS upgrade by fresh install rather than
in place: instead of walking an ageing host through the in-place OS upgrade
paths — a multi-reboot codename chain whose only rollback is a
VM-snapshot restore — install BOA fresh at the same release on a new host running the
newer OS, xmass the whole estate across, and cut over, leaving the old box as a
migration proxy until DNS is repointed.
The working steps (init, sync, status, cutover) all run on the source and
take the target IP as their argument; replication and rsync flow source → target.
post-mig is run on the target after DNS is updated, and the initial pre-mig is run on
both source and target — pre-mig parks the BOA background runners (runner.sh, owl.sh,
usage.sh, graceful.sh, manage_ltd_users.sh) so the task queue is frozen during the
move, and exchanges root SSH keys between the hosts.
# Phase 0 — pre-mig, run on BOTH source then target
xmass pre-mig <source-fqdn>
# On SOURCE — install xtrabackup, enable GTID, snapshot,
# restore on target, start the replica
xmass init <target-ip> [--permanent-proxy]
# On SOURCE — rsync platforms/files/configs/Solr (repeat freely);
# DRY plan by default, re-run with --live to transfer
xmass sync <target-ip> # DRY plan
xmass sync <target-ip> --live
# On SOURCE — replication lag + last-sync timestamp
xmass status <target-ip>
# On SOURCE — final cutover (block web, drain lag, promote target,
# renameaegirhost on target, convert source to proxy); the DRY run
# plans storage for all accounts + shared and stops before any
# destructive step
xmass cutover <target-ip> [--permanent-proxy] # DRY plan
xmass cutover <target-ip> [--permanent-proxy] --live
# On TARGET — finalise after DNS update
xmass post-mig
Dispatch verbs: pre-mig, init, sync, status, cutover, post-mig. The
sync and cutover verbs are gated by the DRY/--live model (see Storage-aware
transfers and the DRY/--live gate below); one DRY plans all accounts plus
shared.
Pre-sync is incremental — once init runs, repeat sync over days or weeks until
ready; the GTID replica keeps the target's databases current and sync carries the
file-system deltas.
Each sync run carries a fixed set of filesystem data to the target: shared BOA data
(/data/all, /data/disk/all, /data/disk/arch, /data/disk/legacy), the static web
root (/var/www/static), DNS zones (/etc/bind), usage logs (/var/log/boa/usage), the
Solr indices, and per-account platforms, site files, drush site-aliases, nginx vhosts,
SSL/LE material, and the FTP account's SSH keys (/home/oN.ftp/.ssh). MySQL data is the
one thing not rsynced — GTID replication keeps it current continuously.
Whole-server cutover downtime is typically 1–3 hours.
You do not pre-create matching Octopus accounts on the target. xmass restores the
full xtrabackup snapshot, brings databases across via GTID replication, and enumerates
the accounts dynamically from /data/disk/ — no Octopus instances are required on the
target beforehand.
xmass init auto-installs the matching percona-xtrabackup-* package and auto-enables
GTID on both servers if not already active.
init also chooses its xtrabackup transfer method automatically from free space on the
source /. With more than 1.5× the data-directory size free it stages — the
backup is written to /var/backups/xmass_stage, prepared, then rsynced to the target.
With less it streams — piped via xbstream over SSH straight to
/var/backups/xmass_restore on the target and prepared there, needing no staging disk on
the source.
xmass cutover invokes xoct proxy per account during the cutover phase to convert the
source vhosts to proxies, plus renameaegirhost on the target for the master and each
Octopus account.
--permanent-proxy
--permanent-proxy marks the source as a permanent HTTP proxy after cutover rather
than a temporary one pending DNS update. It is not cosmetic:
- It keeps the migration-proxy realip/CSF trust on the target in place — the
post-migteardown becomes a deliberate no-op (migration_proxy_trust.sh teardownis skipped). - It drops a
/data/conf/xmass_permanent_proxy.pidflag so notification wording reflects the permanent mode. - It prints the permanent mode in the cutover summary.
The flag can be set at init (_PERM_PROXY_FLAG) and overridden at cutover —
--permanent-proxy on the cutover command wins over whatever was set at init.
Requirements
- Identical Percona versions on source and target — GTID replication is
version-strict. (This is the key difference from
xoct/xcopy, which are cross-version safe viamydumper.) - BOA installed on the target at the same release as the source.
- Root SSH key access source → target, set up by
xmass pre-mig. - The source IP whitelisted in CSF on the target before
init, so xtrabackup streaming and rsync can reach it.
The cutover sequence
Before any destructive step, cutover runs an automatic pre-flight: it confirms the
phase is syncing, that none of the BOA background runners parked by pre-mig are still
active, and that system cron is stopped — refusing to proceed otherwise.
The cutover itself has built-in safety timings. It drains replica lag to zero, polling
every 15 s for up to 30 minutes and aborting on timeout; takes a final
static/files-only rsync under a FLUSH TABLES WITH READ LOCK to catch last-second
uploads; then triple-checks lag = 0 at 10 s intervals, unlocking the source and
aborting if any check fails.
Once replication is decoupled and nginx is serving proxied traffic on the target, cutover
rewires panel database access for every Ægir root before the renames run. The init-time
datadir swap replaced the target's MySQL wholesale, so the fresh-install panel databases the
new host's control-panel dirs pointed at no longer exist; for each root xmass rediscovers
the live (replicated) hostmaster database, resets that database user's password across its
host variants, and rewrites the panel dir's stored credentials to match. This must succeed
before renameaegirhost can bootstrap, and it is idempotent — a root whose panel already
names a live database is skipped. After promotion cutover starts cron and restores the
runner scripts on the target, and writes a proxied.pid marker for every source account.
MySQL credentials and the datadir swap
Because the xtrabackup restore replaces the target's entire /var/lib/mysql — including the
mysql system tables — the target's MySQL root password becomes the source's. xmass
therefore ships /root/.my.pass.txt and /root/.my.cnf from source to target twice: right
after the restore, so client tools work during replica setup, and again after promotion at
cutover. This wholesale swap is also why the panel-database rewire above is required.
State machine and recovery
xmass tracks its progress in /data/conf/xmass_state.cnf (mode 600 — it holds the
replication password), moving through init → syncing → cutover → complete. Each subcommand
checks the current phase and refuses to run out of sequence. To abandon a migration and
start over, remove the state file — but only after replication has been torn down on the
target, or the next init collides with a live replica.
Recovery paths:
initfails before replication starts — remove the state file and retry.initfails after replication starts — on the target runSTOP SLAVE; RESET SLAVE ALL, drop thexmass_repluser on the source, then remove the state file.cutoveraborts mid-flight — source MySQL is unlocked automatically, but the source stays on its 503 freeze; removestatic/control/http-off.pidfrom each account and reload nginx to restore service before investigating.- A panel rewire or a
renameaegirhostpass fails — cutover parks resumably atphase=rename-failedand names the affected roots. Fix the cause and re-runxmass cutover <target-ip> --liveto resume; already-rewired and already-renamed roots no-op.
Storage-aware transfers and the DRY/--live gate
DRY by default, --live to act
xoct/xcopy transfer and pretransfer, and xmass sync and cutover,
default to a read-only DRY run: the tool resolves the target's attached mount,
prints a [DRY-PLAN] line per store, pre-checks disk space, and finishes with
[DRY] CLEAN or [DRY] NOT CLEAN, recording the result in a state file:
/var/log/boa/xoct.migrate.<oct>_<tgt>.state/var/log/boa/xcopy.migrate.<oct>_<tgt>.state/var/log/boa/xmass.migrate.<tgt>.state(xmasskeys per target only: one DRY plans all accounts plus shared)
An explicit --live is required to perform the migration and is accepted only after
a CLEAN dry run for the same account+target (per target for xmass); the clean-dry
token is consumed on use, so one dry run can never arm two live runs.
--dry/--test are explicit-DRY aliases on all three tools. These are flags, not
verbs — only ---prefixed flags are recognised and stripped, so no positional argument
is ever mistaken for one.
The gate covers only the file-transfer verbs. ssl-gen, export, import,
create, proxy (noproxy on xcopy), pre-mig, and post-mig run ungated on
xoct/xcopy; pre-mig, post-mig, init, and status run ungated on xmass.
The DB/SQL steps — the mydumper/mysqldump export-import and the xtrabackup/GTID
pipeline — are never gated.
xmass cutover in DRY mode does the storage-plan pass only, for all accounts plus
shared, and stops before any destructive step — no MySQL lock, no downtime.
Storage-aware placement
Store placement follows the target's disk reality, not the source's
(_xoct_handle_store, with twins _xcopy_handle_store / _xmass_handle_store):
- Target has a single attached
/mntmount → a store's contents are mirrored onto it (static/files→<mount>/files/<account>/static/files— using the destination account name, so a rename lands correctly;arch→<mount>/files/system/arch) and the on-target path re-pointed as a symlink — even if root has room. - No target mount → the store is de-referenced into a real directory on the target root.
- A real-dir source stays a real dir on the target root, using the target mount only as a space fallback.
A catch-all sweep applies the same rule to any other /mnt-anchored symlink in the
account tree, so none is left dangling on the target.
Three conditions are a DENY — the dry run reports NOT CLEAN and --live is
refused:
- a dangling source symlink (attached disk not mounted),
- a target with multiple
/mntmounts, or - a store that fits nowhere.
Site-level sites/*/files and sites/*/private symlinks resolve either way — they
point at the account-level static/files path, which on the target is a real directory
or a symlink onto the mount.
Relocated arch (migratefs)
Once migratefs has relocated
/data/disk/arch onto attached storage it is a symlink, and all three tools
transfer its contents: the shared sync routes arch through the storage-aware
store handler, which resolves the symlink (readlink -f, scoped to this store —
not a blanket rsync --copy-links) and materialises it on the target per the rule
above — mirrored onto the target's mount at <mount>/files/system/arch with
/data/disk/arch re-pointed, or de-referenced into a real /data/disk/arch on the
target root.
In LIVE mode an arch that cannot be placed aborts the shared sync with an error
rather than proceeding without backups.
Previously xoct/xcopy rsynced arch as a bare path and delivered a dangling
symlink, while xmass skipped it entirely — silently transferring zero SQL dumps and
cluster backups.
A real (non-relocated) arch keeps the existing behaviour; a dangling arch symlink
(attached disk unmounted at migration time) is a DENY in the dry run instead of a
silent failure.
The shared sync also refuses an empty target: an empty ssh host resolves to
localhost, so the remote mkdir/rm could have run against the source box and
deleted its live arch symlink.
The target mount is now resolved up-front, and the resolver exits non-zero on an empty
target, an unreachable target, or a multi-mount target (xmass already died on an
empty target).
Before migrating a host whose arch/static-files was relocated by migratefs,
make sure xoct/xcopy/xmass are current on both hosts — BOA auto-updates
them via _fetch_versioned; older versions copy or skip the bare symlink.
Solr indices and the space gate
Solr home data (/opt/solr4, /var/solr7/data, /var/solr9/data) — often tens of
GB — is folded into the migration disk-space gate. Each tool runs a Solr space
check before each Solr transfer: du -sk of the source vs available space at the
target destination dir, measured by df on the nearest existing ancestor on the
target — so it follows the target's own /var/solrN symlink onto its mount, or
lands on root.
If it does not fit, the tool prints DENY: <label>: <N>K does not fit at target <dest> — free space and retry, sets NOT CLEAN (so --live is
refused until resolved), and skips that transfer. Unknown size or unknown target
availability does not block — the measurement fails open.
Solr placement is unchanged: a data-only rsync following the target's own Solr setup. Solr is deliberately not routed through the storage-aware store handler, which moves whole directories and would clobber the target's fresh Solr home.
Previously Solr was only DRY-wrapped, not space-checked — a dry run would not warn, and a live run would pour the index onto a full target.
How web traffic is frozen — http-off
To stop writes on the source while databases are exported or drained, xoct and
xmass write a per-account static/control/http-off.pid. The file's contents are a TTL
in seconds — 3600 when set by xoct export, 7200 when set by xmass cutover.
global.inc reads the marker and short-circuits every site in that account with a
PHP-level 503.
This replaces the old readonlymode mechanism, which is unavailable via the system
Drush 8 on Drupal 8+ and is bypassed by some commerce/API code paths.
http-off is the automatic freeze; the xoct procedure still recommends an
additional manual config_readonly / site_readonly toggle in global-extra.inc as
belt-and-braces before the export window — treat http-off as the automatic mechanism,
not a total replacement for read-only mode.
At each http-off toggle the nginx fastcgi speed cache is purged box-wide so already
cached 200 (or 503) responses don't mask the change; the microcache repopulates
within seconds on live sites. The pid is removed when the account is converted to a
proxy at xoct proxy / xmass cutover time, so the now-proxied sites return to 200.
xcopy never writes http-off — the source is never frozen.
The static/files transfer uses a symlink-safe two-pass helper
(_xoct_transfer_static_symlink_safe, mirrored in xcopy as
_xcopy_transfer_static_symlink_safe):
- pass 1 syncs everything under
static/with symlinks preserved (excludingstatic/files); - pass 2 routes
static/filesthrough the storage-aware store handler (see Storage-aware transfers and the DRY/--livegate above), so on the targetstatic/filesmay legitimately be a real directory on root or a symlink onto the target mount — either way neither the account-levelstatic/filesnor the per-sitesites/*/filessymlinks are left dereferenced into broken layouts.
Migration proxy — real-IP recovery and CSF trust
After cutover the source server is converted to a reverse proxy that relays traffic to
the new host (xoct proxy per account, driven by xmass cutover), so visitors keep
working until DNS is repointed.
The critical wiring is on the target: xmass runs
_xmass_setup_migration_proxy_trust before relaying starts. It scps
migration_proxy_trust.sh + migration_proxy_realip.sh from the source's
/var/xdrago/ to the target's /var/xdrago/, chmod 0755s them, and runs
migration_proxy_trust.sh trust <source-ips> [--permanent] on the target. That tool:
- Writes
set_real_ip_from <proxy-ip>;for the nginx realip layer (viamigration_proxy_realip.sh), so the new host recovers the real client IP instead of seeing every request come from the proxy. - Hard-whitelists the proxy's egress IP in CSF —
csf.allowon ports 80 and 443 pluscsf.ignore(tagged# migration proxy).
Without this, every relayed request would appear to come from the proxy IP, so every
per-client control on the new host (scan_nginx, geo bans, ip_access, the AI guards,
CSF/lfd) would key on the proxy and a single ban would blackhole all migrated sites.
The trusted source IPs are derived from hostname -I on the source, filtered to routable
IPv4 (loopback 127.* dropped, IPv6 dropped, private IPs kept since the
source→target hop may run over a private network).
Both validators reject the all-zeros host and any /0 prefix: trusting 0.0.0.0/0 as a
realip source would honour the spoofable CF-Connecting-IP header from every peer,
collapsing the realip trust boundary (nginx ignores host bits, so 1.2.3.4/0 is
0.0.0.0/0).
The path is IPv4-only by design — CSF's whitelist is IPv4 and proxy_pass targets an
IPv4 target IP, so an IPv6 proxy-peer line would be stripped and flap, and is dropped
deliberately.
The proxy also refreshes its own Cloudflare realip (/var/xdrago/cloudflare_realip.sh)
so that for CF-fronted sites it forwards the real visitor — not the CF edge IP — in the
CF-Connecting-IP header it hands the new host.
Teardown removes the trust unless --permanent-proxy was passed. Note the asymmetry:
xoct proxy converts an account's vhosts to proxy templates but does not itself
call the trust scripts — the realip/CSF-trust wiring is xmass-driven. A standalone
xoct move to a host that should recover real client IPs needs the trust wired
separately.
Which tool when
One site/account, same host → Aegir Migrate/Clone task (see 02.site-cloning)
One account across hosts (move) → xoct
One account across hosts (copy) → xcopy (source stays live)
Whole BOA host → xmass
xoct, xcopy, and xmass are the mature BOA cross-host tools; prefer them over any
legacy manual import. The historical single-site manual remote_import /
hosting_remote_import flow is no longer a functional BOA module and is treated as
retired — see Discontinued features.
Related
- Site cloning & in-host migration — the within-host Clone and Migrate tasks and the FastTrack/MyQuick accelerators.
- Aliases & redirects — the alias/redirect model, relevant when a moved or renamed account changes its canonical hostname.
- Database (MySQL/Percona) —
mydumper/myloaderinternals and the Percona-version constraints that gatexmass. - Security & isolation — CSF,
ip_access, and the realip layer the migration-proxy trust feeds into. - Abuse Guard —
scan_nginxand the geo/AI bans that key on the recovered client IP after a migration proxy is trusted. - See the Reference appendix for the consolidated variable, command, and control-file tables.