# Deploying Afya Tracker on cPanel

The app is a single Node.js process with a built-in database — no external database server, no
`npm install`, no build step. cPanel only needs to run it as a Node.js app.

---

## 0. What you need

- cPanel with **Setup Node.js App** (WHM → *Application Manager*, or the *Setup Node.js App* icon).
  This is standard on CloudLinux/cPanel hosting; ask your host to enable it if you don't see it.
- **Node.js 22.5 or newer** (24 preferred). Node 22.5–22.12 may need `NODE_OPTIONS=--experimental-sqlite`
  (step 4). Node 18 or 20 will **not** work — the app tells you so in its log if you try.

---

## 1. Upload and extract

1. cPanel → **File Manager**
2. Create a folder for the app, e.g. `/home/<user>/afya-tracker`
3. Upload `afya-tracker-cpanel.zip` into it
4. Right-click the zip → **Extract** (the zip already contains the `afya-tracker/` folder structure —
   if it extracted as `afya-tracker/afya-tracker/`, move the inner folder up one level)
5. Delete the zip afterwards

You should end up with `app.js`, `package.json`, `server/`, `public/`, `db/`, `data/` inside the folder.

---

## 2. Create the Node.js app

cPanel → **Setup Node.js App** → **Create Application**

| Field | Value |
|---|---|
| Node.js version | **22** or **24** (not 18/20) |
| Application mode | Production |
| Application root | `afya-tracker` |
| Application URL | your domain or subdomain (e.g. `afya.yourdomain.co.ke`) |
| Application startup file | `app.js` |

Click **Create**. cPanel writes a Passenger config and creates the virtualenv.

The startup file must be exactly `app.js` — that file just loads `server/index.js`.

---

## 3. Environment variables (recommended)

In the same screen, under **Environment variables**, add:

| Name | Value | Why |
|---|---|---|
| `AFYA_HIDE_DEMO` | `1` | removes the demo logins from the sign-in page — **set this on anything public** |
| `AFYA_DEMO_MODE` | `0` | optional second switch that disables the one-click demo login on its own |
| `AFYA_DATA_DIR` | `/home/<user>/afya-data` | keeps the database and uploads outside the app folder, so a redeploy never overwrites your data |

Both are optional for a first look, but set `AFYA_HIDE_DEMO=1` before you share the URL with the team.

**If you are also hosting the demonstration site (below), set `AFYA_HIDE_DEMO=1` on the live one.**
Without it, the one-click demo button on the live sign-in page opens a read-only view of *live*
facilities and figures — which is exactly what the separate demonstration environment exists to avoid.

If you set `AFYA_DATA_DIR`, copy `data/afya.db` into that folder (and create `uploads/` inside it)
**before** starting the app, otherwise you'll start with an empty database.

---

## 4. Restart and check

Click **Restart** in the Node.js app screen, then open:

```
https://your-domain/api/health
```

You should get JSON like:

```json
{ "ok": true, "node": "v24.x.x", "facilities": 392, "national_facilities": 8932 }
```

Then open the site root and sign in.

---

## 4b. Hosting the demonstration site alongside the live one (HMIS Tracker)

The demonstration environment is the same code with `AFYA_ENV=demo`, which gives it a different name,
a different currency, a different geography and — most importantly — a different database. Nothing
in it can read or write the live records.

You need a second URL and therefore a second Passenger application (a subdomain is the cleanest):

| | Live | Demonstration |
|---|---|---|
| URL | `https://afyaai.alextech.co.ke` | `https://demo.afyaai.alextech.co.ke` |
| Application root | `afya-tracker` | `afya-tracker-demo` (same code, separate copy) |
| Startup file | `app.js` | `app.js` |
| Data | `data/afya.db` | `demo-data/hmis-demo.db` (built on first start) |

Steps, all inside cPanel:

1. **Create the subdomain** (Domains → Create A New Domain). Cloudflare makes this a DNS record; this
   assumes a normal subdomain pointing at the same hosting account. If the subdomain must live on a
   different server, the demo copy has to be deployed there separately.
2. **Extract a second copy of the same ZIP** into a second application root (for example
   `afya-tracker-demo`). Do not share the folder with the live app: the two must not share a database.
3. **Setup Node.js App → Create Application** for the subdomain, with the same Node version (22 or 24)
   and startup file `app.js`.
4. **Add these environment variables to the demo application only:**

| Name | Value | Why |
|---|---|---|
| `AFYA_ENV` | `demo` | switches on the demonstration identity, currency, geography and database |
| `AFYA_DATA_DIR` | `/home/<user>/hmis-demo-data` | optional; keeps the demo database outside the app folder so redeploys keep it |

   Do **not** put `AFYA_HIDE_DEMO` on the demo application — the demo sign-in is the point of it. Do
   **not** copy `AFYA_DATA_DIR` from the live app; if you use one for each, they must be different
   folders.
5. **Restart** the demo application. On first start it sees an empty database and builds the whole
   fictional dataset itself, then logs how many facilities and staff it created. Watch for:

