diff --git a/docs/ai/overview.md b/docs/ai/overview.md index a1af1d2b..d785174f 100644 --- a/docs/ai/overview.md +++ b/docs/ai/overview.md @@ -9,12 +9,22 @@ Value proposition: "Telegram works without VPN" — the server runs a mautrix-te ## Infrastructure - **Domain**: vojo.chat -- **Server**: Yandex Cloud VPS, Ubuntu 24.04 -- **Homeserver**: Synapse (Matrix) + PostgreSQL + Caddy (reverse proxy) -- **Bridges**: mautrix-telegram v0.15.3 (Python, SOCKS5 proxy for Telegram) -- **Client**: this repo — deployed as static files via Caddy +- **Server**: Hostinger VPS `187.127.77.124` (hostname `srv1638609`), Ubuntu 24.04, user `vojo-superuser` (in groups `sudo`, `docker`, `docker-mautrix` GID 1337) +- **Homeserver**: Synapse (Matrix) + PostgreSQL 16 + Caddy 2 (reverse proxy, Let's Encrypt) +- **Bridges** (all `dock.mau.dev/mautrix/*` images, each with its own Postgres role + DB): + - `telegram` — bridgev2 (Go) v26.04 + - `discord` — legacy (Go) v0.7.6+dev (image tag v0.7.5; bridgev2 rewrite ETA late 2026) + - `whatsapp` — bridgev2 (Go) v26.04+dev (image v0.12.4) + - Passive `doublepuppet` appservice (no `url`, non-exclusive `@.*:vojo.chat`) — one AS-token shared by all three bridges for double puppeting per megabridge spec. Legacy `synapse-shared-secret-auth` is gone; the `as_token:` prefix in each bridge's `secrets:` / `login_shared_secret_map:` is mandatory. +- **RTC**: LiveKit + `livekit-jwt-service` (Element Call backend; federation gated to `vojo.chat` via `LIVEKIT_FULL_ACCESS_HOMESERVERS` — see `desired_features.md` §5) +- **Push**: Sygnal (FCM + VAPID) +- **TURN**: coturn (host network mode) +- **Observability**: Prometheus + Grafana +- **Synapse modules**: in-tree `username_blocklist.py` bind-mounted into the Synapse image (`synapse/modules/`) +- **Client**: this repo — built and deployed as static files via Caddy +- **Bot widgets**: three Preact apps in `apps/widget-{telegram,discord,whatsapp}/`, built independently and deployed to `~/vojo/widgets/{telegram,discord,whatsapp}/` for BotShell to embed -Client source on server: `~/vojo/cinny/`. Caddy serves it at `https://vojo.chat` (via `/var/www/cinny` symlink to `~/vojo/cinny`). +Server filesystem under `~/vojo/`: `caddy/`, `synapse/`, `postgres/`, `coturn/`, `livekit/`, `sygnal/`, `prometheus/`, `grafana/`, `bridges/{telegram,discord,whatsapp}/`, `widgets/{telegram,discord,whatsapp}/`, `cinny/`, `docker-compose.yml`. Caddy serves the client at `https://vojo.chat` with the host's `cinny/` directory bind-mounted into the container as `/var/www/cinny`. ## Branding @@ -29,10 +39,10 @@ All configs must point to `vojo.chat` as the default and only homeserver. Hide h ```bash npm ci npm run build -scp -r dist/* vojo-superuser@111.88.146.156:~/vojo/cinny/ +rsync -avz --delete dist/ vojo-superuser@187.127.77.124:~/vojo/cinny/ ``` -VSCode task `Deploy to vojo.chat` (Ctrl+Shift+D) automates this. Android build/deploy is covered in [android.md](android.md). +VSCode task `Deploy to vojo.chat` (Ctrl+Shift+D) automates this. The companion task `Deploy widgets` builds and rsyncs the three Preact widget apps under `apps/widget-{telegram,discord,whatsapp}/` to `~/vojo/widgets//` in parallel. Android build/deploy is covered in [android.md](android.md). ## Developer profile diff --git a/docs/ai/server-side.md b/docs/ai/server-side.md index 2e2c8fb3..bbd50dc9 100644 --- a/docs/ai/server-side.md +++ b/docs/ai/server-side.md @@ -1,94 +1,161 @@ -folders on server: -caddy cinny coturn docker-compose.yml element-releases grafana mautrix-telegram mautrix-telegram-config.yaml.go-backup postgres prometheus sygnal synapse +# Server-side configs -docker-compose.yml -services: - postgres: - image: postgres:16 - restart: unless-stopped - environment: - POSTGRES_USER: synapse - POSTGRES_PASSWORD: pass - POSTGRES_DB: synapse - POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" - volumes: - - ./postgres:/var/lib/postgresql/data +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. - synapse: - image: matrixdotorg/synapse:latest +## 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: - - ./synapse:/data - ports: - - "8008:8008" + - ./bridges/:/data +``` - caddy: - image: caddy:2 - restart: unless-stopped - ports: - - "80:80" - - "443:443" - - "8448:8448" - volumes: - - ./caddy/Caddyfile:/etc/caddy/Caddyfile - - ./caddy/data:/data - - ./caddy/config:/config - - ./cinny:/var/www/cinny +## Caddy — `~/vojo/caddy/Caddyfile` - prometheus: - image: prom/prometheus:latest - restart: unless-stopped - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - ./prometheus/data:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.retention.time=30d' +**(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//`).** - grafana: - image: grafana/grafana:latest - restart: unless-stopped - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_PASSWORD= - volumes: - - ./grafana:/var/lib/grafana - - coturn: - image: coturn/coturn:latest - restart: unless-stopped - network_mode: host - volumes: - - ./coturn/turnserver.conf:/etc/coturn/turnserver.conf - - telegram-bridge: - image: dock.mau.dev/mautrix/telegram:v0.15.3 - restart: unless-stopped - volumes: - - ./mautrix-telegram:/data - - sygnal: - image: matrixdotorg/sygnal:latest - restart: unless-stopped - healthcheck: - disable: true - volumes: - - ./sygnal/sygnal.yaml:/sygnal.yaml - - ./sygnal/fcm-service-account.json:/fcm-service-account.json - - ./sygnal/vapid_private_key:/vapid_private_key - command: ["python", "-m", "sygnal", "-c", "/sygnal.yaml"] - -caddy/Caddyfile +```caddy vojo.chat { - handle /_matrix/* { - reverse_proxy synapse:8008 - } - handle /_synapse/* { - reverse_proxy synapse:8008 - } + 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 @@ -107,71 +174,50 @@ vojo.chat { } } -vojo.chat:8448 { - reverse_proxy synapse:8008 -} +vojo.chat:8448 { reverse_proxy synapse:8008 } +``` -synapse/homeserver.yaml -# Configuration file for Synapse. -# -# This is a YAML file: see [1] for a quick introduction. Note in particular -# that *indentation is important*: all the elements of a list or dictionary -# should have the same indentation. -# -# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html -# -# For more information on how to configure Synapse, including a complete accounting of -# each option, go to docs/usage/configuration/config_documentation.md or -# https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html -server_name: "vojo.chat" -pid_file: /data/homeserver.pid -listeners: - - port: 8008 - resources: - - compress: false - names: - - client - - federation - tls: false - type: http - x_forwarded: true - - port: 9000 - type: metrics - bind_addresses: ['0.0.0.0'] -database: - name: psycopg2 - args: - user: synapse - password: DfgoeFDgr12 - database: synapse - host: postgres - cp_min: 5 - cp_max: 10 -push: - enabled: true - include_content: true -log_config: "/data/vojo.chat.log.config" -media_store_path: /data/media_store -registration_shared_secret: "" -report_stats: false -macaroon_secret_key: "" -form_secret: "" -signing_key_path: "/data/vojo.chat.signing.key" -trusted_key_servers: - - server_name: "matrix.org" -enable_registration: true -enable_registration_without_verification: true -enable_metrics: true -turn_uris: - - "turn:vojo.chat:3478?transport=udp" - - "turn:vojo.chat:3478?transport=tcp" -turn_shared_secret: "" -turn_user_lifetime: 86400000 -turn_allow_guests: false -encryption_enabled_by_default_for_room_type: "off" -app_service_config_files: - - /data/telegram-registration.yaml -federation_ip_range_whitelist: - - '172.18.0.0/16' -ip_range_whitelist: - - '172.18.0.0/16' \ No newline at end of file +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.