docs(deploy): record go-live (D39) and correct the deploy references

deploy/PATHS-AND-NAMES.md now reflects reality: Docker Compose V2 isn't
installed on the box (raw docker build/run instead), DB connection lives in
db-pg.ts not server.ts, real password is committed (accepted, not a
CHANGE_ME_PASSWORD placeholder). docs/DEPLOY-HQ.md marked superseded — it
described an RDS/GoDaddy/.com plan that isn't what shipped. STATUS.md and
06-DECISIONS.md (D39) record the actual go-live: real data carried over from
local Postgres (not "born empty + APEX import"), and the npm/cli#4828
Dockerfile fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
main
Thomas Joise 21 hours ago
parent 8456e45206
commit 7e139d91b8

@ -61,6 +61,11 @@ excluded) and a *settled → document* column — the money engine is unchanged.
**stalled-onboarding** query, an owner **onboarding-template editor**, and mobile fixes (the record
header stacks instead of overlapping the title).
**2026-07-21 (D39): live.** HQ is deployed at **`https://hq.simssoftware.in`** — Docker on the
existing company EC2 box, the shared `accurate-postgres` container, real client data carried
over (273 clients, 14 staff, 156 documents, 1826 tickets). See `deploy/PATHS-AND-NAMES.md` for
the accurate deployment reference and `docs/06-DECISIONS.md` D39 for what deviated from plan.
## Verification state