```
Seeded a fresh demonstration dataset: 48 facilities, 20 staff, 28 contracts, 76 commissions
HMIS Tracker running:  ...
Environment: demo (simulated data - not the live system)
```

6. Open the subdomain and check `https://demo.afyaai.alextech.co.ke/api/health` — it must say
   `"environment":"demo"`, `"app_name":"HMIS Tracker"` and `"currency_code":"INR"`. If it says
   Afya Tracker, `AFYA_ENV=demo` is missing.

**Resetting the demo**: sign in on the demo site as Management/Admin → **Admin → Modules → Reset
demonstration data**. It rebuilds the dataset from scratch and never touches the live database. The
live application answers that endpoint with 403 — `POST /api/admin/reset-demo` only works where
`AFYA_ENV=demo`.

**A routine you can run before a demonstration**: reset the data, sign in as each role once, and check
the dashboard totals are non-zero. `node test/demo.mjs` does that against a running demo site
(`DEMO=https://demo.afyaai.alextech.co.ke BASE=https://afyaai.alextech.co.ke node test/demo.mjs`).

### Module switches

**Admin → Modules** turns Finance, HR and Inventory on or off. The core modules (Sales & CRM,
Deployment, Commissions, Subscriptions, Management reports) are locked on by design: the facility
stage, the commission ledger and the monthly billing ledger are all derived from those records, so
switching one off would strand work mid-chain and change reported figures. Switching an optional
module off hides its screens and blocks new money being created or confirmed, but never deletes
records and never hides figures already on the books.

### Owners, consolidated billing and existing data

Adding owners is additive: an existing database keeps every facility, contract, invoice, payment
and commission exactly as it is, and every facility simply starts with no owner. Nothing is
merged or renamed. When you are ready, create owners in **Admin → Owners** and attach facilities
to them — facilities can be detached again without anything being deleted.

Charges addressed to an owner and not yet attributed to a facility stay visible as *not yet
allocated* rather than being spread silently across facilities. Before a first consolidated
invoice is issued in production, take a backup (cPanel → Files → Backups, or copy
`data/afya.db` and its `-wal` file with the app stopped) — not because the change is risky, but
because it is the point at which billing arrangements start changing.

**If it fails:**

- Check `stderr.log` in the app folder, or cPanel → **Errors**.
- `Cannot find module 'node:sqlite'` → the Node version is too old. Change it to 22/24 and restart.
  If only Node 22 is offered, add environment variable `NODE_OPTIONS` = `--experimental-sqlite`.
- Blank page with 503 → the port wasn't passed through; confirm the startup file is `app.js`.

---

## 5. Turn on HTTPS

cPanel → **SSL/TLS Status** → run AutoSSL for the domain. Then add this to `.htaccess` in
`public_html` (or use cPanel's *Force HTTPS Redirect*):

```apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
```

Do this before anyone signs in — the session cookie carries real login sessions.

---

## 6. Immediately after going live

1. Sign in with phone `0700000001` and the temporary PIN from `data/temporary-pins-*.csv`
   (also printed when the database is built). You will be asked to choose your own PIN immediately.
2. Do the same for finance — phone `0700000002`.
3. Hand every field user their own temporary PIN from that CSV. Each of them is forced to choose
   their own PIN the first time they sign in.
4. In **Admin → Logins: one phone number each**, give a phone number to anyone listed as missing one
   (123 of the seeded accounts have none) and fix any number that two accounts share. Those people
   can sign in with their email in the meantime.
5. Confirm `AFYA_HIDE_DEMO=1` is set and `/api/health` reports `"hide_demo": true` **and**
   `"demo_enabled": false`. That one setting both hides the demo button on the sign-in page and makes
   the one-click demo login (`POST /api/auth/demo`) refuse, so nobody can wander into your live data
   without a PIN. If you *want* a public demo, deploy it as a separate copy.

**Keep `data/temporary-pins-*.csv` private** — it contains a working PIN for every account until the
person changes it. Delete it once everyone has signed in.

---

## Backups

Everything lives in two places:

| What | Where |
|---|---|
| Database (facilities, contracts, payments, commissions, national list) | `data/afya.db` (or `$AFYA_DATA_DIR/afya.db`) |
| Uploaded contracts, IDs, receipts | `data/uploads/` (or `$AFYA_UPLOADS_DIR`) |

A daily cPanel backup of the app folder covers both. To take a manual copy, download the whole
folder — no database dump is needed, the SQLite file is self-contained.

---

## Updating the app later

1. Upload the new zip over the old folder (keep `data/` — or keep using `AFYA_DATA_DIR` and you
   can't lose it).
2. cPanel → Setup Node.js App → **Restart**.

The schema upgrades itself on boot, so an existing database keeps working.

---

## Not needed on the server

`etl/` (the spreadsheet import tools) requires Python with pandas and is only used on your own
machine to rebuild the starting database. It can stay in the folder; nothing on the live site
uses it.
