# 🚀 Deploying to a Bluehost VPS (Apache reverse proxy + PM2)

Next.js is a **Node server**, not PHP — Apache can't execute it. So the model is:

```
browser ──HTTPS──▶ Apache :443  ──HTTP──▶  Node/Next.js 127.0.0.1:3000  ──▶  MariaDB :3306
                (TLS, vhost, logs)        (PM2 keeps it alive)
```

Node listens on **loopback only**; Apache is the only public-facing service.

> **You need root/SSH.** This works on a Bluehost **VPS or Dedicated** plan. It does
> **not** work on Bluehost shared hosting — there is no persistent Node process there.

---

## 1. Prepare on Windows (before uploading)

Do **not** upload `node_modules/` or `.next/` — they're platform-specific and huge.
Build happens on the server.

```powershell
cd D:\Web\support
# optional sanity check that it still builds
npm run build
```

Make an upload archive (PowerShell):

```powershell
$exclude = @('node_modules','.next','.env','old','support-portal.zip','.git')
Get-ChildItem -Force | Where-Object { $exclude -notcontains $_.Name } |
  Compress-Archive -DestinationPath support-portal-deploy.zip -Force
```

Upload it with SCP (or WinSCP / cPanel File Manager):

```powershell
scp support-portal-deploy.zip root@YOUR-VPS-IP:/root/
```

---

## 2. Server prerequisites (one time, as root)

```bash
ssh root@YOUR-VPS-IP
```

**Node.js 20+** (the seed script uses `node --env-file`, which needs Node 20):

```bash
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -    # CentOS / AlmaLinux (Bluehost default)
yum install -y nodejs
# Ubuntu/Debian:  curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt install -y nodejs
node -v && npm -v
```

**PM2** (the "npm service" that keeps Next.js running):

```bash
npm install -g pm2
```

**MariaDB** — Bluehost VPS usually has it via cPanel already:

```bash
systemctl enable --now mariadb   # if not already running
mysql -u root -p
```

```sql
CREATE DATABASE support_portal CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'support'@'localhost' IDENTIFIED BY 'A-STRONG-PASSWORD';
GRANT ALL PRIVILEGES ON support_portal.* TO 'support'@'localhost';
FLUSH PRIVILEGES;
```

> On cPanel, create the DB + user in **cPanel → MySQL® Databases** instead — the real
> names will be prefixed (`cpuser_support_portal`, `cpuser_support`). Use those.

**Apache modules** — this is the part people miss:

```bash
# CentOS/AlmaLinux (incl. cPanel EA4): usually already compiled in. Verify:
httpd -M | grep -E 'proxy_module|proxy_http|headers|rewrite|ssl'
# Ubuntu/Debian:
a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl && systemctl restart apache2
```

On **WHM**: *EasyApache 4 → Customize → Apache Modules* → tick `mod_proxy`,
`mod_proxy_http`, `mod_proxy_wstunnel`, `mod_headers`, `mod_rewrite` → Provision.

**SELinux** (CentOS/Alma — otherwise Apache gets `503 Permission denied`):

```bash
setsebool -P httpd_can_network_connect 1
```

---

## 3. Deploy the app

```bash
mkdir -p /var/www/support-portal /var/log/support-portal
cd /var/www/support-portal
unzip /root/support-portal-deploy.zip -d .

cp .env.production.example .env
nano .env          # fill in DATABASE_URL, JWT_SECRET, GEMINI_API_KEY, NEXT_PUBLIC_BASE_URL
chmod 600 .env
```

Generate a real secret:

```bash
openssl rand -hex 48        # paste into JWT_SECRET
```

> ⚠️ **`NEXT_PUBLIC_BASE_URL` is baked in at build time.** Set it to your real
> `https://support.yourdomain.com` *before* running the build, or the widget snippet
> in Admin → Settings will still say `localhost`. Change it later → rebuild.

Install, create tables, seed demo data, build:

```bash
npm ci
npx prisma generate
npx prisma db push
npm run db:seed        # ← ONLY the first time. Re-running duplicates demo data.
npm run build
```

