feat(deploy): hardcode DB connection + owner email (no .env), update docs

Team decision: production DB connection and first-boot owner email are hardcoded in the app instead of a .env file, matching the box's other apps. Gated to NODE_ENV=production so dev/tests keep using SQLite (389 tests green). Committed value is the CHANGE_ME_PASSWORD placeholder; the real password is set only on the box, never pushed. server.ts: HARDCODED_DATABASE_URL. seed.ts: OWNER_EMAIL default. docker-compose.yml: drop env_file. docs: PATHS-AND-NAMES.md + .env.example updated.

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

@ -1,6 +1,11 @@
# SiMS HQ console — environment. Copy to `.env` (gitignored) and fill in. # SiMS HQ console — environment. Copy to `.env` (gitignored) and fill in.
# Nothing here is required to BOOT (the app runs with none set), but each unlocks # Nothing here is required to BOOT (the app runs with none set), but each unlocks
# a capability. Never commit the filled-in file. # a capability. Never commit the filled-in file.
#
# NOTE (current deploy): on the company box the DB connection and owner email are
# HARDCODED in the app (server.ts `HARDCODED_DATABASE_URL`, seed.ts `OWNER_EMAIL`), so
# no .env file is used there — see deploy/PATHS-AND-NAMES.md. The vars below still work
# as OVERRIDES if you do set them (e.g. DATABASE_URL takes precedence when present).
# --- Core --- # --- Core ---
# Where the SQLite database lives (created on first boot). On the AWS box use an # Where the SQLite database lives (created on first boot). On the AWS box use an

