← 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`.