> **Low-RAM VPS (1 GB)?** `next build` can get OOM-killed. Add swap first:
> ```bash
> fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
> echo '/swapfile none swap sw 0 0' >> /etc/fstab
> ```

---

## 4. Start it as a service

```bash
cd /var/www/support-portal
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup          # prints a command — run that command to survive reboots
pm2 status
```

Confirm Node itself is answering **before** touching Apache:

```bash
curl -I http://127.0.0.1:3000/          # expect: HTTP/1.1 200 OK
```

*(Prefer systemd over PM2? Use `deploy/support-portal.service` instead — instructions
are in the file header. Run one or the other, never both.)*

---

## 5. Wire up Apache

```bash
cp deploy/apache-support-portal.conf /etc/httpd/conf.d/support-portal.conf
nano /etc/httpd/conf.d/support-portal.conf     # replace support.example.com everywhere
apachectl configtest && systemctl reload httpd
```

**On cPanel/WHM**, editing `/etc/httpd/conf.d/` works but cPanel owns the domain's
vhost — use the include directory so updates don't wipe it:

```bash
mkdir -p /etc/apache2/conf.d/userdata/ssl/2_4/CPUSER/support.yourdomain.com
cp deploy/apache-support-portal.conf \
   /etc/apache2/conf.d/userdata/ssl/2_4/CPUSER/support.yourdomain.com/proxy.conf
# strip the <VirtualHost> wrapper lines from that copy — cPanel supplies the vhost;
# keep only the ProxyPass / ProxyPassReverse / RequestHeader / Rewrite directives.
/scripts/ensure_vhost_includes --all-users
systemctl restart httpd
```

**TLS:** in WHM, AutoSSL covers the subdomain automatically. Without cPanel:

```bash
certbot --apache -d support.yourdomain.com
```

Point the DNS `A` record for `support.yourdomain.com` at the VPS IP first.

---

## 6. Verify

```bash
curl -I https://support.yourdomain.com/
curl -s https://support.yourdomain.com/api/kb?project=fedposts | head -c 200
```

Then in a browser:

1. `https://support.yourdomain.com/` → the 6-product portal home
2. `/p/fedposts` → help center loads with brand colour
3. `/login` → `admin@support.local` / `admin123` → `/admin` dashboard renders
4. **Change every demo password immediately** (Admin → Agents), and delete or
   rename the `demo@user.com` account.

---

## 7. Updating later

```bash
cd /var/www/support-portal
# upload the new files over the old ones, then:
bash deploy/deploy.sh
```

That script runs `npm ci` → `prisma generate` → `prisma db push` → `npm run build`
→ `pm2 reload`, and curls the app at the end. It deliberately does **not** re-seed.

---

## Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `503 Service Unavailable` | Node isn't running (`pm2 status`), or SELinux — run `setsebool -P httpd_can_network_connect 1` |
| `502` / blank page | Wrong port in the vhost. `ss -ltnp \| grep 3000` to see what Node bound to |
| Apache serves a cPanel default page | Your vhost include wasn't picked up — `/scripts/ensure_vhost_includes --all-users`, then `httpd -S` to see which vhost wins |
| CSS missing / 404 on `/_next/static/...` | A stale `.next` from a different build. `rm -rf .next && npm run build && pm2 reload support-portal` |
| Login says "session ended" straight away | Cookie is `secure` in production — you must be on **HTTPS**, and Apache must send `X-Forwarded-Proto https` |
| `PrismaClientInitializationError` | `DATABASE_URL` wrong, or special characters in the password not URL-encoded (`@` → `%40`) |
| Widget iframe blank on your product site | `NEXT_PUBLIC_BASE_URL` was wrong at build time → fix `.env`, rebuild |
| AI chat replies "not fully set up" | `GEMINI_API_KEY` empty — that's the intentional fallback, add the key and `pm2 reload` |
| `next build` killed silently | Out of RAM — add swap (see step 3) |

Logs:

```bash
pm2 logs support-portal --lines 100
tail -f /etc/httpd/logs/error_log          # or /var/log/apache2/error.log
```
