Table des matières

Install Jitsi on CentOS 7

This page gives the needed steps to install and configure Jitsi on a CentOS server. If like me, you're not a big Docker fan, and you're happier with EL based systems, it might be useful to you. In this guide, you'll learn :

We deploy all this with ansible, see prosody jitsi and jitsi videobridge This page are just some notes to help you setting this up if you don't want to play with ansible. But ansible is our recommanded way to deploy it

Jitsi is composed of several components, and also relies on 3rd party ones. Here is a quick overview of which are using for what :

In this example, we will use visio.fws.fr as jitsi domain name. You'll need to adapt this

This how to assume you already have a valid SSL cert in /etc/prosody/certs/jitsi.crt with its private key /etc/prosody/certs/jitsi.key.

Enable EPEL repo

If not already done

yum install epel-release

Install prosody

Prosody is available in EPEL, so we can install it easily

mkdir -p /opt/prosody/modules
yum install prosody lua-ldap lua-cyrussasl

Jitsi can also use some 3rd party prosody modules

for MOD in ext_events.lib.lua \
           util.lib.lua \
           mod_speakerstats.lua \
           mod_speakerstats_component.lua \
           mod_turncredentials.lua \
           mod_conference_duration.lua \
           mod_conference_duration_component.lua; do
  wget -P /opt/prosody/modules \
    https://raw.githubusercontent.com/jitsi/jitsi-meet/master/resources/prosody-plugins/$MOD
done
wget -P /opt/prosody/modules \
  https://raw.githubusercontent.com/prosody-modules/mod_auth_ldap/master/mod_auth_ldap.lua

Now, lets configure it. Edit /etc/prosody/prosody.cfg.lua

lua
plugin_paths = { "/opt/prosody/modules" }
 
admins = {
}
modules_enabled = {
  "roster";
  "saslauth";   
  "tls";
  "dialback";   
  "disco";
  "carbons";
  "pep";
  "private";
  "blocklist";  
  "vcard4";
  "vcard_legacy";
  "version";
  "uptime";
  "time";
  "ping";
  "register";   
  "admin_adhoc";
  "bosh";
  "pubsub";
}
modules_disabled = {
}
 
allow_registration = false
c2s_require_encryption = true
s2s_require_encryption = true
s2s_secure_auth = false
 
c2s_ports = {   
  5222,
}
s2s_port = {
  5269,
}
http_port = {   
  5280,
}
component_ports = {
  5347,
}
component_interface = "0.0.0.0"
 
authentication = "internal_hashed"
 
log = {
  info = "*syslog";
  error = "*syslog";
}
 
certificates = "/etc/pki/prosody/";
pidfile = "/run/prosody/prosody.pid";
daemonize = false;
 
VirtualHost "localhost"
 
Include "conf.d/*.cfg.lua"

Now edit /etc/prosody/conf.d/jitsi.cfg.lua

lua
muc_mapper_domain_base = "visio.fws.fr";
admins = { "focus@auth.visio.fws.fr" }
http_default_host = "visio.fws.fr"
 
-- If you have a turn server, you can configure it here
-- turncredentials_secret = "TURN_SECRET";
-- turncredentials = {
--   {
--     type = "turns",
--     host = "turn.example.net",
--     port = "3478",
--     transport = "udp"
--   }
-- };
 
cross_domain_bosh = false;
cross_domain_websocket = true;
consider_bosh_secure = true;
 
VirtualHost "visio.fws.fr"
  authentication = "anonymous"
  ssl = {
    key = "/etc/prosody/certs/jitsi.key";
    certificate = "/etc/prosody/certs/jitsi.crt";
  }
 
  modules_enabled = {
    "bosh";
    "pubsub";   
    "ping";
    "websocket";
    "turncredentials";
    "speakerstats";
    "conference_duration";
  }
  c2s_require_encryption = false
  allow_unencrypted_plain_auth = true
  speakerstats_component = "speakerstats.visio.fws.fr"
  conference_duration_component = "conferenceduration.visio.fws.fr"
 
 
VirtualHost "auth.visio.fws.fr"
  ssl = {
    key = "/etc/prosody/certs/jitsi.key";
    certificate = "/etc/prosody/certs/jitsi.crt";
  }
  authentication = "internal_hashed"
  c2s_require_encryption = false
 
