Installation#

Alles auf einem Server#

Anleitung: https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart

Cluster#

Anleitung: https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-scalable

Architektur:

1x Signaling Node mit prosody, jicofo, nginx, jitsi-meet-web: 1 core, 2GB RAM ausreichend

Nx Video Node mit jitsi-videobridge2 (und optional jigasi): 4-8 cores, 8GB RAM, 1GBit/s Netzwerk, dedizierte Hardware (zB. IONOS L-16 HDD, Hetzner AX41 oder vergleichbares)

Setup Signaling Node#

Nginx, Prosody, certbot und gültiges Zertifikat für meet.example.org:

apt install prosody nginx
add-apt-repository ppa:certbot/certbot
apt install certbot python3-certbot-nginx
certbot --nginx

Jicofo und Jitsi Meet Web:

curl https://download.jitsi.org/jitsi-key.gpg.key | sudo 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/' | sudo tee /etc/apt/sources.list.d/jitsi-stable.list > /dev/null
apt update
apt install jicofo
apt install jitsi-meet-web jitsi-meet-prosody jitsi-meet-web-config

Bei der Installation von jitsi-meet-prosody wird ein Videobridge-Passwort abgefragt. Hier ein sicheres Passwort erstellen und dann später beim Setup der Video Nodes verwenden.

Nach der Installation von jicofo kontrollieren, dass in der /etc/jitsi/jicofo/config folgendes gesetzt ist:

JICOFO_HOST=localhost
JICOFO_HOSTNAME=meet.example.org
JICOFO_AUTH_DOMAIN=auth.meet.example.org
JICOFO_AUTH_USER=focus

Außerdem kontrollieren, dass in der /etc/jitsi/jicofo/sip-communicator.properties folgendes gesetzt ist:

org.jitsi.jicofo.BRIDGE_MUC=JvbBrewery@internal.auth.meet.example.org

Setup Video Node#

curl https://download.jitsi.org/jitsi-key.gpg.key | sudo 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/' | sudo tee /etc/apt/sources.list.d/jitsi-stable.list > /dev/null
apt update
apt install jitsi-videobridge2

In /etc/jitsi/videobridge/sip-communicator.properties folgendes hinzufügen:

org.jitsi.videobridge.xmpp.user.shard.DISABLE_CERTIFICATE_VERIFICATION=true

Nach Videobridge-Installation auf jeden Fall Passwort in /etc/jitsi/videobridge/config und /etc/jitsi/jicofo/sip-communicator.properties mit prosody auf Signaling Node abgleichen. Falls Login fehlschlägt, mit prosodyctl auf Signaling Node zurücksetzen.

Setup von websocket als bridge channel:

In /etc/jitsi/videobridge/sip-communicator.properties folgendes hinzufügen:

# datachannel
org.jitsi.videobridge.rest.jetty.port=9090
org.jitsi.videobridge.rest.jetty.host=localhost
org.jitsi.videobridge.rest.COLIBRI_WS_TLS=true
org.jitsi.videobridge.rest.COLIBRI_WS_DOMAIN=jvb1.meet.example.org:443

Nginx installieren, gültiges Zertifikat für jvb1.meet.example.org via certbot besorgen:

apt install nginx
add-apt-repository ppa:certbot/certbot
apt install certbot python3-certbot-nginx
certbot --nginx

Nginx als websocket-Reverse-Proxy für jvb1.meet.example.org einrichten:

Datei /etc/nginx/sites-enabled/jvb1.meet.example.org:

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
 	server_name jvb1.meet.example.org;

        ssl_certificate /etc/letsencrypt/live/jvb1.meet.example.org/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/jvb1.meet.example.org/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        # colibri (JVB) websockets
        location ~ ^/colibri-ws/default-id/(.*) {
            proxy_pass http://127.0.0.1:9090/colibri-ws/default-id/$1$is_args$args;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            tcp_nodelay on;
        }
}

Im Signaling Node in der config.js aktivieren:

    // Enables / disables a data communication channel with the Videobridge.
    // Values can be 'datachannel', 'websocket', true (treat it as
    // 'datachannel'), undefined (treat it as 'datachannel') and false (don't
    // open any channel).
    openBridgeChannel: 'websocket',

Weitere Doku zur Architektur und Skalierung von meet.ffmuc.net: https://www.slideshare.net/AnnikaWickert/freifunk-munich-how-to-scale-jitsi-231999167

Skalierungsinfos#

