Goal: Use a single app to satisfy all my messaging needs. Bonus points if I can get it working on a PinePhone
Prerequisites
- a reasonably recent version of Docker
- a reasonably recent version of Docker-Compose
- somewhere to deploy this and a corresponding domain name (preferable) or IP address
- See naming considerations here
- a Matrix client of some sort
- Element on desktop is pretty slick
- I have not found any Android clients which work with a Matrix server deployed locally on a LAN
- an example
docker-compose.yaml
at the bottom of this post, needs a tiny bit of modification - a rudimentary understanding of Matrix and Matrix bridges
Summary
Just to be upfront, this is all a bit of a pain in the ass. The configuration is stateful, so there’s no docker-compose up
that I’m aware of that will “just work”. The pieces need to be done one at a time.
- We’ll configure a Matrix server – Synapse, start it up, and add a user
- We’ll start a Signal daemon (signald), configure a Matrix-Signal bridge (mautrix-signal) then integrate it with our Matrix server
- We’ll configure a Matrix-WhatsApp bridge, then integrate it with our Matrix server
What’s not included:
- SSL configuration
- Matrix user definition/roles (beyond the bare minimum)
- double puppeting
- considerations for sharing this server with others
Create volumes for shared persistence
- Create Docker volumes that we’ll use throughout the rest of this
- note these must match the names in the
docker-compose.yaml
, so either don’t change them or change them both places docker volume create synapse-data
docker volume create whatsapp-bridge-data
docker volume create signal-bridge-data
- note these must match the names in the
Matrix server
- make sure you update the
docker-compose.yaml
to contain an actual password forsynapse-db
- use an ephemeral Docker container to generate the Synapse configuration file
docker run --rm -e SYNAPSE_SERVER_NAME=nuc.localdomain -e SYNAPSE_REPORT_STATS=yes -v
synapse-data
:/data matrixdotorg/synapse:latest generate- note that it’s extremely unlikely you’ll have the same Synapse server name, so ensure you’re customizing it. This value should be what you decided in the prerequisites above.
- additionally note that the volume must be the same as step 1 above (and the
docker-compose.yaml
file)
$ docker run --rm -e SYNAPSE_SERVER_NAME=nuc.localdomain -e SYNAPSE_REPORT_STATS=yes -v synapse-data:/data matrixdotorg/synapse:latest generate
/usr/local/lib/python3.8/site-packages/twisted/conch/ssh/common.py:14: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
from cryptography.utils import int_from_bytes, int_to_bytes
Generating config file /data/homeserver.yaml
Generating signing key file /data/nuc.localdomain.signing.key
A config file has been generated in '/data/homeserver.yaml' for server name 'nuc.localdomain'. Please review this file and customise it to your needs.
- now we have a Docker volume (a Docker managed directory) that is populated with the most recent template
homeserver.yaml
by a Docker container that attached to the volume, generated it, then removed itself after generating. This next step is configuring thishomeserver.yaml
so you can ultimately run Synapse.- as we’re using a Docker volume, the first step is to actually find the file we want to change:
docker inspect --format='{{.Mountpoint}}' synapse-data
- this should show you something like:
/var/lib/docker/volumes/synapse-data/_data
- you can look in that directory (probably going to need
sudo
) or we can go straight to modifying it (use whatever editor you’re comfortable withsudo nano $(docker inspect --format='{{.Mountpoint}}' synapse-data)/homeserver.yaml
- Assuming you used the right
server_name
, there’s only one thing we have to update right now- search for
database:
- comment out the existing configuration (sqlite3)
- uncomment the Postgres configuration (
psycopg2
) - update the user to the
synapse-db
username in thedocker-compose.yaml
- update the password to the
synapse-db
password in thedocker-compose.yaml
- update the database to the
synapse-db
database name in thedocker-compose.yaml
- update the host to
synapse-db
(the host Docker-Compose introduces) - Save and exit, assuming this section of the
homeserver.yaml
looks like:
- search for
- as we’re using a Docker volume, the first step is to actually find the file we want to change:
database:
name: psycopg2
args:
user: synapse
password: reallyStrongPassword!1
database: synapse_db
host: synapse-db
cp_min: 5
cp_max: 10
# For more information on using Synapse with Postgres, see `docs/postgres.md`.
#
#database:
# name: sqlite3
# args:
# database: /data/homeserver.db
- get that sweet endorphin hit of progress by starting up Synapse!
docker-compose up synapse
- navigate to your host (don’t forget the port!), for me: http://nuc.localdomain:8008
- if you have anything but sweet joy, see the troubleshooting section towards the end of this post
- keep an eye on the logs for anything suspicious
- you can ctrl+c to escape
- Unfortunately, our Matrix server has no users, so it’s not all that useful. With the container running, lets add one to get this moving
docker-compose exec synapse register_new_matrix_user http://nuc.localdomain:8008 -c /data/homeserver.yaml
$ docker-compose exec synapse register_new_matrix_user http://nuc.localdomain:8008 -c /data/homeserver.yaml
New user localpart [root]: toby
Password:
Confirm password:
Make admin [no]: yes
Sending registration request...
Success!
Done this piece!
Synapse the Matrix server is running, it has a user associated with it. Now is a good time to get used to what it looks like running, maybe try out clients to see which ones can actually connect to your set up, generally troubleshoot things.
Troubleshooting
- during set up, the containers are completely stateless – all the state is in the volumes. This means you’re free to delete and recreate them at your leisure
- generally speaking, if you need to update a configuration file (e.g.
homeserver.yaml
), restart the relevant service afterwards - The “nuclear” option is to stop and remove all containers as well as removing all volumes. This shouldn’t be done lightly, you’ll have to start over. The command (executed in the directory where your
docker-compose.yaml
is:docker-compose down --volumes
to remove everything your docker-compose file manages, then you’ll also have to remove the external named volumes withdocker volume rm synapse-data whatsapp-bridge-data signal-bridge-data
.
Example docker-compose.yaml
version: "3.7"
volumes:
# We need to do some initial set up outside of Docker-Compose, so make these volumes external
synapse-data:
external: true
whatsapp-bridge-data:
external: true
signal-bridge-data:
external: true
# The Signal daemon (Signald) and the Signal bridge (Mautrix-Signal) need to share a volume
signald-data:
# Use these named volumes just for convenience
signal-bridge-db:
synapse-db:
services:
synapse-db:
container_name: synapse-db
image: postgres:13-alpine
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: <GENERATE A PASSWORD HERE> # Make sure to update this!
POSTGRES_DB: synapse_db
# ensure the database gets created correctly
# https://github.com/matrix-org/synapse/blob/master/docs/postgres.md#set-up-database
POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- synapse-db:/var/lib/postgresql/data
ports:
- 5435:5432/tcp
synapse:
container_name: synapse
image: docker.io/matrixdotorg/synapse:latest
# Since synapse does not retry to connect to the database, restart upon failure
restart: unless-stopped
environment:
- SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
volumes:
- synapse-data:/data
- signal-bridge-data:/signal-bridge
- whatsapp-bridge-data:/whatsapp-bridge
depends_on:
- synapse-db
ports:
- 8008:8008/tcp
- 8448:8448/tcp
whatsapp-bridge:
container_name: whatsapp-bridge
image: dock.mau.dev/tulir/mautrix-whatsapp:latest
restart: unless-stopped
volumes:
- whatsapp-bridge-data:/data
ports:
- 29318:29318/tcp
# Signal and bridge
signald:
container_name: signald
image: finn/signald:latest
restart: unless-stopped
volumes:
- signald-data:/signald
signal-bridge-db:
container_name: signal-bridge-db
image: postgres:13-alpine
restart: unless-stopped
environment:
POSTGRES_USER: mautrix_signal
POSTGRES_DB: signal_bridge_db
POSTGRES_PASSWORD: <GENERATE A PASSWORD HERE> # Make sure to update this!
volumes:
- signal-bridge-db:/var/lib/postgresql/data
ports:
- 5434:5432/tcp
signal-bridge:
container_name: signal-bridge
image: dock.mau.dev/tulir/mautrix-signal
restart: unless-stopped
volumes:
- signal-bridge-data:/data
- signald-data:/signald
ports:
- 29328:29328/tcp
depends_on:
- signal-bridge-db
- signald