Component "conference.visio.fws.fr" "muc"
  storage = "memory"
  modules_enabled = { "ping"; }
  muc_room_locking = false
  muc_room_default_public_jids = true
 
Component "internal.auth.visio.fws.fr" "muc"
  storage = "memory"
  modules_enabled = { "ping"; }
  muc_room_cache_size = 1000
 
Component "focus.visio.fws.fr"
  component_secret = FOCUS_COMPONENT_SECRET"
 
Component "speakerstats.visio.fws.fr" "speakerstats_component"
  muc_component = "conference.visio.fws.fr"
 
Component "conferenceduration.visio.fws.fr" "conference_duration_component"
  muc_component = "conference.visio.fws.fr"

Now we can start and enable the daemon

systemctl enable --now prosody

And we have to create some xmpp user accounts which will be used by Jitsi (adapt the passwords of course)

prosodyctl register jvb auth.visio.fws.fr JVB_XMPP_PASS
prosodyctl register focus auth.visio.fws.fr FOCUS_XMPP_PASS
prosodyctl register jigasi auth.visio.fws.fr JIGASI_XMPP_PASS

Install a recent maven

Maven is available with yum, but its version is too old to build videobridge. So we'll install a newer one

yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel
mkdir -p /opt/maven/apache-maven/
wget https://miroir.univ-lorraine.fr/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
tar xvzf apache-maven-3.6.3-bin.tar.gz
rsync -rvP --del apache-maven-3.6.3/ /opt/maven/apache-maven/
rm -rf apache-maven-3.6.3-bin.tar.gz apache-maven-3.6.3/
 
cat <<_EOF > /etc/profile.d/maven.sh
#!/bin/sh
 
export JAVA_HOME=/usr/lib/jvm/jre-openjdk
export M2_HOME=/opt/maven/apache-maven
export MAVEN_HOME=/opt/maven/apache-maven
export PATH=${M2_HOME}/bin:${PATH}
 
_EOF
chmod +x /etc/profile.d/maven.sh
exec bash

Create a jitsi user

useradd -d /opt/jitsi jitsi

Install Videobridge

yum install git
mkdir /opt/jitsi/{src,videobridge}
cd /opt/jitsi/src
git clone https://github.com/jitsi/jitsi-videobridge.git
cd jitsi-videobridge
/opt/maven/apache-maven/bin/mvn package -DskipTests -Dassembly.skipAssembly=false
unzip target/jitsi-videobridge-2.1-SNAPSHOT-archive.zip -d /tmp/
rsync -rvP --del /tmp/jitsi-videobridge-2.1-SNAPSHOT/ /opt/jitsi/videobridge/
rm -rf /tmp/jitsi-videobridge-2.1-SNAPSHOT/

Now we have to configure videobridge

mkdir -p /opt/jitsi/etc/videobridge
cat <<_EOF > /opt/jitsi/etc/videobridge/videobridge.conf
JVB_OPTS="--apis=rest"
JAVA_SYS_PROPS="-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/opt/jitsi/etc -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=videobridge"
_EOF
 
cat <<_EOF > /opt/jitsi/etc/videobridge/sip-communicator.properties
org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay=false
org.jitsi.videobridge.SINGLE_PORT_HARVESTER_PORT=10000
org.jitsi.videobridge.TCP_HARVESTER_PORT=4443
org.jitsi.videobridge.DISABLE_TCP_HARVESTER=false
org.ice4j.ipv6.DISABLED=true
# If behind NAT, set your private, and public IP here
# org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=10.99.2.19
# org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=10.11.12.13
 
org.jitsi.videobridge.ENABLE_STATISTICS=true
org.jitsi.videobridge.STATISTICS_TRANSPORT=muc
org.jitsi.videobridge.STATISTICS_INTERVAL=5000
 
