# Phase D — DEV deploy runbook (marketing site on `aiworkfllow.com` apex)

Deploys the new `aiw-voice-marketing` Next.js site to the **dev** environment.
The SPA stays unchanged on `app.aiworkfllow.com`. Run every step **on the VPS**
over SSH (`ssh deploy@65.254.80.37`). Each step notes what "good" looks like.

> **Reversible.** Nothing here is destructive. The only live-traffic change is
> step 5 (nginx). Rollback = remove the one proxy.conf + reload nginx (step 8).
> The existing dev stack (api/app/widget/postgres) is never stopped or rebuilt.

---

## 0. Pre-flight (one-time checks)

```bash
# Confirm the apex currently serves nothing important (you said it's empty).
curl -sI https://aiworkfllow.com | head -5

# Confirm port 5175 is free (dev uses 5173 app / 5174 widget).
ss -ltnp | grep -E '5173|5174|5175' || echo "5175 is free"

# Confirm the compose source dir + AIW_SRC.
cat /opt/aiw/dev/.env | grep AIW_SRC   # expect AIW_SRC=/opt/aiw/src (or default ../..)
```

- ✅ `aiworkfllow.com` returns the cPanel default / empty page (not a real site)
- ✅ `5175` is not in use
- ✅ AutoSSL cert already covers the apex (it's the cPanel main domain) — no new cert needed

## 1. Pull the code

```bash
cd /opt/aiw/src           # the git checkout the compose contexts point at
git fetch origin
git checkout claude/inspiring-jemison-7483ac   # (or main, once merged)
git pull
# sanity: the new package + deploy artifacts exist
ls aiw-voice-marketing/package.json deploy/cpanel-nginx/aiworkfllow/aiworkfllow.com/proxy.conf
```

## 2. (Optional) make the hero orb live

The marketing hero/demo orb calls the real voice widget if a token is baked in;
otherwise it falls back to the in-page demo modal (perfectly fine for testing).
To go live, set the dev marketing widget token **before** building (it's baked
at build time). Edit the committed env file:

```bash
# aiw-voice-marketing/.env.staging  →  set the dev marketing tenant's widget token
#   NEXT_PUBLIC_MARKETING_WIDGET_TOKEN=wt_xxxxxxxxxxxx
# Leave blank to use the DemoModal fallback for now.
nano /opt/aiw/src/aiw-voice-marketing/.env.staging
```

## 3. Build + start the marketing container

```bash
cd /opt/aiw/dev
docker compose build marketing      # first real Next build — watch for TS/lint errors
docker compose up -d marketing
docker compose ps marketing         # expect: running, 127.0.0.1:5175->3001
```

- ✅ Build completes (`next build` prints the route table; `/sitemap.xml` + `/robots.txt` listed)
- ❌ If the build FAILS: nothing changed on the live site. Capture the error
  (`docker compose build marketing 2>&1 | tail -40`) and send it over — it's a
  code fix, then re-run this step. The running dev stack is untouched.

## 4. Verify the container locally (before exposing it)

```bash
curl -sI http://127.0.0.1:5175/                | head -5    # 200
curl -s  http://127.0.0.1:5175/ | grep -o '<title>[^<]*'    # has a real <title>
curl -s  http://127.0.0.1:5175/pricing | grep -ci 'pricing' # > 0; prices rendered server-side
curl -sI http://127.0.0.1:5175/sitemap.xml     | head -3    # 200, content-type xml
curl -sI http://127.0.0.1:5175/robots.txt      | head -3    # 200
```

- ✅ Homepage HTML contains real `<title>` + body copy (NOT a blank shell)
- ✅ `/pricing` HTML contains rendered prices (confirms SSR `fetchPlans` reached
  the API over the internal network — `INTERNAL_API_BASE_URL=http://api:3000`).
  If prices are missing but the page loads, the SSR fetch failed → check
  `docker compose logs marketing` and that `api` is healthy; the page still
  works via its graceful fallback.

## 5. Expose the apex via nginx (the only live change)

```bash
# Install the user-include (preserved across cPanel rebuilds).
sudo mkdir -p /etc/nginx/conf.d/users/aiworkfllow/aiworkfllow.com
sudo cp /opt/aiw/src/deploy/cpanel-nginx/aiworkfllow/aiworkfllow.com/proxy.conf \
        /etc/nginx/conf.d/users/aiworkfllow/aiworkfllow.com/proxy.conf

sudo nginx -t            # MUST print "syntax is ok" + "test is successful"
sudo systemctl reload nginx
```

- ✅ `nginx -t` passes. If it errors with a duplicate-location complaint, STOP
  and send the message — do not force-reload.

## 6. Public smoke tests

```bash
curl -sI https://aiworkfllow.com/                 | head -5          # 200
curl -s  https://aiworkfllow.com/ | grep -o '<title>[^<]*'           # real title
curl -sI https://www.aiworkfllow.com/             | head -5          # 301 → https://aiworkfllow.com
curl -sI https://aiworkfllow.com/pricing          | head -3          # 200
curl -sI https://aiworkfllow.com/about            | head -3          # 200
curl -sI https://aiworkfllow.com/sitemap.xml      | head -3          # 200
curl -sI https://aiworkfllow.com/robots.txt       | head -3          # 200
# bookmark-rescue: an old authed path on the apex should 308 → app subdomain
curl -sI https://aiworkfllow.com/dashboard        | grep -i location # → https://app.aiworkfllow.com/dashboard
```

Then in a browser:
- ✅ All 9 routes render (`/`, `/about`, `/how-it-works`, `/pricing`, `/investors`, `/contact`, `/security`, `/privacy-policy`, `/terms`)
- ✅ `/pricing` shows prices; the ₹/$ switch + monthly/yearly toggle work
- ✅ `/about` + `/how-it-works` motion plays
- ✅ Hero orb → opens voice widget (if token set) or the demo modal
- ✅ Nav "Log in" / "Get started" → `https://app.aiworkfllow.com/login` / `/signup`

## 7. Confirm the SPA is untouched

```bash
curl -sI https://app.aiworkfllow.com/             | head -5   # 200, SPA still served
```
- ✅ `app.aiworkfllow.com` still loads the dashboard SPA
- ✅ Log in / sign up / OAuth on the SPA still work (the backend `APP_FRONTEND_URL`
  + CORS were NOT changed for dev — the SPA never moved)

> **CORS note:** no backend change is needed for dev. `/pricing` fetches plans
> **server-side** (no browser CORS), and the embedded widget uses the
> CORS-permissive public `/api/widget/` + `/api/sessions` endpoints. The existing
> `WIDGET_ALLOWED_ORIGINS=https://app.aiworkfllow.com` stays as-is.

## 8. Rollback (if anything looks wrong)

```bash
# Remove the apex proxy → apex goes back to the cPanel default (empty), SPA + api unaffected.
sudo rm /etc/nginx/conf.d/users/aiworkfllow/aiworkfllow.com/proxy.conf
sudo nginx -t && sudo systemctl reload nginx

# Optionally stop the container (not required — nothing routes to it once nginx is reverted).
cd /opt/aiw/dev && docker compose stop marketing
```

---

## After a clean 24h on dev → Phase E (PROD)

Phase E is the bigger one because PROD currently serves the **SPA on the apex**
(`aiandhumn.com`). It requires, in one window:
1. Create `app.aiandhumn.com` subdomain (cPanel + AutoSSL) + its SPA proxy.conf (mirror dev's `app.` include → `:5183`).
2. Build + start `aiw-prod-marketing` (`docker compose build marketing && up -d marketing` in `/opt/aiw/prod`).
3. Flip the apex `users/aiandhumn/aiandhumn.com/proxy.conf` from `:5183` (SPA) → `:5185` (marketing).
4. Backend PROD env: `APP_FRONTEND_URL=https://app.aiandhumn.com` **and** add `https://app.aiandhumn.com` to `WIDGET_ALLOWED_ORIGINS` (the SPA's new origin needs CORS for its own client calls) — then `docker compose up -d api`.
5. Smoke test both `aiandhumn.com` (marketing) and `app.aiandhumn.com` (SPA + login/OAuth).
6. Submit `https://aiandhumn.com/sitemap.xml` to the existing Search Console property.

A separate PROD runbook will be written before Phase E.
