← back to Grant

DEPLOY.md

181 lines

# Grant App — Deployment Runbook

## Topology

```
Mac2 (local dev)
    |
    | git ls-files → rsync (SSH, port 22)
    v
Kamatera root@45.61.58.125
    /root/Projects/Grant/
    npm install + npm run build
    pm2 reload grant-app --update-env
    Next.js listening on 127.0.0.1:7450 (loopback only)
    |
    | reverse proxy
    v
nginx :443 → grant.agentabrams.com (Let's Encrypt cert)
```

- **No git remote** — deploy is rsync-based, not git push.
- **DNS**: direct-to-origin (no Cloudflare proxy). A record → 45.61.58.125.
- **PM2 process**: `grant-app` (see `ecosystem.config.js`).
- **Database**: `dw_admin@127.0.0.1:5432/grant_app` — connection string in `/root/Projects/Grant/.env.local` on prod (never committed).

---

## Routine Deploy

```sh
# From Mac2, inside the project directory:
cd /Users/stevestudio2/Projects/Grant
./deploy.sh
```

Phases: rsync → npm install → npm run build → pm2 reload → smoke-test (/ and /landing must return HTTP 200).

### Skip the build (config or asset-only push)

```sh
./deploy.sh --no-build
```

### Watch logs after a deploy

```sh
ssh root@45.61.58.125 pm2 logs grant-app --lines 80
```

---

## One-Time Public-Domain Wiring

These steps are operator-run once. `deploy.sh` never touches them.

### 1. DNS (GoDaddy)

Add an A record for `grant.agentabrams.com` pointing to `45.61.58.125`.
TTL 600 (10 min) is fine while testing; raise to 3600 afterward.

### 2. nginx vhost

```sh
# On Kamatera (as root):
cp /root/Projects/Grant/deploy/nginx/grant.agentabrams.com.conf \
     /etc/nginx/sites-available/grant.agentabrams.com

ln -sf /etc/nginx/sites-available/grant.agentabrams.com \
        /etc/nginx/sites-enabled/grant.agentabrams.com

nginx -t && systemctl reload nginx
```

Verify HTTP redirects before certbot:
```sh
curl -I http://grant.agentabrams.com/
# Expect: 301 → https://grant.agentabrams.com/
```

### 3. TLS certificate (certbot)

```sh
# On Kamatera (as root) — DNS must be propagated first:
certbot --nginx -d grant.agentabrams.com
# Follow prompts; certbot will rewrite the nginx vhost to add ssl directives.

nginx -t && systemctl reload nginx
```

Verify HTTPS:
```sh
curl -I https://grant.agentabrams.com/
# Expect: 200
```

### 4. Auto-renewal check

```sh
certbot renew --dry-run
# Should complete without errors. Certbot's systemd timer handles production renewals.
```

---

## Rollback Procedure

### Option A — Re-deploy a previous state (preferred)

On Mac2, check out the last known-good commit and re-run deploy:

```sh
cd /Users/stevestudio2/Projects/Grant
git log --oneline -10          # find the target SHA
git stash                      # save any local edits
git checkout <good-sha>        # or git checkout HEAD~1
./deploy.sh
git checkout -                 # return to previous branch/state
git stash pop
```

### Option B — Roll back on the box without Mac2

```sh
ssh root@45.61.58.125
cd /root/Projects/Grant

# pm2 gracefully drops to last-built .next/ — if the build itself is bad,
# restore from a backup snapshot (if you created one before deploying):
#   cp -r /root/Projects/Grant-backup-<date>/.next /root/Projects/Grant/.next
#   pm2 reload grant-app --update-env

# If process is crashed:
pm2 start ecosystem.config.js
```

### Emergency: previous .next/ snapshot

Before any risky deploy, snapshot the build on the box:
```sh
ssh root@45.61.58.125 "cp -r /root/Projects/Grant/.next /root/Projects/Grant-next-bak-\$(date +%Y%m%d)"
```
Restore with:
```sh
ssh root@45.61.58.125 "rm -rf /root/Projects/Grant/.next && cp -r /root/Projects/Grant-next-bak-<date> /root/Projects/Grant/.next && pm2 reload grant-app --update-env"
```

---

## Verified Facts (as of 2026-05-30)

| Item | Value |
|------|-------|
| Prod host | root@45.61.58.125 (Kamatera) |
| App dir | /root/Projects/Grant |
| pm2 name | grant-app |
| Start command | `next start -p 7450 -H 127.0.0.1` |
| Build command | `npm run build` |
| Node version | v22 |
| Port | 7450 (loopback only) |
| Public domain | grant.agentabrams.com |
| nginx conf dir | /etc/nginx/sites-available/ |
| TLS cert | /etc/letsencrypt/live/grant.agentabrams.com/ |
| Database | dw_admin@127.0.0.1:5432/grant_app |

---

## Known Gotcha — Local File Dependency

`package.json` declares:
```json
"@dw/nextjs-admin-login": "file:/tmp/dw-nextjs-admin-login-0.2.0.tgz"
```

This tarball lives at `/tmp/` on Mac2 only. On prod, `npm ci` will fail because
the file is absent. `deploy.sh` runs `npm install --no-audit --no-fund` instead,
which resolves remaining deps from the registry and skips the missing local tarball
if it is already installed in `node_modules/`.

**Fix**: When `@dw/nextjs-admin-login` is published to a private registry or
converted to a bundled dependency, update `deploy.sh` to use `npm ci`.