# Hosting AssetTrack on cPanel "Setup Node.js App" (Passenger) — 1 GB plan, subdomain

This is the runbook for release 12.3.18. Everything the app does to behave well behind Phusion Passenger is listed in
`CHANGES-12.3.18.md`; this file is what *you* do.

## 1. One-time setup

1. **Subdomain** — cPanel → Domains → create e.g. `inventory.yourdomain.com`. Turn on AutoSSL (HTTPS). Leave its document root empty;
   the Node.js app will own the subdomain.
2. **Database** — cPanel → MySQL Databases: create the database and a user, give the user *All Privileges*.
3. **Upload the zip** to `/home/<cpanel-user>/assettrack/` (a folder **outside** `public_html`) and extract it there.
4. **Setup Node.js App → Create Application**
   * Node.js version: **20.x** (18.x also works)
   * Application mode: **Production**
   * Application root: `assettrack`
   * Application URL: the subdomain (leave the path empty)
   * Application startup file: `server.js`
5. **Environment variables** (same panel) — add at least:

   | Name | Value |
   |---|---|
   | `NODE_ENV` | `production` |
   | `DB_HOST` / `DB_USER` / `DB_PASS` / `DB_NAME` | from step 2 (`DB_HOST` is normally `localhost`) |
   | `SESSION_SECRET` | 64 random hex characters (`node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`) |
   | `NODE_OPTIONS` | `--max-old-space-size=400` |

   Alternatively copy `.env.example` to `.env` (the app now reads it itself). Values in the panel win over the file.
   `NODE_OPTIONS` must be in the panel — Node reads it before any file can be loaded.
6. **Run NPM Install**, then (first install only, over SSH or the panel's terminal) `node seed-blank.js` — see DEPLOYMENT.md step 6.
7. **Restart** the app. Open `https://inventory.yourdomain.com/healthz` — it should answer `{"status":"ok","ready":true,…}` within a few seconds.

## 2. Keeping it always available

* The app opens its port immediately and prepares the database in the background, so Passenger's start-up time limit is no longer
  a risk; during those first seconds browsers see a "starting" reply and retry automatically.
* **Keep-warm (optional but recommended):** a cPanel cron job every 5 minutes, which counts as normal traffic for Passenger:
  `*/5 * * * * curl -fsS https://inventory.yourdomain.com/healthz >/dev/null 2>&1`
  (or set `KEEP_WARM_URL` to the same address and the app pings itself, or use any free uptime monitor).
* **Memory guard:** the app recycles itself cleanly (when idle above ~550 MB, at once above ~750 MB) and Passenger starts a fresh
  process on the next request. Look for `[mem]` lines in the Node.js error log. Tune with `MEM_SOFT_MB` / `MEM_HARD_MB`.
* **Redeploying:** replace the files but **keep `uploads/` and `.env`**; then press *Restart* (or `touch tmp/restart.txt`).

## 3. Uploads behind Passenger / Apache

* Photos are compressed in the browser to ≈220 KB (server limit 256 KB); bills, quotations and receipts allow 6 MB; the Excel import 8 MB.
* At most 6 uploads (`UPLOAD_CONCURRENCY`) are parsed at once and Excel reports/imports take turns (`HEAVY_CONCURRENCY=1`); extra requests wait
  up to 30–60 s, then get a clear "busy, try again" message that the browser retries by itself.
* Files are written to a temporary name and renamed, so a restart in the middle of an upload cannot leave a corrupt file.
* If an upload never reaches the app (nothing in the Node.js log), the block is in front of Node: ask the host to raise Apache
  `LimitRequestBody` / ModSecurity body limits for the subdomain, and check cPanel → Errors (Apache log).

## 4. Troubleshooting

| Symptom | Likely cause → fix |
|---|---|
| Page says "Starting…" for more than a minute | The database is unreachable. Node.js error log shows `[boot] Database not ready … retrying`. Check `DB_*`; the app keeps retrying and recovers by itself when MySQL is back. |
| "Configuration needed — SESSION_SECRET" | The variable is missing/empty. Add it, restart. |
| Login "works" but the next click signs you out | The proxy does not pass `X-Forwarded-Proto`. Set `ASSUME_HTTPS=1` (cookies are already `Secure: auto`). |
| Everyone appears to have the same IP / rate-limit hits | Wrong proxy depth: `TRUST_PROXY=1` for Apache only, `2` with Cloudflare in front. |
| 502 / 503 / 504 bursts | Browser retries reads automatically. If frequent, check `[mem]` and `[db] Connection pool saturated` lines; consider raising `DB_CONNECTION_LIMIT` only if MySQL's per-user limit allows. |
| `Too many connections` / `ER_TOO_MANY_USER_CONNECTIONS` | Lower `DB_CONNECTION_LIMIT` (data pool) — total used ≈ `DB_CONNECTION_LIMIT + SESSION_CONNECTION_LIMIT` per process. |
| Excel export says "would contain N rows" | Narrow the filter, or raise `EXPORT_MAX_ROWS` (≈1–2 KB RAM per row) on a bigger plan. |
| Static files 404 / old page after update | Hard refresh once (Ctrl+F5). HTML and the service worker are always revalidated; scripts/styles are cached by `?v=`. |

## 5. What to check after deploying

1. `/healthz` → ok; `/readyz` → ready.
2. Sign in, open Inventory and POS, upload an item photo and a bill, export one report.
3. Node.js error log: the `[app] memory:` line shows the heap limit and recycle thresholds — confirm the heap limit is near 400 MB.
4. Optional: on a **test** database run `npm run test:qa` (see `tests/qa/README.md`) against the subdomain: `BASE=https://inventory.yourdomain.com`.