org.jitsi.videobridge.xmpp.user.acc1.HOSTNAME=jitsi.fws.fr
org.jitsi.videobridge.xmpp.user.acc1.DOMAIN=auth.visio.fws.fr
org.jitsi.videobridge.xmpp.user.acc1.USERNAME=jvb
org.jitsi.videobridge.xmpp.user.acc1.PASSWORD=JVB_PASSWORD
org.jitsi.videobridge.xmpp.user.acc1.MUC_JIDS=JvbBrewery@internal.auth.visio.fws.fr
# This is just a nickname for the videobridge.
# If you run several videobridge instances, make sure each one uses a unique name
org.jitsi.videobridge.xmpp.user.acc1.MUC_NICKNAME=jitsi.fws.fr
 
_EOF

Now we'll create a systemd unit for the videobridge service

mkdir -p /etc/systemd/system
cat <<_EOF > /etc/systemd/system/jitsi-videobridge.service
[Unit]
Description=Jitsi Videobridge
After=network.target
 
[Service]
Type=simple
SuccessExitStatus=143
EnvironmentFile=/opt/jitsi/etc/videobridge/videobridge.conf
User=jitsi
Group=jitsi
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=full
ReadOnlyDirectories=/opt/jitsi/etc /opt/jitsi/videobridge
Restart=on-failure
StartLimitInterval=0
RestartSec=30
# more threads for this process
TasksMax=65000
# allow more open files for this process
LimitNPROC=65000
LimitNOFILE=65000
ExecStart=/opt/jitsi/videobridge/jvb.sh ${JVB_OPTS}
 
[Install]
WantedBy=multi-user.target
 
_EOF
systemctl daemon-reload
systemctl enable --now jitsi-videobridge
You have to open ports TCP/4443 and UDP/10000. Those ports must be reachable by participants when they join a room. Unless you use a TURN server

Install Jicofo

cd /opt/jitsi/src
git clone https://github.com/jitsi/jicofo.git
cd jicofo
/opt/maven/apache-maven/bin/mvn package -DskipTests -Dassembly.skipAssembly=false
unzip target/jicofo-1.1-SNAPSHOT-archive.zip -d /tmp
mkdir -p /opt/jitsi/jicofo
rsync -rvP --del /tmp/jicofo-1.1-SNAPSHOT/ /opt/jitsi/jicofo/

Now that jicofo is installed, it must be configured

mkdir -p /opt/jitsi/etc/jicofo
cat <<_EOF > /opt/jitsi/etc/jicofo/jicofo.conf
JICOFO_HOST=jitsi.fws.fr
JICOFO_DOMAIN=visio.fws.fr
JICOFO_USER=focus
JICOFO_USERDOMAIN=auth.visio.fws.fr
JICOFO_SECRET='FOCUS_COMPONENT_SECRET'
JICOFO_USER_PASS='FOCUS_XMPP_PASS'
JICOFO_OPTS=''
JAVA_SYS_PROPS="-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/opt/jitsi/etc -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=jicofo"
_EOF
 
cat <<_EOF > /opt/jitsi/etc/jicofo/sip-communicator.properties
org.jitsi.jicofo.BRIDGE_MUC=JvbBrewery@internal.auth.visio.fws.fr
# Comment this line if you do not intend to use Jigasi
org.jitsi.jicofo.jigasi.BREWERY=JigasiBrewery@internal.auth.visio.fws.fr
_EOF

Now we can create a systemd unit and start jicofo

cat <<_EOF > /etc/systemd/system/jitsi-jicofo.service
[Unit]
Description=Jitsi Conference Focus
After=network.target
 
[Service]
Type=simple
SuccessExitStatus=143
EnvironmentFile=/opt/jitsi/etc/jicofo/jicofo.conf
User=jitsi
Group=jitsi
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=full
ReadOnlyDirectories=/opt/jitsi/etc /opt/jitsi/jicofo
Restart=on-failure
StartLimitInterval=0
RestartSec=30
ExecStart=/opt/jitsi/jicofo/jicofo.sh \
            --host=${JICOFO_HOST} \
            --domain=${JICOFO_DOMAIN} \
            --secret=${JICOFO_SECRET} \
            --user_domain=${JICOFO_USERDOMAIN} \
            --user_name=${JICOFO_USER} \
            --user_password=${JICOFO_USER_PASS} \
            ${JICOFO_OPT}
 
[Install]
WantedBy=multi-user.target
 
_EOF
systemctl daemon-reload
systemctl enable --now jitsi-jicofo

You should check your logs now to be sure jicofo discover your videobridge.