@ -12,7 +12,7 @@ import { SCHEDULE_DEFAULTS, type ScheduleRuleKind } from './repos-reminders'
// to log in → can't open the Employees page). Set HQ_OWNER_EMAIL at deploy so the first // to log in → can't open the Employees page). Set HQ_OWNER_EMAIL at deploy so the first
// employee IS the real owner; all other staff are then created in the owner-only // employee IS the real owner; all other staff are then created in the owner-only
// Employees page. Neutral fallback keeps any company address out of the code. // Employees page. Neutral fallback keeps any company address out of the code.
const OWNER_EMAIL = process.env['HQ_OWNER_EMAIL']?.trim() || 'owner@sims.local' const OWNER_EMAIL = process.env['HQ_OWNER_EMAIL']?.trim() || 'simssoftware13@gmail.com'
const SETTING_DEFAULTS: Record<string, string> = { const SETTING_DEFAULTS: Record<string, string> = {
'company.name': 'Tecnostac', 'company.name': 'Tecnostac',

@ -144,8 +144,15 @@ export async function startServer(port: number, opts: { scheduler?: boolean } =
// shares the proxy's IP and per-IP rate limiting collapses to one global bucket. // shares the proxy's IP and per-IP rate limiting collapses to one global bucket.
// The proxy MUST strip client-supplied X-Forwarded-For so it cannot be spoofed. // The proxy MUST strip client-supplied X-Forwarded-For so it cannot be spoofed.
app.set('trust proxy', 1) app.set('trust proxy', 1)
// D19 engine selection: DATABASE_URL → Postgres (production); absent → SQLite file. // Hardcoded production DB connection (team decision — used instead of a .env DATABASE_URL,
const pgUrl = process.env['DATABASE_URL'] ?? '' // to match how the other apps on this box are configured). To change the database, edit
// HARDCODED_DATABASE_URL below and rebuild the image. Gated to NODE_ENV=production so dev
// and the test suite keep selecting SQLite (an unset/empty DATABASE_URL) and stay green.
// ⚠️ Once the real password is filled in, this file holds a secret — do not push the
// filled-in value to a repo others can read.
const HARDCODED_DATABASE_URL = 'postgres://postgres:CHANGE_ME_PASSWORD@host.docker.internal:5432/hq'
// D19 engine selection: DATABASE_URL (or the hardcoded prod value) → Postgres; else SQLite.
const pgUrl = process.env['DATABASE_URL'] || (process.env['NODE_ENV'] === 'production' ? HARDCODED_DATABASE_URL : '')
const db = pgUrl !== '' ? await openPgDb(pgUrl) : openDb(process.env['HQ_DATA_DIR']) const db = pgUrl !== '' ? await openPgDb(pgUrl) : openDb(process.env['HQ_DATA_DIR'])
if (pgUrl !== '') console.log('[db] engine: postgres') if (pgUrl !== '') console.log('[db] engine: postgres')
await seedIfEmpty(db) // first boot prints the owner password once await seedIfEmpty(db) // first boot prints the owner password once

@ -27,7 +27,7 @@ other apps. (D20 target, revised: `.in` not `.com`, Docker not RDS — see notes
| Path | What it is | | 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` | **App folder** — the whole project (owned by `ec2-user`). Docker builds from here. |
| `/usr/share/nginx/sims-hq/apps/hq/.env` | **Environment file** — DB URL + secrets (chmod 600). | | `/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/deploy/sims.hq.conf` | nginx vhost **source** (copied into nginx below). | | `/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/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). | | `/etc/ssl/sims/sims.crt` · `/etc/ssl/sims/sims.key` | Wildcard TLS cert for `*.simssoftware.in` (shared). |
@ -55,17 +55,26 @@ other apps. (D20 target, revised: `.in` not `.com`, Docker not RDS — see notes
| Superuser (admin) | `postgres` (connect via `sudo docker exec -it accurate-postgres psql -U postgres`) | | Superuser (admin) | `postgres` (connect via `sudo docker exec -it accurate-postgres psql -U postgres`) |
| HQ database | `hq` (owned by `postgres`) | | HQ database | `hq` (owned by `postgres`) |
| HQ login | `postgres` — the shared superuser. Every app on this box uses it; there are **no** per-app roles (checked 2026-07-18). A dedicated `hq` role is a future hardening. | | HQ login | `postgres` — the shared superuser. Every app on this box uses it; there are **no** per-app roles (checked 2026-07-18). A dedicated `hq` role is a future hardening. |
| Connection string | `postgres://postgres:<postgres-password>@host.docker.internal:5432/hq` | | Connection string | `postgres://postgres:<postgres-password>@host.docker.internal:5432/hq`**hardcoded** in `apps/hq/src/server.ts` (`HARDCODED_DATABASE_URL`), not a `.env` |
## App environment (`apps/hq/.env`) ## Configuration — hardcoded in code (no `.env`)
| Key | Value | Team decision (for now; may move to `.env`/secrets later): config is **hardcoded in the
|---|---| source**, matching the box's other apps. `NODE_ENV=production` (set in the Dockerfile)
| `HQ_PORT` | `5182` | activates the hardcoded values; dev and the test suite are unaffected and still use SQLite.
| `HQ_OWNER_EMAIL` | `simssoftware13@gmail.com` (first-boot owner login) |
| `DATABASE_URL` | `postgres://postgres:<postgres-password>@host.docker.internal:5432/hq` | | Setting | Where | Value |
| `SIMS_PIN_PEPPER` | generated on box — **set once, never change** | |---|---|---|
| `HQ_SECRET_KEY` | generated on box (needed before Gmail) | | 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** |
| 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).
## Owner login ## Owner login

@ -10,7 +10,8 @@ services:
image: sims-hq image: sims-hq
container_name: sims-hq container_name: sims-hq
restart: unless-stopped restart: unless-stopped
env_file: apps/hq/.env # No env_file: DB connection + owner email are hardcoded in the app (server.ts / seed.ts),
# matching the box's other apps. NODE_ENV=production (set in the Dockerfile) activates them.
# Bind only to localhost — the host nginx terminates TLS and proxies here. # Bind only to localhost — the host nginx terminates TLS and proxies here.
ports: ports:
- "127.0.0.1:5182:5182" - "127.0.0.1:5182:5182"

Loading…
Cancel
Save