vom Freifunk München: 20 Server für 10000 User, We can run 280 people without issue with ressources compareable with AWS c5.2xlarge. (https://twitter.com/i/status/1253353265158332416)

vom den Matrix-Machern: Roughly speaking we're seeing Jitsi serve around 1000 concurrent streams (i.e. 25x 50-user conferences) on a typical 4 core box with 8GB of RAM. However, it's worth noting that Jitsi is pretty low resource - all it's doing is forwarding streams of data around the place. All the heavy lifting is done by the clients when displaying all the concurrent videos, so it's the clients which tend to be the bottleneck. (https://news.ycombinator.com/item?id=22804401)

Eigene Tests:

  • 4 User: 25% CPU-Auslastung für CCX21
  • 4 User: 10% CPU-Auslastung für L-16 HDD
  • 12 User: 26% CPU-Auslasting für L-16 HDD, 30 MBit/s eingehend, 25 MBit/s ausgehend

Anpassung#

Jitsi per User/Passwort schützen, wenn nicht jeder eine Konferenz starten soll: https://github.com/jitsi/jicofo#secure-domain

Um ein Impressum, Datenschutz etc auf Startseite einzufügen:

Dateien von /usr/share/jitsi-meet nicht editieren, sondern nach /etc/jitsi/meet kopieren, dort anpassen und via Webserver-Regeln ausliefern.

Als Beispiel:

/etc/jitsi/meet/static/welcomePageAdditionalContent.html

<template id = "welcome-page-additional-content-template">
	<div class="welcome-page-content">
        	<div class="welcome-footer">
            		<div class="welcome-footer-content">
                		<div class="welcome-footer-about">
					<div>
						<a href="https://jitsi.org/jitsi-meet/" rel="noopener" target="_blank">Powered by Jitsi Meet</a> | <a href="https://example.org/imprint" rel="noopener" target="_blank">Impressum</a> | <a href="https://example.org/privacy" rel="noopener" target="_blank">Datenschutz</a>
					</div>
				</div>
	    		</div>
		</div>
	</div>
</template>

/etc/jitsi/meet/plugin.head.html:

<style>
    .welcome-page-content {
        display: flex;
        flex-direction: column;
        flex-grow: 1;
        justify-content: space-between;
        position: relative;
        z-index: 1;
        margin-top: 35px;
        width: 100%
    }
    .welcome-page-content .welcome-footer {
        color: #FFF;
        display: flex;
        padding-bottom: 20px;
        padding-top: 20px;
        width: 100%;
        z-index: 1
    }
    .welcome-page-content .welcome-footer-content a {
        color: #fff !important;
        text-decoration: underline
    }

    .welcome-page-content .welcome-footer-content {
        display: flex;
        justify-content: center;
        width: 100%;
        z-index: 2
    }

    .welcome-page-content .welcome-footer-about {
        display: flex;
        flex-direction: column;
        flex: 1;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        justify-content: center
    }

    .welcome-page-content .welcome-footer-about:last-child {
        margin-left: 4px
    }
</style>

Ausschnitt aus apache-vhost-config:

  
  Alias "/static/welcomePageAdditionalContent.html" "/etc/jitsi/meet/static/welcomePageAdditionalContent.html"
  <Location /static/welcomePageAdditionalContent.html>
    Require all granted
  </Location>

  Alias "/plugin.head.html" "/etc/jitsi/meet/plugin.head.html"
  <Location /plugin.head.html>
    Require all granted
  </Location>

Wenn die Datei /usr/share/jitsi-meet/interface_config.js angepasst werden soll, ebenfalls mit Kopie unter /etc/jitsi/meet arbeiten.

Außerdem sind folgende Anpassungen in der /etc/jitsi/meet/meet.example.org-config.js empfohlen:

/// Nur letzte 6 Sprecher als Video anzeigen, schont CPU auf Clients und Bandbreite
channelLastN: 6,

/// Sprecherdetektion aus Clientseite deaktivieren, schont CPU auf Clients
disableAudioLevels: true,

defaultLanguage: 'de',
    
/// Schaltet Gravatar und STUN ab
disableThirdPartyRequests: true,

/// Auf eigene Hilfe-/Impressumsseite und Desktop-Apps während Call im Menü hinweisen
deploymentUrls: {
//    // If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
//    // user documentation.
    userDocumentationURL: 'https://example.org/jitsi',
//    // If specified a 'Download our apps' button will be displayed in the overflow menu with a link
//    // to the specified URL for an app download page.
    downloadAppsUrl: 'https://github.com/jitsi/jitsi-meet-electron'
},

Hardening#

Nginx-Konfiguration auf sicheren und schnellen Stand bringen: TLSv1.3 aktivieren, HTTP2 aktivieren,Cipher auf "ECDHE+AESGCM:ECDHE+CHACHA20" beschränken:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name jitsi.luki.org;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "ECDHE+AESGCM:ECDHE+CHACHA20";
}

Jicofo REST-API in /etc/jitsi/jicofo/sip-communicator.properties auf localhost beschränken:

org.jitsi.jicofo.auth.jetty.host=localhost

Prosody auf lokale Netzwerke in der /etc/prosody/prosody.cfg.lua beschränken:

interfaces = { "127.0.0.1", "10.0.0.2" }

Telefoneinwahl#

Setup:

Architektur

  Festnetznummer
+-----------------+
|                 |
|  Sipgate Basic  +<------------------+
|                 |              RTP  |
+--------+--------+                   |
         ^                            |
         |SIP                         |
         v                            |
+--------+--------+          +--------v--------+
|                 |          |                 |
|    Asterisk     +<-------->+     Jigasi      |
|                 |   SIP    |                 |
+-----------------+          +--------+--------+
         |                            ^
         |HTTP                        |
         v                            |
+--------+--------+                   |
|                 |                   |
|ConferenceMapper |                   |Web-RTC
|                 |                   |
+-----------------+                   |
         ^                            |
         |HTTP                        |
         |                            v
+--------+--------+          +--------+--------+
|                 |          |                 |
|   Jitsi Meet    +--------->+   Videobridge   |
|                 |          |                 |
+-----------------+          +-----------------+

Schematischer Ablauf: Eine Jitsi-Konferenz wird per Browser aufgemacht. Dabei ruft der Webclient den ConferenceMapper auf und dieser liefert eine 6-stellige Nummer zum Raumnamen. Diese wird dann auch im Jitsi-Info-Dialog nach der Einwahlnummer angezeigt. Ein weiterer Teilnehmer ruft die Sipgate-Basic-Numme an. Der Anruf kommt bei Asterisk an. Asterisk nimmt den Anruf entgegen und fragt per DTMF die "Konferenz-PIN" ab, schlägt den Raumnamen im ConferenceMapper nach, fügt dann den SIP-Header Jitsi-Conference-Room: mit dem Raumnamen hinzu und ruft bei der Jigasi-Gegenstelle an. Wenn Jigasi den Anruf entgegennimmt, stellt Asterisk die VoIP-RTP-Sprachverbindung direkt zwischen Sipgate und Jigasi her. Jigasi selbst nimmt erfährt dann via XMPP die Videobridge-Details und stellt seinerseits eine Verbindung zur Videobridge her, über die dann die Audiokommunikation läuft.

Zusammengefasst: Asterisk und der ConferenceMapper übernehmen nur Signaling-Funktionen und sind an der eigentlichen Sprachverbindung nicht beteiligt, Jigasi ist als "RTP <-> WebRTC-Proxy" Teil der Audiokette.

Die Anleitung basiert auf https://doku.jgz-energie.net/jitsi-meet/#telefoneinwahl und dem "Hello World"-Kapitel des deutschen Askterisk-Buchs http://das-asterisk-buch.de/1.6/installation-einleitung.html

Setup Asterisk#

Ubuntu Server 20.04, Asterisk installieren, default-Konfiguration löschen

apt install asterisk
rm /etc/asterisk/sip.conf
rm /etc/asterisk/extensions*

/etc/asterisk/sip.conf, aaaa ist Sipgate User, bbbb Sipgate Passwort, cccc ist Jigasi-SIP-Passwort, d.d.d.d ist die öffentliche IP-Adresse des Jigasi-Servers.

[general] 
port=5060 
bindaddr=0.0.0.0
language=de
deny=0.0.0.0/0.0.0.0
permit=d.d.d.d/255.255.255.255
allowguest=no

register => aaaa:bbbb@sipgate.de/aaaa

[ext-sip-account]
type=friend
context=von-voip-provider
username=aaaa
fromuser=aaaa
secret=bbbb
host=sipgate.de
fromdomain=sipgate.de
qualify=yes
insecure=port,invite
nat=yes
disallow=all
allow=alaw

[2000]
type=friend
context=meine-telefone
secret=cccc
host=dynamic
disallow=all
allow=alaw

extensions.conf, aaaa ist Sipgate User, https://meet.example.org/conferenceMapper ist die Adresse des ConferenceMappers (siehe unten)

[default]

[von-voip-provider]
exten => aaaa,1,Answer()
same => n,Read(confid,conf-getpin&astcc-followed-by-pound)
same => n,Set(CURL_RESULT=${SHELL(curl --silent https://meet.example.org/conferenceMapper?id=${confid} | sed -e 's/.*"conference":"\(.*\)@.*/\1/')})
same => n,Verbose(0, ${CURL_RESULT});
same => n,SIPAddHeader(Jitsi-Conference-Room:${CURL_RESULT})
same => n,Set(CDR(userfield)=Jitsi:${CURL_RESULT})
same => n,Dial(SIP/2000,3)
same => n,Verbose(0, Contacting Jigasi... Status is ${DIALSTATUS} );
same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?unknown)
same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?conf-busy)
same => n,GotoIf($["${DIALSTATUS}" = "CANCEL"]?unknown)
same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?unknown)
same => n,GotoIf($["${DIALSTATUS}" = "CHANUNAVAIL"]?unknown)
same => n,GotoIf($["${DIALSTATUS}" = "DONTCALL"]?unknown)
same => n,GotoIf($["${DIALSTATUS}" = "TORTURE"]?unknown)
same => n,GotoIf($["${DIALSTATUS}" = "INVALIDARGS"]?unknown)
same => n,Hangup()
same => n(conf-busy),Playback(confbridge-begin-leader)
same => n,Dial(SIP/2000,120)

Asterisk neustarten um Konfiguration vollständig anzuwenden:

system restart asterisk

Deutsche Sprachdateien nachinstallieren#

In den Ubuntu-Paketquellen gibt es keine deutschen Asterisk-Sprachdateien, aber https://www.asterisksounds.org stellt deutsche Sprachdateien bereit.

Installation:

mkdir /var/lib/asterisk/sounds/de
cd /var/lib/asterisk/sounds/de
wget -O core.zip https://www.asterisksounds.org/de/download/asterisk-sounds-core-de-sln16.zip
wget -O extra.zip https://www.asterisksounds.org/de/download/asterisk-sounds-extra-de-sln16.zip
unzip core.zip
unzip extra.zip
chown -R asterisk.asterisk /var/lib/asterisk/sounds/de
find /var/lib/asterisk/sounds/de -type d -exec chmod 0775 {} \;

Setup Jigasi#

Jigasi installieren

apt install jigasi

Bei der Installation wird nach Jigasi-SIP-User und Passwort gefragt.

Jigasi-SIP-User ist 2000@<IP-Asterisk> Jigasi-Passwort cccc aus Asterisk-Konfiguration oben

In der Jigasi-Konfiguration /etc/jitsi/jigasi/sip-communicator.properties auf jeden Fall das Packet-Logging deaktivieren:

net.java.sip.communicator.packetlogging.PACKET_LOGGING_ENABLED=false

Außerdem für Prosody-Self-Signed-Zertifikate ALWAYS_TRUST_MODE_ENABLED aktivieren:

net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED=true

Die Standard-Konfiguration von Jigasi geht davon aus, dass der Jitsi-Prosody auf dem selben Host installiert ist. Wenn das Im Cluster-Setup nicht so ist, dann: in /etc/jitsi/jigasi/config JIGASI_HOSTNAME und JIGASI_HOST auf den Prosody-Host setzen. In /etc/jitsi/jigasi/sip-communicator.properties die org.jitsi.jigasi.xmpp.acc.SERVER_ADDRESS auf den Prosody-Host setzen.

Mit der obenstehenden Asterisk-Konfiguration ist nur ein Call-In in eine Konferenz möglich, kein Call-Out. Um Call-Out von Jitsi auch in der Jitsi-Meet-UI zu deaktivieren, folgende Anpassung in der /etc/jitsi/jigasi/config:

JIGASI_OPTS="--nocomponent=true"

Setup ConferenceMapper#

ConferenceMapper von https://github.com/luki-ev/conferencemapper installieren und konfigurieren.

Die resultierende URL dann noch in der Asterisk-Konfiguration /etc/asterisk/extentions.conf hinterlegen.

Eine statische Datei mit den Einwahlnummern unter https://meet.example.com/numbers.json via apache-vhost verfügbar machen:

Alias "/numbers.json" "/etc/jitsi/meet/numbers.json"
<Location /numbers.json>
    Require all granted
</Location>

Inhalt von /etc/jitsi/meet/numbers.json, FFFF/FFFF durch Sipgate Basic-Festnetznummer ersetzen:

{"message":"Einwahlnummbern","numbers":{"DE":["FFFF/FFFF"]},"numbersEnabled":true}

Setup Jitsi-Meet#

Audio-Konfiguration in Jitsi-Meet config.js aktivieren:

    dialInNumbersUrl: 'https://meet.example.org/numbers.json',
    dialInConfCodeUrl: 'https://meet.example.org/conferenceMapper',

Troubleshooting und Stolperfallen#

Ton von Asterisk kommt noch, aber kein Ton mehr nach Join der Konferenz, mögliche Ursachen

  • Jigasi meldet sich nicht mit öffentlicher IP an Asterisk an, deswegen kann keine direkte Verbindung von Sipgate zu Jigasi aufgebaut werden.
  • Codecs von jigasi und codecs von initialer DTMF-Übertragung stimmen nicht über ein -> Asterisk, sip.conf: disallow=all, allow=alaw sowohl bei Sipgate-Abschnitt als auch bei Jigasi-Abschnitt

Asterisk-Konfiguration wird nur nach Neustart von Asterisk vollständig übernommen. Daher gerade bei Fehlersuche und Konfigurationsänderungen immer askterisk komplett neustarten! Das in manchen Dokumentationen genannte asterisk -rx 'core reload' reicht nicht.