Install Meet

It's time to install the Meet interface now.

cat <<_EOF > /etc/yum.repos.d/nodejs.repo
[nodejs]
baseurl = https://rpm.nodesource.com/pub_12.x/el/7/$basearch
gpgcheck = 1
gpgkey = https://rpm.nodesource.com/pub/el/NODESOURCE-GPG-SIGNING-KEY-EL
name = Node.js Packages for Enterprise Linux
_EOF
yum install nodejs
cd /opt/jitsi/src/
git clone https://github.com/jitsi/jitsi-meet.git
cd jitsi-meet
npm i
make

This should build Jitsi Meet. We now can put it somewhere to be served by a web server :

mkdir -p /opt/jitsi/meet
rm -rf /opt/jitsi/meet/*
mkdir -p /opt/jitsi/meet/css
cp -r *.js *.html connection_optimization favicon.ico fonts images libs static sounds LICENSE lang /opt/jitsi/meet/
cp css/all.css /opt/jitsi/meet/css/

Serving this from your webserver is a bit out of scope for this how to because it can be done in a lot of different ways depending on your infra. Here's a sample nginx conf :

<hideen Here's a sample nginx config>

server {
  listen 443 ssl http2;
  server_name visio.fws.fr;
 
  ssl_certificate_key /etc/prosody/certs/jitsi.key;
  ssl_certificate     /etc/prosody/certs/jitsi.crt;
 
  if ($request_method !~ ^(GET|POST|HEAD)$ ) {
    return 405;
  }
 
  root /opt/jitsi/meet;
  index index.html;
 
  # conferenceMapper endpoint
  location ~ ^/(phoneNumberList|conferenceMapper) {
    proxy_pass http://localhost:8823;
    proxy_socket_keepalive on;
  }
 
  # BOSH endpoint
  location /http-bind {
    proxy_socket_keepalive on;
    proxy_pass http://localhost:5280/http-bind;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $http_host;
  }
 
  # Websocket endpoint
  location /xmpp-websocket {
    proxy_pass http://localhost:5280/xmpp-websocket?$args;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $remote_addr;
    tcp_nodelay on;
  }
 
  # Conference rooms
  location ~ ^/([a-zA-Z0-9=\?]+)$ {
    rewrite ^/(.*)$ / break;
  }
  location / {
    ssi on;
    limit_req zone=limit_req_std burst=100 nodelay;
    limit_conn limit_conn_std 80;
  }
 
  allow 0.0.0.0/0;
  deny all;
}

</hidden>

You also have to edit /opt/jitsi/meet/config.js and adapt it to your needs.

Here's a example of config.js

Here's a example of config.js

js
var config = {
    "bosh": "//visio.fws.fr/http-bind",
    "channelLastN": -1,
    "clientNode": "http://jitsi.org/jitsimeet",
    "desktopSharingChromeExtId": null,
    "desktopSharingChromeMinExtVersion": 0.1,
    "desktopSharingChromeSources": [
        "screen",
        "window",
        "tab"
    ],
    "dialInConfCodeUrl": "https://visio.fws.fr/conferenceMapper",
    "dialInNumbersUrl": "https://visio.fws.fr/phoneNumberList",
    "disableAudioLevels": true,
    "disableThirdPartyRequests": true,
    "enableCalendarIntegration": false,
    "enableLayerSuspension": true,
    "enableNoAudioDetection": true,
    "enableNoisyMicDetection": false,
    "enableWelcomePage": true,
    // Uncomment to enable Etherpad integration
    //"etherpad_base": "https://etherpad.fws.fr/p/",
    "focusUserJid": "focus@auth.visio.fws.fr",
    "hosts": {
        "domain": "visio.fws.fr",
        "muc": "conference.visio.fws.fr"
    },
    "localRecording": {
        "enabled": true
    },
    "p2p": {
        "enabled": true,
        "preferH264": true,
        "useStunTurn": true
    },
    "requireDisplayName": false,
    "resolution": 480,
    "testing": {
        "p2pTestMode": false
    },
    "useStunTurn": true,
    "websocket": "wss://visio.fws.fr/xmpp-websocket"
};

Now you should be able to reach the Meet interface and join a conference.

Install Jigasi

Now that we have a working Jitsi install, we may want to integrate it with our telephony system. With this, we'll be able to join phone numbers to jitsi conference. Jigasi is the component doing this bridge. It connects to your SIP server just as a phone, and also on prosody as an XMPP user. It can send connect calls to Jitsi Videobridge.

For outbound calls, it's quite easy. Once configured and enabled, you'll get a small + in Meet interface from where you can type the number you want to call. The call will be made by Jigasi and routed by your SIP server. If the phone answer, it'll be imédiatly joined in the conf.

Inbound calls are a bit trickier, and the general workflow is the following

But first, lets install Jigasi

mkdir -p /opt/jitsi/jigasi
cd /opt/jitsi/src
git clone https://github.com/jitsi/jigasi.git
cd jigasi
/opt/maven/apache-maven/bin/mvn package -DskipTests -Dassembly.skipAssembly=false
unzip jigasi/target/jigasi-linux-x64-1.1-SNAPSHOT.zip -d /tmp
rsync -rvP --del /tmp/jigasi-linux-x64-1.1-SNAPSHOT/ /opt/jitsi/jigasi/

Now, we have to configure it. In this example, the SIP server on which we register is ast.fws.fr and we'll use SIP extension 304 with secret SIP_SECRET (we'll see later in this how to how to craete the extension through FreePBHX web interface)

mkdir -p /opt/jitsi/etc/jigasi
cat <<_EOF > /opt/jitsi/jigasi/jigasi.conf
JIGASI_OPTS=''
JAVA_SYS_PROPS=''
_EOF
cat <<_EOF > /opt/jitsi/jigasi/sip-communicator.properties
# Default room to which inbound called without a Jitsi-Conference-Room header
org.jitsi.jigasi.DEFAULT_JVB_ROOM_NAME=sip
 
net.java.sip.communicator.impl.protocol.SingleCallInProgressPolicy.enabled=false
 
# Disable packet capture
net.java.sip.communicator.packetlogging.PACKET_LOGGING_ENABLED=false
 
# Enable brewery
org.jitsi.jigasi.BREWERY_ENABLED=true
org.jitsi.jigasi.MUC_SERVICE_ADDRESS=conference.visio.fws.fr
 
# SIP acount
net.java.sip.communicator.impl.protocol.sip.acc=acc
net.java.sip.communicator.impl.protocol.sip.acc.ACCOUNT_UID=SIP\:304
# THis is the base64 encoded SIP secret. Obtained with
# echo -n SIP_SECRET | base64
net.java.sip.communicator.impl.protocol.sip.acc.PASSWORD=U0lQX1NFQ1JFVA==
net.java.sip.communicator.impl.protocol.sip.acc.PROTOCOL_NAME=SIP
net.java.sip.communicator.impl.protocol.sip.acc.SERVER_ADDRESS=ast.fws.fr
net.java.sip.communicator.impl.protocol.sip.acc.USER_ID=304
net.java.sip.communicator.impl.protocol.sip.acc.KEEP_ALIVE_INTERVAL=25
net.java.sip.communicator.impl.protocol.sip.acc.KEEP_ALIVE_METHOD=OPTIONS
net.java.sip.communicator.impl.protocol.sip.acc.VOICEMAIL_ENABLED=false
net.java.sip.communicator.impl.protocol.sip.acc.OVERRIDE_ENCODINGS=false
net.java.sip.communicator.impl.protocol.sip.acc.DOMAIN_BASE=visio.fws.fr
net.java.sip.communicator.impl.protocol.sip.acc.PROXY_ADDRESS=ast.fws.fr
net.java.sip.communicator.impl.protocol.sip.acc.PROXY_AUTO_CONFIG=false
net.java.sip.communicator.impl.protocol.sip.acc.PROXY_PORT=5060
net.java.sip.communicator.impl.protocol.sip.acc.PREFERRED_TRANSPORT=UDP
 
# XMPP account
net.java.sip.communicator.impl.protocol.jabber.acc=acc
net.java.sip.communicator.impl.protocol.jabber.acc.ACCOUNT_UID=Jabber:jigasi@auth.visio.fws.fr
net.java.sip.communicator.impl.protocol.jabber.acc.USER_ID=jigasi@auth.visio.fws.fr
net.java.sip.communicator.impl.protocol.jabber.acc.IS_SERVER_OVERRIDDEN=true
net.java.sip.communicator.impl.protocol.jabber.acc.SERVER_ADDRESS=jitsi.fws.fr
# This is the base64 encoded XMPP secret
# obtained with echo -n JIGASI_XMPP_PASS | base64
net.java.sip.communicator.impl.protocol.jabber.acc.PASSWORD=SklHQVNJX1hNUFBfUEFTUw==
net.java.sip.communicator.impl.protocol.jabber.acc.RESOURCE_PRIORITY=30
net.java.sip.communicator.impl.protocol.jabber.acc.BREWERY=JigasiBrewery@internal.auth.visio.fws.fr
net.java.sip.communicator.impl.protocol.jabber.acc.DOMAIN_BASE=visio.fws.fr
 
org.jitsi.jigasi.xmpp.acc.USER_ID=jigasi@auth.visio.fws.fr
org.jitsi.jigasi.xmpp.acc.PASS=JIGASI_XMPP_PASS
org.jitsi.jigasi.xmpp.acc.ANONYMOUS_AUTH=false
org.jitsi.jigasi.xmpp.acc.IS_SERVER_OVERRIDDEN=true
org.jitsi.jigasi.xmpp.acc.SERVER_ADDRESS=jitsi.fws.fr
org.jitsi.jigasi.xmpp.acc.JINGLE_NODES_ENABLED=false
org.jitsi.jigasi.xmpp.acc.AUTO_DISCOVER_STUN=false
org.jitsi.jigasi.xmpp.acc.IM_DISABLED=true
org.jitsi.jigasi.xmpp.acc.SERVER_STORED_INFO_DISABLED=true
org.jitsi.jigasi.xmpp.acc.IS_FILE_TRANSFER_DISABLED=true
 
_EOF

Now we can create a systemd unit and start the service

cat <<_EOF > /etc/systemd/system/jitsi-jigasi.service
[Unit]
Description=Jitsi Gateway to SIP
After=network.target
 
[Service]
Type=simple
SuccessExitStatus=143
EnvironmentFile=/opt/jitsi/etc/jigasi/jigasi.conf
User=jitsi
Group=jitsi
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=full
Restart=on-failure
StartLimitInterval=0
RestartSec=30
ExecStart=/opt/jitsi/jigasi/jigasi.sh \
            --configdir=/opt/jitsi/etc \
            --configdirname=jigasi \
            --nocomponent=true \
            ${JIGASI_OPT}
 
[Install]
WantedBy=multi-user.target
_EOF
systemctl daemon-reload
systemctl enable --now jitsi-jigasi

Jigasi is now running, but it's not yet ready to be used.

Install confmapper daemon

The confmapper daemon is a small tool to register Jitsi room name ↔ PIN. We'll use https://github.com/gronke/jitsi-conferencemapper-api as it's a simple and lightweigt daemon in python, using an SQLite database to store the mappings

yum install python3
mkdir -p /opt/jitsi/{data,confmapper}
chown jitsi:jitsi /opt/jitsi/data
chmod 700 /opt/jitsi/data
wget -P /opt/jitsi/confmapper/ \
  https://raw.githubusercontent.com/gronke/jitsi-conferencemapper-api/master/daemon.py
chmod 755 /opt/jitsi/confmapper/daemon.py

Now, lets configure it

cat <<_EOF > /opt/jitsi/confmapper/config.json
{
    "db_file": "/opt/jitsi/data/confmapper.sqlite",
    "expire_seconds": 86400,
    "host": "0.0.0.0",
    "id_max_length": 4,
    "numbers": {
        "FR": [
            "0510101010"
        ]
    },
    "port": 8823
}
_EOF

Here :

In any case, make sure requests to https://visio.fws.fr/conferenceMapper and https://visio.fws.fr/phoneNumberList are routed to this daemon (because those are the URL configured in Jitsi meet

Now, we can create a systemd unit and start the service

cat <<_EOF > /etc/systemd/system/jitsi-confmapper.service
[Unit]
Description=Jitsi Conference Mapper
After=network.target
 
[Service]
Type=simple
User=jitsi
Group=jitsi
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=full
Restart=on-failure
StartLimitInterval=0
RestartSec=30
ExecStart=/opt/jitsi/confmapper/daemon.py
 
[Install]
WantedBy=multi-user.target
_EOF
systemctl daemon-reload
systemctl enable --now jitsi-confmapper

Configure Asterisk/FreePBX

Create a SIP extension

Now, we have to configure Asterisk. First step is to create an SIP extension for Jigasi. So we create a PJSIP extension, with ID 304 and secret SIP_SECRET (this is what we've configured in jigasi). In the advanced tab of the extension, there's a few things we can change

Create a custom IVR

Now, we have to create a custom IVR which will ask callers the PIN of the room they want to join. you can put it in /etc/asterisk/extension_custom.conf

[jitsi-ivr]
exten => s,1,Answer
exten => s,n,Set(IVR_MSG=conf-getpin)
exten => s,n,Set(TIMEOUT(digit)=3)
exten => s,n,Read(JITSI_PIN,${IVR_MSG})

; Fetch the conf name from the PIN entered
exten => s,n,AGI(jitsi_conf_pin,"https://visio.fws.fr/conferenceMapper",${JITSI_PIN}

; If we got a result, dial JIGASI SIP account, else, loop and ask again
exten => s,n,GotoIf($["${JITSI_ROOM}" != "error"]?jitsi,1)
exten => s,n(error),Playback(conf-invalid)
exten => s,n,Goto(s,1)

; We got a result, lets join jitsi room
exten => jitsi,1,Verbose(PIN ${JITSI_PIN} maps to Jitsi room ${JITSI_ROOM})
exten => jitsi,n,Dial(PJSIP/304,,b(jitsi-conference-room-header^addheader^1(${JITSI_ROOM})))

Create an AGI script to lookup roomname from their PIN

We have to create an AGI script so that asterisk can query the confmapper daemon to get the name of a room from the PIN. For this, create the script /usr/share/asterisk/agi-bin/jitsi_conf_pin with the following content :

#!/usr/bin/perl
 
use warnings;
use strict;
use LWP::UserAgent;
use JSON;
 
my $ret = 'error';
 
my $url = $ARGV[0] . '?id=' . $ARGV[1];
my $ua = LWP::UserAgent->new(timeout => 10);
$ua->env_proxy;
 
my $response = $ua->get($url);
if ($response->is_success){
  my $json = from_json($response->content);
  if (defined $json and defined $json->{conference}){
    $ret = $json->{conference};
    $ret =~ s/@.*//;
  }
}
 
