Skip to content

Account migration between nodes (TIGER-310)

Move a hosting account from one fleet node to another — files, databases, DNS, and the control-plane binding — idempotently and safely. The source stays live and authoritative until a green verify authorizes the irreversible teardown, so the whole flow can run unattended (the primitive a nightly fleet optimizer uses to consolidate under-used nodes and rebalance hot ones).

  • Orchestrator: Hosting_Lib_Migrate (control plane).
  • Node verbs: Server_Service_Account::migrate* (TigerServer), reached as role billing.
  • CLI: bin/tigerhosting-migrate.php.
  • State: one resumable hosting_migration row per attempt.

Why dedicated verbs (not account create/delete)

A custom-domain account's authoritative DNS zone lives on the shared PowerDNS cluster. The generic account/create/delete create and DELETE that zone — so using them to migrate would let a mid-flight failure (or the source teardown) destroy the zone and break the source too. Migration instead uses verbs that only ever repoint the zone's A record:

Verb Role in the move
migrateCreate Target skeleton (user/web/db) without the DNS zone or custom cert (both deferred). Validates inputs directly — not the create form, whose availability gates reject an existing domain.
migrateTest Pre-cutover: the target serves the real domain on its own vhost (loopback), before DNS moves.
migrateDns Cutover: repoint apex + www A records to the target (Server_Provision_Dns::setA — a PATCH in the existing zone, never create/delete). Re-issues the deferred cert.
migrateVerify Comprehensive, read-only: authoritative A (read from the pdns primary) points at the target and the site is served at the target's public IP. Returns the node's mailbox count.
migratePurge Source teardown that keeps the shared zone (deprovisions every step except dns).
mailboxCount Per-node mailbox count, for mail-parity gating.

The data move itself reuses the backup transfer (backup read/write/ingest/restore, sha256-verified); the restore clears the node's provisioning placeholder where it lays down a real app.

State machine

backing_up → creating_target → transferring → restoring → direct_url → verifying → cutover → confirming → verified → (tearing_down → done)
  • verifying — target serves (loopback) + mail parity. Fail ⇒ rollback (purge target; DNS never moved).
  • cutover — repoint the A record to the target (binding NOT flipped yet).
  • confirming — the commit, gated on migrateVerify green + mail parity: flip the CP binding (node_id/account_ref/direct_url) and recompute both nodes' used. Fail ⇒ rollback (DNS back to source, purge target).
  • verified — moved + confirmed; source still intact, awaiting teardown.
  • teardown → migratePurge the source (keeps the zone) → done.

The verify gate (automated)

A green verify is the only thing that authorizes teardown; teardown() re-runs it live and refuses if not green. Green = all of:

  1. DNS — authoritative A (apex + www), read from the PowerDNS primary, equals the target's public IP.
  2. Site — the real domain is served at the target's public IP (2xx/3xx, not the bare placeholder).
  3. Email — opt-in. No mailboxes (domain-less / email not set up) ⇒ N/A (pass). If the source has mailboxes, the target must have at least as many. v1 does not migrate mail, so a mail-bearing account fails verify and is not torn down.

CLI

CLI="sudo -u apache env APPLICATION_ENV=<env> php .../modules/hosting/bin/tigerhosting-migrate.php"

$CLI plan     --account=<account_id> [--to=<node>]          # DRY RUN — always first; mutates nothing
$CLI start    --account=<account_id> [--to=<node>] [--teardown]   # move → verified (--teardown = auto on green)
$CLI advance  --migration=<id>                              # resume
$CLI verify   --migration=<id>                              # read-only comprehensive verify
$CLI status   --migration=<id>
$CLI teardown --migration=<id>                              # removes the SOURCE (self-gates on green verify)
$CLI list
  • Operator: plan → start (stops at verified) → verify/spot-check → teardown.
  • Unattended: start --to=<node> --teardown (teardown fires automatically once verify is green; a non-green verify leaves the source live).

Preconditions

The target node must carry the fleet DNS config and its own temp base in its config tier (tiger.server.dns.{api_url,server_id,nameservers,public_ip,api_key_enc}, tiger.server.temp_url_base). Without them the migration fails at creating_target or direct_url.

v1 limitations

  • Mail is not migrated (box-level dovecot/postfix state); mail-bearing accounts are blocked by verify.
  • Limits are not re-synced — the target uses node defaults; re-apply the account's package post-move.
  • External-DNS domains — migrateDns reports managed=false; the A record must be pointed at the target manually, and verify stays red until it is.