# Cutover runbook — dev-wp becomes www.brentwood.ca

**Method: Reserved IP reassignment. No DNS change, no Rebel.ca, no client involvement.**
Email (Microsoft 365 + SPF) is never touched, because no DNS record changes.

Canonical host is **`www.brentwood.ca`** — live redirects the apex to www today, so we keep
that. Changing the canonical at the same moment as the platform would make any ranking
movement impossible to attribute.

**Who does what:** Rian drives the DigitalOcean console and Google Cloud. Claude drives the
droplet over SSH (Caddy, WordPress, verification).

---

## Phase 0 — Pre-flight (no impact, do now)

| # | Step | Who |
|---|---|---|
| 0.1 | **Snapshot the live droplet** (`brentwood.ca`, TOR1). Droplets → brentwood.ca → Snapshots → Take Snapshot. It has no automated backups. | Rian |
| 0.2 | **Add the Google OAuth redirect URI**: `https://www.brentwood.ca/wp-admin/admin-ajax.php?action=bw_google_callback`. Leave the dev one in place — Google allows several, so both work and there's no gap. | Rian |
| 0.3 | **Back up the dev database** — the rollback point for anything after 2.3. | Claude |
| 0.4 | **Confirm dev is the content you want live.** Everything on dev-wp right now becomes the public site. | Rian |

**Rollback:** nothing to roll back. Nothing public has changed.

---

## Phase 1 — Resize (the long step; start it early)

`dev-wp` is 4 GB; live is 8 GB / 4 vCPUs. The site is about to go from five gated users to the
public during admissions season, and 4 GB running WordPress + MariaDB + Caddy under real
traffic is the shape that produces OOM loops.

| # | Step | Who |
|---|---|---|
| 1.1 | Power off `dev-wp` (DO console). | Rian |
| 1.2 | Resize → **CPU and RAM only** (disk is already 80 GB, larger than live's 50 GB). Pick 8 GB / 4 vCPU to match live. CPU/RAM-only resizes are **reversible**. | Rian |
| 1.3 | Power on. Tell Claude — containers are `restart: unless-stopped` but this gets verified, not assumed. | Rian |
| 1.4 | Verify all containers are up and the site answers on the dev URL. | Claude |

**Rollback:** resize back down. Still nothing public has changed.

---

## Phase 2 — Stage the new configuration (still reversible, still nothing public)

| # | Step | Who |
|---|---|---|
| 2.1 | Write the new Caddyfile with `www.brentwood.ca` (canonical), `brentwood.ca` → 301 to www, and the existing gated `dev.brentwood.ca` stanza left intact. **Written but NOT reloaded** — Caddy keeps running the old config. | Claude |
| 2.2 | `caddy validate` the new file. | Claude |
| 2.3 | Prepare (do not run) the WordPress URL rewrite. | Claude |

Certificates **cannot** be obtained before the IP moves — Let's Encrypt's HTTP-01 challenge has
to reach the IP that DNS already points at, which is still the Laravel droplet. So expect a
**few seconds of TLS warning immediately after the switch** while Caddy issues. That is the one
unavoidable blip, and it is short.

**Rollback:** delete the staged file. Caddy never read it.

---

## Phase 3 — The switch

This is the only step visitors notice. Total elapsed time: under a minute.

**Ordering correction (found while staging).** WordPress redirects every request to whatever
`siteurl` says. If the IP moved while `siteurl` still read `dev.brentwood.ca`, the public would
be 301'd to a hostname that no longer resolves — the site would look dead. So the URL rewrite
happens **before** the IP moves, not after. The breakage that creates is internal only: for the
minute between, `dev.brentwood.ca` redirects to `www.brentwood.ca`, which is still Laravel.
Nobody but us is looking, and wp-cli over SSH doesn't care.

| # | Step | Who |
|---|---|---|
| 3.1 | Rewrite WordPress URLs `dev.brentwood.ca` → `www.brentwood.ca` (23,264 occurrences), flush rewrites. Dev URL stops working from here — expected. | Claude |
| 3.2 | DO → Networking → Reserved IPs → **unassign `159.203.50.215`** from `dev-wp`. A droplet holds one Reserved IP at a time. | Rian |
| 3.3 | **Assign `159.203.48.227` to `dev-wp`.** Live traffic now reaches WordPress. | Rian |
| 3.4 | Swap in the staged Caddyfile and reload — certificates issue within seconds. Command is ready and runs the instant 3.3 lands. | Claude |

Between 3.3 and 3.4 Caddy is still serving the old config and doesn't recognise
`www.brentwood.ca`, so expect roughly **20 seconds of errors** — a few for the reload, a few
more while Let's Encrypt issues. That is the whole visible outage.

### Rollback

- **After 3.3, before 3.4:** reassign `159.203.48.227` back to the `brentwood.ca` droplet.
  Laravel is serving again in seconds.
- **After 3.4:** same move, plus reverse the URL rewrite (or restore the Phase 0 backup).
  Minutes, not hours.
- **To get the dev URL back as well**, reattach `159.203.50.215` to `dev-wp`. Restoring the
  public site is one step; restoring dev is a second, optional one — do the first, breathe,
  then the second.

---

## Phase 4 — Verify before telling anyone

| # | Check | Who |
|---|---|---|
| 4.1 | `https://www.brentwood.ca` returns 200 with a valid certificate. | Claude |
| 4.2 | `https://brentwood.ca` 301s to www. | Claude |
| 4.3 | Spot-check real pages: `/admissions`, `/academics`, `/athletics`, `/giving`, `/regatta`, `/summer-camps`, `/events/graduation`, `/trips/everest`. | Claude |
| 4.4 | No `dev.brentwood.ca` left anywhere in the database or page source. | Claude |
| 4.5 | **Sign in with Google** on `/wp-admin` — this is what proves 0.2 worked. | Rian |
| 4.6 | Submit a test inquiry form; confirm the entry arrives. | Rian |
| 4.7 | Watch memory and CPU for a few minutes under real traffic. | Claude |

---

## Phase 5 — After the site is up

| # | Step | Who |
|---|---|---|
| 5.1 | Update the GTM handoff tags: `SITE` constant → `https://www.brentwood.ca`. | Rian |
| 5.2 | Watch Redirection's 404 log. Live has no sitemap, so **URL parity is unverified** — the eleven top-level paths sampled all match, which suggests the structure was preserved, but deep URLs are unproven. The 404 log is how stragglers get found. | Both |
| 5.3 | Decide what happens to `dev.brentwood.ca`. It now points at a detached IP. Re-pointing it at the droplet's primary IP (`68.183.201.76`) needs a Rebel.ca record change — the one thing on this list that does need Brentwood IT, and it is not urgent. | Rian |
| 5.4 | Leave the Laravel droplet **running but detached** for a few days. It costs little and it is the fastest rollback that exists. | Rian |

---

## Known consequences, accepted deliberately

- **`dev.brentwood.ca` stops working** at 3.1 and stays down until 5.3. Any journey link already
  written into a Finalsite record points there and will break — those are test records from the
  proof-of-concept, not client data. New links will carry the live domain automatically, since
  the plugin builds them from the site URL.
- **The access grants live in dev's database**, so they survive the cutover untouched. They would
  only be lost if someone ran a full `push-to-dev` afterwards — which must not happen again now
  that this database is production.
- **A few seconds of TLS warning** right after 3.2, while certificates issue.