print "SET VARIABLE JITSI_ROOM $ret\n";

The script must be executable

chmod +x /usr/share/asterisk/agi-bin/jitsi_conf_pin

Create a Custom Destination pointing on your custom IVR

OK, now we need to way to route calls to our new custom IVR. For this, we'll create a Custom Destination in FreePBX. Just set the target to jitsi-ivr,s,1

This Custom Destination make the IVR available in all the FreePBX routing logic.

Assign an internal number to the IVR to test

We can assign it an internal number to test it with a new Misc Application :

You can now try it. Create a new room in Jitsi, and if you click on the small i button (bottom right), you should see a popup with the number to dial and the PIN

So, this conf has PIN 4845

Now, call your internal test number, which points on the custom IVR (381 in the previous screenshot). You should be prompted to enter a PIN. Once typed, asterisk will lookup on the confmapper daemon to find to which room this PIN maps. If found, you'll join the conference right away. If a wrong PI is entered, you'll be prompted again to enter the PIN.

Now, all you have to do is to define a new Inbound Route which points on the same Custom Destination

Integrate with Etherpad

Deploying an Etherpad instance is out of scope for this guide (but we also have an ansible role for this). But, say you have it available at https://etherpad.fws.fr. All you have to do is to indicate it in /opt/jitsi/meet/config.js

[...]
      "etherpad_base": "https://etherpad.fws.fr/p/",
[...]
The trailing / is important

Note on reverse proxy and Content-Security-Policy

We use a reverse proxy to serve all the web resources, and this reverse proxy insert CSP headers to response. In this case, we have to allow a few things to get everything working :

1)
Selective Forwarding Unit