@ -5,6 +5,10 @@ If a name here disagrees with a command you're about to run, this file wins. Ser
existing company **Amazon Linux 2** EC2 box; HQ runs as its own Docker container beside the
other apps. (D20 target, revised: `.in` not `.com`, Docker not RDS — see notes at bottom.)
> ✅ **Live** at `https://hq.simssoftware.in` since 2026-07-21 (D39 in `docs/06-DECISIONS.md`).
> `docs/DEPLOY-HQ.md` describes an earlier RDS/GoDaddy/`.com` plan that was **not** what
> shipped — this file is the accurate one.
## Identity
| Thing | Value |
@ -27,7 +31,7 @@ other apps. (D20 target, revised: `.in` not `.com`, Docker not RDS — see notes
| Path | What it is |
|---|---|
| `/usr/share/nginx/sims-hq` | **App folder** — the whole project (owned by `ec2-user`). Docker builds from here. |
| `/usr/share/nginx/sims-hq/apps/hq/src/server.ts` | Holds the **hardcoded DB connection** (`HARDCODED_DATABASE_URL`) — no `.env` is used. |
| `/usr/share/nginx/sims-hq/apps/hq/src/db-pg.ts` | Holds the **hardcoded DB connection** (`HARDCODED_DATABASE_URL`) — moved here from `server.ts` (pre-ship audit FIX A, one shared `resolveDbUrl()`). No `.env` is used. |
| `/usr/share/nginx/sims-hq/deploy/sims.hq.conf` | nginx vhost **source** (copied into nginx below). |
| `/etc/nginx/conf.d/sims.hq.conf` | **Live nginx vhost** (must end in `.conf`; `sudo` to edit). |
| `/etc/ssl/sims/sims.crt` · `/etc/ssl/sims/sims.key` | Wildcard TLS cert for `*.simssoftware.in` (shared). |
@ -65,16 +69,19 @@ activates the hardcoded values; dev and the test suite are unaffected and still
| Setting | Where | Value |
|---|---|---|
| DB connection | `apps/hq/src/server.ts` → `HARDCODED_DATABASE_URL` | `postgres://postgres:<password>@host.docker.internal:5432/hq`**replace `CHANGE_ME_PASSWORD` on the box, then rebuild** |
| DB connection | `apps/hq/src/db-pg.ts` → `HARDCODED_DATABASE_URL` | `postgres://postgres:inv123@host.docker.internal:5432/hq` |
| Owner login email | `apps/hq/src/seed.ts``OWNER_EMAIL` default | `simssoftware13@gmail.com` |
| Port | `HQ_PORT` env, else default | `5182` |
| PIN pepper | *(unset)* | password hashes are un-peppered — a future hardening |
| Gmail secret key | *(unset)* | set only when connecting Gmail |
> ⚠️ **To change the DB later:** edit `HARDCODED_DATABASE_URL` in `server.ts` on the box, then
> `sudo docker compose up -d --build` (a rebuild, not just a restart). The real password lives
> only in the box's copy — **never push the filled-in `server.ts` to Gitea** (the repo keeps
> the `CHANGE_ME_PASSWORD` placeholder).
> ⚠️ **Deviation from the original plan (accepted, D39):** the real password is committed to
> Gitea, not kept as a `CHANGE_ME_PASSWORD` placeholder — Gitea is internal/single-user, so
> this was accepted for now. Rotating this password is the top item whenever the hardening
> pass happens; whoever does it must also update the value in `db-pg.ts` and rebuild.
>
> **To change the DB later:** edit `HARDCODED_DATABASE_URL` in `db-pg.ts`, then rebuild
> (see Everyday commands below).
## Owner login
@ -85,14 +92,24 @@ activates the hardcoded values; dev and the test suite are unaffected and still
## Everyday commands (run from `/usr/share/nginx/sims-hq`)
> ⚠️ **Docker Compose V2 is not installed on this box** (only the standalone `docker-compose`
> 1.21.0 from 2018, too old for this file's `extra_hosts: host-gateway`). `docker-compose.yml`
> is kept as the source of truth for what the container needs, but it's run manually via plain
> `docker build`/`docker run` below until the Compose V2 plugin is installed (see install
> command in `docs/06-DECISIONS.md` D39, or run `docker compose version` to check if someone
> already added it).
| Do this | Command |
|---|---|
| Build + start | `sudo docker compose up -d --build` |
| Build | `sudo docker build -t sims-hq .` |
| Start (first time / after removing the old container) | `sudo docker run -d --name sims-hq --restart unless-stopped -p 127.0.0.1:5182:5182 --add-host host.docker.internal:host-gateway sims-hq` |
| Rebuild + replace (code changed) | `sudo docker stop sims-hq && sudo docker rm sims-hq`, then Build + Start above |
| View logs | `sudo docker logs sims-hq` (`-f` to follow) |
| Restart | `sudo docker compose restart hq` |
| Stop | `sudo docker compose down` |
| Restart (no code change) | `sudo docker restart sims-hq` |
| Stop | `sudo docker stop sims-hq` |
| Health check | `curl -s localhost:5182/api/health``{"ok":true}` |
| nginx test + reload | `sudo nginx -t && sudo systemctl reload nginx` |
| Deploy code changes | WinSCP-copy the whole project folder (except `node_modules`, `dist`, `.git`, `.env`, `data`, `*.log`) into `/usr/share/nginx/sims-hq`, overwriting — this box doesn't use `git pull` |
## Notes / decisions
- Domain is **`.in`** (not `.com`): the wildcard cert is `*.simssoftware.in` and every other

@ -584,6 +584,17 @@ scripts (`resolveDbUrl`, D19) and logs the resolved host/db before dumping — t
`scripts/backup.mjs` read `DATABASE_URL` straight off the environment and so could never
reach production, where that variable is not exported.
**Working invocation (final review FIX 3):** `resolveDbUrl` only returns the hardcoded
production connection when `NODE_ENV=production` — and an operator's own interactive shell on
the prod box does NOT have that set (only the deployed service's env does), so a bare
`npm run backup` typed by hand still resolves nothing and refuses. Run the backup with ONE of:
- `NODE_ENV=production npm run backup` — matches how the deployed service resolves it, or
- `DATABASE_URL=postgres://… npm run backup` — explicit target, overrides resolution.
The refusal error itself now names both of these exact commands, so an operator who hits it
can act without reading source. This does not change `resolveDbUrl`'s production semantics and
does not embed the connection string anywhere new.
## D34 — Employee master import + invited/first-login flag (2026-07-19)
Imported the 11-row employee master (`empmaster.xlsx`) into `staff_user` via
`scripts/import-employees.ts`: username = first-name slug, default password `<username>@123`
@ -655,3 +666,39 @@ Also: a **stalled-onboarding** query for the dashboard/MIS, and a mobile fix so
stacks instead of overlapping the client name.
Spec + plans under `docs/superpowers/`.
## D39 — Go-live: hq.simssoftware.in ✅ DELIVERED (2026-07-20/21)
HQ is live in production at **`https://hq.simssoftware.in`**, on the existing company EC2 box,
superseding the RDS/GoDaddy/`.com` plan in `docs/DEPLOY-HQ.md` (that doc predates the actual
setup — `deploy/PATHS-AND-NAMES.md` is the accurate reference; DEPLOY-HQ.md is marked superseded).
**Deployed as built, three deviations from the original runbook:**
- **Docker Compose V2 isn't installed** on the box (only the old standalone `docker-compose`
1.21.0, too old for this file's `extra_hosts: host-gateway`). Deployed via plain
`docker build` + `docker run --add-host host.docker.internal:host-gateway ...` instead —
the exact equivalent of `docker compose up -d --build`. `deploy/PATHS-AND-NAMES.md`
documents both forms; installing the Compose v2 plugin is a later nice-to-have, not a blocker.
- **`Dockerfile` drops `package-lock.json` before `npm install`** (new step). A Windows-generated
lockfile hit npm/cli#4828 — `@rollup/rollup-linux-x64-gnu` failed to resolve during the
container build even on a clean install. Removing the lockfile forces same-platform resolution.
- **Real data was carried over, not "born empty + APEX import."** The working dataset turned out
to live in a local Postgres instance (273 clients, 14 staff, 156 documents, 1826 tickets, etc.),
not the local SQLite file assumed by D19/D20 — SQLite only had 2 leftover test accounts. A
Postgres→Postgres copy (`apps/hq/scripts/migrate-pg-to-pg.ts`) wiped the target `hq` database's
schema, rebuilt it via the app's own migrations, then copied every table across in one
transaction (FK checks disabled for the load via `session_replication_role=replica`). The
APEX CSV importer is no longer needed for the initial 300 clients — they're already in.
(`apps/hq/scripts/migrate-sqlite-to-pg.ts` was the first attempt, against the wrong source;
left in the repo, harmless — it refuses to run against a non-empty target.)
**Known accepted debt, deferred on purpose (owner decision):**
- `HARDCODED_DATABASE_URL` (`db-pg.ts`) carries the real Postgres password in git history —
Gitea is internal/single-user, so this is accepted for now; rotating it is the top item
whenever the hardening pass happens.
- Postgres port 5432 is open to the public internet (`0.0.0.0`), not restricted to the EC2
security group — same deferred bucket.
- `SIMS_PIN_PEPPER` is still unset (login hashes un-peppered) — same bucket.
- Shared `postgres` superuser role, not a dedicated least-privilege `hq` role — same bucket.
**Still open, not yet done:** `share.base_url` setting, and a backup cron for the shared
Postgres (`pg_dump` — no RDS automated snapshots here).

@ -1,5 +1,11 @@
# Go-Live Runbook — HQ ops console (`apps/hq`)
> ⚠️ **SUPERSEDED (2026-07-21, D39).** HQ is live at `https://hq.simssoftware.in` — Docker on
> the existing EC2 box against the shared `accurate-postgres` container, **not** the RDS +
> GoDaddy + `.com` plan below. For the actual setup, commands, and paths, use
> **`deploy/PATHS-AND-NAMES.md`** instead. This file is kept for its Postgres-engine background
> (§7) but its go-live steps (§0§6, §6b) do not reflect what shipped.
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. Engine: SQLite for dev, **Postgres in production via `DATABASE_URL`**

Loading…
Cancel
Save