docs: D20 — hq.simssoftware.com on the company EC2; runbook gets the decided target + RDS-first sequencing (D19 switch delivered)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 4 days ago
parent 30a1ea1c0a
commit bb8dfc3b97

@ -289,3 +289,21 @@ is born in Postgres and nothing is ever migrated. Spec:
- Local dev Postgres: PostgreSQL 17 Windows service, superuser `postgres`, role `hq`,
databases `hq` (runtime) + `hq_test` (integration suite). Prod = RDS at go-live, same
`DATABASE_URL` mechanism.
## D20 — HQ hosting target: hq.simssoftware.com on the company AWS box (2026-07-17)
Founder call. The console goes live at **`https://hq.simssoftware.com`**:
- **DNS stays at GoDaddy** (where simssoftware.com lives) — one `A` record `hq` → the
EC2 Elastic IP. No nameserver moves, no Route 53 migration; the rest of the domain
(www, mail) is untouched.
- **The existing company EC2 hosts it** as its own section (`/opt/sims-hq`), beside
whatever else the box serves — no new instance for v1. TLS via Caddy (or a server
block in the box's existing nginx), pm2 from the repo root, only 80/443 public.
- **Sequencing: database first, then the server section.** Provision RDS Postgres, set
`DATABASE_URL` (D19's switch), boot + migrate, run the APEX cutover into Postgres —
and only then wire DNS/TLS and hand the URL to staff. Production data is born in
Postgres; the SQLite dev DB is never migrated.
- Runbook updated with the exact configs (GoDaddy record, Caddyfile/nginx block, RDS
sizing + security-group shape, pm2 invocation): `DEPLOY-HQ.md` §0 and §7. The founder
has hosted this way before — the runbook now carries the fiddly configuration detail
so setup on the box is a checklist, not archaeology.

@ -2,7 +2,23 @@
How to put the internal console on your AWS box for real use. The store product is
unaffected (it stays local, D14); this is the vendor-side cloud app doc 11 always
prescribed. Today the engine is SQLite; the Postgres switch is called out where it lands.
prescribed. Engine: SQLite for dev, **Postgres in production via `DATABASE_URL`**
(D19 — the dual-engine switch is delivered; see §7).
## 0. The decided target (D20)
| Piece | Decision |
|---|---|
| URL | **`https://hq.simssoftware.com`** (subdomain of the company domain) |
| DNS | **GoDaddy** — simssoftware.com stays at the registrar; one A record added |
| Server | The existing company **AWS EC2** — HQ gets its own section on the box (`/opt/sims-hq`), beside whatever else runs there |
| TLS | Caddy reverse proxy (auto Let's Encrypt) — or a server block in the box's existing nginx if one already fronts other sites |
| Process | pm2, started **from the repo root** |
| Prod data | **Born in RDS Postgres** — provision RDS first, set `DATABASE_URL`, THEN run the APEX cutover import straight into it. Sequencing: database first, app section second, DNS/TLS last. |
**GoDaddy DNS (5 minutes):** My Products → simssoftware.com → DNS → Add record:
Type `A`, Name `hq`, Value `<EC2 Elastic IP>`, TTL 600 (raise to 1h once stable).
Nothing else on the domain changes; existing www/mail records are untouched.
## What you provide (the only blockers)
1. **A server** — a small AWS EC2 instance (t3.small is plenty for 5 users), Ubuntu 22.04+,
@ -49,10 +65,17 @@ immediately). The Gmail-disconnected banner is expected until step 6.
## 4. Put HTTPS in front (never expose Node directly)
Reverse-proxy 443 → `localhost:5182` with Caddy (simplest) or nginx + certbot:
```
# Caddyfile
hq.yourdomain.com {
# /etc/caddy/Caddyfile
hq.simssoftware.com {
reverse_proxy localhost:5182
}
# If the box already runs nginx for other sites, use a server block instead:
# server {
# listen 443 ssl; server_name hq.simssoftware.com;
# # certbot --nginx -d hq.simssoftware.com issues the cert
# location / { proxy_pass http://127.0.0.1:5182; proxy_set_header Host $host;
# proxy_set_header X-Forwarded-For $remote_addr; }
# }
```
Lock the EC2 security group so only 443 is public; 5182 stays localhost-only. The public
`/share/:token` links are served through this same proxy — that's intended (they're the
@ -79,7 +102,7 @@ Do a **restore drill** once: copy a backup to a scratch box and boot the app aga
## 6b. Funnel slice knobs (D16 — set these at go-live)
- **`share.base_url` setting** (required before quote follow-ups can email): set it to the
public origin, e.g. `https://hq.yourdomain.com`, via the settings API or a one-line SQL
public origin, i.e. `https://hq.simssoftware.com`, via the settings API or a one-line SQL
insert into `setting`. The escalating quote follow-up **refuses to send** without it —
a client email carrying a dead relative link is worse than no email.
- **`quote.followup.policy` setting**: `manual` (default — nudges queue on the dashboard
@ -95,13 +118,23 @@ Do a **restore drill** once: copy a backup to a scratch box and boot the app aga
- **Account owners**: assign an owner on each client (Client 360 → Account owner) so
leads route to the right person's queue; unassigned rows stay visible to owner/manager.
## 7. The Postgres switch (before or at go-live)
Decision D15 locks Postgres as the production engine. It is a **dedicated engineering task,
not a config flag** — the repositories move from synchronous SQLite to async Postgres
behind the same interfaces, validated against your RDS instance. Provide the RDS endpoint +
a dedicated empty `hq` database and a scoped user (`HQ_PG_URL`), and it is built and tested
against that target, then the 300 clients import directly into Postgres — data never
migrates engines mid-life. Until then the app runs correctly on SQLite with the backups above.
## 7. Production database — RDS Postgres (do this FIRST; D19 delivered the switch)
The dual-engine work is **done** (D19): the app selects its engine from `DATABASE_URL`
unset → SQLite (dev), set → Postgres. Migrations apply automatically from
`migrations-pg.ts` on boot. Go-live order (so data is born in Postgres, never migrated):
1. Provision RDS PostgreSQL 17 (db.t4g.micro is plenty), same VPC as the EC2, **not**
publicly accessible; security group allows 5432 from the EC2's group only.
2. Create database `hq` + a scoped `hq` role; put
`DATABASE_URL=postgres://hq:<pw>@<rds-endpoint>:5432/hq` in `apps/hq/.env`.
3. Boot once — migrations run, first-boot seed prints the owner password.
4. **Then** run the APEX cutover UI (stage → verify → commit): the 300 clients import
directly into Postgres.
Known nuance (D19): client search is case-sensitive on Postgres until repos move to
`LOWER(...) LIKE LOWER(?)`. SQLite + `data/hq.db` remains the dev/test path; the §5
sqlite backup script is superseded in prod by **RDS automated snapshots** (enable 7-day
retention) — keep the S3 dump cron as belt-and-braces via `pg_dump` if desired.
## Health & operations
- `GET /api/health``{ ok: true }` for your uptime monitor.

Loading…
Cancel
Save