diff options
Diffstat (limited to 'wh.sh')
| -rwxr-xr-x | wh.sh | 500 |
1 files changed, 500 insertions, 0 deletions
| @@ -0,0 +1,500 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | set -eu | ||
| 3 | |||
| 4 | BASE_DIR="/opt/matrix" | ||
| 5 | |||
| 6 | DOCKER_COMPOSE=$(command -v docker-compose || command -v docker compose) || { | ||
| 7 | echo "[ERROR] docker-compose or docker compose not found" >&2 | ||
| 8 | exit 1 | ||
| 9 | } | ||
| 10 | |||
| 11 | usage() { | ||
| 12 | cat <<'EOF' | ||
| 13 | whiterabbit: auto-configure Matrix homeservers (Dendrite or Synapse) | ||
| 14 | with a global Coturn instance and Nginx + Let's Encrypt proxy. | ||
| 15 | |||
| 16 | Main actions: | ||
| 17 | turn Install or configure the global Coturn server | ||
| 18 | dendrite Add a new Matrix server (Dendrite) | ||
| 19 | synapse Add a new Matrix server (Synapse) | ||
| 20 | list Show all configured Matrix servers | ||
| 21 | remove Remove a Matrix server by domain | ||
| 22 | EOF | ||
| 23 | exit 1 | ||
| 24 | } | ||
| 25 | |||
| 26 | require_nonempty() { | ||
| 27 | VAR_NAME="$1" | ||
| 28 | VAR_VALUE="$2" | ||
| 29 | [ -n "$VAR_VALUE" ] || { | ||
| 30 | echo "[ERROR] $VAR_NAME cannot be empty" >&2 | ||
| 31 | exit 1 | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | list_servers() { | ||
| 36 | echo "=== Installed servers ===" | ||
| 37 | for dir in "$BASE_DIR"/*; do | ||
| 38 | [ -d "$dir" ] || continue | ||
| 39 | name=$(basename "$dir") | ||
| 40 | |||
| 41 | status=$(docker ps --filter "name=^${name}$" --format "{{.Status}}") | ||
| 42 | ports=$(docker ps --filter "name=^${name}$" --format "{{.Ports}}" | sed 's/, /\n /g') | ||
| 43 | |||
| 44 | [ -n "$status" ] && { | ||
| 45 | echo "- $name [RUNNING] on:" | ||
| 46 | echo " $ports" | ||
| 47 | } || echo "- $name [STOPPED]" | ||
| 48 | done | ||
| 49 | } | ||
| 50 | |||
| 51 | install_turn() { | ||
| 52 | echo "=== Installing global Coturn ===" | ||
| 53 | printf "TURN domain (eg. turn.example.com): " | ||
| 54 | read -r TURN_DOMAIN | ||
| 55 | require_nonempty TURN_DOMAIN "$TURN_DOMAIN" | ||
| 56 | |||
| 57 | printf "TURN IP (eg. 127.0.0.1): " | ||
| 58 | read -r TURN_SERVER_IP | ||
| 59 | require_nonempty TURN_SERVER_IP "$TURN_SERVER_IP" | ||
| 60 | |||
| 61 | printf "Listening device (eg. eth0): " | ||
| 62 | read -r TURN_LISTENING_DEVICE | ||
| 63 | require_nonempty TURN_LISTENING_DEVICE "$TURN_LISTENING_DEVICE" | ||
| 64 | |||
| 65 | printf "TURN shared secret: " | ||
| 66 | read -r TURN_SECRET | ||
| 67 | require_nonempty TURN_SECRET "$TURN_SECRET" | ||
| 68 | |||
| 69 | mkdir -p /etc/turn | ||
| 70 | cat <<EOF > /etc/turn/turnserver.conf | ||
| 71 | listening-device=$TURN_LISTENING_DEVICE | ||
| 72 | listening-port=3478 | ||
| 73 | tls-listening-port=5349 | ||
| 74 | listening-ip=$TURN_SERVER_IP | ||
| 75 | min-port=49152 | ||
| 76 | max-port=65535 | ||
| 77 | use-auth-secret | ||
| 78 | static-auth-secret=$TURN_SECRET | ||
| 79 | realm=$TURN_DOMAIN | ||
| 80 | syslog | ||
| 81 | no-rfc5780 | ||
| 82 | no-stun-backward-compatibility | ||
| 83 | response-origin-only-with-rfc5780 | ||
| 84 | EOF | ||
| 85 | |||
| 86 | echo "$TURN_DOMAIN" > /etc/turn/domain | ||
| 87 | echo "$TURN_SECRET" > /etc/turn/secret | ||
| 88 | |||
| 89 | case $(command -v systemctl >/dev/null && echo y || echo n) in | ||
| 90 | y) systemctl enable --now coturn || true ;; | ||
| 91 | n) service coturn start || true ;; | ||
| 92 | esac | ||
| 93 | |||
| 94 | echo "[OK] Global Coturn configured ($TURN_DOMAIN)" | ||
| 95 | } | ||
| 96 | |||
| 97 | common_prompts() { | ||
| 98 | printf "Matrix domain (eg. matrix.example.com): " | ||
| 99 | read -r MATRIX_DOMAIN | ||
| 100 | require_nonempty MATRIX_DOMAIN "$MATRIX_DOMAIN" | ||
| 101 | |||
| 102 | printf "Let's Encrypt certificate email: " | ||
| 103 | read -r EMAIL | ||
| 104 | require_nonempty EMAIL "$EMAIL" | ||
| 105 | DB_PASS=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32) | ||
| 106 | REG_SECRET=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64) | ||
| 107 | printf "MATRIX server IP (eg. 127.0.0.1): " | ||
| 108 | read -r MATRIX_SERVER_IP | ||
| 109 | require_nonempty MATRIX_SERVER_IP "$MATRIX_SERVER_IP" | ||
| 110 | TURN_DOMAIN=$(cat /etc/turn/domain) | ||
| 111 | TURN_SECRET=$(cat /etc/turn/secret) | ||
| 112 | HASH=$(printf "%s" "$MATRIX_DOMAIN" | cksum | cut -d ' ' -f1) | ||
| 113 | PORT_BASE=$(( (HASH % 1000) + 8000 )) | ||
| 114 | PORT_HTTP=$PORT_BASE | ||
| 115 | PORT_HTTPS=$((PORT_BASE+1)) | ||
| 116 | } | ||
| 117 | |||
| 118 | configure_nginx() { | ||
| 119 | DOMAIN="$1" | ||
| 120 | SERVICE_PORT="$2" | ||
| 121 | |||
| 122 | mkdir -p /etc/nginx/sites-available | ||
| 123 | mkdir -p /etc/nginx/sites-enabled | ||
| 124 | |||
| 125 | cat <<EOF > "/etc/nginx/sites-available/$DOMAIN" | ||
| 126 | server { | ||
| 127 | listen 80; | ||
| 128 | server_name $DOMAIN; | ||
| 129 | |||
| 130 | location /.well-known/matrix/server { | ||
| 131 | default_type application/json; | ||
| 132 | return 200 '{ "m.server": "$DOMAIN:443" }'; | ||
| 133 | } | ||
| 134 | |||
| 135 | location /.well-known/matrix/client { | ||
| 136 | default_type application/json; | ||
| 137 | return 200 '{ | ||
| 138 | "m.homeserver": { "base_url": "https://$DOMAIN" }, | ||
| 139 | "m.identity_server": { "base_url": "https://vector.im" } | ||
| 140 | }'; | ||
| 141 | } | ||
| 142 | |||
| 143 | location / { | ||
| 144 | proxy_pass http://127.0.0.1:$SERVICE_PORT; | ||
| 145 | proxy_set_header Host \$host; | ||
| 146 | proxy_set_header X-Forwarded-For \$remote_addr; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | EOF | ||
| 150 | |||
| 151 | ln -sf "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/$DOMAIN" | ||
| 152 | nginx -t || { echo "[ERROR] Invalid nginx config"; exit 1; } | ||
| 153 | |||
| 154 | |||
| 155 | case $(command -v systemctl >/dev/null && echo y || echo n) in | ||
| 156 | y) systemctl reload nginx ;; | ||
| 157 | n) nginx -s reload ;; | ||
| 158 | esac | ||
| 159 | |||
| 160 | case $(command -v certbot >/dev/null && echo y || echo n) in | ||
| 161 | y) certbot --nginx --non-interactive --agree-tos -m "$EMAIL" -d "$DOMAIN" || { | ||
| 162 | echo "[WARN] Certbot failed for $DOMAIN" | ||
| 163 | } ;; | ||
| 164 | n) echo "[WARN] Certbot not installed, skipping TLS setup" ;; | ||
| 165 | esac | ||
| 166 | } | ||
| 167 | |||
| 168 | |||
| 169 | install_dendrite() { | ||
| 170 | echo "=== Installing Matrix Dendrite ===" | ||
| 171 | common_prompts | ||
| 172 | |||
| 173 | INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" | ||
| 174 | mkdir -p "$INSTALL_DIR/config" | ||
| 175 | cd "$INSTALL_DIR" || exit 1 | ||
| 176 | |||
| 177 | cat <<EOF > .env | ||
| 178 | POSTGRES_HOSTNAME=postgres | ||
| 179 | POSTGRES_VERSION=15-alpine | ||
| 180 | POSTGRES_USER=dendrite | ||
| 181 | POSTGRES_PASSWORD=$DB_PASS | ||
| 182 | POSTGRES_DB=dendrite | ||
| 183 | |||
| 184 | MONOLITH_HOSTNAME=monolith | ||
| 185 | MONOLITH_IMAGE=matrixdotorg/dendrite-monolith:latest | ||
| 186 | MONOLITH_PORT_HTTP=$PORT_HTTP | ||
| 187 | MONOLITH_PORT_HTTPS=$PORT_HTTPS | ||
| 188 | EOF | ||
| 189 | |||
| 190 | cat <<EOF > compose.yml | ||
| 191 | services: | ||
| 192 | postgres: | ||
| 193 | hostname: \${POSTGRES_HOSTNAME:-postgres} | ||
| 194 | container_name: ${MATRIX_DOMAIN}-db | ||
| 195 | image: postgres:\${POSTGRES_VERSION:-15-alpine} | ||
| 196 | restart: always | ||
| 197 | volumes: | ||
| 198 | - ./dendrite_postgres_data:/var/lib/postgresql/data | ||
| 199 | environment: | ||
| 200 | POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-$DB_PASS} | ||
| 201 | POSTGRES_USER: \${POSTGRES_USER:-dendrite} | ||
| 202 | POSTGRES_DB: \${POSTGRES_DB:-dendrite} | ||
| 203 | healthcheck: | ||
| 204 | test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-dendrite}"] | ||
| 205 | interval: 5s | ||
| 206 | timeout: 5s | ||
| 207 | retries: 5 | ||
| 208 | networks: | ||
| 209 | - internal | ||
| 210 | |||
| 211 | monolith: | ||
| 212 | hostname: \${MONOLITH_HOSTNAME:-monolith} | ||
| 213 | container_name: ${MATRIX_DOMAIN} | ||
| 214 | image: \${MONOLITH_IMAGE:-matrixdotorg/dendrite-monolith:latest} | ||
| 215 | ports: | ||
| 216 | - \${MONOLITH_PORT_HTTP:-8008}:8008 | ||
| 217 | - \${MONOLITH_PORT_HTTPS:-8448}:8448 | ||
| 218 | volumes: | ||
| 219 | - ./config:/etc/dendrite | ||
| 220 | - ./dendrite_media:/var/dendrite/media | ||
| 221 | - ./dendrite_jetstream:/var/dendrite/jetstream | ||
| 222 | - ./dendrite_search_index:/var/dendrite/searchindex | ||
| 223 | - ./:/mnt | ||
| 224 | depends_on: | ||
| 225 | postgres: | ||
| 226 | condition: service_healthy | ||
| 227 | networks: | ||
| 228 | - internal | ||
| 229 | restart: unless-stopped | ||
| 230 | |||
| 231 | networks: | ||
| 232 | internal: | ||
| 233 | attachable: true | ||
| 234 | volumes: | ||
| 235 | dendrite_postgres_data: | ||
| 236 | dendrite_media: | ||
| 237 | dendrite_jetstream: | ||
| 238 | dendrite_search_index: | ||
| 239 | EOF | ||
| 240 | |||
| 241 | cat <<EOF > config/dendrite.yaml | ||
| 242 | version: 2 | ||
| 243 | global: | ||
| 244 | server_name: $MATRIX_DOMAIN | ||
| 245 | private_key: /mnt/matrix_key.pem | ||
| 246 | tls_cert: /mnt/server.crt | ||
| 247 | tls_key: /mnt/server.key | ||
| 248 | old_private_keys: [] | ||
| 249 | key_validity_period: 168h0m0s | ||
| 250 | database: | ||
| 251 | connection_string: postgresql://dendrite:$DB_PASS@postgres/dendrite?sslmode=disable | ||
| 252 | max_open_conns: 90 | ||
| 253 | max_idle_conns: 5 | ||
| 254 | conn_max_lifetime: -1 | ||
| 255 | well_known_server_name: "$MATRIX_DOMAIN:443" | ||
| 256 | well_known_client_name: "https://$MATRIX_DOMAIN" | ||
| 257 | cache: | ||
| 258 | max_size_estimated: 1gb | ||
| 259 | max_age: 1h | ||
| 260 | trusted_third_party_id_servers: | ||
| 261 | - matrix.org | ||
| 262 | - vector.im | ||
| 263 | disable_federation: false | ||
| 264 | report_stats: | ||
| 265 | enabled: false | ||
| 266 | client_api: | ||
| 267 | registration_disabled: true | ||
| 268 | registration_shared_secret: "$REG_SECRET" | ||
| 269 | turn: | ||
| 270 | turn_user_lifetime: "5m" | ||
| 271 | turn_uris: | ||
| 272 | - turn:$TURN_DOMAIN?transport=udp | ||
| 273 | - turn:$TURN_DOMAIN?transport=tcp | ||
| 274 | turn_shared_secret: "$TURN_SECRET" | ||
| 275 | federation_api: | ||
| 276 | send_max_retries: 16 | ||
| 277 | disable_tls_validation: false | ||
| 278 | media_api: | ||
| 279 | base_path: ./media_store | ||
| 280 | max_file_size_bytes: 10485760 | ||
| 281 | sync_api: | ||
| 282 | search: | ||
| 283 | enabled: false | ||
| 284 | index_path: "./searchindex" | ||
| 285 | user_api: | ||
| 286 | auto_join_rooms: | ||
| 287 | - "#main:$MATRIX_DOMAIN" | ||
| 288 | logging: | ||
| 289 | - type: std | ||
| 290 | level: info | ||
| 291 | - type: file | ||
| 292 | level: info | ||
| 293 | params: | ||
| 294 | path: ./logs | ||
| 295 | jetstream: | ||
| 296 | addresses: [] | ||
| 297 | disable_tls_validation: false | ||
| 298 | storage_path: ./ | ||
| 299 | topic_prefix: Dendrite | ||
| 300 | metrics: | ||
| 301 | enabled: false | ||
| 302 | basic_auth: | ||
| 303 | username: metrics | ||
| 304 | password: metrics | ||
| 305 | dns_cache: | ||
| 306 | enabled: false | ||
| 307 | cache_size: 256 | ||
| 308 | cache_lifetime: "5m" | ||
| 309 | EOF | ||
| 310 | |||
| 311 | echo "[INFO] Generating Dendrite keys..." | ||
| 312 | |||
| 313 | 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 | ||
| 314 | echo "[OK] Keys generated in $PWD" | ||
| 315 | |||
| 316 | $DOCKER_COMPOSE up -d | ||
| 317 | configure_nginx "$MATRIX_DOMAIN" "$PORT_HTTP" | ||
| 318 | echo "[OK] Dendrite server ($MATRIX_DOMAIN) is running." | ||
| 319 | } | ||
| 320 | |||
| 321 | install_synapse() { | ||
| 322 | echo "=== Installing Matrix Synapse ===" | ||
| 323 | common_prompts | ||
| 324 | |||
| 325 | INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" | ||
| 326 | mkdir -p "$INSTALL_DIR" | ||
| 327 | cd "$INSTALL_DIR" || exit 1 | ||
| 328 | mkdir -p data db | ||
| 329 | chown -R 991:991 data/ | ||
| 330 | chown -R 999:999 db/ | ||
| 331 | DB_USER="synapse" | ||
| 332 | DB_NAME="synapse" | ||
| 333 | DB_PASS=$(openssl rand -hex 16) | ||
| 334 | REG_SECRET=$(openssl rand -hex 32) | ||
| 335 | |||
| 336 | : "${PORT_HTTP:=8008}" | ||
| 337 | : "${PORT_HTTPS:=8448}" | ||
| 338 | |||
| 339 | cat <<EOF > .env | ||
| 340 | # Matrix Synapse environment | ||
| 341 | MATRIX_DOMAIN=$MATRIX_DOMAIN | ||
| 342 | PORT_HTTP=$PORT_HTTP | ||
| 343 | PORT_HTTPS=$PORT_HTTPS | ||
| 344 | |||
| 345 | DB_USER=$DB_USER | ||
| 346 | DB_NAME=$DB_NAME | ||
| 347 | DB_PASS=$DB_PASS | ||
| 348 | |||
| 349 | REG_SECRET=$REG_SECRET | ||
| 350 | TURN_DOMAIN=$TURN_DOMAIN | ||
| 351 | TURN_SECRET=$TURN_SECRET | ||
| 352 | EOF | ||
| 353 | |||
| 354 | |||
| 355 | cat <<EOF > compose.yml | ||
| 356 | services: | ||
| 357 | db: | ||
| 358 | image: postgres:15-alpine | ||
| 359 | restart: always | ||
| 360 | environment: | ||
| 361 | POSTGRES_USER: ${DB_USER} | ||
| 362 | POSTGRES_PASSWORD: ${DB_PASS} | ||
| 363 | POSTGRES_DB: ${DB_NAME} | ||
| 364 | LANG: C | ||
| 365 | LC_ALL: C | ||
| 366 | command: postgres -c lc_collate=C -c lc_ctype=C | ||
| 367 | volumes: | ||
| 368 | - ./db:/var/lib/postgresql/data | ||
| 369 | networks: | ||
| 370 | - synapse-net | ||
| 371 | |||
| 372 | synapse: | ||
| 373 | image: matrixdotorg/synapse:latest | ||
| 374 | restart: always | ||
| 375 | depends_on: | ||
| 376 | - db | ||
| 377 | environment: | ||
| 378 | SYNAPSE_SERVER_NAME: ${MATRIX_DOMAIN} | ||
| 379 | SYNAPSE_REPORT_STATS: "yes" | ||
| 380 | ports: | ||
| 381 | - "${PORT_HTTP}:8008" | ||
| 382 | - "${PORT_HTTPS}:8448" | ||
| 383 | volumes: | ||
| 384 | - ./data:/data | ||
| 385 | networks: | ||
| 386 | - synapse-net | ||
| 387 | |||
| 388 | networks: | ||
| 389 | synapse-net: | ||
| 390 | driver: bridge | ||
| 391 | EOF | ||
| 392 | |||
| 393 | $DOCKER_COMPOSE --env-file .env up -d | ||
| 394 | docker run -it --rm -v ./data:/data -e SYNAPSE_SERVER_NAME=$MATRIX_DOMAIN -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:latest generate | ||
| 395 | |||
| 396 | echo "[INFO] Generating homeserver.yaml for Postgres + TURN..." | ||
| 397 | cat <<EOF > data/homeserver.yaml | ||
| 398 | server_name: $MATRIX_DOMAIN | ||
| 399 | pid_file: /data/homeserver.pid | ||
| 400 | |||
| 401 | listeners: | ||
| 402 | - port: $PORT_HTTP | ||
| 403 | tls: false | ||
| 404 | type: http | ||
| 405 | x_forwarded: true | ||
| 406 | resources: | ||
| 407 | - names: [client, federation] | ||
| 408 | compress: false | ||
| 409 | |||
| 410 | database: | ||
| 411 | name: psycopg2 | ||
| 412 | args: | ||
| 413 | user: $DB_USER | ||
| 414 | password: $DB_PASS | ||
| 415 | host: db | ||
| 416 | database: $DB_NAME | ||
| 417 | cp_min: 5 | ||
| 418 | cp_max: 10 | ||
| 419 | |||
| 420 | enable_registration: false | ||
| 421 | registration_shared_secret: "$REG_SECRET" | ||
| 422 | |||
| 423 | turn_uris: | ||
| 424 | - turn:$TURN_DOMAIN?transport=udp | ||
| 425 | - turn:$TURN_DOMAIN?transport=tcp | ||
| 426 | turn_shared_secret: "$TURN_SECRET" | ||
| 427 | turn_user_lifetime: "5m" | ||
| 428 | |||
| 429 | log_config: "/data/$MATRIX_DOMAIN.log.config" | ||
| 430 | media_store_path: /data/media_store | ||
| 431 | report_stats: false | ||
| 432 | |||
| 433 | macaroon_secret_key: $REG_SECRET | ||
| 434 | form_secret: $REG_SECRET | ||
| 435 | signing_key_path: "/data/$MATRIX_DOMAIN.signing.key" | ||
| 436 | |||
| 437 | trusted_key_servers: | ||
| 438 | - server_name: "matrix.org" | ||
| 439 | suppress_key_server_warning: true | ||
| 440 | EOF | ||
| 441 | |||
| 442 | echo "[INFO] Restarting Synapse with full config..." | ||
| 443 | $DOCKER_COMPOSE --env-file .env restart synapse | ||
| 444 | $DOCKER_COMPOSE --env-file .env restart synapse | ||
| 445 | |||
| 446 | configure_nginx "$MATRIX_DOMAIN" "$PORT_HTTP" | ||
| 447 | echo "[OK] Synapse server ($MATRIX_DOMAIN) is running with Postgres." | ||
| 448 | } | ||
| 449 | |||
| 450 | |||
| 451 | remove_server() { | ||
| 452 | [ "$#" -eq 0 ] && echo "Usage: remove_server <domain1> [domain2 ...]" && return 1 | ||
| 453 | |||
| 454 | for DOMAIN in "$@"; do | ||
| 455 | SERVER_DIR="$BASE_DIR/$DOMAIN" | ||
| 456 | |||
| 457 | case "$DOMAIN" in | ||
| 458 | ""|"/"|".") | ||
| 459 | echo "[ERROR] Refusing to delete dangerous path ($DOMAIN)" | ||
| 460 | continue | ||
| 461 | ;; | ||
| 462 | esac | ||
| 463 | |||
| 464 | [ -d "$SERVER_DIR" ] && \ | ||
| 465 | echo "[INFO] Stopping and removing server: $DOMAIN" && \ | ||
| 466 | (cd "$SERVER_DIR" && $DOCKER_COMPOSE down -v) || \ | ||
| 467 | echo "[WARN] Failed to stop container for $DOMAIN" | ||
| 468 | |||
| 469 | rm -rf "$SERVER_DIR" | ||
| 470 | rm -f "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/$DOMAIN" | ||
| 471 | done | ||
| 472 | |||
| 473 | nginx -t && (systemctl reload nginx 2>/dev/null || nginx -s reload) | ||
| 474 | echo "[OK] Done removing servers: $*" | ||
| 475 | } | ||
| 476 | |||
| 477 | CMD="${1:-}" | ||
| 478 | |||
| 479 | case "$CMD" in | ||
| 480 | -t) install_turn ;; | ||
| 481 | -d) install_dendrite ;; | ||
| 482 | -s) install_synapse ;; | ||
| 483 | -l) list_servers ;; | ||
| 484 | -r) | ||
| 485 | [ -z "$2" ] && { echo "Usage: $0 -r <domain1> [domain2 ...]"; exit 1; } | ||
| 486 | shift | ||
| 487 | remove_server "$@" | ||
| 488 | ;; | ||
| 489 | turn) install_turn ;; | ||
| 490 | dendrite) install_dendrite ;; | ||
| 491 | synapse) install_synapse ;; | ||
| 492 | list) list_servers ;; | ||
| 493 | remove) | ||
| 494 | [ -z "$2" ] && { echo "Usage: $0 remove <domain1> [domain2 ...]"; exit 1; } | ||
| 495 | shift | ||
| 496 | remove_server "$@" | ||
| 497 | ;; | ||
| 498 | *) usage ;; | ||
| 499 | esac | ||
| 500 | |||
