9.7 KiB
Server-side configs
This file is a snapshot of the server-side layout for context, not the source of truth — if you're touching server config, SSH to the box and read the live file. Last refreshed 2026-05-06 from a directory walk + the 2026-05-05 handoff. Sections explicitly marked (not on hand) weren't dumped at refresh time and may have drifted.
Top-level layout: ~/vojo/
~/vojo/
├── docker-compose.yml
├── caddy/ # Caddyfile + ACME state (./data, ./config)
├── synapse/
│ ├── homeserver.yaml
│ ├── doublepuppet.yaml # passive AS for double puppeting (no url)
│ ├── telegram-registration.yaml # bind-mounted from bridges/telegram/registration.yaml
│ ├── discord-registration.yaml # bind-mounted from bridges/discord/registration.yaml
│ ├── whatsapp-registration.yaml # bind-mounted from bridges/whatsapp/registration.yaml
│ ├── modules/ # username_blocklist.py + blocked_usernames.txt + blocked_patterns.txt
│ ├── media_store/
│ ├── vojo.chat.log.config
│ └── vojo.chat.signing.key
├── postgres/ # data dir (root-owned, 700)
├── coturn/turnserver.conf
├── livekit/ # livekit.yaml + secrets/ (LiveKit + livekit-jwt-service)
├── sygnal/ # sygnal.yaml + fcm-service-account.json + vapid_private_key
├── prometheus/ # prometheus.yml + data/
├── grafana/ # grafana.db + plugins/ + report dirs
├── widgets/ # static widget bundles served by Caddy
│ ├── telegram/
│ ├── discord/
│ └── whatsapp/
├── bridges/ # mautrix bridge configs + per-bridge state
│ ├── telegram/ (config.yaml, registration.yaml, logs/)
│ ├── discord/ (config.yaml, registration.yaml, logs/)
│ └── whatsapp/ (config.yaml, registration.yaml, logs/)
└── cinny/ # static client bundle (rsync target)
Postgres roles & databases
synapse is the historical superuser. Each bridge has its own role + DB with a 32-char
openssl rand -base64 32 | tr -d '/+=' | head -c 32 password.
| Role | Database | Used by |
|---|---|---|
synapse |
synapse |
Synapse homeserver |
mautrix_telegram |
mautrix_telegram |
Telegram bridge |
mautrix_discord |
mautrix_discord |
Discord bridge |
mautrix_whatsapp |
mautrix_whatsapp |
WhatsApp bridge |
~/vojo/synapse/homeserver.yaml — relevant slices
server_name: "vojo.chat"
listeners:
- port: 8008
type: http
tls: false
x_forwarded: true
resources:
- names: [client, federation]
compress: false
- port: 9000
type: metrics
bind_addresses: ['0.0.0.0']
database:
name: psycopg2
args:
user: synapse
password: <redacted>
database: synapse
host: postgres
cp_min: 5
cp_max: 10
push:
enabled: true
include_content: true
app_service_config_files:
- /data/telegram-registration.yaml
- /data/doublepuppet.yaml
- /data/discord-registration.yaml
- /data/whatsapp-registration.yaml
# (also: media_store_path, signing_key_path, turn_*, encryption_enabled_by_default_for_room_type: "off",
# trusted_key_servers, enable_metrics, federation_ip_range_whitelist, ip_range_whitelist; unchanged from earlier snapshot)
~/vojo/synapse/doublepuppet.yaml — passive AS for double puppeting
One non-exclusive AS, no url: (so Synapse never pushes events to it), shared by all three
bridges. The as_token here is the value each bridge config references with the
as_token:<token> prefix in its double_puppet.secrets (bridgev2) or
bridge.login_shared_secret_map (legacy mautrix-discord).
id: doublepuppet
url:
as_token: <hex 64>
hs_token: <hex 64>
sender_localpart: doublepuppet33ef71bf
rate_limited: false
namespaces:
users:
- regex: '@.*:vojo\.chat'
exclusive: false
~/vojo/docker-compose.yml — service map
The full file is not committed to this repo. Below are the service names, images, and non-default details captured at refresh time. Expect minor drift — read the live file when in doubt.
| Service | Image | Notes |
|---|---|---|
postgres |
postgres:16 |
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" |
synapse |
matrixdotorg/synapse:latest |
Bind-mounts: ./synapse:/data plus three module mounts (username_blocklist.py, blocked_usernames.txt, blocked_patterns.txt → /usr/local/lib/python3.13/site-packages/), and four registration mounts (bridges/telegram/registration.yaml, synapse/doublepuppet.yaml, bridges/discord/registration.yaml, bridges/whatsapp/registration.yaml) into /data/*-registration.yaml. Exposes 8008. |
caddy |
caddy:2 |
Ports 80/443/8448. Bind-mounts ./caddy/Caddyfile, ./caddy/data, ./caddy/config, ./cinny:/var/www/cinny, and the three widgets/<bridge> directories (Caddy serves them at the bot widget origins). |
coturn |
coturn/coturn:latest |
network_mode: host; ./coturn/turnserver.conf mounted read-only at /etc/coturn/turnserver.conf. |
livekit |
LiveKit server image | Backed by ./livekit/livekit.yaml + ./livekit/secrets. Element Call backend. (env / image tag not on hand — read the file) |
livekit-jwt |
livekit-jwt-service |
Issues JWTs for Element Call widget; env includes LIVEKIT_FULL_ACCESS_HOMESERVERS=vojo.chat (see desired_features.md §5). (rest not on hand) |
sygnal |
matrixdotorg/sygnal:latest |
Mounts sygnal.yaml, fcm-service-account.json, vapid_private_key. healthcheck: disable: true. |
prometheus |
prom/prometheus:latest |
--storage.tsdb.retention.time=30d |
grafana |
grafana/grafana:latest |
Port 3000 |
telegram-bridge |
dock.mau.dev/mautrix/telegram:<v26.04 bridgev2 tag> |
./bridges/telegram:/data |
discord-bridge |
dock.mau.dev/mautrix/discord:v0.7.5 |
./bridges/discord:/data (legacy bridge — runtime reports 0.7.6+dev) |
whatsapp-bridge |
dock.mau.dev/mautrix/whatsapp:v0.12.4 |
./bridges/whatsapp:/data |
Bridge service stanza (template)
All three mautrix bridges share the same shape:
<bridge>-bridge:
image: dock.mau.dev/mautrix/<bridge>:<tag>
container_name: vojo-<bridge>-bridge
restart: unless-stopped
depends_on:
- postgres
- synapse
volumes:
- ./bridges/<bridge>:/data
Caddy — ~/vojo/caddy/Caddyfile
(not on hand at refresh time — the snippet below is the pre-bots single-domain config; the live
Caddyfile additionally serves the three widget origins from ~/vojo/widgets/<bridge>/).
vojo.chat {
handle /_matrix/* { reverse_proxy synapse:8008 }
handle /_synapse/* { reverse_proxy synapse:8008 }
handle /.well-known/matrix/server {
respond `{"m.server": "vojo.chat:443"}`
header Content-Type application/json
}
handle /.well-known/matrix/client {
respond `{"m.homeserver": {"base_url": "https://vojo.chat"}, "io.element.e2ee": {"force_disable": true}}`
header Content-Type application/json
header Access-Control-Allow-Origin *
}
handle {
root * /var/www/cinny
@nocache path /config.json /index.html /manifest.json /sw.js
header @nocache Cache-Control "no-cache, no-store, must-revalidate"
try_files {path} /index.html
file_server
}
}
vojo.chat:8448 { reverse_proxy synapse:8008 }
The .well-known/matrix/client block also advertises an org.matrix.msc4143.rtc_foci
entry pointing at the LiveKit JWT service — see desired_features.md §6 for the planned
stable-prefix migration.
Operational gotchas (preserve when refreshing)
These bit us during the 2026-05 bridge migration; keep them in mind when editing configs:
appservice.addressmust use the Docker service name, notlocalhost. From inside a bridge container,localhostresolves to the bridge itself, not Synapse. Usehttp://<service-name>:<port>.appservice.hostname: 0.0.0.0inwhatsapp/config.yaml(the default127.0.0.1binds inside the container only and Synapse can't reach it).- Synapse caches appservice configs in memory at startup. After editing any
*-registration.yaml,restart synapse— restarting only the bridge does not invalidate the cache. - Bridge containers chown
/data/config.yamlto root on first run. Fix withsudo chown 1337:docker-mautrix && chmod 660so the host user (in thedocker-mautrixgroup) can edit again. POSIX ACL viasetfacl -m g:docker-mautrix:rwwould survive the chmod-inside-container quirk; not yet applied. - Legacy
mautrix-discord -gwrites the registration but does not mirror the generatedas_token/hs_tokenback intoconfig.yaml. Copy them by hand fromregistration.yaml. (bridgev2 — telegram/whatsapp — does this correctly.) - Double puppeting secret prefix
as_token:is mandatory. Without it, the bridge tries to find the deprecatedsynapse-shared-secret-authSynapse module.
Refresh procedure
To regenerate this file with current state, on the server:
cd ~/vojo
sudo cat docker-compose.yml > /tmp/dump-compose.yml
sudo cat synapse/homeserver.yaml > /tmp/dump-homeserver.yaml
sudo cat synapse/doublepuppet.yaml > /tmp/dump-doublepuppet.yaml
sudo cat caddy/Caddyfile > /tmp/dump-caddyfile
docker compose ps > /tmp/dump-ps.txt
docker compose exec -T postgres psql -U synapse -d postgres -c "\du" > /tmp/dump-roles.txt
tar czf /tmp/vojo-configs-$(date +%Y%m%d).tar.gz -C /tmp dump-*
Hand the tarball to the AI session refreshing this doc — it has enough to bring every
section back in sync without (not on hand) placeholders.