From 53c9924ad4db79bc9c4b1c2fce045e7fe7d6f23e Mon Sep 17 00:00:00 2001 From: Filip Wandzio Date: Wed, 20 Aug 2025 22:30:38 +0200 Subject: Add turn server setup and nginx reverse proxy Signed-off-by: Filip Wandzio --- readme.txt | 1 + whiterabbit.sh | 358 +++++++++++++++++++++++++++++++-------------------------- 2 files changed, 195 insertions(+), 164 deletions(-) create mode 100644 readme.txt mode change 100644 => 100755 whiterabbit.sh diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..2ecdd73 --- /dev/null +++ b/readme.txt @@ -0,0 +1 @@ +Simple shell script allowing sysadmins to easily spawn mutiple matrix dendrite monolith instances. diff --git a/whiterabbit.sh b/whiterabbit.sh old mode 100644 new mode 100755 index 8f1d77d..e68351f --- a/whiterabbit.sh +++ b/whiterabbit.sh @@ -1,194 +1,224 @@ -#!/bin/sh -set -e - -echo "Warning: secrets will be visible when typing. Press Enter after each input." - -# --- User input --- -printf "Base domain (e.g., example.com): " -read DOMAIN -printf "Subdomain for this instance (e.g., matrix1): " -read SUBDOMAIN -CN="$SUBDOMAIN.$DOMAIN" - -printf "Postgres secret: " -read POSTGRES_SECRET -printf "REG Secret (registration_shared_secret): " -read REG_SECRET - -# --- Directories --- -BASE_DIR="/opt/matrix/$CN" -mkdir -p "$BASE_DIR/data" -mkdir -p "$BASE_DIR/db" - -# --- Automatic port assignment --- -BASE_PORT=8008 -FEDERATION_PORT=8448 - -for dir in /opt/matrix/*; do - if [ -f "$dir/docker-compose.yml" ]; then - used_ports=$(grep 'ports:' -A1 "$dir/docker-compose.yml" | awk -F: '{print $2}' | tr -d '"') - for port in $used_ports; do - if [ "$port" ] && [ "$port" -ge "$BASE_PORT" ]; then - BASE_PORT=$((port + 1)) - fi - if [ "$port" ] && [ "$port" -ge "$FEDERATION_PORT" ]; then - FEDERATION_PORT=$((port + 1)) - fi - done - fi -done - -echo "Assigning ports: client-server=$BASE_PORT, federation=$FEDERATION_PORT" +#!/usr/bin/sh +set -euo pipefail + +echo "=== Matrix Dendrite + Coturn auto-setup (Docker + standalone Coturn) ===" + +# ---- User Input ---- +read -rp "Matrix domain (eg. matrix.example.com): " MATRIX_DOMAIN +read -rp "TURN domain(eg. turn.example.com): " TURN_DOMAIN +read -rp "Let's Encrypt certificate email: " EMAIL +read -rp "Postgres secret: " DB_PASS +read -rp "registration_shared_secret: " REG_SECRET +read -rp "TURN shared secret: " TURN_SECRET +read -rp "TURN IP (eg. 127.0.0.1): " TURN_SERVER_IP +read -rp "MATRIX server IP (eg. 127.0.0.1): " MATRIX_SERVER_IP +read -rp "Listening device (eg. eth0): " TURN_LISTENING_DEVICE + +INSTALL_DIR="/opt/matrix/$MATRIX_DOMAIN" +mkdir -p "$INSTALL_DIR/config" +cd "$INSTALL_DIR" + +cat < .env +# PostgreSQL +POSTGRES_HOSTNAME=postgres +POSTGRES_VERSION=15-alpine +POSTGRES_USER=dendrite +POSTGRES_PASSWORD=$DB_PASS +POSTGRES_DB=dendrite + +# Monolith +MONOLITH_HOSTNAME=monolith +MONOLITH_IMAGE=matrixdotorg/dendrite-monolith:latest +MONOLITH_PORT_HTTP=8008 +MONOLITH_PORT_HTTPS=8448 +EOF -# --- Docker Compose --- -cat > "$BASE_DIR/docker-compose.yml" < compose.yml services: - dendrite: - image: ghcr.io/element-hq/dendrite-monolith:latest - restart: unless-stopped + postgres: + hostname: ${POSTGRES_HOSTNAME:-postgres} + image: postgres:${POSTGRES_VERSION:-15-alpine} + restart: always + volumes: + - ./dendrite_postgres_data:/var/lib/postgresql/data environment: - POSTGRES_URI: postgres://dendrite:$POSTGRES_SECRET@db/dendrite - SERVER_NAME: $CN - REGISTRATION_SHARED_SECRET: $REG_SECRET - DISABLE_TLS: "true" + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-itsasecret} + POSTGRES_USER: ${POSTGRES_USER:-dendrite} + POSTGRES_DB: ${POSTGRES_DB:-dendrite} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-dendrite}"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - internal + monolith: + hostname: ${MONOLITH_HOSTNAME:-monolith} + image: ${MONOLITH_IMAGE:-matrixdotorg/dendrite-monolith:latest} ports: - - "$BASE_PORT:8008" - - "$FEDERATION_PORT:8448" + - ${MONOLITH_PORT_HTTP:-8008}:8008 + - ${MONOLITH_PORT_HTTPS:-8448}:8448 volumes: - - ./data:/data - - db: - image: postgres:15 + - ./config:/etc/dendrite + - ./dendrite_media:/var/dendrite/media + - ./dendrite_jetstream:/var/dendrite/jetstream + - ./dendrite_search_index:/var/dendrite/searchindex + - ./:/mnt + depends_on: + postgres: + condition: service_healthy + networks: + - internal restart: unless-stopped - environment: - POSTGRES_USER: dendrite - POSTGRES_PASSWORD: $POSTGRES_SECRET - volumes: - - ./db:/var/lib/postgresql/data +networks: + internal: + attachable: true +volumes: + dendrite_postgres_data: + dendrite_media: + dendrite_jetstream: + dendrite_search_index: EOF -# --- Nginx config --- -NGINX_CONF="/etc/nginx/sites-available/$CN" -sudo tee "$NGINX_CONF" > /dev/null < config/dendrite.yaml +version: 2 +global: + server_name: $MATRIX_DOMAIN + private_key: /mnt/matrix_key.pem + old_private_keys: + key_validity_period: 168h0m0s + database: + connection_string: postgresql://dendrite:$DB_PASS@postgres/dendrite?sslmode=disable + max_open_conns: 90 + max_idle_conns: 5 + conn_max_lifetime: -1 + + well_known_server_name: "$MATRIX_DOMAIN:443" + well_known_client_name: "https://$MATRIX_DOMAIN" + +client_api: + registration_disabled: true + guests_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" + +federation_api: + send_max_retries: 16 + disable_tls_validation: false + +media_api: + base_path: ./media_store + max_file_size_bytes: 10485760 + dynamic_thumbnails: false + thumbnail_sizes: + - width: 96 + height: 96 + method: crop + - width: 640 + height: 480 + method: scale + +user_api: + bcrypt_cost: 10 + auto_join_rooms: + - "#main:$MATRIX_DOMAIN" + +logging: + - type: std + level: info +EOF - location / { - proxy_pass http://127.0.0.1:$BASE_PORT; - proxy_http_version 1.1; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - client_max_body_size 50M; - proxy_read_timeout 600s; - } -} +cat < /etc/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 +syslog +no-rfc5780 +no-stun-backward-compatibility +response-origin-only-with-rfc5780 EOF -sudo ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl reload nginx +echo "[INFO] Generating server keys..." +docker run --rm --entrypoint="" -v $(pwd):/mnt matrixdotorg/dendrite-monolith:latest /usr/bin/generate-keys -private-key /mnt/matrix_key.pem -tls-cert /mnt/server.crt -tls-key /mnt/server.key -# --- Start Docker Compose --- -cd "$BASE_DIR" +echo "[INFO] Booting up containers(HTTP-only test)..." docker compose up -d -# --- DNS propagation check --- -if ! command -v dig >/dev/null 2>&1; then - echo "Installing dnsutils (needed for DNS checks)..." - sudo apt-get update && sudo apt-get install -y dnsutils -fi - -# Collect all VPS IPs (IPv4 + IPv6) -VPS_IPS=$(hostname -I | tr ' ' '\n') -echo "VPS addresses: $VPS_IPS" - -echo "Checking DNS propagation for $CN ..." -MAX_RETRIES=30 -SLEEP_SEC=10 -count=0 - -while true; do - DNS_IPS=$( (dig +short "$CN" A; dig +short "$CN" AAAA) | sort -u ) - MATCH="false" - - for dns_ip in $DNS_IPS; do - for vps_ip in $VPS_IPS; do - if [ "$dns_ip" = "$vps_ip" ]; then - MATCH="true" - break - fi - done - done - - if [ "$MATCH" = "true" ]; then - echo "$CN resolves correctly to one of the VPS IPs: $DNS_IPS" - break - else - count=$((count + 1)) - if [ "$count" -ge "$MAX_RETRIES" ]; then - echo "DNS propagation not detected after $((MAX_RETRIES*SLEEP_SEC)) seconds." - echo "Please make sure $CN points to this VPS and rerun the script." - exit 1 - fi - echo "DNS not ready yet ($count/$MAX_RETRIES). Found: $DNS_IPS Expected one of: $VPS_IPS" - echo "Retrying in $SLEEP_SEC seconds..." - sleep $SLEEP_SEC - fi +echo "[INFO] Waiting for dendrite monolith to answer..." +for i in {1..30}; do + if curl -fs "http://$MATRIX_SERVER_IP:8008/_matrix/client/versions" >/dev/null 2>&1; then + echo "[OK] Monolith dziaƂa na http://$MATRIX_SERVER_IP:8008" + break + fi + echo " ...retry $i" + sleep 5 done -# --- Obtain HTTPS certificate --- -sudo certbot certonly --nginx -d "$CN" --non-interactive --agree-tos -m "admin@$DOMAIN" - -sudo tee "$NGINX_CONF" > /dev/null < /etc/nginx/sites-available/$MATRIX_DOMAIN server { listen 80; listen [::]:80; - server_name $CN; - return 301 https://\$host\$request_uri; + server_name $MATRIX_DOMAIN; + location /.well-known/matrix/client { + return 301 https://$host$request_uri; + } + location /.well-known/matrix/server { + return 301 https://$host$request_uri; + } + location / { + return 301 https://$host$request_uri; + } } server { listen 443 ssl http2; listen [::]:443 ssl http2; - server_name $CN; - - ssl_certificate /etc/letsencrypt/live/$CN/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/$CN/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 1h; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - add_header X-Content-Type-Options nosniff; - add_header X-Frame-Options DENY; - add_header X-XSS-Protection "1; mode=block"; - add_header Referrer-Policy "no-referrer-when-downgrade"; - - location /.well-known/matrix/ { - try_files \$uri =404; - } + server_name $MATRIX_DOMAIN; + + ssl_certificate /etc/letsencrypt/live/$MATRIX_DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/$MATRIX_DOMAIN/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; location / { - proxy_pass http://127.0.0.1:$BASE_PORT; + proxy_pass http://127.0.0.1:8008; proxy_http_version 1.1; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - client_max_body_size 50M; - proxy_read_timeout 600s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /.well-known/matrix/client { + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"m.homeserver": {"base_url": "https://$MATRIX_DOMAIN"}}'; + } + + location /.well-known/matrix/server { + default_type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"m.server": "$MATRIX_DOMAIN:443"}'; } } EOF -sudo nginx -t -sudo systemctl reload nginx -echo "HTTPS active for $CN with federation support!" + +ln -s /etc/nginx/sites-available/$MATRIX_DOMAIN +echo "============================================" +echo " Matrix server setup finished!" +echo " HomeServer: https://$MATRIX_DOMAIN:8448" +echo " TURN: $TURN_DOMAIN (3478/5349)" -- cgit v1.2.3