Menu Chiudi

Pillola #66: Installazione Jitsi come sistema di videoconferenza

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" }
};

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
WordPress Appliance - Powered by TurnKey Linux