Skip to main content

Snikket XMPP

1. Introduction

Snikket is a chat application based on XMPP. A Snikket server allows you to chat securely via XMPP, create chat groups, make one-to-one audio and video calls, and much more!

The basic installation steps are described at the Snikket quick-start guide. We will install it in an Incus container.

note

The Snikket docker-compose.yml config file uses network_mode: host, and this makes it difficult to install Snikket alongside other Dockerized applications. That's why we are installing it inside an Incus container, where Docker is installed as well, instead of installing it on the host.

The deployment diagram looks like this:


2. Set the DNS records

For Snikket we need these three DNS records:

chat.user1.fs.al.         300  IN  A      188.245.242.143

groups.chat.user1.fs.al. 300 IN CNAME chat.user1.fs.al.
share.chat.user1.fs.al. 300 IN CNAME chat.user1.fs.al.

The subdomains groups. and share. provide group chat functionality and file-sharing.

cd /var/ds/nsd/
ls zones/
nano zones/user1.fs.al.db
ds restart
note

Besides adding the DNS records listed above, don't forget to update the serial number as well.

Check the DNS records
dig +short chat.user1.fs.al
dig +short groups.chat.user1.fs.al
dig +short share.chat.user1.fs.al

3. Create the container

In order to create and setup an Indus container for Snikket, we should follow the same steps that we did for the edu container. However we can automate these steps with a script like create-container.sh:

create-container.sh
#!/bin/bash

usage() {
cat <<EOF
Usage: $0 <name> [<fixed-ip>]

EOF
}

name=$1
fixed_ip=$2

[[ -z $name ]] && usage && exit 1

### create the container
incus launch images:debian/12 $name \
-c security.nesting=true \
-c security.syscalls.intercept.mknod=true \
-c security.syscalls.intercept.setxattr=true

### create a configuration script
cat <<'__EOF__' > /tmp/config.sh
#!/bin/bash -x

fixed_ip=$1

main() {
update
customize_bashrc
install_docker
install_docker_scripts
set_an_ip $fixed_ip
}

update() {
# update and upgrade
export DEBIAN_FRONTEND=noninteractive
apt update
apt upgrade --yes

# enable automatic security updates
apt install --yes unattended-upgrades

# install some tools
apt install --yes nano curl wget
}

