# Server Setup Guide — visitor_demo

**Target environment:** Ubuntu Server · Apache2 · Node.js 22 · PostgreSQL  
**Repo path on server:** `/var/www/html/visitor_demo/vcarrd-events/visitor_demo`

---

## Table of Contents

1. [Prerequisites](#1-prerequisites)
2. [Install Node.js 22](#2-install-nodejs-22)
3. [Install & Configure PostgreSQL](#3-install--configure-postgresql)
4. [Install PM2](#4-install-pm2)
5. [Clone the Repository](#5-clone-the-repository)
6. [Configure the API (.env)](#6-configure-the-api-env)
7. [Build & Start the API](#7-build--start-the-api)
8. [Build the Web App](#8-build-the-web-app)
9. [Configure Apache2](#9-configure-apache2)
10. [Enable HTTPS (SSL)](#10-enable-https-ssl)
11. [Post-Deploy Smoke Tests](#11-post-deploy-smoke-tests)
12. [Updating the App](#12-updating-the-app)

---

## 1. Prerequisites

```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl unzip build-essential apache2
```

Enable required Apache2 modules (proxy, WebSocket, headers, rewrites):

```bash
sudo a2enmod proxy proxy_http proxy_wstunnel rewrite headers ssl
sudo systemctl restart apache2
```

---

## 2. Install Node.js 22

Use the NodeSource setup script:

```bash
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v   # should print v22.x.x
npm -v
```

---

## 3. Install & Configure PostgreSQL

```bash
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
```

Create the database and user:

```bash
sudo -u postgres psql <<'SQL'
CREATE USER visitor_user WITH PASSWORD 'CHANGE_ME_STRONG_PASSWORD';
CREATE DATABASE visitor_demo OWNER visitor_user;
GRANT ALL PRIVILEGES ON DATABASE visitor_demo TO visitor_user;
SQL
```

> **Note:** Replace `CHANGE_ME_STRONG_PASSWORD` with a secure password.  
> Update `DATABASE_URL` in `.env` to match (see step 6).

---

## 4. Install PM2

PM2 keeps the Node API alive across crashes and reboots:

```bash
sudo npm install -g pm2
pm2 startup systemd -u $USER --hp $HOME
# Run the command that PM2 prints — it looks like:
# sudo env PATH=... pm2 startup systemd -u <user> --hp /home/<user>
```

---

## 5. Clone the Repository

```bash
sudo mkdir -p /var/www/html/visitor_demo/vcarrd-events
sudo chown -R $USER:$USER /var/www/html/visitor_demo

cd /var/www/html/visitor_demo/vcarrd-events
git clone <your-repo-url> visitor_demo
cd visitor_demo
```

---

## 6. Configure the API (.env)

```bash
cd /var/www/html/visitor_demo/vcarrd-events/visitor_demo/apps/api
cp .env .env.backup   # keep the sample as a reference
nano .env
```

Set every value for production. Minimum required fields:

```dotenv
# ── Server ────────────────────────────────────────────────────────────────────
PORT=4200
FRONTEND_URL=https://yourdomain.com
PUBLIC_API_URL=https://yourdomain.com/api

# ── Database ──────────────────────────────────────────────────────────────────
DATABASE_URL=postgresql://visitor_user:CHANGE_ME_STRONG_PASSWORD@localhost:5432/visitor_demo

# ── Auth ──────────────────────────────────────────────────────────────────────
JWT_SECRET=replace-with-a-long-random-string-min-32-chars

# ── Mail (SMTP) ───────────────────────────────────────────────────────────────
MAIL_HOST=smtp.yourmailprovider.com
MAIL_PORT=587
MAIL_USERNAME=your@email.com
MAIL_PASSWORD=your-smtp-password
MAIL_FROM_ADDRESS=noreply@yourdomain.com
MAIL_FROM_NAME=Gate Pass

# ── Push Notifications ────────────────────────────────────────────────────────
# Firebase — paste the service-account JSON as a single-line string.
# Go to Firebase Console → Project Settings → Service Accounts → Generate new private key.
# Then compact the downloaded JSON to one line and paste it here (single-quoted).
# Never commit this value or store it in a file inside the repo.
FIREBASE_SERVICE_ACCOUNT_JSON='{"type":"service_account","project_id":"...","private_key_id":"...","private_key":"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n","client_email":"...","client_id":"...","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"...","universe_domain":"googleapis.com"}'
FIREBASE_SERVICE_ACCOUNT_PATH=

# Web Push / VAPID — generate fresh keys (do NOT reuse dev keys):
# Run: node -e "const wp=require('web-push');const k=wp.generateVAPIDKeys();console.log('PUBLIC='+k.publicKey+'\nPRIVATE='+k.privateKey)"
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:noreply@yourdomain.com

# ── Dev flags (MUST be false in production) ───────────────────────────────────
EXPOSE_OTP_IN_RESPONSE=false
```

### Generate VAPID keys (one-time):

```bash
cd /var/www/html/visitor_demo/vcarrd-events/visitor_demo/apps/api
node -e "const wp=require('web-push');const k=wp.generateVAPIDKeys();console.log('VAPID_PUBLIC_KEY='+k.publicKey);console.log('VAPID_PRIVATE_KEY='+k.privateKey);"
```

Paste the output into `.env`.

---

## 7. Build & Start the API

### Install dependencies & set up database schema

```bash
cd /var/www/html/visitor_demo/vcarrd-events/visitor_demo/apps/api

npm install --omit=dev

# Generate Prisma client
npx prisma generate --schema=src/prisma/schema.prisma

# Push schema directly to the database (creates all tables, no migration history needed)
npx prisma db push --schema=src/prisma/schema.prisma
```

### Compile TypeScript

```bash
npx tsc
```

> The compiled output lands in `apps/api/dist/`. Entry point: `dist/server.js`.

### Start with PM2

```bash
pm2 start dist/server.js --name visitor-api
pm2 save   # persist across reboots
```

Verify it's running:

```bash
pm2 status
pm2 logs visitor-api --lines 50
curl -s http://localhost:4200/api/health
```

---

## 8. Build the Web App

```bash
cd /var/www/html/visitor_demo/vcarrd-events/visitor_demo/apps/web

npm install

npm run build
```

The production-ready static files are output to `apps/web/dist/`.  
Apache will serve this directory (configured in the next step).

---

## 9. Configure Apache2

Create a new virtual host file:

```bash
sudo nano /etc/apache2/sites-available/visitor-demo.conf
```

Paste the configuration below, replacing `yourdomain.com` with your actual domain:

```apache
<VirtualHost *:80>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com

    # Redirect all HTTP to HTTPS (uncomment after SSL is set up)
    # RewriteEngine On
    # RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # ── Static Web App ─────────────────────────────────────────────────────────
    DocumentRoot /var/www/html/visitor_demo/vcarrd-events/visitor_demo/apps/web/dist

    <Directory /var/www/html/visitor_demo/vcarrd-events/visitor_demo/apps/web/dist>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted

        # React Router — serve index.html for all non-asset routes
        RewriteEngine On
        RewriteBase /
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule ^ index.html [L]
    </Directory>

    # ── API Reverse Proxy ──────────────────────────────────────────────────────
    ProxyPreserveHost On
    ProxyRequests Off

    <Location /api>
        ProxyPass        http://127.0.0.1:4200/api
        ProxyPassReverse http://127.0.0.1:4200/api
        RequestHeader set X-Forwarded-Proto "http"
    </Location>

    # ── File Uploads ───────────────────────────────────────────────────────────
    <Location /uploads>
        ProxyPass        http://127.0.0.1:4200/uploads
        ProxyPassReverse http://127.0.0.1:4200/uploads
    </Location>

    # ── Socket.io (WebSocket + HTTP polling) ───────────────────────────────────
    <Location /socket.io>
        ProxyPass        http://127.0.0.1:4200/socket.io
        ProxyPassReverse http://127.0.0.1:4200/socket.io
        ProxyPassMatch   ws://127.0.0.1:4200/socket.io

        # WebSocket upgrade headers
        RewriteEngine On
        RewriteCond %{HTTP:Upgrade} websocket [NC]
        RewriteCond %{HTTP:Connection} upgrade  [NC]
        RewriteRule ^/socket.io/(.*) ws://127.0.0.1:4200/socket.io/$1 [P,L]

        RequestHeader set Connection "upgrade"
        RequestHeader set Upgrade    "websocket"
    </Location>

    ErrorLog  ${APACHE_LOG_DIR}/visitor-demo-error.log
    CustomLog ${APACHE_LOG_DIR}/visitor-demo-access.log combined
</VirtualHost>
```

Enable the site and disable the default:

```bash
sudo a2ensite visitor-demo.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest       # must print "Syntax OK"
sudo systemctl reload apache2
```

---

## 10. Enable HTTPS (SSL)

HTTPS is **required** for:
- Service Workers (Web Push notifications)
- Secure cookies / JWT

### Option A — Let's Encrypt (Certbot) — Recommended for public domains

```bash
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
```

Certbot will automatically update the Apache config for HTTPS and set up auto-renewal.

After Certbot finishes, update these values in `apps/api/.env`:

```dotenv
FRONTEND_URL=https://yourdomain.com
PUBLIC_API_URL=https://yourdomain.com
```

Then restart the API:

```bash
pm2 restart visitor-api
```

Also uncomment the HTTP→HTTPS redirect block in the `<VirtualHost *:80>` config above:

```bash
sudo nano /etc/apache2/sites-available/visitor-demo.conf
# Uncomment the RewriteEngine / RewriteRule lines under "Redirect all HTTP to HTTPS"
sudo systemctl reload apache2
```

### Option B — Self-signed (internal/dev server only)

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/visitor-demo.key \
  -out /etc/ssl/certs/visitor-demo.crt \
  -subj "/CN=yourdomain.com"

sudo nano /etc/apache2/sites-available/visitor-demo-ssl.conf
```

Add an HTTPS vhost pointing to the same `DocumentRoot` and proxy rules as above, with:

```apache
<VirtualHost *:443>
    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/visitor-demo.crt
    SSLCertificateKeyFile /etc/ssl/private/visitor-demo.key
    # ... same DocumentRoot, proxy, and socket.io blocks as the HTTP vhost
</VirtualHost>
```

```bash
sudo a2ensite visitor-demo-ssl.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```

---

## 11. Post-Deploy Smoke Tests

```bash
# 1. API health check
curl -s https://yourdomain.com/api/health

# 2. Auth endpoint is reachable (expect 400, not 404/502)
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/api/auth/login

# 3. VAPID public key (confirms web-push env vars loaded)
curl -s https://yourdomain.com/api/push/public-key
# expect: {"publicKey":"..."}

# 4. Automation timings endpoint is mounted (expect 401, not 404/502)
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/api/automation-timings

# 5. Static web app loads
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/
# expect: 200

# 6. React Router — deep link returns index.html (not 404)
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/visitors
# expect: 200
```

Check PM2 logs for cron startup confirmation:

```bash
pm2 logs visitor-api --lines 100 | grep cron
# expect: [cron] started — tick every 30 minutes ...
```

---

## 12. Updating the App

Pull the latest code and redeploy both services:

```bash
cd /var/www/html/visitor_demo/vcarrd-events/visitor_demo
git pull origin main   # or your target branch
```

### Redeploy API

```bash
cd apps/api
npm install --omit=dev
npx prisma generate --schema=src/prisma/schema.prisma
npx prisma db push --schema=src/prisma/schema.prisma
npx tsc
pm2 restart visitor-api
pm2 logs visitor-api --lines 30
```

### Redeploy Web

```bash
cd ../web
npm install
npm run build
# Apache serves apps/web/dist/ directly — no restart needed
```

---

## Useful PM2 Commands

```bash
pm2 status                    # list all processes
pm2 logs visitor-api          # tail live logs
pm2 logs visitor-api --lines 100  # last 100 lines
pm2 restart visitor-api       # restart after code change
pm2 stop visitor-api          # stop the process
pm2 delete visitor-api        # remove from PM2
pm2 monit                     # live CPU / memory dashboard
```

## Useful Apache2 Commands

```bash
sudo apache2ctl configtest            # validate config syntax
sudo systemctl reload apache2         # reload config without downtime
sudo systemctl restart apache2        # full restart
sudo tail -f /var/log/apache2/visitor-demo-error.log   # watch errors live
sudo tail -f /var/log/apache2/visitor-demo-access.log  # watch access log
```

---

## Directory Reference

| Path on Server | Purpose |
|---|---|
| `/var/www/html/visitor_demo/vcarrd-events/visitor_demo/` | Repository root |
| `apps/api/` | Express API (TypeScript source) |
| `apps/api/dist/` | Compiled JS — PM2 entry point (`dist/server.js`) |
| `apps/api/.env` | Production environment variables |
| `apps/api/src/prisma/schema.prisma` | Prisma schema |
| `apps/api/uploads/` | User-uploaded files (QR codes, photos) |
| `apps/web/` | React/Vite frontend source |
| `apps/web/dist/` | Built static files — Apache `DocumentRoot` |
| `apps/web/dist/service-worker.js` | Web Push service worker (auto-copied by Vite) |

## Port Reference

| Service | Port | Notes |
|---|---|---|
| Apache2 (HTTP) | 80 | Redirect to 443 after SSL setup |
| Apache2 (HTTPS) | 443 | Serves web app + proxies API |
| Express API | 4200 | Internal only — not exposed to internet |
| PostgreSQL | 5432 | Internal only — bind to `localhost` |

