248 lines
8.1 KiB
Markdown
248 lines
8.1 KiB
Markdown
# Jitsi & Nginx Deployment Configuration
|
|
|
|
## Overview
|
|
|
|
This document covers the configuration required to deploy **uPortal** alongside a **Docker-based Jitsi Meet** instance behind **Nginx**.
|
|
|
|
---
|
|
|
|
## Environment Variables (.env)
|
|
|
|
These are the variables used by uPortal for Jitsi integration. Copy them into your `.env` file:
|
|
|
|
| Variable | Required | Default | Purpose |
|
|
|----------|----------|---------|---------|
|
|
| `JITSI_SECRET` | **Yes** | — | HMAC-SHA256 shared secret for signing JWTs. Must match the secret configured in Jitsi's prosody (`JWT_APP_SECRET`). Generate with: `openssl rand -hex 32` |
|
|
| `JITSI_APP_ID` | **Yes** | — | Jitsi App ID. Used as the `iss` (issuer) and `aud` (audience) in JWTs. Match this to `JWT_APP_ID` in Jitsi's prosody config. |
|
|
| `JITSI_DOMAIN` | No | `meet.jit.si` | Your Jitsi Meet instance FQDN (e.g. `meet.example.com`). |
|
|
| `SITE_DOMAIN` | No | — | The public domain of the portal (e.g. `portal.example.com`). Required for absolute avatar URLs in JWT context — otherwise Jitsi cannot fetch avatars. |
|
|
| `TRUSTED_DOMAIINS` | No | — | Comma-separated domains that bypass the external-redirect warning. Add your Jitsi domain here to skip the interstitial page. |
|
|
|
|
> **Note on `TRUSTED_DOMAIINS`**: The variable name contains a typo (double "I") — this matches the codebase, so keep it as-is.
|
|
|
|
### Minimal .env for Jitsi
|
|
|
|
```
|
|
JITSI_SECRET=your-64-char-hex-secret-here
|
|
JITSI_APP_ID=my_uportal_app
|
|
JITSI_DOMAIN=meet.example.com
|
|
SITE_DOMAIN=portal.example.com
|
|
TRUSTED_DOMAIINS=meet.example.com
|
|
```
|
|
|
|
---
|
|
|
|
## Docker-Based Jitsi Meet Setup
|
|
|
|
### 1. Prerequisites
|
|
|
|
- Docker and Docker Compose installed
|
|
- A server with a public IP and domain names pointing to it
|
|
- Ports 80 and 443 accessible (or behind Nginx)
|
|
|
|
### 2. Clone Jitsi Meet Docker
|
|
|
|
```bash
|
|
git clone https://github.com/jitsi/docker-jitsi-meet.git
|
|
cd docker-jitsi-meet
|
|
cp env.example .env
|
|
```
|
|
|
|
### 3. Configure Jitsi `.env`
|
|
|
|
Edit the Jitsi `.env` file. The critical settings for JWT authentication:
|
|
|
|
```bash
|
|
# --- Domain / TLS ---
|
|
CONFIG=~/.jitsi-meet-cfg
|
|
HTTP_PORT=8000
|
|
HTTPS_PORT=8443
|
|
|
|
# --- JWT Authentication (REQUIRED for uPortal integration) ---
|
|
ENABLE_AUTH=1
|
|
AUTH_TYPE=jwt
|
|
JWT_APP_ID=my_uportal_app # Must match uPortal's JITSI_APP_ID
|
|
JWT_APP_SECRET=your-64-char-hex-secret-here # Must match uPortal's JITSI_SECRET
|
|
JWT_ACCEPTED_ISSUERS=my_uportal_app
|
|
JWT_ACCEPTED_AUDIENCES=my_uportal_app
|
|
JWT_ASAP_KEYSERVER=
|
|
JWT_ALLOW_EMPTY=false
|
|
|
|
# --- Optional: Disable guest access ---
|
|
# JWT_AUTH_MODULE=jwt
|
|
```
|
|
|
|
### 4. Start Jitsi Meet
|
|
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
This starts the full Jitsi stack: prosody (XMPP), jicofo (focus), jvb (video bridge), and web (nginx frontend).
|
|
|
|
---
|
|
|
|
## Nginx Reverse Proxy Configuration
|
|
|
|
### Sample `/etc/nginx/sites-available/uportal`
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name portal.example.com meet.example.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
# uPortal
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name portal.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/portal.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/portal.example.com/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
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;
|
|
}
|
|
|
|
location /static/ {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
expires 7d;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
}
|
|
|
|
# Jitsi Meet
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name meet.example.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/meet.example.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/meet.example.com/privkey.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8000; # Jitsi HTTP port
|
|
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;
|
|
|
|
# WebSocket support (required for Jitsi)
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
|
|
# Large file uploads (recording, streaming)
|
|
client_max_body_size 10M;
|
|
client_body_buffer_size 128k;
|
|
}
|
|
|
|
# Long-polling for Jitsi
|
|
location ~ ^/(xmpp|http-bind) {
|
|
proxy_pass http://127.0.0.1:8000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
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;
|
|
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Key Points
|
|
|
|
- **uPortal** runs on `127.0.0.1:8080` (use `docker compose run -p 127.0.0.1:8080:8080` or systemd service)
|
|
- **Jitsi** HTTP port is `8000` (from Jitsi's `HTTP_PORT=8000`), proxied on a different subdomain
|
|
- **WebSocket** support is essential for Jitsi video/audio — the `Upgrade` and `Connection` headers must be forwarded
|
|
- Both services can share the same Nginx instance with different `server_name` blocks
|
|
|
|
---
|
|
|
|
## Docker Compose for uPortal
|
|
|
|
The project includes [`docker-compose.yml`](../docker-compose.yml) and [`Dockerfile`](../Dockerfile) for containerized deployment:
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
uportal:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: uportal
|
|
ports:
|
|
- "127.0.0.1:8080:8080" # Bind to loopback behind Nginx
|
|
volumes:
|
|
- ./data:/app # Persist SQLite database locally
|
|
environment:
|
|
LOG_LEVEL: info
|
|
JITSI_SECRET: your-64-char-hex-secret-here
|
|
JITSI_APP_ID: my_uportal_app
|
|
JITSI_DOMAIN: meet.example.com
|
|
SITE_DOMAIN: portal.example.com
|
|
TRUSTED_DOMAIINS: meet.example.com
|
|
restart: unless-stopped
|
|
```
|
|
|
|
> Bind to `127.0.0.1:8080` (not `0.0.0.0:8080`) when running behind Nginx on the same host to avoid direct external access.
|
|
|
|
---
|
|
|
|
## Architecture Diagram
|
|
|
|
```
|
|
Internet
|
|
│
|
|
├── portal.example.com:443 ──┐
|
|
│ Nginx (reverse proxy) │
|
|
│ ├── / → uPortal :8080 │
|
|
│ └── /static/ → uPortal │
|
|
│ │
|
|
└── meet.example.com:443 ────┤
|
|
Nginx (reverse proxy) │
|
|
├── / → Jitsi Web :8000 │
|
|
├── /xmpp → Jitsi │
|
|
└── /http-bind → Jitsi │
|
|
│
|
|
Docker Network │
|
|
├── uPortal container │
|
|
│ ├── uportal binary │
|
|
│ ├── uportal.db (SQLite) │
|
|
│ ├── templates/ │
|
|
│ └── static/ │
|
|
│ │
|
|
└── jitsi-docker stack │
|
|
├── prosody (XMPP) │
|
|
├── jicofo (focus) │
|
|
├── jvb (video bridge) │
|
|
└── web (nginx front) │
|
|
│
|
|
┌──────────────────────────────┘
|
|
│
|
|
JWT Signing: uPortal signs JWT with JITSI_SECRET
|
|
→ Redirects user to meet.example.com/room?jwt=...
|
|
→ Jitsi verifies JWT with matching JWT_APP_SECRET
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Likely Cause | Fix |
|
|
|---------|-------------|-----|
|
|
| "Jitsi secret is not configured" | `JITSI_SECRET` not set in uPortal `.env` | Set the variable |
|
|
| "Jitsi app id is not configured" | `JITSI_APP_ID` not set | Set the variable |
|
|
| "Authentication failed" in Jitsi | JWT secret mismatch between uPortal and Jitsi | Verify `JITSI_SECRET` = `JWT_APP_SECRET` |
|
|
| "Invalid issuer" in Jitsi logs | `JITSI_APP_ID` ≠ `JWT_APP_ID` | Make them match |
|
|
| Avatar not showing in Jitsi | `SITE_DOMAIN` not set, or avatar missing | Set `SITE_DOMAIN` to the portal's public URL |
|
|
| External redirect warning page appears | Jitsi domain not in `TRUSTED_DOMAIINS` | Add the domain | |