Setting up matrix chat server with a bridge for WhatsApp

Setting up matrix chat server with a bridge for WhatsApp

matrix is a federated instant-messaging protocol with at least one fully functional open source implementation for the server at the time of writing this (synapse) and clients on various platforms (element being the most prominent).

One of the interesting features of matrix is bridges. Bridges on matrix can be set up by the server admin and they can be used to connect to other instant messaging platforms like WhatsApp, Discord and Telegram to name a few.

Setting up a self-hosted matrix instance the way I wanted was not super straight-forward unlike a lot of other apps that I have tried self-hosting. I did not find a complete set of working instructions to do it anywhere. So once I have everything working now,  I decided to write down the steps that I followed.

As like most other times, I am using docker (on an Ubuntu server) and portainer for this setup.

The docker daemon runs as rootless by a user without sudo group membership.

Setting up the Synapse server

Let's assume that user that runs the docker container is called dashik.

Login as the user ( su - dashik if you were in a root shell)

Create a directory for the bind mounts for the data inside the containers.

mkdir $HOME/matrix

We would like to set up three containers now. One for synapse server, one for the element web client and one for the Postgres database.

But before everything we need to generate a configuration for our matrix server.

We can do this by

docker run -it --rm \
    -v "/home/dashik//matrix/synapse:/data" \
    -e SYNAPSE_SERVER_NAME=matrix.<yourdomain.com> \
    -e SYNAPSE_REPORT_STATS=yes \
    matrixdotorg/synapse:latest generate

We would also like an isolated network for the docker containers to talk to each other.

We create one by

docker network create --driver=bridge --subnet=10.10.10.0/24 --gateway=10.10.10.1 network_matrix

Next step is to edit the generated configuration of our synapse server to customize the settings. The file to /home/dashik/matrix/synapse/homeserver.yaml

Change the server_name field to your domain. You can use something like matrix.yourdomain.com for example. Under the database section delete the default MySQL settings and instead add

database:
  name: psycopg2
  args:
    user: synapse
    password: passwordstring
    database: synapse
    host: 10.10.10.102
    cp_min: 5
    cp_max: 10

And then we may go to portainer and under stacks, create new stack and enter the configuration for our new stack.

version: '2.3'
services:
  postgres:
    image: postgres:14
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 10.10.10.102
    volumes:
     - /home/dashik/matrix/postgresdata:/var/lib/postgresql/data

    environment:
     - POSTGRES_DB=synapse
     - POSTGRES_USER=synapse
     - POSTGRES_INITDB_ARGS=--encoding='UTF8' --lc-collate='C' --lc-ctype='C'
     - POSTGRES_PASSWORD=passwordstring


  element:
    image: vectorim/element-web:latest
    restart: unless-stopped
    volumes:
      - /home/dashik/matrix/element-config.json:/app/config.json
    networks:
      default:
        ipv4_address: 10.10.10.103

  synapse:
    image: matrixdotorg/synapse:latest
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 10.10.10.104
    volumes:
     - /home/dashik/matrix/synapse:/data


networks:
  default:
    external:
      name: matrix_net

Click on the button to start the stack and your 3 docker containers should be up and running now.

