Jitsi detrás de un proxy reverso

En este artículo abordaremos la instalación y configuración de un proxy reverso para disponibilizar un servidor de videoconferencias Jitsi Meet detrás de un servidor nginx funcionando como proxy reverso con certificado TLS.

Para esta implementación partimos de este esquema, a esto queremos llegar.

Definimos un registro tipo A en el DNS para el dominio jitsi.dominio.edu.ar usando la IP pública del servidor

En todos los caso el sistema base es Debian, nos cercioramos que se encuentre actualizado y que los paquetes necesarios estén instalados.

$ apt install apt-transport-https gnupg2 curl

Atención

Se debe utilizar OpenJDK 11.

Certificado TLS

Para que las comunicaciones sean encriptadas necesitamos contar con un certificado TLS. Para ello, dado que nginx carece de un módulo como el md de Apache, vamos a utilizar un shell script que implementa el protocolo cliente ACME. Esto lo hacemos en el proxy reverso dado que a futuro podemos disponibilizar otros servicios.

Proxy reverso

$ apt install nginx-full

Luego de finalizada la instalación, verificamos que el servicio está funcionando.

$ systemctl status nginx Salida

 nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2021-09-17 04:23:45 UTC; 4min 23s ago
Docs: man:nginx(8)
Main PID: 3942 (nginx)
Tasks: 3 (limit: 4719)
Memory: 6.1M
CGroup: /system.slice/nginx.service
├─3942 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
├─3943 nginx: worker process
└─3944 nginx: worker process
As you can see above, the service appears to have started successfully. However, the best way to test this is to actually request a page from Nginx.

Ingresando http://127.0.0.1 en el navegador deberías ver la página de inicio de Nginx:

Vamos a eliminar la configuración predeterminada

$ rm /etc/nginx/sites-available/default
$ rm /etc/nginx/sites-enabled/default

Y ahora, crearemos una nueva en /etc/nginx/sites-available/jitsi.dominio.edu.ar.conf donde agregaremos los parámetros necesarios para nuestro jitsi

types {
application/wasm wasm;
}

server {
listen 80;
server_name jitsi.dominio.edu.ar;
server_tokens off;
# Don't show the nginx version number

include /etc/nginx/snippets/location-letsencrypt.conf;

return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;

server_name jitsi.dominio.edu.ar;
server_tokens off;
# Don't show the nginx version number

include /etc/nginx/snippets/location-letsencrypt.conf;

ssl_certificate /etc/letsencrypt/live/jitsi.dominio.edu.ar/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jitsi.dominio.edu.ar/privkey.pem;

# Mozilla Guideline v5.4, nginx 1.17.7, OpenSSL 1.1.1d, intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m; # about 40000 sessions
# Defining option to share SSL Connection with Passed Proxy
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=63072000" always;
set $prefix "";

ssl_dhparam /etc/ssl/dhparams.pem;
ssl_ecdh_curve secp384r1;

location / {
ssi on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;

proxy_pass http://RANGO.IP.PRIVADO.120/;

# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location ~ ^/(?!(http-bind|external_api\.|xmpp-websocket))([a-zA-Z0-9=_äÄöÖüÜß\?\-]+)$ {
rewrite ^/(.*)$ / break;
}
# BOSH
location /http-bind {
proxy_pass http://RANGO.IP.PRIVADO.120:5280/http-bind;
#IP servidor jitsi
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
tcp_nodelay on;
}
# xmpp websockets
location /xmpp-websocket {
proxy_pass http://RANGO.IP.PRIVADO.120:5280/xmpp-websocket;
#IP servidor jitsi
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
tcp_nodelay on;
}

access_log /var/log/nginx/jitsi.dominio.edu.ar/access.log;
error_log /var/log/nginx/jitsi.dominio.edu.ar/error.log;

}

Habilitamos esta configuración en nuestro servidor

$ ln -s /etc/nginx/sites-available/jitsi.dominio.edu.ar.conf /etc/nginx/sites-enabled/jitsi.dominio.edu.ar.conf

Para evitar un posible problema de memoria que puede surgir de la adición de nombres de servidores adicionales a nuestra configuración, es necesario ajustar un solo valor en el archivo /etc/nginx/nginx.conf y descomentar la linea server_names_hash_bucket_size.

Guardamos los cambios y reiniciamos nginx

$ systemctl restart nginx

Configurar Nginx para Jitsi Meet

