diff --git a/docs/DEPLOY_FROM_SCRATCH.md b/docs/DEPLOY_FROM_SCRATCH.md index 32a0e2e..c3c1f05 100644 --- a/docs/DEPLOY_FROM_SCRATCH.md +++ b/docs/DEPLOY_FROM_SCRATCH.md @@ -46,6 +46,16 @@ Notes: - If you manage Postgres on a separate host, you only need `postgresql-client` on this server. - If you install Postgres locally, install `postgresql` (server) too, not just the client. +### Firewall Rules (One-time setup) + +Configure firewall to allow access to the application ports: + +```bash +sudo ufw allow 3000/tcp # Admin frontend +sudo ufw allow 3001/tcp # Viewer frontend +sudo ufw allow 8000/tcp # Backend API +``` + --- ## Fast path (recommended): run the deploy script @@ -60,10 +70,13 @@ chmod +x scripts/deploy_from_scratch.sh The script will: - Install system packages (including Redis) +- Configure firewall rules (optional, with prompt) - Copy `*_example` env files to real `.env` files (if missing) - Install Python + Node dependencies - Generate Prisma clients for the viewer - Create auth DB tables and admin user (idempotent) +- Build frontend applications for production +- Configure PM2 (copy ecosystem.config.js from example if needed) - Start services with PM2 If you prefer manual steps, continue below. @@ -131,10 +144,19 @@ cp .env_example .env 2. Edit `.env`: +**For direct access (no reverse proxy):** ```bash VITE_API_URL=http://YOUR_SERVER_IP_OR_DOMAIN:8000 ``` +**For reverse proxy setup (HTTPS via Caddy/nginx):** +```bash +# Leave empty to use relative paths - API calls will go through the same proxy +VITE_API_URL= +``` + +**Important:** When using a reverse proxy (Caddy/nginx) with HTTPS, set `VITE_API_URL` to empty. This allows the frontend to use relative API paths that work correctly with the proxy, avoiding mixed content errors. + ### 2.3 Viewer env: `/opt/punimtag/viewer-frontend/.env` 1. Copy and rename: @@ -211,10 +233,44 @@ npx tsx scripts/fix-admin-user.ts --- -## Step 6 — Start the services (PM2) +## Step 6 — Build frontends -This repo includes a PM2 config at `ecosystem.config.js`. -Verify the paths/ports match your server, then: +Build the frontend applications for production: + +```bash +# Admin frontend +cd /opt/punimtag/admin-frontend +npm run build + +# Viewer frontend +cd /opt/punimtag/viewer-frontend +npm run build +``` + +Note: The admin frontend build creates a `dist/` directory that will be served by PM2. +The viewer frontend build creates an optimized Next.js production build. + +--- + +## Step 7 — Configure PM2 + +This repo includes a PM2 config template. If `ecosystem.config.js` doesn't exist, copy it from the example: + +```bash +cd /opt/punimtag +cp ecosystem.config.js.example ecosystem.config.js +``` + +Edit `ecosystem.config.js` and update: +- All `cwd` paths to your deployment directory (e.g., `/opt/punimtag`) +- All `error_file` and `out_file` paths to your user's home directory +- `PYTHONPATH` and `PATH` environment variables to match your deployment paths + +--- + +## Step 8 — Start the services (PM2) + +Start all services using PM2: ```bash cd /opt/punimtag @@ -230,7 +286,7 @@ pm2 startup --- -## Step 7 — First-run DB initialization (automatic) +## Step 9 — First-run DB initialization (automatic) On first startup, the backend will connect to Postgres and create missing tables automatically. @@ -248,7 +304,7 @@ curl -sS http://127.0.0.1:3001/api/health --- -## Step 8 — Open the apps +## Step 10 — Open the apps - **Admin**: `http://YOUR_SERVER:3000` - **Viewer**: `http://YOUR_SERVER:3001` @@ -256,8 +312,112 @@ curl -sS http://127.0.0.1:3001/api/health --- +## Step 11 — Reverse Proxy Setup (HTTPS via Caddy/nginx) + +If you're using a reverse proxy (Caddy, nginx, etc.) to serve the application over HTTPS, you need to configure it to route `/api/*` requests to the backend **before** serving static files. + +### Issue: API requests returning HTML instead of JSON + +**Symptom:** Login works but navigation tabs don't show, or API calls return HTML (the frontend's `index.html`) instead of JSON data. + +**Cause:** The reverse proxy is serving static files for all requests, including `/api/*` requests, instead of forwarding them to the backend. + +### Solution: Configure proxy to route `/api/*` first + +The proxy must forward `/api/*` requests to the backend (port 8000) **before** trying to serve static files. + +#### Caddy Configuration + +Update your Caddyfile on the proxy server: + +```caddyfile +your-admin-domain.com { + import security-headers + + # CRITICAL: Route API requests to backend FIRST (before static files) + handle /api/* { + reverse_proxy http://YOUR_BACKEND_IP:8000 { + header_up Host {host} + header_up X-Real-IP {remote} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + } + } + + # Proxy everything else to the frontend + reverse_proxy http://YOUR_BACKEND_IP:3000 +} +``` + +**Important:** The `handle /api/*` block **must come before** the general `reverse_proxy` directive. + +After updating: +```bash +# Test configuration +caddy validate --config /path/to/Caddyfile + +# Reload Caddy +sudo systemctl reload caddy +``` + +#### Nginx Configuration + +```nginx +server { + listen 80; + server_name your-admin-domain.com; + + root /opt/punimtag/admin-frontend/dist; + index index.html; + + # CRITICAL: API proxy must come FIRST, before static file location + location /api { + proxy_pass http://YOUR_BACKEND_IP:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Serve static files for everything else + location / { + try_files $uri $uri/ /index.html; + } +} +``` + +After updating: +```bash +# Test configuration +sudo nginx -t + +# Reload nginx +sudo systemctl reload nginx +``` + +### Environment Variable Setup + +When using a reverse proxy, ensure `admin-frontend/.env` has: + +```bash +VITE_API_URL= +``` + +This allows the frontend to use relative API paths (`/api/v1/...`) that work correctly with the proxy. + +--- + ## Common fixes +### API requests return HTML instead of JSON (reverse proxy issue) + +**Symptom:** Browser console shows API responses are HTML (the frontend's `index.html`) instead of JSON. Login may work but navigation tabs don't appear. + +**Solution:** +1. Ensure your reverse proxy (Caddy/nginx) routes `/api/*` requests to the backend **before** serving static files (see Step 11 above). +2. Verify `admin-frontend/.env` has `VITE_API_URL=` (empty) when using a proxy. +3. Rebuild the frontend after changing `.env`: `cd admin-frontend && npm run build && pm2 restart punimtag-admin` + ### Viewer `/api/health` says permission denied Run the provided grant script on the DB server (as a privileged Postgres user):