There are so many different chat platforms out there, all needing their own apps and account to use. Each one is just another app to install, and the list isn’t growing shorter. Apps like Beeper and Sunbird do already exist, but what’s the fun in that? So, I thought, let’s see if we can self host our own version of Beeper. Considering all of the core components are open source, it shouldn’t be too tricky.
There are a few main components we need in order for this to work.
- A backend DB - I’m using PostgreSQL
- A homeserver - Synapse
- An authentication platform - Matrix Authentication Service
- Bridges - e.g. Mautrix-Whatsapp
- An admin panel (optional) - such as Element Admin
In my current setup, I am already using Traefik and Authentik, so you may need to tweak things a bit so it works with your setup.
compose.yml
This compose file has become a bit of a behemoth to behold, but comments always help.
1services:
2 # -------------------------
3 # CORE: Synapse Homeserver
4 # -------------------------
5 main:
6 image: matrixdotorg/synapse:latest
7 container_name: synapse
8 restart: unless-stopped
9 healthcheck:
10 test: ["CMD-SHELL", "curl -f http://localhost:8008/health || exit 1"]
11 interval: 30s
12 timeout: 20s # High timeout because the Pi CPU is at 98%
13 retries: 10 # Allow up to 5 minutes of "busy" time before marking down
14 start_period: 3m # Give the Pi 3 minutes to load the DB before checking
15 environment:
16 - SYNAPSE_SERVER_NAME=example.com
17 - SYNAPSE_REPORT_STATS=no
18 - PYTHONPATH=/data/py
19 - LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2
20 volumes:
21 - /lab/cluster/home-1/matrix/synapse:/data
22 networks:
23 - matrix-net
24 - proxy
25 - postgres
26 - redis
27 deploy:
28 resources:
29 limits:
30 memory: 4G
31 labels:
32 - traefik.enable=true
33 - traefik.http.routers.synapse-main.entrypoints=websecure
34 - traefik.http.routers.synapse-main.rule=Host(`matrix.example.com`) || Host(`federation.example.com`)
35 - traefik.http.routers.synapse-main.tls=true
36 - traefik.http.routers.synapse-main.tls.certresolver=dns-cloudflare
37 - traefik.http.routers.synapse-main.middlewares=chain-synapse@file
38 - traefik.http.routers.synapse-main.service=synapse-main
39 - traefik.http.services.synapse-main.loadbalancer.server.port=8008
40
41 # -------------------------
42 # AUTH: Matrix Authenticaton Server
43 # -------------------------
44 matrix-authentication-service:
45 image: ghcr.io/element-hq/matrix-authentication-service:latest
46 container_name: matrix-authentication-service
47 restart: unless-stopped
48 volumes:
49 - /lab/cluster/home-1/matrix/mas/config.yaml:/app/config.yaml
50 - /lab/cluster/home-1/matrix/mas/policy.rego:/app/policy.rego
51 environment:
52 - MAS_CONFIG=/app/config.yaml
53 networks:
54 - matrix-net
55 - proxy
56 - postgres
57 deploy:
58 resources:
59 limits:
60 memory: 400M
61 labels:
62 - autoheal=true
63 - traefik.enable=true
64 - traefik.http.routers.synapse-mas-rtr.entrypoints=websecure
65 - traefik.http.routers.synapse-mas-rtr.priority=100
66 - "traefik.http.routers.synapse-mas-rtr.rule=((Host(`matrix.example.com`) && (PathRegexp(`^/_matrix/client/.+/(login|logout|refresh)`) || PathPrefix(`/assets`) || PathPrefix (`/auth`) || PathPrefix(`/.well-known/openid-configuration`) || PathPrefix(`/.well-known/oauth-authorization-server`))))"
67 - traefik.http.routers.synapse-mas-rtr.tls=true
68 - traefik.http.routers.synapse-mas-rtr.tls.certresolver=dns-cloudflare
69 - "traefik.http.middlewares.mas-strip.stripprefix.prefixes=/auth/"
70 - traefik.http.routers.synapse-mas-rtr.middlewares=chain-synapse@file,mas-strip
71 - traefik.http.routers.synapse-mas-rtr.service=synapse-mas-svc
72 - traefik.http.services.synapse-mas-svc.loadbalancer.server.port=8080
73
74 # -------------------------
75 # ADINISTRATION: Element Admin
76 # -------------------------
77 element-admin:
78 image: oci.element.io/element-admin:latest
79 container_name: element-admin
80 restart: unless-stopped
81 environment:
82 - SERVER_NAME=example.com
83 networks:
84 - proxy
85 labels:
86 - traefik.enable=true
87 - traefik.http.routers.synapse-admin-rtr.entrypoints=websecure
88 - "traefik.http.routers.synapse-admin-rtr.rule=Host(`admin.matrix.example.com`) || Host(`matrix-admin.example.com`)"
89 - traefik.http.routers.synapse-admin-rtr.tls=true
90 - traefik.http.routers.synapse-admin-rtr.tls.certresolver=dns-cloudflare
91 - traefik.http.routers.synapse-admin-rtr.middlewares=chain-no-auth@file
92 - traefik.http.routers.synapse-admin-rtr.service=synapse-admin-svc
93 - traefik.http.services.synapse-admin-svc.loadbalancer.server.port=8080
94
95 # -------------------------
96 # BRIDGE: WhatsApp
97 # -------------------------
98 mautrix-whatsapp:
99 image: dock.mau.dev/mautrix/whatsapp:latest
100 container_name: mautrix-whatsapp
101 restart: unless-stopped
102 volumes:
103 - /lab/cluster/home-1/matrix/bridges/whatsapp:/data
104 networks:
105 - matrix-net
106 - postgres
107 - proxy
108 deploy:
109 resources:
110 limits:
111 memory: 600M
112 labels:
113 - traefik.enable=true
114 - traefik.http.routers.bridge-wa.entrypoints=websecure
115 - traefik.http.routers.bridge-wa.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/whatsapp`)
116 - traefik.http.routers.bridge-wa.tls=true
117 - traefik.http.routers.bridge-wa.tls.certresolver=dns-cloudflare
118 - traefik.http.middlewares.wa-strip.stripprefix.prefixes=/v1/bridges/whatsapp
119 - traefik.http.routers.bridge-wa.middlewares=wa-strip,chain-synapse@file
120 - traefik.http.routers.bridge-wa.service=wa-svc
121 - traefik.http.services.wa-svc.loadbalancer.server.port=29318
122
123 # -------------------------
124 # BRIDGE: Instagram (Meta)
125 # -------------------------
126 mautrix-meta:
127 image: dock.mau.dev/mautrix/meta:latest
128 container_name: mautrix-meta
129 restart: unless-stopped
130 volumes:
131 - /lab/cluster/home-1/matrix/bridges/meta:/data
132 networks:
133 - matrix-net
134 - postgres
135 - proxy
136 deploy:
137 resources:
138 limits:
139 memory: 600M
140 labels:
141 - traefik.enable=true
142 - traefik.http.routers.bridge-meta.entrypoints=websecure
143 - traefik.http.routers.bridge-meta.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/meta`)
144 - traefik.http.routers.bridge-meta.tls=true
145 - traefik.http.routers.bridge-meta.tls.certresolver=dns-cloudflare
146 - traefik.http.middlewares.meta-strip.stripprefix.prefixes=/v1/bridges/meta
147 - traefik.http.routers.bridge-meta.middlewares=meta-strip,chain-synapse@file
148 - traefik.http.routers.bridge-meta.service=meta-svc
149 - traefik.http.services.meta-svc.loadbalancer.server.port=29319
150
151 # -------------------------
152 # BRIDGE: Discord
153 # -------------------------
154 mautrix-discord:
155 image: dock.mau.dev/mautrix/discord:v0.7.6
156 container_name: mautrix-discord
157 restart: unless-stopped
158 volumes:
159 - /lab/cluster/home-1/matrix/bridges/discord:/data
160 networks:
161 - matrix-net
162 - postgres
163 - proxy
164 deploy:
165 resources:
166 limits:
167 memory: 600M
168 labels:
169 - traefik.enable=true
170 - traefik.http.routers.bridge-discord.entrypoints=websecure
171 - traefik.http.routers.bridge-discord.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/discord`)
172 - traefik.http.routers.bridge-discord.tls=true
173 - traefik.http.routers.bridge-discord.tls.certresolver=dns-cloudflare
174 - traefik.http.middlewares.discord-strip.stripprefix.prefixes=/v1/bridges/discord
175 - traefik.http.routers.bridge-discord.middlewares=discord-strip,chain-synapse@file
176 - traefik.http.routers.bridge-discord.service=discord-svc
177 - traefik.http.services.discord-svc.loadbalancer.server.port=29334
178
179 # -------------------------
180 # BRIDGE: Google Chat
181 # -------------------------
182 mautrix-googlechat:
183 image: dock.mau.dev/mautrix/googlechat:latest
184 container_name: mautrix-googlechat
185 restart: unless-stopped
186 volumes:
187 - /lab/cluster/home-1/matrix/bridges/googlechat:/data
188 networks:
189 - matrix-net
190 - postgres
191 - proxy
192 deploy:
193 resources:
194 limits:
195 memory: 400M
196 labels:
197 - traefik.enable=true
198 - traefik.http.routers.bridge-gchat.entrypoints=websecure
199 - traefik.http.routers.bridge-gchat.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/googlechat`)
200 - traefik.http.routers.bridge-gchat.tls=true
201 - traefik.http.routers.bridge-gchat.tls.certresolver=dns-cloudflare
202 - traefik.http.middlewares.gchat-strip.stripprefix.prefixes=/v1/bridges/googlechat
203 - traefik.http.routers.bridge-gchat.middlewares=gchat-strip,chain-synapse@file
204 - traefik.http.routers.bridge-gchat.service=gchat-svc
205 - traefik.http.services.gchat-svc.loadbalancer.server.port=29320
206
207 # -------------------------
208 # BRIDGE: Google Messages
209 # -------------------------
210 mautrix-gmessages:
211 image: dock.mau.dev/mautrix/gmessages:latest
212 container_name: mautrix-gmessages
213 restart: unless-stopped
214 volumes:
215 - /lab/cluster/home-1/matrix/bridges/gmessages:/data
216 networks:
217 - matrix-net
218 - postgres
219 - proxy
220 deploy:
221 resources:
222 limits:
223 memory: 400M
224 labels:
225 - traefik.enable=true
226 - traefik.http.routers.bridge-gmsg.entrypoints=websecure
227 - traefik.http.routers.bridge-gmsg.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/gmessages`)
228 - traefik.http.routers.bridge-gmsg.tls=true
229 - traefik.http.routers.bridge-gmsg.tls.certresolver=dns-cloudflare
230 - traefik.http.middlewares.gmsg-strip.stripprefix.prefixes=/v1/bridges/gmessages
231 - traefik.http.routers.bridge-gmsg.middlewares=gmsg-strip,chain-synapse@file
232 - traefik.http.routers.bridge-gmsg.service=gmsg-svc
233 - traefik.http.services.gmsg-svc.loadbalancer.server.port=29336
234
235 # -------------------------
236 # BRIDGE: Twitter
237 # -------------------------
238 mautrix-twitter:
239 image: dock.mau.dev/mautrix/twitter:latest
240 container_name: mautrix-twitter
241 restart: unless-stopped
242 volumes:
243 - /lab/cluster/home-1/matrix/bridges/twitter:/data
244 networks:
245 - matrix-net
246 - postgres
247 - proxy
248 deploy:
249 resources:
250 limits:
251 memory: 400M
252 labels:
253 - traefik.enable=true
254 - traefik.http.routers.bridge-twit.entrypoints=websecure
255 - traefik.http.routers.bridge-twit.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/twitter`)
256 - traefik.http.routers.bridge-twit.tls=true
257 - traefik.http.routers.bridge-twit.tls.certresolver=dns-cloudflare
258 - traefik.http.middlewares.twit-strip.stripprefix.prefixes=/v1/bridges/twitter
259 - traefik.http.routers.bridge-twit.middlewares=twit-strip,chain-synapse@file
260 - traefik.http.routers.bridge-twit.service=twit-svc
261 - traefik.http.services.twit-svc.loadbalancer.server.port=29327
262
263 # -------------------------
264 # BRIDGE: LinkedIn
265 # -------------------------
266 mautrix-linkedin:
267 image: dock.mau.dev/mautrix/linkedin:latest
268 container_name: mautrix-linkedin
269 restart: unless-stopped
270 volumes:
271 - /lab/cluster/home-1/matrix/bridges/linkedin:/data
272 networks:
273 - matrix-net
274 - postgres
275 - proxy
276 deploy:
277 resources:
278 limits:
279 memory: 400M
280 labels:
281 - traefik.enable=true
282 - traefik.http.routers.bridge-li.entrypoints=websecure
283 - traefik.http.routers.bridge-li.rule=Host(`bridges.example.com`) && PathPrefix(`/v1/bridges/linkedin`)
284 - traefik.http.routers.bridge-li.tls=true
285 - traefik.http.routers.bridge-li.tls.certresolver=dns-cloudflare
286 - traefik.http.middlewares.li-strip.stripprefix.prefixes=/v1/bridges/linkedin
287 - traefik.http.routers.bridge-li.middlewares=li-strip,chain-synapse@file
288 - traefik.http.routers.bridge-li.service=li-svc
289 - traefik.http.services.li-svc.loadbalancer.server.port=29341
290
291 draupnir:
292 container_name: draupnir
293 volumes:
294 - /lab/cluster/home-1/matrix/draupnir:/data
295 image: gnuxie/draupnir:latest
296 command: bot --draupnir-config /data/config/production.yaml
297 networks:
298 - matrix-net
299 - postgres
300
301networks:
302 matrix-net:
303 proxy:
304 external: true
305 postgres:
306 external: true
307 redis:
308 external: true
Traefik Config
Synapse and web-based clients (like my Finny) are incredibly picky about CORS and security headers. If you don’t get these right, your client will just hang on “Connecting” forever. I use a file-based provider in Traefik to keep things clean.
/clstr/traefik/rules/chain-synapse.yml
1http:
2 middlewares:
3 chain-synapse:
4 chain:
5 middlewares:
6 - synapse-cors
7 - middlewares-rate-limit
8 - middlewares-secure-headers
/clstr/traefik/rules/synapse-cors.yml
1http:
2 middlewares:
3 synapse-cors:
4 headers:
5 accessControlAllowOriginList:
6 - "*"
7 accessControlAllowMethods:
8 - "GET", "POST", "PUT", "DELETE", "OPTIONS"
9 accessControlAllowHeaders:
10 - "*"
Initial configuration
My setup follows the standard monoloth setup. This is because, when running on a Pi5, the extra RAM reuiqred for each worker, outweighs the performance gains.
/clstr/matrix/synapse/homeserver.yaml
1# ----------------------------------------------------------------------
2# SYNAPSE CONFIGURATION FOR EXAMPLE.COM
3# ----------------------------------------------------------------------
4
5# The domain part of your user IDs (e.g. @user:example.com)
6server_name: "example.com"
7
8# The public URL where clients/servers connect (Reverse Proxy URL)
9public_baseurl: "https://matrix.example.com"
10
11# Location of the PID file
12pid_file: /data/homeserver.pid
13
14suppress_key_server_warning: true
15
16# ----------------------------------------------------------------------
17# LISTENERS
18# ----------------------------------------------------------------------
19listeners:
20 - port: 8008
21 tls: false
22 type: http
23 x_forwarded: true # Trusts Nginx/Traefik headers
24 bind_addresses: ['::', '0.0.0.0']
25 resources:
26 - names: [client,openid,static,federation,media]
27 compress: false
28 - port: 9093
29 bind_address: '0.0.0.0'
30 type: http
31 resources:
32 - names: [replication]
33
34# ----------------------------------------------------------------------
35# DATABASE (PostgreSQL)
36# ----------------------------------------------------------------------
37database:
38 name: psycopg2
39 args:
40 user: synapse
41 password: "REDACTED"
42 database: synapse
43 host: pgbouncer
44 cp_min: 2
45 cp_max: 5
46
47# ----------------------------------------------------------------------
48# AUTHENTICATION & LDAP
49# ----------------------------------------------------------------------
50
51matrix_authentication_service:
52 enabled: true
53 endpoint: http://matrix-authentication-service:8080/
54 secret: "Nbal2MLkHK2wfd3WvHnqk0d4C1OhR56X"
55
56# Disable open registration
57enable_registration: false
58
59experimental_features:
60 # Enable MSC4108 (QR code login)
61 msc4108_enabled: true
62
63# ----------------------------------------------------------------------
64# LOGGING & STATS
65# ----------------------------------------------------------------------
66report_stats: false
67
68# ----------------------------------------------------------------------
69# SECRETS (Preserved from your generated file)
70# ----------------------------------------------------------------------
71registration_shared_secret: "REDACTED"
72macaroon_secret_key: "REDACTED"
73form_secret: "REDACTED"
74signing_key_path: "/data/vmd1.dev.signing.key"
75
76# ----------------------------------------------------------------------
77# FEDERATION
78# ----------------------------------------------------------------------
79trusted_key_servers:
80 - server_name: "matrix.org"
81
82# ----------------------------------------------------------------------
83# MEDIA
84# ----------------------------------------------------------------------
85media_store_path: /data/media_store
86max_upload_size: 104857600 # 100MB Limit
87url_preview_enabled: true
88url_preview_ip_range_blacklist:
89 - '127.0.0.0/8'
90 - '10.0.0.0/8'
91 - '172.16.0.0/12'
92 - '192.168.0.0/16'
93 - '100.64.0.0/10'
94 - '192.0.0.0/24'
95 - '169.254.0.0/16'
96 - '192.88.99.0/24'
97 - '198.18.0.0/15'
98 - '198.51.100.0/24'
99 - '203.0.113.0/24'
100 - '224.0.0.0/4'
101 - '::1/128'
102 - 'fe80::/10'
103 - 'fc00::/7'
104 - '2001:db8::/32'
105 - 'ff00::/8'
106 - 'fec0::/10'
107
108# ----------------------------------------------------------------------
109# EMAIL
110# ----------------------------------------------------------------------
111
112email:
113 # The hostname of the outgoing SMTP server
114 smtp_host: "mail-eu.smtp2go.com"
115
116 # The port. Common ports: 587 (STARTTLS), 465 (Implicit TLS), or 25 (Plain)
117 smtp_port: 587
118
119 # Username and password for the SMTP server
120 smtp_user: "REDACTED"
121 smtp_pass: "REDACTED"
122
123 # TLS/SSL Settings (See explanation below)
124 # Use force_tls: true for port 465. Use require_transport_security: true for port 587.
125 force_tls: false
126 require_transport_security: true
127
128 # The "From" address for emails sent by Synapse
129 notif_from: "Synapse <REDACTED>"
130
131 # Enable email notifications (missed messages, mentions, etc.)
132 enable_notifs: true
133
134 # Automatically subscribe new users to email notifications
135 notif_for_new_users: true
136
137 # Custom URL for client links (e.g., in password reset emails)
138 # Set this to your Element/web client URL.
139 client_base_url: "https://chat.example.com"
140
141 # (Optional) Lifetime of the validation token
142 validation_token_lifetime: 1h
143
144# ----------------------------------------------------------------------
145# BRIDGES (APP SERVICES)
146# ----------------------------------------------------------------------
147# UNCOMMENT these lines only AFTER you have generated the registration.yaml
148# for the specific bridge and copied it into the synapse data folder.
149# If you leave these uncommented but the files don't exist, Synapse will crash.
150
151app_service_config_files:
152 - /data/bridge-registrations/whatsapp-registration.yaml
153 - /data/bridge-registrations/discord-registration.yaml
154 - /data/bridge-registrations/meta-registration.yaml
155 - /data/bridge-registrations/googlechat-registration.yaml
156 - /data/double-puppet/doublepuppet.yaml
157 - /data/bridge-registrations/gmessages-registration.yaml
158 - /data/bridge-registrations/linkedin-registration.yaml
159 - /data/bridge-registrations/twitter-registration.yaml
160
161# ----------------------------------------------------------------------
162# REDIS
163# ----------------------------------------------------------------------
164redis:
165 enabled: true
166 host: redis
167
168# ----------------------------------------------------------------------
169# PERFORMANCE
170# ----------------------------------------------------------------------
171caches:
172 global_factor: 2.0
173
174presence:
175 enabled: false
You can generate the secret values by generating a config file using the Synapse docker image:
1docker run -it --rm \
2 --mount type=bind,src=$(pwd),dst=/data \
3 -e SYNAPSE_SERVER_NAME=example.com \
4 -e SYNAPSE_REPORT_STATS=yes \
5 matrixdotorg/synapse:latest generate
Matrix Authentication Service
MAS is the modern way to handle Matrix auth. In my cluster, MAS acts as an OIDC proxy that talks to Authentik. This means I have one single place to manage my users.
/clstr/matrix/mas/config.yaml
You can generate the base of this with mas-cli config generate, but here is the redacted version of my production config. Make sure the matrix.secret matches the one in your homeserver.yaml.
1http:
2 listeners:
3 - name: web
4 resources:
5 - name: discovery
6 - name: human
7 - name: oauth
8 - name: compat
9 - name: graphql
10 - name: assets
11 - name: adminapi
12 binds:
13 - address: '[::]:8080'
14 public_base: https://matrix.example.com/auth/
15
16database:
17 uri: postgres://synapse:REDACTED@pgbouncer/matrix_authentication_service?sslmode=disable
18
19secrets:
20 encryption: REDACTED_HEX_SECRET
21 keys:
22 - key: |
23 -----BEGIN RSA PRIVATE KEY-----
24 # Generate your own keys!
25 -----END RSA PRIVATE KEY-----
26
27matrix:
28 kind: synapse
29 homeserver: example.com
30 secret: Nbal2MLkHK2wfd3WvHnqk0d4C1OhR56X
31 endpoint: http://synapse:8008/
32
33upstream_oauth2:
34 providers:
35 - id: 01HFRQFT5QFMJFGF01P7JAV2ME
36 synapse_idp_id: oidc-authentik
37 human_name: Authentik
38 issuer: "https://authentik.example.com/application/o/synapse/"
39 client_id: "REDACTED"
40 client_secret: "REDACTED"
41 scope: "openid profile email"
42 claims_imports:
43 localpart:
44 action: require
45 template: "{{ user.preferred_username }}"
For the Authentik side of things, check out the MAS documentation to set up your OAuth provider.
Bridge Config
As you can see in the syanpse’s app_service_config_files key, there are various bridges which have been connected to synapse. These config files need to be generated. I’m not going to rewrite the mautrix docs, so it’s best to just follow the documentation for each bridge when it comes to setting it up.
I would just like to draw attention to the following, however, as this is crucial to ensure optimal performance of ALL bridges:
Double-puppeting
Instead of requiring everyone to manually enable double puppeting, you can give the bridge access to enable double puppeting automatically. This makes the process much smoother for users, and removes problems like access tokens getting invalidated.
Previously there were multiple different automatic double puppeting methods, but the older methods are deprecated and were completely removed in the megabridge rewrites. Only the new appservice method is now supported.
Automatic double puppeting should work on all homeserver implementations that support appservices. However, some servers don’t follow the spec, and may not work with a null url field.
Using appservices means it requires administrator access to the homeserver, so it can’t be used if your account is on someone elses server (e.g. using self-hosted bridges from matrix.org). In such cases, manual login is the only option.
This method also makes timestamp massaging work correctly and disables ratelimiting for double puppeted messages.
-
First create a new appservice registration file. The name doesn’t really matter, but
doublepuppet.yamlis a good choice. Don’t touch the bridge’s main registration file, and make sure the ID and as/hs tokens are different (having multiple appservices with the same ID or as_token isn’t allowed).1 2# The ID doesn't really matter, put whatever you want. 3id: doublepuppet 4# The URL is intentionally left empty (null), as the homeserver shouldn't 5# push events anywhere for this extra appservice. If you use a 6# non-spec-compliant server, you may need to put some fake URL here. 7url: 8# Generate random strings for these three fields. Only the as_token really 9# matters, hs_token is never used because there's no url, and the default 10# user (sender_localpart) is never used either. 11as_token: random string 12hs_token: random string 13sender_localpart: random string 14# Bridges don't like ratelimiting. This should only apply when using the 15# as_token, normal user tokens will still be ratelimited. 16rate_limited: false 17namespaces: 18 users: 19 # Replace your\.domain with your server name (escape dots for regex) 20 - regex: '@.*:your\.domain' 21 # This must be false so the appservice doesn't take over all users completely. 22 exclusive: false -
Install the new registration file the usual way (see Registering appservices).
-
Finally set
as_token:$TOKENas the secret indouble_puppet->secrets(e.g. if you haveas_token: meowin the registration, setas_token:meowin the bridge config).1 2double_puppet: 3 ... 4 secrets: 5 your.domain: "as_token:meow" 6 ...N.B. For old bridges, the map is
bridge->login_shared_secret_map.
If you set up double puppeting for multiple bridges, you can safely reuse the same registration by just setting the same token in the config of each bridge (i.e. no need to create a new double puppeting registration for each bridge).
This method works for other homeservers too, you just have to create a new registration file for each server, add the token to secrets, and also add the server address to the servers map (for the bridge server, adding to the server map is not necessary as it defaults to using the one configured in homeserver -> address).
Bridge Setup
Follow the steps here to do so. All bridges are run in docker, for convenience and security.
Setup Summary
Generate config
1docker run -it --rm \
2 -v $(pwd):/data:z \
3 dock.mau.dev/mautrix/whatsapp:latest
Edit the default config file to add your database, synapse and bridge config.
Generate Registration file
1docker run -it --rm -v $(pwd):/data:z dock.mau.dev/mautrix/whatsapp:latest -g
Add to synapse in the app_service_config_files key
Prepping the database
On the assumption that you are using a PostgreSQL DB, you need to configure a user, and database for each and every one of the bridges and synapse itself. Synapse requires the Database to be in the ‘C’ locale, so see below how to do:
1createuser --pwprompt synapse
2createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse synapse
Repeat for each bridge, to create users, and their corresponding databases.
.well-known delegation
To setup federation, you need to setup .well-known delegation. To do this, serve a json file at DOMAIN/.well-known/matrix/server
This should be in the following format:
1{
2 "m.server": "federation.example.com:443"
3}
Another useful one to configure is the client delegation, which allows clients to automatically discover your matrix server.
Serve a file at DOMAIN/.well-known/matrix/client , with the following format:
1{
2 "m.homeserver": {
3 "base_url": "https://matrix.example.com"
4 }
5}
Start-up
Now, start your Synapse instance with everything configured, and boom, you have a functioning Synapse instance.
The client
To match Beeper’s aesthetic, and UI-driven bridge management, I use my own custom client called Finny, a fork of Cinny. The desktop app is a WIP, but the web app is deployed on my infrastructure.
Final Thoughts
Overall, this is a pretty cool system, allowing you to see all your chats in one place, and have your own matrix server, ensuring your privacy and control of your own data.
Loading comments...