Running Jitsi-Meet in a FreeBSD Jail

Introduction

Due to the situation with COVID-19 that also lead to people being confined to their homes in South Africa as well, we decided to provide a (freely usable of course) Jitsi Meet instance to the community being hosted in South Africa on our FreeBSD environment.

That way, communities in South Africa and beyond have a free alternative to the commercial conferencing solutions with sometimes dubious security and privacy histories and at the same time improved user experience due to the lower latency of local hosting.

Our instance is available at jitsi.honeyguide.net for those wanting to try it out.

This tutorial will show you how to set up your own Jitsi Meet from scratch on FreeBSD.

Initial Set Up

We first of all initialise the jail which we will use (we use iocage for jail management):

$ iocage create -n jitsi ip4_addr="igb0|10.10.0.1/24" -r 11.3-RELEASE boot="on"  

Then we connect to the jail and prepare pkg and ports (some ports are so new that we need to build them ourselves):

$ iocage console jitsi
...
$ pkg
...
$ portsnap fetch
...
$ portsnap extract
...

Installing All Packages and Ports

jitsi-meet and jitsi-videobridge are built from ports:

$ cd /usr/ports/www/jitsi-meet
$ make install clean
...
$ cd /usr/ports/net-im/jitsi-videobridge
$ make install clean
...

nginx, acme.sh for SSL certificate management and prosody can be installed from packages:

$ pkg install nginx
...
$ pkg install prosody
...
$ pkg install acme.sh

If you run into problems with your setup, we recommend you compare your configuration with https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md.

Setting Up prosody

The prosody configuration is located in /usr/local/etc/prosody/prosody.cfg.lua.
The log files are located in /var/db/prosody.

First of all, we need to create and register the certificates:

$ prosodyctl cert generate jitsi.honeyguide.net
...
$ prosodyctl cert generate auth.jitsi.honeyguide.net
...
$ prosodyctl register focus auth.jitsi.honeyguide.net KEYUSEDINCONFIG

Replace KEYUSEDINCONFIG with a key that you will need to use in the config files.

In /usr/local/etc/prosody/prosody.cfg.lua, we added the following lines at the end (otherwise, the default configuration works fine):

VirtualHost "jitsi.honeyguide.net"
    authentication = "anonymous"
    ssl = {
        key = "/var/db/prosody/jitsi.honeyguide.net.key";
        certificate = "/var/db/prosody/jitsi.honeyguide.net.crt";
    }
    modules_enabled = {
        "bosh";
        "pubsub";
    }
    c2s_require_encryption = false

VirtualHost "auth.jitsi.honeyguide.net"
    ssl = {
        key = "/var/db/prosody/auth.jitsi.honeyguide.net.key";
        certificate = "/var/db/prosody/auth.jitsi.honeyguide.net.crt";
    }
    authentication = "internal_plain"

admins = { "focus@auth.jitsi.honeyguide.net" }

Component "conference.jitsi.honeyguide.net" "muc"
Component "jitsi-videobridge.jitsi.honeyguide.net"
    component_secret = "KEYUSEDINCONFIG"
Component "focus.jitsi.honeyguide.net"
    component_secret = "KEYUSEDINCONFIG"

Setting up jitsi-videobridge

The jitsi-videobridge configuration is located in /usr/local/etc/jitsi/videobridge/jitsi-videobridge.conf and /usr/local/etc/jitsi/videobridge/sip-communicator.properties.

There is one minor problem though: /usr/local/etc/jitsi/videobridge/jitsi-videobridge.conf is currently ignored, the /usr/local/etc/rc.d/jitsi-videobridge startup script does not read the environment file correctly.

Being pragmatic, we adjusted the startup script (and saved the original file in jitsi-videobridge.orig). Since also the additional flags from /etc/rc.conf are ignored, we also added –apis=rest,xmpp for the telegraf set up (for the grafana dashboard, see our separate blog post) there:

