# 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 ```yaml 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: 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:` prefix in its `double_puppet.secrets` (bridgev2) or `bridge.login_shared_secret_map` (legacy mautrix-discord). ```yaml id: doublepuppet url: as_token: hs_token: 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/` 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:` | `./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: ```yaml -bridge: image: dock.mau.dev/mautrix/: container_name: vojo--bridge restart: unless-stopped depends_on: - postgres - synapse volumes: - ./bridges/:/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//`).** ```caddy 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: 1. **`appservice.address` must use the Docker service name**, not `localhost`. From inside a bridge container, `localhost` resolves to the bridge itself, not Synapse. Use `http://:`. 2. **`appservice.hostname: 0.0.0.0`** in `whatsapp/config.yaml` (the default `127.0.0.1` binds inside the container only and Synapse can't reach it). 3. **Synapse caches appservice configs in memory** at startup. After editing any `*-registration.yaml`, `restart synapse` — restarting only the bridge does not invalidate the cache. 4. **Bridge containers chown `/data/config.yaml` to root on first run.** Fix with `sudo chown 1337:docker-mautrix && chmod 660` so the host user (in the `docker-mautrix` group) can edit again. POSIX ACL via `setfacl -m g:docker-mautrix:rw` would survive the chmod-inside-container quirk; not yet applied. 5. **Legacy `mautrix-discord -g`** writes the registration but does **not** mirror the generated `as_token`/`hs_token` back into `config.yaml`. Copy them by hand from `registration.yaml`. (bridgev2 — telegram/whatsapp — does this correctly.) 6. **Double puppeting secret prefix `as_token:` is mandatory.** Without it, the bridge tries to find the deprecated `synapse-shared-secret-auth` Synapse module. ## Refresh procedure To regenerate this file with current state, on the server: ```bash 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.