customize_bashrc() {
# uncomment some aliases
sed -i ~/.bashrc \
-e 's/# export LS_OPTIONS=/export LS_OPTIONS=/' \
-e 's/# alias ls=/alias ls=/' \
-e 's/# alias ll=/alias ll=/' \
-e 's/# alias l=/alias l=/'

# customize bashrc
echo 'source ~/.bashrc_custom' >> ~/.bashrc

cat <<'EOF' > ~/.bashrc_custom
# set a better prompt
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;31m\]\u\[\033[01;33m\]@\[\033[01;36m\]\h \[\033[01;33m\]\w \[\033[01;35m\]\$ \[\033[00m\]'

# enable programmable completion features
if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
source /etc/bash_completion
fi
EOF

apt install --yes bash-completion

# in case of using vim, uncomment its dark background setting
apt install --yes vim
sed -i /etc/vim/vimrc \
-e 's/^"set background=dark/set background=dark/'
}

install_docker() {
wget https://download.docker.com/linux/ubuntu/gpg \
-O /etc/apt/keyrings/docker.asc

cat <<EOF > /etc/apt/sources.list.d/docker.sources
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: bookworm
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt update
apt install --yes \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
}

install_docker_scripts() {
apt install --yes git make m4 highlight tree
git clone \
https://gitlab.com/docker-scripts/ds \
/opt/docker-scripts/ds
cd /opt/docker-scripts/ds/
make install
}

set_an_ip() {
local fixed_ip=$1
[[ -z $fixed_ip ]] && return

local gateway=${fixed_ip%.*}.1
cat <<EOF > /etc/systemd/network/eth0.network
[Match]
Name=eth0

[Address]
Address=$fixed_ip

[Route]
Gateway=$gateway

[Network]
DHCP=no
DNS=8.8.8.8
EOF

systemctl restart systemd-networkd
}

### call main
main "$@"

__EOF__

### execute config script
chmod +x /tmp/config.sh
incus file push /tmp/config.sh $name/tmp/
incus exec $name -- /tmp/config.sh $fixed_ip

### remove config script
rm /tmp/config.sh

### restart container
incus restart $name
wget https://linux-cli.fs.al/apps/part6/create-container.sh
chmod +x create-container.sh
./create-container.sh
incus ls
./create-container.sh snikket 10.31.96.202
incus ls

4. Forward ports

The required ports are:

  • 80/443 -- for the web interface, and group file sharing

  • 5222, 5269, 5000 -- XMPP ports and file transfer proxy

  • For audio and video communication (STUN/TURN server) these ports are needed as well:

    • TCP and UDP: 3478, 3479, 5349, 5350
    • UDP only: 49152-65535

4.1 HTTP/HTTPS ports

For forwarding these ports we use SNI Proxy:

cd /var/ds/sniproxy/
nano etc/sniproxy.conf
ds restart

Add these lines to the table of sniproxy:

table {
# . . . . .

# container: snikket
chat\.user1\.fs\.al 10.31.96.202
.*\.chat\.user1\.fs\.al 10.31.96.202

# . . . . .
}

4.2 XMPP ports

We can use the command incus network forward to forward the TCP ports 5222, 5269, 5000 from the host to the IP of the snikket container:

incus network --help | less
incus network forward --help | less

HOST_IP=188.245.242.143 # the public IP of the host
CONTAINER_IP=10.31.96.202

incus network forward list incusbr0
incus network forward create incusbr0 $HOST_IP
incus network forward list incusbr0
incus network forward show incusbr0 $HOST_IP

incus network forward \
port add incusbr0 $HOST_IP \
tcp 5222,5269,5000 \
$CONTAINER_IP

incus network forward show incusbr0 $HOST_IP
How to test port forwarding

We can use netcat to test that ports are forwarded correctly. On the server (VPS) run:

incus exec snikket -- apt install --yes netcat-openbsd
incus exec snikket -- nc -l 5222

Outside the server run:

nc chat.user1.fs.al 5222

Every line that is typed outside the server should be displayed inside the server, and vice-versa.

4.3 TURN ports

Snikket has a built-in STUN/TURN server, to support audio/video calls.

note

If you want to use Snikket only for instant messaging (XMPP), not for audio/video calls, you can skip this part.

We need to forward these ports to the container:

# tcp ports
incus network forward \
port add incusbr0 $HOST_IP \
tcp 3478,3479,5349,5350 \
$CONTAINER_IP

# udp ports
incus network forward \
port add incusbr0 $HOST_IP \
udp 3478,3479,5349,5350,49152-65535 \
$CONTAINER_IP

incus network forward show incusbr0 $HOST_IP
Test UDP port forwarding

For testing UDP port forwarding, use the option -u of the command nc. On the server run:

incus exec snikket -- nc -u -l 60000

Outside the server run:

nc -u chat.user1.fs.al 60000

5. Install Snikket

We are now ready to install Snikket inside the container.

  1. Get a shell inside the container:

    incus shell snikket
  2. Download the file docker-compose.yml:

    mkdir -p /root/snikket
    cd /root/snikket/

    curl -o docker-compose.yml \
    https://snikket.org/service/resources/docker-compose.yml
  3. Customize a bit docker-compose.yml:

    sed -i docker-compose.yml \
    -e '/^version:/ d' \
    -e '/^volumes:/,$ d' \
    -e 's%snikket_data:%./data:%' \
    -e 's%acme_challenges:%./data/acme_challenges:%'

    nano docker-compose.yml
    • Remove the line version: (at the top of the file).
    • Remove the section volumes: (at the end of the file).
    • Replace the data volume snikket_data with the local directory ./data.
    • Replace the data volume acme_challenges with the local directory ./data/acme_challenges.

    These modifications ensure that everything about snikket is contained in the directory /root/snikket/. This way it is easier to make a backup/restore.

  4. Create a configuration file, called snikket.conf, in the same directory:

    cat <<EOF > snikket.conf
    # The primary domain of your Snikket instance
    SNIKKET_DOMAIN=chat.user1.fs.al

    # An email address where the admin can be contacted
    # (also used to get a Let's Encrypt certificate)
    SNIKKET_ADMIN_EMAIL=dashohoxha@gmail.com
    EOF

    nano snikket.conf
  5. Launch it:

    docker compose up -d
  6. As soon as Snikket is running, you can create an admin account:

    docker exec snikket \
    create-invite --admin --group default

    Follow the link to open the invitation, and follow the instructions to get signed in.

  7. Once you’ve created your admin account, you can log in to the web dashboard by visiting https://chat.user1.fs.al/ in your browser. From there you can create more invitation links to share with your family, friends and anyone else you want to join your Snikket instance.

6. Maintenance

For backup and restore, it is enough to backup and restore the directory /root/snikket/.

For update:

cd /root/snikket/

docker-compose pull
docker-compose up -d

See also these scripts: https://github.com/snikket-im/snikket-selfhosted/tree/main/scripts