...
jitsi_videobridge_start()
{
        daemon -p ${pidfile} -o /var/log/jitsi-videobridge.log ${command} -Xmx3072m \
                -XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError \
                -XX:HeapDumpPath=/tmp \
                -Djava.util.logging.config.file=${jitsi_videobridge_logging_config} \
                -Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/usr/local/etc/jitsi \
                -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=videobridge \
                -Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/ \
                -cp ${jitsi_videobridge_jar} \
                org.jitsi.videobridge.Main \
                --host=localhost \
                --domain=jitsi.honeyguide.net \
                --port=5347 \
                --secret=KEYUSEDINCONFIG --apis=rest,xmpp ${jitsi_videobridge_flags}
                echo "Started"
}
...

Please note that if you want to use the restart command for the service, you also need to adjust the jitsi_videobridge_restart() function similarly.

As soon as reading from the config file is fixed, our config file will look like this:

JVB_XMPP_HOST=localhost
JVB_XMPP_DOMAIN=jitsi.honeyguide.net
JVB_XMPP_PORT=5347
JVB_XMPP_SECRET=KEYUSEDINCONFIG

VIDEOBRIDGE_MAX_MEMORY=3072m
# VIDEOBRIDGE_DEBUG_OPTIONS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"

We also need to adjust /usr/local/etc/jitsi/videobridge/sip-communicator.properties (this file also already is prepared for the grafana dashboard, but you need to adjust the IP address in any case if your jail does not use the public IP address because e.g. you have 1:1 NAT):

# The videobridge uses 443 by default with 4443 as a fallback, but since we're already
# running nginx on 443 in this example doc, we specify 4443 manually to avoid a race condition
org.jitsi.videobridge.TCP_HARVESTER_PORT=4443
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=10.10.0.1
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=197.155.21.68
# 197.155.21.68 is the address assigned to jitsi.honeyguide.net

#
# For Grafana dashboard
#

# the callstats credentials
io.callstats.sdk.CallStats.appId="hgstats"
io.callstats.sdk.CallStats.keyId="GRAFANAKEY"
##io.callstats.sdk.CallStats.keyPath=
##io.callstats.sdk.CallStats.appSecret=

# the id of the videobridge
#io.callstats.sdk.CallStats.bridgeId=jitsi-videobridge.jitsi.honeyguide.net
#io.callstats.sdk.CallStats.conferenceIDPrefix=conference.jitsi.honeyguide.net

# enable statistics and callstats statistics and the report interval
org.jitsi.videobridge.ENABLE_STATISTICS=true
org.jitsi.videobridge.STATISTICS_INTERVAL.callstats.io=30000
org.jitsi.videobridge.STATISTICS_TRANSPORT=callstats.io

Setting up jicofo

The jicofo startup script /usr/local/etc/rc.d/jicofo expects a /usr/local/etc/ssl/java.pem, so we create it:

$ keytool -noprompt -keystore /usr/local/etc/ssl/java.pem -importcert -alias prosody -file /var/db/prosody/auth.jitsi.honeyguide.net.crt  

Remember your keystore password for later on.

The jicofo config file is in /usr/local/etc/jitsi/jicofo/jicofo.conf but it is currently ignored as well.

So we also adjusted /usr/local/etc/rc.d/jicofo and saved the original file as jicofo.orig:

...
jicofo_start()
{
        daemon -p ${pidfile} -o /var/log/${name}.log ${command} -Xmx3072m \
                -XX:+HeapDumpOnOutOfMemoryError \
                -XX:HeapDumpPath=/tmp \
                -Djava.util.logging.config.file=${jicofo_logging_config} \
                -Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION=/usr/local/etc/jitsi \
                -Dnet.java.sip.communicator.SC_HOME_DIR_NAME=jicofo \
                -Dnet.java.sip.communicator.SC_LOG_DIR_LOCATION=/var/log/ \
                -Djavax.net.ssl.trustStore=/usr/local/etc/ssl/java.pem \
                -cp ${jicofo_jar} \
                org.jitsi.jicofo.Main \
                --host=localhost \
                --domain=jitsi.honeyguide.net \
                --port=5347 \
                --secret=3r8wer8DH \
                --user_domain=auth.jitsi.honeyguide.net \
                --user_name=focus \
                --user_password=KEYUSEDINCONFIG ${jicofo_flags}
                echo "Started"
}
...

