aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFilip Wandzio <contact@philw.dev>2025-08-20 22:30:38 +0200
committerFilip Wandzio <contact@philw.dev>2025-08-20 22:30:38 +0200
commit53c9924ad4db79bc9c4b1c2fce045e7fe7d6f23e (patch)
treebc971ea26b2c9ffc9fc258d3bea61d7d261668ad
parent8684505db67bc41822130dfec9110670f5655834 (diff)
downloadwhiterabbit-53c9924ad4db79bc9c4b1c2fce045e7fe7d6f23e.tar.gz
whiterabbit-53c9924ad4db79bc9c4b1c2fce045e7fe7d6f23e.zip
Add turn server setup and nginx reverse proxy
Signed-off-by: Filip Wandzio <contact@philw.dev>
-rw-r--r--readme.txt1
-rwxr-xr-x[-rw-r--r--]whiterabbit.sh358
2 files changed, 195 insertions, 164 deletions
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
index 8f1d77d..e68351f 100644..100755
--- a/whiterabbit.sh
+++ b/whiterabbit.sh
@@ -1,194 +1,224 @@
1#!/bin/sh 1#!/usr/bin/sh
2set -e 2set -euo pipefail
3 3
4echo "Warning: secrets will be visible when typing. Press Enter after each input." 4echo "=== Matrix Dendrite + Coturn auto-setup (Docker + standalone Coturn) ==="
5 5
6# --- User input --- 6# ---- User Input ----
7printf "Base domain (e.g., example.com): " 7read -rp "Matrix domain (eg. matrix.example.com): " MATRIX_DOMAIN
8read DOMAIN 8read -rp "TURN domain(eg. turn.example.com): " TURN_DOMAIN
9printf "Subdomain for this instance (e.g., matrix1): " 9read -rp "Let's Encrypt certificate email: " EMAIL
10read SUBDOMAIN 10read -rp "Postgres secret: " DB_PASS
11CN="$SUBDOMAIN.$DOMAIN" 11read -rp "registration_shared_secret: " REG_SECRET
12 12read -rp "TURN shared secret: " TURN_SECRET
13printf "Postgres secret: " 13read -rp "TURN IP (eg. 127.0.0.1): " TURN_SERVER_IP
14read POSTGRES_SECRET 14read -rp "MATRIX server IP (eg. 127.0.0.1): " MATRIX_SERVER_IP
15printf "REG Secret (registration_shared_secret): " 15read -rp "Listening device (eg. eth0): " TURN_LISTENING_DEVICE
16read REG_SECRET 16
17 17INSTALL_DIR="/opt/matrix/$MATRIX_DOMAIN"
18# --- Directories --- 18mkdir -p "$INSTALL_DIR/config"
19BASE_DIR="/opt/matrix/$CN" 19cd "$INSTALL_DIR"
20mkdir -p "$BASE_DIR/data" 20
21mkdir -p "$BASE_DIR/db" 21cat <<EOF > .env
22 22# PostgreSQL
23# --- Automatic port assignment --- 23POSTGRES_HOSTNAME=postgres
24BASE_PORT=8008 24POSTGRES_VERSION=15-alpine
25FEDERATION_PORT=8448 25POSTGRES_USER=dendrite
26 26POSTGRES_PASSWORD=$DB_PASS
27for dir in /opt/matrix/*; do 27POSTGRES_DB=dendrite
28 if [ -f "$dir/docker-compose.yml" ]; then 28
29 used_ports=$(grep 'ports:' -A1 "$dir/docker-compose.yml" | awk -F: '{print $2}' | tr -d '"') 29# Monolith
30 for port in $used_ports; do 30MONOLITH_HOSTNAME=monolith
31 if [ "$port" ] && [ "$port" -ge "$BASE_PORT" ]; then 31MONOLITH_IMAGE=matrixdotorg/dendrite-monolith:latest
32 BASE_PORT=$((port + 1)) 32MONOLITH_PORT_HTTP=8008
33 fi 33MONOLITH_PORT_HTTPS=8448
34 if [ "$port" ] && [ "$port" -ge "$FEDERATION_PORT" ]; then 34EOF
35 FEDERATION_PORT=$((port + 1))
36 fi
37 done
38 fi
39done
40
41echo "Assigning ports: client-server=$BASE_PORT, federation=$FEDERATION_PORT"
42 35
43# --- Docker Compose --- 36cat <<'EOF' > compose.yml
44cat > "$BASE_DIR/docker-compose.yml" <<EOF
45services: 37services:
46 dendrite: 38 postgres:
47 image: ghcr.io/element-hq/dendrite-monolith:latest 39 hostname: ${POSTGRES_HOSTNAME:-postgres}
48 restart: unless-stopped 40 image: postgres:${POSTGRES_VERSION:-15-alpine}
41 restart: always
42 volumes:
43 - ./dendrite_postgres_data:/var/lib/postgresql/data
49 environment: 44 environment:
50 POSTGRES_URI: postgres://dendrite:$POSTGRES_SECRET@db/dendrite 45 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-itsasecret}
51 SERVER_NAME: $CN 46 POSTGRES_USER: ${POSTGRES_USER:-dendrite}
52 REGISTRATION_SHARED_SECRET: $REG_SECRET 47 POSTGRES_DB: ${POSTGRES_DB:-dendrite}
53 DISABLE_TLS: "true" 48 healthcheck:
49 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-dendrite}"]
50 interval: 5s
51 timeout: 5s
52 retries: 5
53 networks:
54 - internal
55 monolith:
56 hostname: ${MONOLITH_HOSTNAME:-monolith}
57 image: ${MONOLITH_IMAGE:-matrixdotorg/dendrite-monolith:latest}
54 ports: 58 ports:
55 - "$BASE_PORT:8008" 59 - ${MONOLITH_PORT_HTTP:-8008}:8008
56 - "$FEDERATION_PORT:8448" 60 - ${MONOLITH_PORT_HTTPS:-8448}:8448
57 volumes: 61 volumes:
58 - ./data:/data 62 - ./config:/etc/dendrite
59 63 - ./dendrite_media:/var/dendrite/media
60 db: 64 - ./dendrite_jetstream:/var/dendrite/jetstream
61 image: postgres:15 65 - ./dendrite_search_index:/var/dendrite/searchindex
66 - ./:/mnt
67 depends_on:
68 postgres:
69 condition: service_healthy
70 networks:
71 - internal
62 restart: unless-stopped 72 restart: unless-stopped
63 environment: 73networks:
64 POSTGRES_USER: dendrite 74 internal:
65 POSTGRES_PASSWORD: $POSTGRES_SECRET 75 attachable: true
66 volumes: 76volumes:
67 - ./db:/var/lib/postgresql/data 77 dendrite_postgres_data:
78 dendrite_media:
79 dendrite_jetstream:
80 dendrite_search_index:
68EOF 81EOF
69 82
70# --- Nginx config --- 83cat <<EOF > config/dendrite.yaml
71NGINX_CONF="/etc/nginx/sites-available/$CN" 84version: 2
72sudo tee "$NGINX_CONF" > /dev/null <<EOF 85global:
73server { 86 server_name: $MATRIX_DOMAIN
74 listen 80; 87 private_key: /mnt/matrix_key.pem
75 listen [::]:80; 88 old_private_keys:
76 server_name $CN; 89 key_validity_period: 168h0m0s
77 90 database:
78 location /.well-known/matrix/ { 91 connection_string: postgresql://dendrite:$DB_PASS@postgres/dendrite?sslmode=disable
79 try_files \$uri =404; 92 max_open_conns: 90
80 } 93 max_idle_conns: 5
94 conn_max_lifetime: -1
95
96 well_known_server_name: "$MATRIX_DOMAIN:443"
97 well_known_client_name: "https://$MATRIX_DOMAIN"
98
99client_api:
100 registration_disabled: true
101 guests_disabled: true
102 registration_shared_secret: "$REG_SECRET"
103
104 turn:
105 turn_user_lifetime: "5m"
106 turn_uris:
107 - turn:$TURN_DOMAIN?transport=udp
108 - turn:$TURN_DOMAIN?transport=tcp
109 turn_shared_secret: "$TURN_SECRET"
110
111federation_api:
112 send_max_retries: 16
113 disable_tls_validation: false
114
115media_api:
116 base_path: ./media_store
117 max_file_size_bytes: 10485760
118 dynamic_thumbnails: false
119 thumbnail_sizes:
120 - width: 96
121 height: 96
122 method: crop
123 - width: 640
124 height: 480
125 method: scale
126
127user_api:
128 bcrypt_cost: 10
129 auto_join_rooms:
130 - "#main:$MATRIX_DOMAIN"
131
132logging:
133 - type: std
134 level: info
135EOF
81 136
82 location / { 137cat <<EOF > /etc/turnserver.conf
83 proxy_pass http://127.0.0.1:$BASE_PORT; 138listening-device=$TURN_LISTENING_DEVICE
84 proxy_http_version 1.1; 139listening-port=3478
85 proxy_set_header Host \$host; 140tls-listening-port=5349
86 proxy_set_header X-Real-IP \$remote_addr; 141listening-ip=$TURN_SERVER_IP
87 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 142min-port=49152
88 proxy_set_header X-Forwarded-Proto \$scheme; 143max-port=65535
89 client_max_body_size 50M; 144use-auth-secret
90 proxy_read_timeout 600s; 145static-auth-secret=$TURN_SECRET
91 } 146syslog
92} 147no-rfc5780
148no-stun-backward-compatibility
149response-origin-only-with-rfc5780
93EOF 150EOF
94 151
95sudo ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/ 152echo "[INFO] Generating server keys..."
96sudo nginx -t 153docker 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
97sudo systemctl reload nginx
98 154
99# --- Start Docker Compose --- 155echo "[INFO] Booting up containers(HTTP-only test)..."
100cd "$BASE_DIR"
101docker compose up -d 156docker compose up -d
102 157
103# --- DNS propagation check --- 158echo "[INFO] Waiting for dendrite monolith to answer..."
104if ! command -v dig >/dev/null 2>&1; then 159for i in {1..30}; do
105 echo "Installing dnsutils (needed for DNS checks)..." 160 if curl -fs "http://$MATRIX_SERVER_IP:8008/_matrix/client/versions" >/dev/null 2>&1; then
106 sudo apt-get update && sudo apt-get install -y dnsutils 161 echo "[OK] Monolith dziaƂa na http://$MATRIX_SERVER_IP:8008"
107fi 162 break
108 163 fi
109# Collect all VPS IPs (IPv4 + IPv6) 164 echo " ...retry $i"
110VPS_IPS=$(hostname -I | tr ' ' '\n') 165 sleep 5
111echo "VPS addresses: $VPS_IPS"
112
113echo "Checking DNS propagation for $CN ..."
114MAX_RETRIES=30
115SLEEP_SEC=10
116count=0
117
118while true; do
119 DNS_IPS=$( (dig +short "$CN" A; dig +short "$CN" AAAA) | sort -u )
120 MATCH="false"
121
122 for dns_ip in $DNS_IPS; do
123 for vps_ip in $VPS_IPS; do
124 if [ "$dns_ip" = "$vps_ip" ]; then
125 MATCH="true"
126 break
127 fi
128 done
129 done
130
131 if [ "$MATCH" = "true" ]; then
132 echo "$CN resolves correctly to one of the VPS IPs: $DNS_IPS"
133 break
134 else
135 count=$((count + 1))
136 if [ "$count" -ge "$MAX_RETRIES" ]; then
137 echo "DNS propagation not detected after $((MAX_RETRIES*SLEEP_SEC)) seconds."
138 echo "Please make sure $CN points to this VPS and rerun the script."
139 exit 1
140 fi
141 echo "DNS not ready yet ($count/$MAX_RETRIES). Found: $DNS_IPS Expected one of: $VPS_IPS"
142 echo "Retrying in $SLEEP_SEC seconds..."
143 sleep $SLEEP_SEC
144 fi
145done 166done
146 167
147# --- Obtain HTTPS certificate --- 168echo "[INFO] Setting up NGINX RevPrx files"
148sudo certbot certonly --nginx -d "$CN" --non-interactive --agree-tos -m "admin@$DOMAIN" 169cat <<EOF > /etc/nginx/sites-available/$MATRIX_DOMAIN
149
150sudo tee "$NGINX_CONF" > /dev/null <<EOF
151server { 170server {
152 listen 80; 171 listen 80;
153 listen [::]:80; 172 listen [::]:80;
154 server_name $CN; 173 server_name $MATRIX_DOMAIN;
155 return 301 https://\$host\$request_uri; 174 location /.well-known/matrix/client {
175 return 301 https://$host$request_uri;
176 }
177 location /.well-known/matrix/server {
178 return 301 https://$host$request_uri;
179 }
180 location / {
181 return 301 https://$host$request_uri;
182 }
156} 183}
157 184
158server { 185server {
159 listen 443 ssl http2; 186 listen 443 ssl http2;
160 listen [::]:443 ssl http2; 187 listen [::]:443 ssl http2;
161 server_name $CN; 188 server_name $MATRIX_DOMAIN;
162 189
163 ssl_certificate /etc/letsencrypt/live/$CN/fullchain.pem; 190 ssl_certificate /etc/letsencrypt/live/$MATRIX_DOMAIN/fullchain.pem;
164 ssl_certificate_key /etc/letsencrypt/live/$CN/privkey.pem; 191 ssl_certificate_key /etc/letsencrypt/live/$MATRIX_DOMAIN/privkey.pem;
165 ssl_protocols TLSv1.2 TLSv1.3; 192 include /etc/letsencrypt/options-ssl-nginx.conf;
166 ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'; 193 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
167 ssl_prefer_server_ciphers on;
168 ssl_session_cache shared:SSL:10m;
169 ssl_session_timeout 1h;
170 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
171 add_header X-Content-Type-Options nosniff;
172 add_header X-Frame-Options DENY;
173 add_header X-XSS-Protection "1; mode=block";
174 add_header Referrer-Policy "no-referrer-when-downgrade";
175
176 location /.well-known/matrix/ {
177 try_files \$uri =404;
178 }
179 194
180 location / { 195 location / {
181 proxy_pass http://127.0.0.1:$BASE_PORT; 196 proxy_pass http://127.0.0.1:8008;
182 proxy_http_version 1.1; 197 proxy_http_version 1.1;
183 proxy_set_header Host \$host; 198 proxy_set_header Host $host;
184 proxy_set_header X-Real-IP \$remote_addr; 199 proxy_set_header X-Real-IP $remote_addr;
185 proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 200 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
186 proxy_set_header X-Forwarded-Proto \$scheme; 201 proxy_set_header X-Forwarded-Proto $scheme;
187 client_max_body_size 50M; 202 proxy_set_header Upgrade $http_upgrade;
188 proxy_read_timeout 600s; 203 proxy_set_header Connection "upgrade";
204 }
205
206 location /.well-known/matrix/client {
207 default_type application/json;
208 add_header Access-Control-Allow-Origin *;
209 return 200 '{"m.homeserver": {"base_url": "https://$MATRIX_DOMAIN"}}';
210 }
211
212 location /.well-known/matrix/server {
213 default_type application/json;
214 add_header Access-Control-Allow-Origin *;
215 return 200 '{"m.server": "$MATRIX_DOMAIN:443"}';
189 } 216 }
190} 217}
191EOF 218EOF
192sudo nginx -t 219
193sudo systemctl reload nginx 220ln -s /etc/nginx/sites-available/$MATRIX_DOMAIN
194echo "HTTPS active for $CN with federation support!" 221echo "============================================"
222echo " Matrix server setup finished!"
223echo " HomeServer: https://$MATRIX_DOMAIN:8448"
224echo " TURN: $TURN_DOMAIN (3478/5349)"