From bb8dfc3b97f4c30b9a1e245f4e6a69968d7b7b07 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Fri, 17 Jul 2026 18:56:57 +0530 Subject: [PATCH] =?UTF-8?q?docs:=20D20=20=E2=80=94=20hq.simssoftware.com?= =?UTF-8?q?=20on=20the=20company=20EC2;=20runbook=20gets=20the=20decided?= =?UTF-8?q?=20target=20+=20RDS-first=20sequencing=20(D19=20switch=20delive?= =?UTF-8?q?red)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/06-DECISIONS.md | 18 +++++++++++++++ docs/DEPLOY-HQ.md | 55 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/docs/06-DECISIONS.md b/docs/06-DECISIONS.md index b35d921..ad370ae 100644 --- a/docs/06-DECISIONS.md +++ b/docs/06-DECISIONS.md @@ -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. diff --git a/docs/DEPLOY-HQ.md b/docs/DEPLOY-HQ.md index 48e8b01..5e89396 100644 --- a/docs/DEPLOY-HQ.md +++ b/docs/DEPLOY-HQ.md @@ -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 ``, 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:@: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.