Here, the restart command works as it has been implemented more elegantly in the startup script already.

For later on, when the startup script reads the config file correctly, here is our /usr/local/etc/jitsi/jicofo/jicofo.conf:

JVB_XMPP_HOST=localhost
JVB_XMPP_DOMAIN=jitsi.honeyguide.net
JVB_XMPP_PORT=5347
JVB_XMPP_SECRET=KEYUSEDINCONFIG
JVB_XMPP_USER_DOMAIN=auth.jitsi.honeyguide.net
JVB_XMPP_USER_NAME=focus
JVB_XMPP_USER_SECRET=KEYUSEDINCONFIG

MAX_MEMORY=3072m

Setting up nginx

The nginx configuration is in /usr/local/etc/nginx/nginx.conf. Of course it might be done differently, but we set up everything in this one file as it is not complicated.

We only need to add two server entries:

...
    server {
        listen 80 default_server;

        server_name _;

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

    server {
        listen 0.0.0.0:443 ssl http2;
        ssl_certificate      /usr/local/etc/ssl/jitsi.honeyguide.net.cer;
        ssl_certificate_key  /usr/local/etc/ssl/jitsi.honeyguide.net.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        server_name jitsi.honeyguide.net;
        # set the root
        root /usr/local/www/jitsi-meet;
        index index.html;
        location ~ ^/([a-zA-Z0-9=\?]+)$ {
            rewrite ^/(.*)$ / break;
        }
        location / {
            ssi on;
        }
        # BOSH, Bidirectional-streams Over Synchronous HTTP
        # https://en.wikipedia.org/wiki/BOSH_(protocol)
        location /http-bind {
            proxy_pass      http://localhost:5280/http-bind;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header Host $http_host;
        }
        # external_api.js must be accessible from the root of the
        # installation for the electron version of Jitsi Meet to work
        # https://github.com/jitsi/jitsi-meet-electron
        location /external_api.js {
            alias /srv/jitsi-meet/libs/external_api.min.js;
        }
    }
...

An easy way to maintain Let’s Encrypt SSL certificates is acme.sh stand alone mode:

$ /usr/local/etc/rc.d/nginx stop
...
$ cd
$ acme.sh --force --issue -d jitsi.honeyguide.net --standalone
...
$ cd /root/.acme.sh/jitsi.honeyguide.net/
$ mv * /usr/local/etc/ssl/
$ /usr/local/etc/rc.d/nginx start

Setting up jitsi-meet

The jitsi-meet configuration is located in /usr/local/www/jitsi-meet/config.js.

Most of the values there can remain as they are (though you might want to customise them depending on your needs), but you need to change the first lines of the file to reflect your domain:

var config = {
    // Connection
    //

    hosts: {
        // XMPP domain.
        domain: 'jitsi.honeyguide.net',

        // When using authentication, domain for guest users.
        // anonymousdomain: 'guest.example.com',

        // Domain for authenticated users. Defaults to <domain>.
        // authdomain: 'jitsi-meet.example.com',

        // Jirecon recording component domain.
        // jirecon: 'jirecon.jitsi-meet.example.com',

        // Call control component (Jigasi).
        // call_control: 'callcontrol.jitsi-meet.example.com',

        bridge: 'jitsi-videobridge.jitsi.honeyguide.net',

        // Focus component domain. Defaults to focus.<domain>.
        focus: 'focus.jitsi.honeyguide.net',

        // XMPP MUC domain. FIXME: use XEP-0030 to discover it.
        muc: 'conference.jitsi.honeyguide.net'
    },

...

If you want to adjust the frontend and look and feel, look at the content in the directories static, images and at interface_config.js.

Finishing Up

To make sure everything is started automatically, add the services to /etc/rc.conf:

...
jitsi_videobridge_enable="YES"
jisti_videobridge_flags="--apis=rest,xmpp"
nginx_enable="YES"
prosody_enable="YES"
jicofo_enable="YES"
...