# 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 |