Editamos un archivo para nuestra configuración /etc/nginx/sites-enabled/jitsi.dominio.edu.ar.conf

types {
# nginx's default mime.types doesn't include a mapping for wasm
application/wasm wasm;
}

server {
listen 80;
server_name jitsi.dominio.edu.ar;

set_real_ip_from RANGO.IP.PRIVADO.100;
# IP del Proxy
real_ip_header X-Real-IP;

set $prefix "";

root /usr/share/jitsi-meet;

# ssi on with javascript for multidomain variables in config.js
ssi on;
ssi_types application/x-javascript application/javascript;

index index.html index.htm;
error_page 404 /static/404.html;

gzip on;
gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm;
gzip_vary on;
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 512;

location = /config.js {
alias /etc/jitsi/meet/jitsi.dominio.edu.ar-config.js;
}

location = /external_api.js {
alias /usr/share/jitsi-meet/libs/external_api.min.js;
}

# ensure all static content can always be found first
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
{
add_header 'Access-Control-Allow-Origin' '*';
alias /usr/share/jitsi-meet/$1/$2;

# cache all versioned files
if ($arg_v) {
expires 1y;
}
}

# load test minimal client, uncomment when used
#location ~ ^/_load-test/([^/?&:'"]+)$ {
# rewrite ^/_load-test/(.*)$ /load-test/index.html break;
#}
#location ~ ^/_load-test/libs/(.*)$ {
# add_header 'Access-Control-Allow-Origin' '*';
# alias /usr/share/jitsi-meet/load-test/libs/$1;
#}

location ~ ^/([^/?&:'"]+)$ {
try_files $uri @root_path;
}

location @root_path {
rewrite ^/(.*)$ / break;
}

# Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
location ~ ^/([^/?&:'"]+)/(.*)$ {
set $subdomain "$1.";
set $subdir "$1/";
rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
}

}

Configurar el nombre de host

El nombre del servidor debe ser un subdominio como “jitsi.dominio.edu.ar”. En primer lugar, debemos comprobar el nombre de host en /etc/hosts y cambiarlo cuando sea necesario.

$ hostnamectl set-hostname jitsi.dominio.edu.ar

Luego editamos el archivo/etc/hosts:

127.0.0.1 localhost.localdomain localhost x.x.x.x jitsi.dominio.edu.ar jitsi

Atención

x.x.x.x es la IP pública de nuestro servidor.

Finalmente en el mismo equipo probamos que se puede hacer ping al dominio:

ping "$(hostname)"

Si todo funciona correctamente, deberíamos ver: jitsi.dominio.edu.ar

Configuramos el firewall

Los siguientes puertos tienen que estar abiertos en el firewall para permitir el tráfico al servidor Jitsi Meet:

  • 80 TCP – para la verificación / renovación del certificado SSL con Let’s Encrypt

  • 443 TCP – para el acceso general a Jitsi Meet

  • 10000 UDP – para comunicaciones generales de vídeo/audio en red

  • 22 TCP – si accedes a tu servidor usando SSH (cambia el puerto en consecuencia si no es el 22)

  • 3478 UDP – para consultar el servidor stun (coturn, opcional, necesita cambiar config.js para habilitarlo)

  • 5349 TCP – para comunicaciones de video/audio en red a través de TCP (cuando UDP está bloqueado, por ejemplo), servido por coturn

Si estás usando ufw, podés usar los siguientes comandos:

ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 10000/udp
ufw allow 22/tcp
ufw allow 3478/udp
ufw allow 5349/tcp
ufw enable

Verificamos el estado del firewall con:

ufw status verbose

Instalar Jitsi Meet

Añadimos el repositorio jitsi a los orígenes de software para que los paquetes de Jitsi Meet estén disponibles.curl https://download.jitsi.org/jitsi-key.gpg.key | sh -c “gpg –dearmor > /usr/share/keyrings/jitsi-keyring.gpg” echo “deb [signed-by=/usr/share/keyrings/jitsi-keyring.gpg] https://download.jitsi.org stable/” | tee /etc/apt/sources.list.d/jitsi-stable.list > /dev/null Actualizamos

$ apt update

Atención

El instalador comprobará si Nginx o Apache están presentes (en ese orden) y configurará un host virtual dentro del servidor web que encuentre para servir a Jitsi Meet.

Si ya tenemos Nginx escuchando en el puerto 443 en la misma máquina, la configuración del servidor turn se omitirá ya que entrará en conflicto con su actual puerto 443.

$ apt install jitsi-meet

Nombre de host: Introducimos el nombre de host de la instancia de Jitsi Meet. Si tenés un dominio, utilizá el nombre de dominio específico, por ejemplo: jitsi.dominio.edu.ar.

Este nombre de host se utilizará para la configuración del virtualhost dentro de Jitsi Meet y también es la que se utilizará para acceder a las conferencias web.

Generación de certificados SSL/TLS: Se te preguntará sobre la generación de certificados SSL/TLS. Vamos a elegir la opción Quiero usar mi propio certificado (I want to use my own certificate):

Configuración avanzada

Si la instalación está en una máquina detrás de NAT, jitsi-videobridge debería configurarse automáticamente en el arranque. Si las llamadas de tres vías no funcionan, se necesita una configuración adicional de jitsi-videobridge para que sea accesible desde el exterior.

Siempre que todos los puertos requeridos sean enrutados (reenviados) a la máquina en la que se ejecuta, estos puertos son (TCP/443 o TCP/4443 y UDP/10000).

Es necesario añadir las siguientes líneas adicionales al archivo /etc/jitsi/videobridge/sip-communicator.properties:

org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=<Dirección.IP.local>
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Dirección IP pública>
Y comentar el org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES existente.

Systemd/Límites: Los despliegues predeterminados en sistemas que utilizan systemd pueden tener valores bajos por omisión para los procesos máximos y los archivos abiertos. Si el puente utilizado espera un mayor número de participantes, los valores por defecto deben ser ajustados (los valores por defecto son buenos para menos de 100 participantes).

Para actualizar los valores editamos /etc/systemd/system.conf y nos aseguramos de que tenemos los siguientes valores, solo los actualizamos si son menores.

DefaultLimitNOFILE=65000
DefaultLimitNPROC=65000
DefaultTasksMax=65000

Para comprobar los valores sólo hay que ejecutar

systemctl show --property DefaultLimitNPROC
systemctl show --property DefaultLimitNOFILE
systemctl show --property DefaultTasksMax

Detalles de systemd

Para recargar los cambios de systemd en un sistema en funcionamiento ejecutamos systemctl daemon-reload y systemctl restart jitsi-videobridge2. Para comprobar la parte de las tareas ejecute sudo systemctl status jitsi-videobridge2 y debería ver Tasks: XX (límite: 65000). Para comprobar la parte de archivos y procesos ejecutamos cat /var/run/jitsi-videobridge/jitsi-videobridge.pid/limits y deberías ver

Max processes             65000                65000                processes
Max open files            65000                65000                files

Confirmá que tu instalación está funcionando

Inicie un navegador web e ingresá el nombre de host o la dirección IP del paso anterior en la barra de direcciones.

Asegurate que podés crear una reunión y que los demás participantes pueden unirse a la sesión.

Si todo esto ha funcionado, ¡felicidades! Tenés un servicio de conferencia Jitsi operativo.

Ajustes básicos

La configuración predeterminada de jitsi puede funcionar bien con pocos participantes, pero a medida que el número aumenta, algunos de ellos pueden experimentar problemas y caídas (no debido al servidor Jitsi, sino a las limitaciones de ancho de banda y hardware en el lado del cliente): por omisión, cada cliente codifica (y envía al servidor) múltiples capas de flujo de vídeo y, al mismo tiempo, recibe las de todos los demás a una resolución ideal de 720p. Los cambios son hechos en nano /etc/jitsi/meet/$(hostname -f)-config.js, puede ser necesario descomentar algunas opciones.

Después de guardar los ajustes, siempre hay que reiniciar los servicios jitsi para aplicarlos: systemctl restart jicofo && systemctl restart jitsi-videobridge2.

Cambiar el idioma predeterminado

Es posible que quieras cambiar el idioma predeterminado de la interfaz, si te encuentras en un país donde no se habla inglés.

Los usuarios pueden seguir eligiendo su idioma preferido en la configuración del cliente.

defaultLanguage: 'en'

Cambiar el nombre de la sala

sed -i 's/"headerTitle":"Jitsi Meet"/"headerTitle":"Sala de Reuniones"/g' /usr/share/jitsi-meet/libs/app.bundle.min.js
sed -i 's/"headerTitle": "Jitsi Meet"/"headerTitle": "Sala de Reuniones"/g' /usr/share/jitsi-meet/lang/main-es.json
sed -i 's/"headerTitle": "Jitsi Meet"/"headerTitle": "Sala de Reuniones"/g' /usr/share/jitsi-meet/lang/main-esUS.json

Algunas variables de idioma pueden estar ausentes en /usr/share/jitsi-meet/lang/main-esUS.json, basta con agregarlas.

"welcomepage": {
...
"headerTitle": "Sala de Reuniones",
"headerSubtitle": "Comunicaciones seguras y de alta calidad",

Si queremos permitir que se graben las conversaciones, en el archivo anterior, descomentámos:

localRecording: {
enabled: true,
format: ‘ogg’
},

Forzar a los usuarios a establecer un nombre para mostrar

Para identificar fácilmente quién está hablando se puede forzar a los usuarios a elegir un nombre para mostrar.

requireDisplayName: true

RECOMENDADO: habilitar la suspensión de capas

Es posible que desee suspender las capas de vídeo no utilizadas hasta que se soliciten de nuevo, para ahorrar recursos tanto en el servidor como en los clientes.

active enableLayerSuspension: true

RECOMENDADO: Limitar la resolución de vídeo

Para ahorrar recursos tanto en el servidor como en los clientes.

Por ejemplo: establecer resolución: 480 (y descomentar si es necesario); descomentar toda la sección constraints: y establecer ideal: 480, max: 480

RECOMENDADO: Limitar el número de señales de vídeo reenviadas

Podemos limitar el número de transmisiones de vídeo enviadas a cada cliente, para ahorrar recursos tanto en el servidor como en los clientes. Como el ancho de banda y la CPU de los clientes pueden no soportar la carga, esta configuración puede evitar retrasos y caídas.

Esta función se encuentra de manera predeterminada en otras aplicaciones de conferencia web, dónde el límite está establecido en 4.

Lee cómo funciona en la documentación oficial de Jitsi y la evaluación del rendimiento en este estudio.

Establecer el número de canales de vídeo sin silenciar channelLastN: 4 (y descomentar si es necesario)

OPCIONAL: Desactivar las cámaras web al inicio

Es posible desactivar todos los canales de vídeo al inicio para ahorrar recursos tanto en el servidor como en los clientes. Para ello, establecer

startAudioOnly: true

OPCIONAL: Desactivar las cámaras web después de X participantes

Podemos limitar el inicio de las transmisiones de vídeo a partir de cierto número de participantes.

startVideoMuted: 10

OPCIONAL: Desactivar los micrófonos al inicio

Para mantener el orden podemos iniciar la conferencia con todas las fuentes de audio desactivadas.

La opción startWithAudioMuted establecida a true desactiva la entrada de audio al unirse.

OPCIONAL: Desactivar los micrófonos después de X participantes

Podemos limitar algunas fuentes de audio al inicio para mantener el orden. Definimos el número de microfonos que no estarán silenciados de manera predeterminada.

startAudioMuted: 10

Personalización

Es posible ajustarlo a gusto, cambiar el fondo o algún color, el logo… y así tendremos nuestro servicio totalmente listo:

  • Cambiar logo, sustituir el archivo /usr/share/jitsi-meet/images/watermark.svg

Los siguientes cambios están relacionados a /usr/share/jitsi-meet/css/all.css

  • Imagen de fondo: buscar .welcome{background-image: y poner url(../images/FONDO.jpg)

  • Color del texto del Título: .header .header-text-title{color:#XXXXXX;

  • Color del texto del Home: .header .header-text-description{display:inherit;color:#XXXXXX;

  • Color del boton: .welcome .welcome-page-button{width:51px;min-width:inherit;height:35px;font-size:14px;font-weight:inherit;background:#XXXXXX;

  • Color del cuadrado de las ultimas reuniones: .welcome .header .tab-container{font-size:16px;position:relative;text-align:left;min-height:354px;width:710px;background:#XXXX;

  • Color al pasar el ratón sobre la reunión seleccionada en la zona central: .item.with-click-handler:hover{background-color:#XXXXXX}

  • Color del texto del nombre de las reuniones: .meetings-list{font-size:14px;color:#XXXXXX

  • Color de texto de “Comenzar una reunion”: .enter-room-input-container{width:100%;padding:0 8px 5px 0;text-align:left;color:#XXXXXX;