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`, - Local dev Postgres: PostgreSQL 17 Windows service, superuser `postgres`, role `hq`,
databases `hq` (runtime) + `hq_test` (integration suite). Prod = RDS at go-live, same databases `hq` (runtime) + `hq_test` (integration suite). Prod = RDS at go-live, same
`DATABASE_URL` mechanism. `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 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 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) ## What you provide (the only blockers)
1. **A server** — a small AWS EC2 instance (t3.small is plenty for 5 users), Ubuntu 22.04+, 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) ## 4. Put HTTPS in front (never expose Node directly)
Reverse-proxy 443 → `localhost:5182` with Caddy (simplest) or nginx + certbot: Reverse-proxy 443 → `localhost:5182` with Caddy (simplest) or nginx + certbot:
``` ```
# Caddyfile # /etc/caddy/Caddyfile
hq.yourdomain.com { hq.simssoftware.com {
reverse_proxy localhost:5182 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 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 `/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) ## 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 - **`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 — 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. a client email carrying a dead relative link is worse than no email.
- **`quote.followup.policy` setting**: `manual` (default — nudges queue on the dashboard - **`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 - **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. leads route to the right person's queue; unassigned rows stay visible to owner/manager.
## 7. The Postgres switch (before or at go-live) ## 7. Production database — RDS Postgres (do this FIRST; D19 delivered the switch)
Decision D15 locks Postgres as the production engine. It is a **dedicated engineering task, The dual-engine work is **done** (D19): the app selects its engine from `DATABASE_URL`
not a config flag** — the repositories move from synchronous SQLite to async Postgres unset → SQLite (dev), set → Postgres. Migrations apply automatically from
behind the same interfaces, validated against your RDS instance. Provide the RDS endpoint + `migrations-pg.ts` on boot. Go-live order (so data is born in Postgres, never migrated):
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 1. Provision RDS PostgreSQL 17 (db.t4g.micro is plenty), same VPC as the EC2, **not**
migrates engines mid-life. Until then the app runs correctly on SQLite with the backups above. 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 ## Health & operations
- `GET /api/health``{ ok: true }` for your uptime monitor. - `GET /api/health``{ ok: true }` for your uptime monitor.

Loading…
Cancel
Save