11 KiB
layout, author, tags
| layout | author | tags |
|---|---|---|
| post | Sam Hadow | messaging podman sysadmin |
In this blog post I'll describe how to self host a 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 and 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 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:
echo -n "<postgres-pass>" > /tmp/secret
podman secret create synapse_postgres_pass /tmp/secret
Adapt the path to your own path for the directories:
mkdir -p /home/data/podman/synapse/{db,config,media,logs}
generating the configuration file
(adapt your-domain to your own, for example: example.org)
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=<your-domain> \
-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)
database:
name: psycopg2
txn_limit: 10000
args:
user: synapse
password: <POSTGRES_PASSWORD>
dbname: synapse
host: 127.0.0.1
port: 5432
cp_min: 5
cp_max: 10
- set
enable_registrationto false unless you want users to register freely on your instance. - set a shared secret file with
registration_shared_secret_pathto have access to an API 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:
# synapse.conf
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name synapse.<your-domain>;
ssl_certificate /etc/letsencrypt/live/<your-domain>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<your-domain>/privkey.pem;
location /.well-known/matrix/server {
return 200 '{"m.server": "synapse.<your-domain>:443"}';
add_header Content-Type application/json;
}
location /.well-known/matrix/client {
return 200 '{"m.homeserver": {"base_url": "https://synapse.<your-domain>"},"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:
location /.well-known/matrix/server {
return 200 '{"m.server": "synapse.<your-domain>: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.<your-domain>"},"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.
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:
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/.
# 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
# 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
# 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:
podman exec -it synapse-app /bin/sh
register_new_matrix_user --user <username> --config /data/homeserver.yaml
Or with the registration API, you can use this script.
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:
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)
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)
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
And for the bridge:
Adapt the file /home/data/podman/synapse/mautrix-discord/config.yaml:
for the database part:
database:
type: postgres
uri: postgres://discordmautrix:password:5432/discordmautrix?sslmode=disable
Then run the bridge:
podman run -d --pod=synapse \
-v /home/data/podman/synapse/mautrix-discord:/data:z \
--name=synapse-discord-mautrix \
--label io.containers.autoupdate=registry dock.mau.dev/mautrix/discord: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.