# Deploying i-Learned

You have `dist/i-Learned-deploy.zip` — 1.7 MB, 422 files.

**It contains source, not a compiled build, and that is deliberate.** A build made
on Windows carries Windows binaries (`query_engine-windows.dll.node`,
`argon2.win32-x64-msvc.node`). Your server runs Linux; those files cannot execute
there. The server installs and builds, which is what cPanel's Node.js App feature
and every managed host expect anyway.

---

## What the server must provide

Check these before doing anything else. If the second one is missing, stop and
read *Path B*.

1. **Node.js 20 or newer.** In cPanel this is the **Setup Node.js App** icon
   (sometimes under Software). If that icon is absent, your plan cannot run this
   application at all.
2. **PostgreSQL.** Not MySQL. In cPanel, look for **PostgreSQL Databases**. Most
   shared plans only offer MySQL — see *Path B*, which solves this without
   changing hosts.

---

## Path A — cPanel with Node.js App

Use this if you found **Setup Node.js App** in cPanel.

### A1. Get a PostgreSQL database

If cPanel offers PostgreSQL, create a database and user there and note the
connection string.

If it does not — the common case — use a managed provider. Free tiers are ample
for a demonstration:

- **Neon** (neon.tech) — fastest to set up, generous free tier
- **Supabase** (supabase.com) — free tier, also gives you a table browser

Create a project, then copy the connection string. It looks like:

```
postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname?sslmode=require
```

Keep it to hand. This is a credential — do not paste it into email or chat.

### A2. Upload and extract

1. cPanel → **File Manager** → navigate to `public_html/subdomain/iLearned`.
2. **Upload** `i-Learned-deploy.zip`.
3. Select it → **Extract**.
4. Delete the zip afterwards.

### A3. Create the Node.js application

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

| Field | Value |
| --- | --- |
| Node.js version | 20 or higher |
| Application mode | Production |
| Application root | `public_html/subdomain/iLearned` |
| Application URL | `ilearned.ichthuslance.com` |
| Application startup file | `server.js` |

Click **Create**. It will not start yet — there is nothing built.

### A4. Set the environment

Still in Setup Node.js App, add these under **Environment variables**. They are
described in full in `.env.production.example`.

| Variable | Value |
| --- | --- |
| `DATABASE_URL` | the connection string from A1 |
| `APP_URL` | `https://ilearned.ichthuslance.com` |
| `SESSION_SECRET` | generate one — see below |
| `SESSION_COOKIE_SECURE` | `true` |
| `NODE_ENV` | `production` |
| `TZ` | `Asia/Manila` |

Generate the secret on your own machine and paste the output:

```bash
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
```

> **`APP_URL` must be right before you build.** Account-setup and password-reset
> links are constructed from it. Wrong value, and every link you hand somebody
> points at the wrong place.

### A5. Install, migrate, build

In Setup Node.js App, click **Run JS script** — or open **Terminal** and run
`source /home/USER/nodevenv/.../bin/activate && cd ~/public_html/subdomain/iLearned`
first, using the exact activation line cPanel shows you.

Then, in order:

```bash
npm install -g pnpm@9.15.4
pnpm install --frozen-lockfile --prod=false
pnpm db:migrate:deploy
pnpm build
```

> **`--prod=false` is not optional, and leaving it off is the most likely way
> this step fails.** You set `NODE_ENV=production` in A4, and pnpm honours that
> by skipping `devDependencies` — which is where the build tools live:
> `prisma`, `typescript` and `dotenv-cli`. Without the flag the install
> *succeeds*, and then the next command fails with
> `sh: dotenv: command not found`, which looks like a missing program rather
> than a missing install.
>
> If you have already hit that, just re-run the install with the flag. Nothing
> needs undoing.

`db:migrate:deploy` creates all 107 tables, the constraints, and the
append-only audit triggers. `pnpm build` takes a few minutes and compiles the
native binaries for *this* server — which is the whole reason you uploaded
source.

The development dependencies stay on disk afterwards. That is harmless: the
running application loads only what it imports, and re-installing without them
would remove the tooling you need for the next deploy.

### A6. Add the demonstration data

**With realistic data** — 80 students, applications, payments, grades:

```bash
NODE_ENV=development pnpm db:seed
```

**Or empty, with one superadmin**, to fill in yourself:

```bash
NODE_ENV=development pnpm db:seed && pnpm db:clear-demo --apply
```

> The `NODE_ENV=development` prefix is needed because the seed refuses to run in
> production — a guard so nobody ever writes fictional students over a real
> institution's database. Set it only for that one command.

### A7. Start it

Setup Node.js App → **Restart**. Then open
`https://ilearned.ichthuslance.com`.

---

## Path B — a host that runs both

Use this if cPanel has no **Setup Node.js App**, or if A5 fails on the build.

Shared cPanel is built for PHP. Rather than fight it, put the application on a
host designed for Node and keep your domain pointing at it — your client still
sees `ilearned.ichthuslance.com`.

**Railway** is the shortest path: app and PostgreSQL in one project, no sleeping
on idle. The full step-by-step is in [`deploy-railway.md`](deploy-railway.md).

In outline: push the code to a private GitHub repository, create a Railway
project from it, add PostgreSQL, set the same variables as A4, run the same
commands as A5 and A6 in Railway's shell, then add `ilearned.ichthuslance.com`
as a custom domain and point a `CNAME` at the hostname Railway gives you.

---

## Before you show a client

Work through these. Each has failed at least once in development.

1. `https://ilearned.ichthuslance.com/login` loads over **https**, padlock shown.
2. Sign in as `superadmin@example.local`.
3. `/privacy` loads in a **private window** — it must work signed out.
4. **Administration → Users → Add user.** Check the setup link points at your
   domain, not `localhost`. If it says localhost, `APP_URL` is wrong: fix it,
   rebuild, restart.
5. Open that link and set a password — exercises the whole reset flow.
6. **Registrar → Programs → Add programme.** Confirms the database is writable.
7. **Account → Your data → Download my data.** Confirms file responses work.

### Change the demo passwords

Every seeded account uses `Demo!Pass123`. That is fine on your laptop and not
fine on a public URL — anyone who finds the address can sign in as a student.
Before sending the link out, sign in as superadmin and use
**Users → Change password** on the accounts you will demonstrate, and delete the
rest.

---

## When something goes wrong

| Symptom | Cause |
| --- | --- |
| Blank page, or a PHP error | The Node app is not running, or the startup file is wrong. It must be `server.js`, at the root of the application folder. |
| `sh: dotenv: command not found` (or `prisma`, or `tsc`) | `pnpm install` ran with `NODE_ENV=production`, which skips devDependencies. Re-run it as `pnpm install --frozen-lockfile --prod=false`. |
| `SESSION_SECRET must be at least 32 characters` | Exactly what it says. This is a startup guard, not a bug. |
| Pages load, every action fails | `DATABASE_URL` wrong, or `db:migrate:deploy` was not run. |
| `Can't reach database server` | Wrong host, or the provider needs `?sslmode=require` on the end. |
| Setup links say `localhost` | `APP_URL` was not set before the build. Fix, rebuild, reissue the links. |
| Build fails on a native module | Node is older than 20, or the plan forbids native compilation. Use Path B. |

---

## What this deployment is not

A **demonstration**, not a production system. Before it holds a real student's
record, the conditions in [`security-checklist.md`](security-checklist.md)
apply — multi-factor authentication for privileged roles, encryption at rest,
shared rate limiting, malware scanning, off-host audit shipping, and the
institutional approvals including Data Protection Officer sign-off.

The standing banner and the watermark on generated documents say the data is
fictional. Leave them on.
