diff options
| -rw-r--r-- | whiterabbit.sh | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/whiterabbit.sh b/whiterabbit.sh new file mode 100644 index 0000000..8f1d77d --- /dev/null +++ b/whiterabbit.sh | |||
| @@ -0,0 +1,194 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | set -e | ||
| 3 | |||
| 4 | echo "Warning: secrets will be visible when typing. Press Enter after each input." | ||
| 5 | |||
| 6 | # --- User input --- | ||
| 7 | printf "Base domain (e.g., example.com): " | ||
| 8 | read DOMAIN | ||
| 9 | printf "Subdomain for this instance (e.g., matrix1): " | ||
| 10 | read SUBDOMAIN | ||
| 11 | CN="$SUBDOMAIN.$DOMAIN" | ||
| 12 | |||
| 13 | printf "Postgres secret: " | ||
| 14 | read POSTGRES_SECRET | ||
| 15 | printf "REG Secret (registration_shared_secret): " | ||
| 16 | read REG_SECRET | ||
| 17 | |||
| 18 | # --- Directories --- | ||
| 19 | BASE_DIR="/opt/matrix/$CN" | ||
| 20 | mkdir -p "$BASE_DIR/data" | ||
| 21 | mkdir -p "$BASE_DIR/db" | ||
| 22 | |||
| 23 | # --- Automatic port assignment --- | ||
| 24 | BASE_PORT=8008 | ||
| 25 | FEDERATION_PORT=8448 | ||
| 26 | |||
| 27 | for dir in /opt/matrix/*; do | ||
| 28 | if [ -f "$dir/docker-compose.yml" ]; then | ||
| 29 | used_ports=$(grep 'ports:' -A1 "$dir/docker-compose.yml" | awk -F: '{print $2}' | tr -d '"') | ||
| 30 | for port in $used_ports; do | ||
| 31 | if [ "$port" ] && [ "$port" -ge "$BASE_PORT" ]; then | ||
| 32 | BASE_PORT=$((port + 1)) | ||
| 33 | fi | ||
| 34 | if [ "$port" ] && [ "$port" -ge "$FEDERATION_PORT" ]; then | ||
| 35 | FEDERATION_PORT=$((port + 1)) | ||
| 36 | fi | ||
| 37 | done | ||
| 38 | fi | ||
| 39 | done | ||
| 40 | |||
| 41 | echo "Assigning ports: client-server=$BASE_PORT, federation=$FEDERATION_PORT" | ||
| 42 | |||
| 43 | # --- Docker Compose --- | ||
| 44 | cat > "$BASE_DIR/docker-compose.yml" <<EOF | ||
| 45 | services: | ||
| 46 | dendrite: | ||
| 47 | image: ghcr.io/element-hq/dendrite-monolith:latest | ||
| 48 | restart: unless-stopped | ||
| 49 | environment: | ||
| 50 | POSTGRES_URI: postgres://dendrite:$POSTGRES_SECRET@db/dendrite | ||
| 51 | SERVER_NAME: $CN | ||
| 52 | REGISTRATION_SHARED_SECRET: $REG_SECRET | ||
| 53 | DISABLE_TLS: "true" | ||
| 54 | ports: | ||
| 55 | - "$BASE_PORT:8008" | ||
| 56 | - "$FEDERATION_PORT:8448" | ||
| 57 | volumes: | ||
| 58 | - ./data:/data | ||
| 59 | |||
| 60 | db: | ||
| 61 | image: postgres:15 | ||
| 62 | restart: unless-stopped | ||
| 63 | environment: | ||
| 64 | POSTGRES_USER: dendrite | ||
| 65 | POSTGRES_PASSWORD: $POSTGRES_SECRET | ||
| 66 | volumes: | ||
| 67 | - ./db:/var/lib/postgresql/data | ||
| 68 | EOF | ||
| 69 | |||
| 70 | # --- Nginx config --- | ||
| 71 | NGINX_CONF="/etc/nginx/sites-available/$CN" | ||
| 72 | sudo tee "$NGINX_CONF" > /dev/null <<EOF | ||
| 73 | server { | ||
| 74 | listen 80; | ||
| 75 | listen [::]:80; | ||
| 76 | server_name $CN; | ||
| 77 | |||
| 78 | location /.well-known/matrix/ { | ||
| 79 | try_files \$uri =404; | ||
| 80 | } | ||
| 81 | |||
| 82 | location / { | ||
| 83 | proxy_pass http://127.0.0.1:$BASE_PORT; | ||
| 84 | proxy_http_version 1.1; | ||
| 85 | proxy_set_header Host \$host; | ||
| 86 | proxy_set_header X-Real-IP \$remote_addr; | ||
| 87 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||
| 88 | proxy_set_header X-Forwarded-Proto \$scheme; | ||
| 89 | client_max_body_size 50M; | ||
| 90 | proxy_read_timeout 600s; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | EOF | ||
| 94 | |||
| 95 | sudo ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/ | ||
| 96 | sudo nginx -t | ||
| 97 | sudo systemctl reload nginx | ||
| 98 | |||
| 99 | # --- Start Docker Compose --- | ||
| 100 | cd "$BASE_DIR" | ||
| 101 | docker compose up -d | ||
| 102 | |||
| 103 | # --- DNS propagation check --- | ||
| 104 | if ! command -v dig >/dev/null 2>&1; then | ||
| 105 | echo "Installing dnsutils (needed for DNS checks)..." | ||
| 106 | sudo apt-get update && sudo apt-get install -y dnsutils | ||
| 107 | fi | ||
| 108 | |||
| 109 | # Collect all VPS IPs (IPv4 + IPv6) | ||
| 110 | VPS_IPS=$(hostname -I | tr ' ' '\n') | ||
| 111 | echo "VPS addresses: $VPS_IPS" | ||
| 112 | |||
| 113 | echo "Checking DNS propagation for $CN ..." | ||
| 114 | MAX_RETRIES=30 | ||
| 115 | SLEEP_SEC=10 | ||
| 116 | count=0 | ||
| 117 | |||
| 118 | while 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 | ||
| 145 | done | ||
| 146 | |||
| 147 | # --- Obtain HTTPS certificate --- | ||
| 148 | sudo certbot certonly --nginx -d "$CN" --non-interactive --agree-tos -m "admin@$DOMAIN" | ||
| 149 | |||
| 150 | sudo tee "$NGINX_CONF" > /dev/null <<EOF | ||
| 151 | server { | ||
| 152 | listen 80; | ||
| 153 | listen [::]:80; | ||
| 154 | server_name $CN; | ||
| 155 | return 301 https://\$host\$request_uri; | ||
| 156 | } | ||
| 157 | |||
| 158 | server { | ||
| 159 | listen 443 ssl http2; | ||
| 160 | listen [::]:443 ssl http2; | ||
| 161 | server_name $CN; | ||
| 162 | |||
| 163 | ssl_certificate /etc/letsencrypt/live/$CN/fullchain.pem; | ||
| 164 | ssl_certificate_key /etc/letsencrypt/live/$CN/privkey.pem; | ||
| 165 | ssl_protocols TLSv1.2 TLSv1.3; | ||
| 166 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305'; | ||
| 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 | |||
| 180 | location / { | ||
| 181 | proxy_pass http://127.0.0.1:$BASE_PORT; | ||
| 182 | proxy_http_version 1.1; | ||
| 183 | proxy_set_header Host \$host; | ||
| 184 | proxy_set_header X-Real-IP \$remote_addr; | ||
| 185 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | ||
| 186 | proxy_set_header X-Forwarded-Proto \$scheme; | ||
| 187 | client_max_body_size 50M; | ||
| 188 | proxy_read_timeout 600s; | ||
| 189 | } | ||
| 190 | } | ||
| 191 | EOF | ||
| 192 | sudo nginx -t | ||
| 193 | sudo systemctl reload nginx | ||
| 194 | echo "HTTPS active for $CN with federation support!" | ||
