#!/bin/sh set -eu BASE_DIR="/opt/matrix" usage() { cat <<'EOF' whiterabbit: auto-configure Matrix homeservers (Dendrite or Synapse) with a global Coturn instance and Nginx + Let's Encrypt proxy. Main actions: turn Install or configure the global Coturn server dendrite Add a new Matrix server (Dendrite) synapse Add a new Matrix server (Synapse) list Show all configured Matrix servers remove Remove a Matrix server by domain Notes: • Coturn should be installed only once using 'turn'. • All Matrix servers share the same Coturn (TURN domain/secret). • TLS certificates are issued automatically using Let's Encrypt. • Requirements: docker, docker-compose, nginx, certbot, coturn. EOF exit 1 } list_servers() { echo "=== Installed servers ===" ls -1 "$BASE_DIR" 2>/dev/null || echo "No servers installed" } install_turn() { echo "=== Installing global Coturn ===" printf "TURN domain (eg. turn.example.com): " read -r TURN_DOMAIN printf "TURN IP (eg. 127.0.0.1): " read -r TURN_SERVER_IP printf "Listening device (eg. eth0): " read -r TURN_LISTENING_DEVICE printf "TURN shared secret: " read -r TURN_SECRET mkdir -p /etc/turn cat < /etc/turn/turnserver.conf listening-device=$TURN_LISTENING_DEVICE listening-port=3478 tls-listening-port=5349 listening-ip=$TURN_SERVER_IP min-port=49152 max-port=65535 use-auth-secret static-auth-secret=$TURN_SECRET realm=$TURN_DOMAIN syslog no-rfc5780 no-stun-backward-compatibility response-origin-only-with-rfc5780 EOF echo "$TURN_DOMAIN" > /etc/turn/domain echo "$TURN_SECRET" > /etc/turn/secret systemctl enable --now coturn || true echo "[OK] Global Coturn configured ($TURN_DOMAIN)" } common_prompts() { printf "Matrix domain (eg. matrix.example.com): " read -r MATRIX_DOMAIN printf "Let's Encrypt certificate email: " read -r EMAIL printf "Postgres secret: " read -r DB_PASS printf "registration_shared_secret: " read -r REG_SECRET printf "MATRIX server IP (eg. 127.0.0.1): " read -r MATRIX_SERVER_IP TURN_DOMAIN=$(cat /etc/turn/domain) TURN_SECRET=$(cat /etc/turn/secret) } configure_nginx() { DOMAIN="$1" SERVICE_PORT="$2" cat < "/etc/nginx/sites-available/$DOMAIN" server { listen 80; server_name $DOMAIN; location /.well-known/matrix/server { default_type application/json; return 200 '{ "m.server": "$DOMAIN:443" }'; } location /.well-known/matrix/client { default_type application/json; return 200 '{ "m.homeserver": { "base_url": "https://$DOMAIN" }, "m.identity_server": { "base_url": "https://vector.im" } }'; } location / { proxy_pass http://127.0.0.1:$SERVICE_PORT; proxy_set_header Host \$host; proxy_set_header X-Forwarded-For \$remote_addr; } } EOF ln -sf "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/$DOMAIN" nginx -t && systemctl reload nginx certbot --nginx --non-interactive --agree-tos -m "$EMAIL" -d "$DOMAIN" echo "[OK] Nginx and SSL certificate configured for $DOMAIN" } install_dendrite() { echo "=== Installing Matrix Dendrite ===" common_prompts INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" mkdir -p "$INSTALL_DIR/config" cd "$INSTALL_DIR" || exit 1 cat < .env POSTGRES_HOSTNAME=postgres POSTGRES_VERSION=15-alpine POSTGRES_USER=dendrite POSTGRES_PASSWORD=$DB_PASS POSTGRES_DB=dendrite MONOLITH_HOSTNAME=monolith MONOLITH_IMAGE=matrixdotorg/dendrite-monolith:latest MONOLITH_PORT_HTTP=8008 MONOLITH_PORT_HTTPS=8448 EOF cat <<'EOF' > compose.yml services: postgres: image: postgres:${POSTGRES_VERSION:-15-alpine} restart: always volumes: - ./dendrite_postgres_data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_DB: ${POSTGRES_DB} networks: [ internal ] monolith: image: ${MONOLITH_IMAGE} ports: - ${MONOLITH_PORT_HTTP}:8008 - ${MONOLITH_PORT_HTTPS}:8448 volumes: - ./config:/etc/dendrite - ./dendrite_media:/var/dendrite/media depends_on: [ postgres ] networks: [ internal ] restart: unless-stopped networks: internal: attachable: true EOF cat < config/dendrite.yaml version: 2 global: server_name: $MATRIX_DOMAIN private_key: /mnt/matrix_key.pem database: connection_string: postgresql://dendrite:$DB_PASS@postgres/dendrite?sslmode=disable well_known_server_name: "$MATRIX_DOMAIN:443" well_known_client_name: "https://$MATRIX_DOMAIN" client_api: registration_disabled: true registration_shared_secret: "$REG_SECRET" turn: turn_user_lifetime: "5m" turn_uris: - turn:$TURN_DOMAIN?transport=udp - turn:$TURN_DOMAIN?transport=tcp turn_shared_secret: "$TURN_SECRET" EOF docker compose up -d configure_nginx "$MATRIX_DOMAIN" "8008" echo "[OK] Dendrite server ($MATRIX_DOMAIN) is running." } install_synapse() { echo "=== Installing Matrix Synapse ===" common_prompts INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" mkdir -p "$INSTALL_DIR" cd "$INSTALL_DIR" || exit 1 cat < docker-compose.yml version: '3.4' services: db: image: postgres:15-alpine environment: POSTGRES_USER: synapse POSTGRES_PASSWORD: $DB_PASS POSTGRES_DB: synapse volumes: - ./db:/var/lib/postgresql/data synapse: image: matrixdotorg/synapse:latest depends_on: [ db ] environment: SYNAPSE_SERVER_NAME: $MATRIX_DOMAIN SYNAPSE_REPORT_STATS: "yes" POSTGRES_PASSWORD: $DB_PASS ports: - 8008:8008 - 8448:8448 volumes: - ./data:/data EOF docker compose up -d cat < data/homeserver-extra.yaml turn_uris: [ "turn:$TURN_DOMAIN?transport=udp", "turn:$TURN_DOMAIN?transport=tcp" ] turn_shared_secret: "$TURN_SECRET" turn_user_lifetime: "5m" EOF configure_nginx "$MATRIX_DOMAIN" "8008" echo "[OK] Synapse server ($MATRIX_DOMAIN) is running." } remove_server() { DOMAIN="$1" [ -z "$DOMAIN" ] && usage SERVER_DIR="$BASE_DIR/$DOMAIN" rm -rf "$SERVER_DIR" rm -f "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/$DOMAIN" nginx -t && systemctl reload nginx echo "[OK] Server $DOMAIN removed." } # --- CLI router --- CMD="${1:-}" case "$CMD" in turn) install_turn ;; dendrite) install_dendrite ;; synapse) install_synapse ;; list) list_servers ;; remove) remove_server "${2:-}" ;; *) usage ;; esac