Click on the button in portainer to open the shell for the matrix-synapse-1 container as root (remember that we are running rootless docker on the host, so this is only the container's root shell)  and issue the following command to create a new user for your fresh matrix chat server:

register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008

Follow the prompts and enter the username, password etc.

Setting up entries in reverse proxy (NGINX Proxy Manager)

Add a new host entry for matrix.yourdomain.com and point it to matrix-synapse-1 container's port 8008 and obtain the necessary LetsEncrypt certs with the usual security settings enabled.

You should see a message that matrix is up and running if you go to https://matrix.yourdomain.com once this is done.

The same way, you can point element.yourdomain.com to matrix-element-1's port 80.

Login as the user you created by going to https://element.yourdomain.com

Now it's time to start chatting with yourself and with other users that you created :-)

Setting up Federation

Once you start getting bored chatting with yourself, you might want to start chatting with users on other matrix servers. Or even join public chat rooms on matrix.org or elsewhere. You will need to configure your reverse proxy settings to allow this and also open port 8448 (the port that synapse uses for federation).

sudo ufw allow 8448/tcp

Add the following entry under the Custom NGINX configuration text box under advanced section in npm for the host matrix.yourdomain.com

add_header Access-Control-Allow-Origin *;
listen 8448 ssl http2;
location /.well-known/matrix/server {
return 200 '{"m.server": "matrix.yourdomain.com:443"}';
default_type application/json;
add_header Access-Control-Allow-Origin *;
}

Once these settings are saved, you should go to https://federationtester.matrix.org and enter matrix.yourdomain.com and see if detects that your server is working for federation.

If everything looks good, you should be able to join public rooms and chat with users on other servers!

Note that it might take a long time to join big rooms (For me it took several minutes to join some of the public rooms that I was interested in).

Configuring the WhatsApp bridge

We are going to add two more containers to our matrix stack on portainer. One for the Mautrix-Whatsapp bridge and one for the DB it uses. CLick on the edit stack button on portainer and under services add

  postgres-mw:
    image: postgres:14
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 10.10.10.120
    volumes:
     - /home/dashik/matrix/postgresdata_mw:/var/lib/postgresql/data
     - POSTGRES_DB=mw
     - POSTGRES_USER=synapse
     - POSTGRES_INITDB_ARGS=--encoding='UTF8' --lc-collate='C' --lc-ctype='C'
     - POSTGRES_PASSWORD=passwordstring
     
   mautrix-whatsapp:
    container_name: mautrix-whatsapp
    image: dock.mau.dev/mautrix/whatsapp:latest
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 10.10.10.105
    volumes:
     - /home/dashik/mautrix-whatsapp:/data

Just restart the stack so that the mautrix-whatsapp container generates the default configuration. Once this is done, you may stop the stack and edit the configuraiton file /home/dashik/mautrix-whatsapp/config.yamlyourself.

You would need to change the following values

address: https://10.10.10.104:8008
domain: matrix.yourdomain.com

...

appservice:
	address: http://10.10.10.105:29318

...

databse:
	uri: postgres://synapse:<passwordstring>@10.10.10.120/mw?sslmode=disable

...

permissions:
        "*": relay
        "matrix.yourdomain.com": user
        "@ashik@matrix.yordomain.se": admin

Run the stack now and once the registration file is generated copy it to a place where the synapse container can access it.

cp registration.yaml /home/dashik/matrix/synapse/mautrix-whatsapp-registration.yaml

Add then the following at the end of your homeserver.yaml for the synapse server:

app_service_config_files:
- /data/mautrix-whatsapp-registration.yaml

Optionally, you might want to turn on E2E encryption by changing

encryption:
	allow: true
	default: true

For this to work as expected, you would also need to add

encryption_enabled_by_default_for_room_type: all

inside your homeserver.yaml for the synapse server.

Restart the stack again once all the files have been updated. Open the log window in portainer and make sure that all containers have started without errors.

Now you should be able to open a private chat with the whatsapp bot to connect to WhatsApp.

Open private chat and specify @whatsappbot:matrix.yourdomain.com as the user.

Type help and the bot should help you to connect to WhatsApp, open new DMs and more!

At this stage, you can also enable Double Puppeting if you want (Read more about it at https://docs.mau.fi/bridges/general/double-puppeting.html).

You can open a root shell on portainer and type

curl -XPOST -d '{"type":"m.login.password","identifier":{"type": "m.id.user","user":"name"},"password":"pwd","initial_device_display_name":"mautrix-whatsapp bridge"}' https://matrix.yourdomain.com/_matrix/client/v3/login

Copy the access token and in the bot chat window enter

login-matrix <access_token>

Double puppeting should be ON now and you should be able to do things like toggle-presence. Enjoy!

NB: It's debatable if you should run WhatsApp on a real device. Many people run it on a vm. I prefer running it on a real device plugged in to the server with adb access and a VNC server running on the phone. My VNC app does not turn on/off the screen as needed, but I can do it with

adb shell input keyevent 26