vojo/docs/ai/server-side.md
2026-05-06 23:12:57 +03:00

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:

  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://<service-name>:<port>.
  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:

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.