Jitsi https://jitsi.org/ è un sistema di videoconferenza molto potente.
Tra le varie funzionalità messe a disposizione troviamo:
– chat
– videoconferenza
– condivisione di parti o dell’intero monitor
– inviare la videoconferenza live su youtube
– trasmettere in videoconferenza un video esistente su youtube
– gestire le “alzate di mano” dei partecipanti
– zittire i partecipanti
– espellere i partecipanti
Pacchetti da installare prima di jitsi:
apt-get install sudo apt-get install pgp
Ho dovuto disattivare anche IPV6 altrimenti nginx non partiva !
modificare il file /etc/sysctl.conf aggiungere net.ipv6.conf.all.disable_ipv6 = 1 applicare al volo la modifica sysctl -p
Ho installato jitsi direttamente tramite i repository su di una Debian Buster 10.3 (minimal, con solo SSH preinstallato e i pacchetti base)
basandomi su questa guida: https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md
Configurazione di COTURN per consentire l’accesso a tutti quei client per i quali viene
inibito l’utilizzo della porta UDP/10000 in uscita
Configurare il modulo di jitsi per consentire lo “smistamento” del traffico di tipo “turn” da
quello “web”.
Modificare il contenuto del file /etc/nginx/modules-enabled/60-jitsi-meet.conf
# this is jitsi-meet nginx module configuration # this forward all http traffic to the nginx virtual host port # and the rest to the turn server stream { upstream web { server 127.0.0.1:4444; } upstream turn { server 127.0.0.1:4445; } # since 1.13.10 map $ssl_preread_alpn_protocols $upstream { "h2" web; "http/1.1" web; "h2,http/1.1" web; default turn; } server { listen 443; # since 1.11.5 ssl_preread on; proxy_pass $upstream; # Increase buffer to serve video proxy_buffer_size 10m; } }
modificare /etc/nginx/sites-enabled/NOMEHOSTPUBBLICO.conf
server_names_hash_bucket_size 64; server { listen 80; listen [::]:80; server_name NOMEHOSTPUBBLICO; location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /usr/share/jitsi-meet; } location = /.well-known/acme-challenge/ { return 404; } location / { return 301 https://$host$request_uri; } } server { listen 4444 ssl http2; listen [::]:4444 ssl http2; server_name NOMEHOSTPUBBLICO; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED"; add_header Strict-Transport-Security "max-age=31536000"; ssl_certificate /etc/letsencrypt/live/NOMEHOSTPUBBLICO/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/NOMEHOSTPUBBLICO/privkey.pem; 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; location = /interface_config.js { alias /etc/jitsi/meet/interface_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; } # BOSH location = /http-bind { proxy_pass http://localhost:5280/http-bind; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $http_host; } # xmpp websockets location = /xmpp-websocket { proxy_pass http://127.0.0.1:5280/xmpp-websocket?prefix=$prefix&$args; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; tcp_nodelay on; } location ~ ^/([^/?&:'"]+)$ { try_files $uri @root_path; } location @root_path { rewrite ^/(.*)$ / break; } location ~ ^/([^/?&:'"]+)/config.js$ { set $subdomain "$1."; set $subdir "$1/"; alias /etc/jitsi/meet/NOMEHOSTPUBBLICO-config.js; } #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; } # BOSH for subdomains location ~ ^/([^/?&:'"]+)/http-bind { set $subdomain "$1."; set $subdir "$1/"; set $prefix "$1"; rewrite ^/(.*)$ /http-bind; } # websockets for subdomains location ~ ^/([^/?&:'"]+)/xmpp-websocket { set $subdomain "$1."; set $subdir "$1/"; set $prefix "$1"; rewrite ^/(.*)$ /xmpp-websocket; } }
modificare il file /etc/turnserver.conf
# jitsi-meet coturn config. Do not modify this line lt-cred-mech use-auth-secret keep-address-family static-auth-secret= LA_PASSWORD_DA_UTILIZZARE_IN_PROSODY realm=NOMEHOSTPUBBLICO cert=/etc/jitsi/meet/NOMEHOSTPUBBLICO.crt pkey=/etc/jitsi/meet/NOMEHOSTPUBBLICO.key no-tcp listening-port=443 tls-listening-port=4445 external-ip=NOMEHOSTPUBBLICO syslog
modificare il file /etc/prosody/conf.d/videoconferenza.comune.levico-terme.tn.it.cfg.lua
turncredentials_secret = "LA_PASSWORD_DA_UTILIZZARE_IN_PROSODY"; turncredentials = { { type = "stun", host = "NOMEHOSTPUBBLICO", port = "443" }, { type = "turn", host = "NOMEHOSTPUBBLICO", port = "443", transport = "udp" }, { type = "turns", host = "NOMEHOSTPUBBLICO", port = "443", transport = "tcp" } };
modificare il file /lib/systemd/system/coturn.service aggiungendo il parametro “AmbientCapabilities=CAP_NET_BIND_SERVICE”
https://github.com/coturn/coturn/issues/421
es.
[Unit]
Description=coTURN STUN/TURN Server
Documentation=man:coturn(1) man:turnadmin(1) man:turnserver(1)
After=network.target
[Service]
User=turnserver
Group=turnserver
Type=notify
ExecStart=/usr/bin/turnserver -c /etc/turnserver.conf –pidfile=
Restart=on-failure
InaccessibleDirectories=/home
PrivateTmp=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
Per l’autenticazione tramite LDAP invece non ho trovato una guida ufficiale e quindi di seguito riporto alcune note con cui sono riuscito a far girare il tutto:
apt-get -y install prosody-modules lua-ldap
preparo il file necessario alla connessione LDAP
# cat /etc/prosody/conf.avail/ldap.cfg.lua authentication = 'ldap2' ldap = { hostname = 'IP DEL SERVER LDAP', bind_dn = 'cn=UTENTE,cn=Users,dc=DOMINIO,dc=LOCALE', bind_password = 'PASSWORD', -- use_tls = true, user = { basedn = 'dc=DOMINIO,dc=LOCALE', -- filter = '(objectClass=User)', usernamefield = 'sAMAccountName', namefield = 'cn', }, }
e linkato alla cartella corretta
cp -s /etc/prosody/conf.avail/ldap.cfg.lua /etc/prosody/conf.d
modificato il file /etc/prosody/conf.d/NOMEHOSTPUBBLICO.cfg.lua modificando la tipologia di autenticazione
authentication = "ldap2"
e aggiungo anche questo
VirtualHost "guest.NOMEHOSTPUBBLICO" authentication = "anonymous" c2s_require_encryption = false
modificato il file /etc/prosody/prosody.cfg.lua
-- Debian: -- Please, don't change this option since /run/prosody/ -- is one of the few directories Prosody is allowed to write to -- pidfile = "/run/prosody/prosody.pid"; -- Necessario per gestire correttamente LDAP consider_bosh_secure = true -- Force clients to use encrypted connections? This option will -- prevent clients from authenticating unless they are using encryption. c2s_require_encryption = true
modificato il file /etc/jitsi/meet/NOMEHOSTPUBBLICO-config.js
hosts: { // XMPP domain. domain: 'NOMEHOSTPUBBLICO', anonymousdomain: 'guest.NOMEHOSTPUBBLICO',
modifico il file /etc/jitsi/jicofo/sip-communicator.properties
org.jitsi.jicofo.auth.URL=XMPP:NOMEHOSTPUBBLICO
riavviato i servizi
service prosody restart && service jicofo restart
Per attivare il collegamento con un centralino telefonico Asterisk:
apt-get install jigasi
modificare il file /etc/jitsi/jigasi/sip-communicator.properties nel modo seguente:
# If you want to use the SIP user part of the incoming/outgoing call SIP URI # you can set the following property to true. org.jitsi.jigasi.USE_SIP_USER_AS_XMPP_RESOURCE=true # Activate this property if you are using self-signed certificates or other # type of non-trusted certicates. In this mode your service trust in the # remote certificates always. net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED=true e se avete abilitato l’autenticazione tramite prodosody (es. tramite LDAP): org.jitsi.jigasi.xmpp.acc.USER_ID=NOMEUTENTE@NOMEHOSTPUBBLICO org.jitsi.jigasi.xmpp.acc.PASS=la password ! org.jitsi.jigasi.xmpp.acc.ANONYMOUS_AUTH=false
NB. a partire da una certa versione (non ho idea da quando !!) le chiamate in uscita effettuate tramite jigasi NON funzionano. Come risoluzione sembra sufficiente riavviare il servizio jicofo. Probabilmente dipende dall’ordine di avvio dei processi in systemd.
Risolto in questo modo:
– creare il file /etc/rc.local
#! /bin/sh /usr/bin/sleep 5 /usr/bin/systemctl restart jicofo
– abilitare il servizio e riavviare il server
systemctl enable rc-local reboot
Note relative all’integrazione tra Asterisk e Jigasi.
——————————————————
L’obiettivo è quello di consentire la chiamata ad un numero Asterisk tramite un normale apparecchio
telefonico, digitare il numero di stanza + PIN code ed accedere alla conferenza.
Jitsi utilizza per questo tipo di giochetto delle API esterne.
Questo tipo di soluzione non mi entusiasma più di tanto in quanto non avendo accesso a queste API rischio
che l’accrocchio dal giorno alla notte smetta di funzionare.
Ho seguito questo tutorial e l’ho riadattato alle mie esigenze:
https://community.jitsi.org/t/tutorial-jitsi-jigasi-freepbx-integration-…
https://community.jitsi.org/t/tutorial-self-hosted-conference-mapper-api…
Lato server Jitsi:
– installo mariadb
apt-get install mariadb-server mysql_secure_installation
– entro in console “mariadb” tramite il comando mysql ed eseguo il seguente codice
CREATE USER 'jigasi'@'%' IDENTIFIED BY 'PASSWORD'; grant all privileges on *.* TO 'jigasi'@'%'; FLUSH PRIVILEGES; CREATE DATABASE jigasi; USE jigasi; CREATE TABLE jitsiapi(id INT NOT NULL, conference VARCHAR(255) NOT NULL, PRIMARY KEY ( id ) ); EXIT;
– installare php
apt install php-fpm php-mysql
– creare lo script che gestisce la tabella mysql
# cat /usr/share/jitsi-meet/static/jitsi_number_api.php <?php //header('Content-Type: application/json'); header("Content-Type: text/plain"); $conference = $_REQUEST['conference']; $id = $_REQUEST['id']; //echo "Message received, conference is $conference\r\n"; $db_host = 'localhost'; $db_username = 'jigasi'; $db_password = 'PASSWORD'; $db_name = 'jigasi'; $db_table = 'jitsiapi'; // Create connection $conn = new mysqli($db_host, $db_username, $db_password, $db_name); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } if ($id > 0) { $sql = "SELECT id, conference FROM $db_table WHERE id='$id'"; $result = $conn->query($sql); if ($result->num_rows > 0) { // output data of each row while($row = $result->fetch_assoc()) { echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}"; } } else { echo "false"; } } else { $sql = "SELECT id, conference FROM $db_table WHERE conference='$conference'"; $result = $conn->query($sql); if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}"; } } else { if ($conference == NULL) { echo "FALSE"; } else { // genero un numero di stanza/codice casuale $codice = mt_rand(100000,999999); $sql = "INSERT INTO $db_table (id, conference) VALUES ($codice, '$conference')"; if ($conn->query($sql) === TRUE) { // begin $sql = "SELECT id, conference FROM $db_table WHERE conference='$conference'"; $result = $conn->query($sql); while($row = $result->fetch_assoc()) { echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}"; } } else { echo "Error: " . $sql . " " . $conn->error; } } } } $conn->close(); ?>
– predisporre un link simbolico per consentire l’esecuzione dello script tramite una connessione diretta HTTP utilizzata da Asterisk
ln -s /usr/share/jitsi-meet/static/jitsi_number_api.php /var/www/html/
– installare php sulla macchina Jitsi-Meet
apt-get install php-fpm php-mysql
– modificare il file /etc/jitsi/meet/jitsi_numbers.json
{"message":"Phone numbers available.","numbers":{"IT":["+39 XXXX.XXXXXX"]},"numbersEnabled":true}
– modificare il file /etc/nginx/sites-enabled/NOMEHOST.conf
location ~ [^/]\.php(/|$) { fastcgi_pass unix:/run/php/php7.3-fpm.sock; include snippets/fastcgi-php.conf; } location = /jitsi_numbers.json { alias /etc/jitsi/meet/jitsi_numbers.json; }
– riavviare nginx
service nginx restart
– modificare il file /etc/jitsi/meet/NOMEHOST-config.js in questo modo
dialInNumbersUrl: 'https://NOMEHOST/jitsi_numbers.json', dialInConfCodeUrl: 'https://NOMEHOST/static/jitsi_number_api.php',
– verificare che lato Jitsi-Meet tutto funzioni correttamente
https://NOMEHOST/static/dialInInfo.html?room=test
Lato Asterisk:
Riporto di seguito un semplice esempio della configurazione di un IVR per gestire le chiamate in entrata verso Jitsi-Meet
// // Contesto jigasi // context jigasi { s => { inizio: Answer(); &debug(IVR jigasi); // aspetta un secondo Wait(1); // seleziona come lingua predefinita l’italiano Set(CHANNEL(language)=it); // 5 secondi è il tempo che aspetta fra il primo tasto premuto e i successivi Set(TIMEOUT(digit)=5); // 10 secondi è il tempo a disposizione per il chiamante per premere un tasto Set(TIMEOUT(response)=10); // imposto il numero di jigasi Set(Jitsi=601); // aspetta che il chiamante digiti il pin Read(confid,conf-getpin,6,,1,10); // verifico se il PIN digitato e' corretto Set(CURL_RESULT=${SHELL(curl --silent http://192.168.33.117/jitsi_number_api.php?id=${confid} | sed -e 's/.*"conference":"\(.*\)@.*/\1/' | tr -d "\n")}); noop(Nome stanza: ${CURL_RESULT}); if (${CURL_RESULT}=false) { Playback(conf-invalidpin); goto jigasi|s|inizio; } SIPAddHeader(Jitsi-Conference-Room:${CURL_RESULT}); SIPAddHeader(Jitsi-Conference-Room-Pass:${confid}); Set(CDR(userfield)=Jitsi:${CURL_RESULT}); Dial(SIP/${Jitsi},3,m(silence)A(conf-otherinparty)); Hangup(); }; // eventuali numeri non gestiti ._. => { Hangup(); } };
Statistiche piattaforma di videoconferenza
Siti legati al discorso delle statistiche:
https://github.com/jitsi/jitsi-videobridge/blob/master/doc/statistics.md
https://github.com/jitsi/jitsi-videobridge/blob/master/doc/rest.md
https://github.com/jitsi/jitsi-videobridge/blob/master/doc/rest-colibri.md
Modifico il file /etc/jitsi/videobridge/config per attivare l’interfaccia interna:
# extra options to pass to the JVB daemon JVB_OPTS="--apis=rest" # adds java system props that are passed to jvb (default are for home and logging config file) JAVA_SYS_PROPS="-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/etc/jitsi -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=videobridge -Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/jitsi -Djava.util.logging.config.file=/etc/jitsi/videobridge/logging.properties" # con le ultime versioni questa modifica NON serve più e quindi ripristino la configurazione precedente !! #JAVA_SYS_PROPS="-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/etc/jitsi -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=videobridge -Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/jitsi -Djava.util.logging.config.file=/etc/jitsi/videobridge/logging.properties --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED"
modifico le seguenti righe al file “/etc/jitsi/videobridge/sip-communicator.properties”
org.jitsi.videobridge.ENABLE_STATISTICS=true org.jitsi.videobridge.STATISTICS_TRANSPORT=muc,colibri org.jitsi.videobridge.STATISTICS_INTERVAL=1000
a questo punto dopo aver avviato una videoconferenza (NB. è indispensabile che ci siano almento due utenti connessi ad una stanza !!) è possibile interrogare le statistiche in questo modo:
http://ip-locale-della-macchina:8080/colibri/conferences -> per ottenere l'elenco delle stanze attive http://ip-locale-della-macchina:8080/colibri/conferences/id-conferenza -> per ottenere il dettaglio di una specifica conferenza http://ip-locale-della-macchina:8080/colibri/stats -> per avere delle statistiche complete
NOTE INSTALLAZIONE JIBRI
Seguire le note di installazione presenti al seguente indirizzo: https://github.com/jitsi/jibri
NB. In base alla configurazione che ho adottato ho riscontrato NON pochi problemi nel far funzionare Jibri.
Configurazione utilizzata:
– un server reverse proxy su cui sono presenti i certificati LetsEncrypt
– un server jitsi con certificati auto generati
– us server jibri
Rispetto a quanto riportato nella documentazione ufficiale ho dovuto modificare il file /etc/prosody/conf.d/NOMEHOSTPUBBLICO.cfg.lua nel modo seguente
-- internal muc component, meant to enable pools of jibri and jigasi clients Component "internal.auth.NOMEHOSTPUBBLICO" "muc" modules_enabled = { "ping"; } storage = "memory" muc_room_cache_size = 1000 VirtualHost "recorder.NOMEHOSTPUBBLICO" modules_enabled = { "ping"; } authentication = "internal_plain"
da notare come lo storage vada impostato su memory e non su null !!
Ho dovuto configurare anche il file /etc/hosts presente sul server jibri per far puntare NOMEHOSTPUBBLICO all’ip locale del mio server jitsi
Il problema principale che ho riscontrato era legato al fatto che l’istanza di Chrome avviata da jibri rifiutava il certificato autogenerato presente sul server jitsi.
Per sistemare il problema ho dovuto scaricare i sorgenti di jibri e apportare alcune modifiche (https://github.com/jitsi/jibri/issues/246)
apt-get -y install openjdk-8-jdk apt-get -y install maven cd git clone https://github.com/jitsi/jibri.git cd jibri ... modify the source code ! add the chromeOption --ignore-certificate-errors at https://github.com/jitsi/jibri/blob/master/src/main/kotlin/org/jitsi/jib... can also solve this problem like this logPrefs.enable(LogType.DRIVER, Level.ALL) chromeOptions.setCapability(CapabilityType.LOGGING_PREFS, logPrefs) chromeOptions.addArguments("--ignore-certificate-errors")//ADD chromeDriver = ChromeDriver(chromeDriverService, chromeOptions) chromeDriver.manage().timeouts().pageLoadTimeout(60, TimeUnit.SECONDS)
ricompilato il tutto e aggiornato l’eseguibile
mvn clean verify package systemctl stop jibri cp target/jibri-8.0-SNAPSHOT-jar-with-dependencies.jar /opt/jitsi/jibri/jibri.jar systemctl start jibri
Ho dovuto inoltre aggiornare il file /home/jibri/.asoundrc
pcm.amix { type dmix ipc_key 219345 slave.pcm "hw:Loopback,0,0" } pcm.asnoop { type dsnoop ipc_key 219346 slave.pcm "hw:Loopback_1,1,0" } pcm.aduplex { type asym playback.pcm "amix" capture.pcm "asnoop" } pcm.bmix { type dmix ipc_key 219347 slave.pcm "hw:Loopback_1,0,0" } pcm.bsnoop { type dsnoop ipc_key 219348 slave.pcm "hw:Loopback,1,0" } pcm.bduplex { type asym playback.pcm "bmix" capture.pcm "bsnoop" } pcm.pjsua { type plug slave.pcm "bduplex" } pcm.!default { type plug slave.pcm "aduplex" }
Performance
Per ridurre al minimo il consumo di banda durante la trasmissione del video ho impostato nel
file /etc/jitsi/meet/NOMEHOST-config.js
resolution: 180, constraints: { video: { aspectRatio: 16 / 9, height: { ideal: 180, max: 180, min: 180 } } },
modificato il file /usr/share/jitsi-meet/interface_config.js:
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: false,
Personalizzare la “Welcom Page” !
Add in your nginx config in /etc/nginx/sites-available/HOSTNAME.conf above of location /config.js: location = / { rewrite ^/$ /custom_index.html break; } where custom_index.html is your own welcome-page and it is in the same directory as the original index.html