--- layout: post author: Sam Hadow tags: messaging podman sysadmin --- In this blog post I'll describe how to self host a [matrix.org](https://matrix.org/) instance using podman. The homeserver used will be synapse. # What are matrix.org and synapse? First a short introduction: matrix.org is an open source, secure and decentralized communication protocol. Secure because it defines how end to end encryption should be implemented in the clients. With matrix.org you have [homeservers](https://matrix.org/ecosystem/servers/) and [clients](https://matrix.org/ecosystem/clients/). Homeservers are what the clients connect to and can federate with each other *(meaning someone with an account on homeserver A can talk to someone with an account on homeserver B)*. [Bridges](https://matrix.org/ecosystem/bridges/) to other messaging services can also be hosted alongside a homeserver. Hosting your own homeserver allows you to host the bridges you want. In this blog post I explain how to self host a specific homeserver, which is maintained by the matrix.org foundation: synapse. # self hosting synapse ## initial setup ### creating secret and directories Create the database secret not to expose it in your configuration files: ```bash echo -n "" > /tmp/secret podman secret create synapse_postgres_pass /tmp/secret ``` Adapt the path to your own path for the directories: ```bash mkdir -p /home/data/podman/synapse/{db,config,media,logs} ``` ### generating the configuration file *(adapt your-domain to your own, for example: example.org)* ```bash podman pod create --name synapse -p 8008:8008 -m=2048m podman run -it --rm \ -v /home/data/podman/synapse/config:/data:Z \ -e SYNAPSE_SERVER_NAME= \ -e SYNAPSE_REPORT_STATS=no \ docker.io/matrixdotorg/synapse:latest generate ``` ### modifying the configuration file The previous command will generate a homeserver.yaml file. You'll have to modify this file before using synapse. You'll have to modify at least the following parts: + modify the database part *(replace the password)* ```yaml database: name: psycopg2 txn_limit: 10000 args: user: synapse password: dbname: synapse host: 127.0.0.1 port: 5432 cp_min: 5 cp_max: 10 ``` + set `enable_registration` to false unless you want users to register freely on your instance. + set a shared secret file with `registration_shared_secret_path` to have access to an [API](https://element-hq.github.io/synapse/latest/admin_api/register_api.html) to create users. Be sure to use a secure secret as anyone having this secret can register on your instance as an admin. Also keep in mind the path is relative to the container, not the host. ### reverse proxy In this guide the port used is 8008, we'll use nginx to server the synapse homeserver on the port 443. *(adapt your-domain to your own)* This part is important for users to have the name user@your-domain while hosting synapse on a subdomain. And it's also important for clients and other homeserver to recognize your server. in sites-available: ```nginx # synapse.conf server { listen 443 ssl; listen [::]:443 ssl; server_name synapse.; ssl_certificate /etc/letsencrypt/live//fullchain.pem; ssl_certificate_key /etc/letsencrypt/live//privkey.pem; location /.well-known/matrix/server { return 200 '{"m.server": "synapse.:443"}'; add_header Content-Type application/json; } location /.well-known/matrix/client { return 200 '{"m.homeserver": {"base_url": "https://synapse."},"m.identity_server": {"base_url": "https://vector.im"}}'; add_header Content-Type application/json; add_header "Access-Control-Allow-Origin" *; } location / { proxy_pass http://127.0.0.1:8008; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; # Nginx by default only allows file uploads up to 1M in size # Increase client_max_body_size to match max_upload_size defined in homeserver.yaml client_max_body_size 256M; # Synapse responses may be chunked, which is an HTTP/1.1 feature. proxy_http_version 1.1; } add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; } ``` In your http.conf add this snippet in the https server block: ```nginx location /.well-known/matrix/server { return 200 '{"m.server": "synapse.:443"}'; add_header Content-Type application/json; add_header "Access-Control-Allow-Origin" *; } location /.well-known/matrix/client { return 200 '{"m.homeserver": {"base_url": "https://synapse."},"m.identity_server": {"base_url": "https://vector.im"}}'; add_header Content-Type application/json; add_header "Access-Control-Allow-Origin" *; } ``` ## running the pod ### with podman command line Again adapt the path: *The database version can be different but it needs to be pinned to a specific version to avoid issues with updates. You'll have to manually update this container.* ```bash podman run -d --pod=synapse \ --secret synapse_postgres_pass,type=env,target=POSTGRES_PASSWORD \ -e POSTGRES_DB="synapse" \ -e POSTGRES_USER="synapse" \ -e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --lc-collate=C --lc-ctype=C" \ -v /home/data/podman/synapse/db:/var/lib/postgresql/data:Z \ --name=synapse-db \ docker.io/library/postgres:16 podman run -d --pod=synapse \ -e SYNAPSE_CONFIG_PATH=/data/homeserver.yaml \ -v /home/data/podman/synapse/config:/data:Z \ -v /home/data/podman/synapse/media:/data/media:z \ -v /home/data/podman/synapse/logs:/data/logs:Z \ --name=synapse-app \ --label io.containers.autoupdate=registry docker.io/matrixdotorg/synapse:latest ``` Then you can generate the systemd services: ```bash cd ~/.config/systemd/user/ podman generate systemd --restart-policy=on-failure --files --new --name synapse systemctl --user daemon-reload systemctl --user enable --now pod-synapse.service ``` ### with systemd services directly Create these files in `~/.config/systemd/user/`. ```ini # pod-synapse.service [Unit] Description=Podman pod-synapse.service Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor=/run/user/1000/containers Wants=container-synapse-app.service container-synapse-db.service Before=container-synapse-app.service container-synapse-db.service [Service] Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStartPre=/usr/bin/podman pod create \ --infra-conmon-pidfile %t/pod-synapse.pid \ --pod-id-file %t/pod-synapse.pod-id \ --exit-policy=stop \ --name synapse \ -p 8008:8008 \ -m=2048m \ --replace ExecStart=/usr/bin/podman pod start \ --pod-id-file %t/pod-synapse.pod-id ExecStop=/usr/bin/podman pod stop \ --ignore \ --pod-id-file %t/pod-synapse.pod-id \ -t 10 ExecStopPost=/usr/bin/podman pod rm \ --ignore \ -f \ --pod-id-file %t/pod-synapse.pod-id PIDFile=%t/pod-synapse.pid Type=forking [Install] WantedBy=default.target ``` ```ini # container-synapse-db.service [Unit] Description=Podman container-synapse-db.service Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor=%t/containers BindsTo=pod-synapse.service After=pod-synapse.service [Service] Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStart=/usr/bin/podman run \ --cidfile=%t/%n.ctr-id \ --cgroups=no-conmon \ --rm \ --pod-id-file %t/pod-synapse.pod-id \ --sdnotify=conmon \ --replace \ -d \ --secret synapse_postgres_pass,type=env,target=POSTGRES_PASSWORD \ -e POSTGRES_DB=synapse \ -e POSTGRES_USER=synapse \ -e "POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C" \ -v /home/data/podman/synapse/db:/var/lib/postgresql/data:Z \ --name=synapse-db \ docker.io/library/postgres:16 ExecStop=/usr/bin/podman stop \ --ignore -t 10 \ --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm \ -f \ --ignore -t 10 \ --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all [Install] WantedBy=default.target ``` ```ini # container-synapse-app.service [Unit] Description=Podman container-synapse-app.service Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor=%t/containers BindsTo=pod-synapse.service After=pod-synapse.service [Service] Environment=PODMAN_SYSTEMD_UNIT=%n Restart=on-failure TimeoutStopSec=70 ExecStart=/usr/bin/podman run \ --cidfile=%t/%n.ctr-id \ --cgroups=no-conmon \ --rm \ --pod-id-file %t/pod-synapse.pod-id \ --sdnotify=conmon \ --replace \ -d \ -e SYNAPSE_CONFIG_PATH=/data/homeserver.yaml \ -v /home/data/podman/synapse/config:/data:Z \ -v /home/data/podman/synapse/media:/data/media:z \ -v /home/data/podman/synapse/logs:/data/logs:Z \ --name=synapse-app \ --label io.containers.autoupdate=registry docker.io/matrixdotorg/synapse:latest ExecStop=/usr/bin/podman stop \ --ignore -t 10 \ --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm \ -f \ --ignore -t 10 \ --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all [Install] WantedBy=default.target ``` ## managing the homeserver ### registering users With access to the server, creating the user interactively: ```bash podman exec -it synapse-app /bin/sh register_new_matrix_user --user --config /data/homeserver.yaml ``` Or with the registration API, you can use [this script](https://git.hadow.fr/sam.hadow/Useful-scripts/src/branch/main/create_matrix_account.sh). ## what about bridges? Bridges allow you to receive message from other services in matrix. In this section I'll give an example on how to self host a bridge alongside your matrix instance with the discord bridge. ### discord bridge example Creating the initial bridge files: ```bash mkdir -p /home/data/podman/synapse/mautrix-discord podman run --rm -v /home/data/podman/synapse/mautrix-discord:/data:z dock.mau.dev/mautrix/discord:latest ``` Create the database: *(adapt the password)* ```bash podman exec -it synapse-db /bin/bash psql -U synapse CREATE USER discordmautrix WITH PASSWORD 'password'; CREATE DATABASE discordmautrix WITH OWNER = 'discordmautrix' ; ``` Then delete synapse container and recreate it with the bridge registration file: *(don't forget to regenerate, or adapt, the systemd service)* ```bash podman run -d --pod=synapse \ -e SYNAPSE_CONFIG_PATH=/data/homeserver.yaml \ -v /home/data/podman/synapse/config:/data:Z \ -v /home/data/podman/synapse/media:/data/media:z \ -v /home/data/podman/synapse/logs:/data/logs:Z \ -v /home/data/podman/synapse/mautrix-discord-registration.yaml:/data/mautrix-discord-registration.yaml:Z \ --name=synapse-app \ --label io.containers.autoupdate=registry docker.io/matrixdotorg/synapse:latest ``` #### using the bridge You'll then have access to a user "discord bridge bot" on your instance where you can log in into your discord account and then bridge your messages.