← back to Wallpapercontractor
DEPLOY.md
63 lines
# wallpapercontractor.com — deploy runbook
**Status (2026-05-31):** Full build **complete + smoke-tested locally** — Express + EJS
contractor/installer directory (home, `/contractor/:id` profiles, `/find-installer`
lead form + `POST /api/lead`, `/about`, `/privacy`). All routes 200. **Compliance:
clean** — it lists installers (companies/people), not vendor brands, so no
vendor-name leak (the lone `romo` denylist hit was a "p**romo**tional" substring
false-positive). Steve chose to ship the FULL build (overriding the DTD "thin" verdict).
> Note: this overlaps nationalpaperhangers.com (also an installer directory). Per the
> DTD verdict that's a duplicate-directory / split-SEO tradeoff Steve accepted. Consider
> a `rel=canonical` strategy or distinct positioning (commercial/bonded-crew angle —
> the current tagline) to avoid the two competing for the same queries.
## Registrar / DNS facts
- Registrar **GoDaddy** · expires 2028-11-08 · **locked + privacy ON**
- Nameservers: `ns5.afternic.com` / `ns6.afternic.com` (Afternic parking)
- Target host: Kamatera `45.61.58.125`, app port **9930**
## STEP 1 — Take it off Afternic *(Steve — account action, cannot be automated)*
GoDaddy → the domain → *List for Sale / Afternic* → remove listing (or Afternic
dashboard → My Domains → wallpapercontractor.com → delist).
## STEP 2 — Repoint DNS *(Steve — registrar action / DNS is classifier-gated)*
GoDaddy → unlock → set nameservers to **GoDaddy default** → add **A `@` → 45.61.58.125**
(+ `www` CNAME → `@`) → re-lock. Verify `dig +short wallpapercontractor.com` = `45.61.58.125`.
## STEP 3 — Deploy to Kamatera *(Claude can run once DNS resolves to 45.61.58.125)*
```sh
rsync -az --exclude node_modules --exclude .git --exclude .env \
~/Projects/wallpapercontractor/ my-server:/root/Projects/wallpapercontractor/
ssh my-server 'cd /root/Projects/wallpapercontractor && npm install --omit=dev'
ssh my-server 'cd /root/Projects/wallpapercontractor && pm2 start server.js --name wallpapercontractor && pm2 save'
# nginx vhost → 127.0.0.1:9930 (server_name wallpapercontractor.com www.…)
# certbot --nginx -d wallpapercontractor.com -d www.wallpapercontractor.com
```
## Lead handling — WIRED ✅ (2026-05-31)
`POST /api/lead` validates + captures the lead locally to `data/leads.jsonl`
(gitignored — customer PII never enters git), then returns a `redirect` into the
**nationalpaperhangers funnel**: `https://nationalpaperhangers.com/find?...` with the
project segment mapped (`residential|multifamily→luxury_residential`,
`hospitality→hospitality`, `retail→retail`; corporate/healthcare omit segment → full
breadth) plus UTM attribution + `lead_id`. Verified: residential lead → `/find?...&segment=luxury_residential` → 200 live.
### NPH ops email — WIRED ✅ (2026-05-31)
Each lead also fires a **server-to-server, fire-and-forget** POST to NPH's
`POST /api/partner-lead` (token-gated, CSRF-exempt, rate-limited; added to NPH this
session, commit e3c2ed5). NPH emails ops (`NPH_LEADS_EMAIL`, default
info@nationalpaperhangers.com) with the lead. The call has a 4s timeout and never blocks
the visitor's redirect; the local `leads.jsonl` capture is the durable record. Verified
end-to-end: contractor lead → NPH `/api/partner-lead 200` (UA `node`) → ops emailed.
### Required env on prod *(create `/root/Projects/wallpapercontractor/.env` — gitignored, NOT rsynced)*
```
PARTNER_LEAD_TOKEN=<must MATCH the value in NPH's .env> # internal shared secret
NPH_LEAD_ENDPOINT=http://127.0.0.1:9765/api/partner-lead # same-box internal (faster than public)
```
> The deploy rsync in STEP 3 must add `--exclude .env` so the local test `.env`
> (which points NPH_LEAD_ENDPOINT at the public URL) doesn't overwrite the prod one.
> If `PARTNER_LEAD_TOKEN` is unset, the ops-notify is silently skipped (lead still
> captured + redirected) — so a missing token degrades gracefully, never errors.