From e08f7273e48b25a910dc9c55a72fe230e401f5b5 Mon Sep 17 00:00:00 2001 From: Filip Wandzio Date: Fri, 3 Oct 2025 00:05:56 +0200 Subject: Implement automatic tuwunel pipeline Fix the path in coturn server configuration file Update manual with actual information Implement cli parameters instead of reading user input Signed-off-by: Filip Wandzio --- wh.1 | 89 +++++++------- wh.sh | 441 +++++++++++++++++++++++++++++++++++++----------------------------- 2 files changed, 295 insertions(+), 235 deletions(-) diff --git a/wh.1 b/wh.1 index 8a6230f..53ffeb6 100644 --- a/wh.1 +++ b/wh.1 @@ -1,67 +1,74 @@ .\" Manpage for whiterabbit -.TH WHITERABBIT 1 "2025-09-25" "1.0.0" "whiterabbit manual" +.TH WHITERABBIT 1 "2025-10-02" "1.1.0" "whiterabbit manual" .SH NAME -whiterabbit \- auto-configure Matrix homeservers (Dendrite or Synapse) +whiterabbit \- auto-configure Matrix homeservers (Dendrite, Synapse, or Tuwunel) .SH SYNOPSIS .B whiterabbit -[\fIturn\fR|\fIdendrite\fR|\fIsynapse\fR|\fIlist\fR|\fIremove \fR| -\fI-t\fR|\fI-d\fR|\fI-s\fR|\fI-l\fR|\fI-r \fR] +[\fIturn \fR| + \fIdendrite \fR| + \fIsynapse \fR| + \fItuwunel \fR| + \fIlist\fR| + \fIremove ...\fR] .SH DESCRIPTION -whiterabbit sets up Matrix homeservers with a global Coturn server and Nginx/Let's Encrypt support. +whiterabbit sets up Matrix homeservers (Dendrite, Synapse, or Tuwunel) with a global Coturn server and Nginx/Let's Encrypt proxying. -Main commands and their short flag equivalents: +Main subcommands: .TP -.B turn, -t +.B turn Install or configure the global Coturn server. .TP -.B dendrite, -d -Add a new Matrix Dendrite server. +.B dendrite +Add a new Matrix Dendrite server with automatic TLS and Nginx reverse proxy. .TP -.B synapse, -s -Add a new Matrix Synapse server. +.B synapse +Add a new Matrix Synapse server with automatic TLS and Nginx reverse proxy. .TP -.B list, -l -List all configured Matrix servers. +.B tuwunel +Add a Tuwunel homeserver with automatic TLS and Nginx reverse proxy. .TP -.B remove , -r -Remove a Matrix server by domain. +.B list +List all configured Matrix servers and their container status. +.TP +.B remove [domain2 ...] +Remove one or more Matrix servers by domain name. .SH EXAMPLES .TP -whiterabbit turn -Set up the global Coturn server. -.TP -whiterabbit -t -Same as above using flag. -.TP -whiterabbit dendrite -Add a Dendrite server with TLS and Nginx. +whiterabbit turn turn.example.com 192.168.1.10 eth0 myturnsecret +Set up the global Coturn server bound to eth0 on IP 192.168.1.10. .TP -whiterabbit -d -Same as above using flag. +whiterabbit dendrite matrix.example.com admin@example.com 192.168.1.20 +Add a Dendrite server. .TP -whiterabbit synapse -Add a Synapse server with TLS and Nginx. +whiterabbit synapse matrix.example.com admin@example.com 192.168.1.21 +Add a Synapse server. .TP -whiterabbit -s -Same as above using flag. +whiterabbit tuwunel tuw.example.com admin@example.com 192.168.1.22 +Add a Tuwunel server. .TP whiterabbit list -Show all servers. -.TP -whiterabbit -l -Same as above using flag. -.TP -whiterabbit remove matrix.example.com -Remove a server. +Show all installed servers and their status. .TP -whiterabbit -r matrix.example.com -Same as above using flag. +whiterabbit remove matrix.example.com another.example.org +Remove one or more servers. .SH NOTES -All Matrix servers share the same Coturn instance. +All Matrix homeservers share the same Coturn instance. TLS certificates are issued automatically via Let's Encrypt. Requires docker, docker-compose, nginx, certbot, and coturn. -.SH AUTHOR -Filip Wandzio +.SH COMMAND DISPATCH +The script internally dispatches commands as follows: +.nf +CMD="${1:-}" +case "$CMD" in + turn) shift; install_turn "$@" ;; + dendrite) shift; install_dendrite "$@" ;; + # synapse) shift; install_synapse "$@" ;; + tuwunel) shift; install_tuwunel "$@" ;; + list) list_servers ;; + remove) shift; remove_server "$@" ;; + *) usage ;; +esac +.fi diff --git a/wh.sh b/wh.sh index db1c12d..35de2b6 100755 --- a/wh.sh +++ b/wh.sh @@ -1,5 +1,11 @@ #!/bin/sh set -eu +ESCALATE=$(command -v sudo || command -v doas) || { + echo "[ERROR] Neither sudo nor doas found. Cannot run as root." >&2 + exit 1 +} + +[ "$(id -u)" -eq 0 ] || exec $ESCALATE "$0" "$@" BASE_DIR="/opt/matrix" @@ -10,15 +16,15 @@ DOCKER_COMPOSE=$(command -v docker-compose || command -v docker compose) || { usage() { cat <<'EOF' -whiterabbit: auto-configure Matrix homeservers (Dendrite or Synapse) +whiterabbit: auto-configure [matrix] Homeservers 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 +Usage: + wh turn + wh dendrite + wh tuwunel + wh list + wh remove [domain2 ...] EOF exit 1 } @@ -38,36 +44,35 @@ list_servers() { [ -d "$dir" ] || continue name=$(basename "$dir") - status=$(docker ps --filter "name=^${name}$" --format "{{.Status}}") - ports=$(docker ps --filter "name=^${name}$" --format "{{.Ports}}" | sed 's/, /\n /g') + container_info=$(docker ps --format "{{.Names}} {{.Status}} {{.Ports}}" | grep "^${name}") || container_info="" - [ -n "$status" ] && { + [ -n "$container_info" ] && { echo "- $name [RUNNING] on:" - echo " $ports" + echo "$container_info" | while read line; do + cname=$(echo "$line" | awk '{print $1}') + cstatus=$(echo "$line" | awk '{print $2}') + cports=$(echo "$line" | awk '{$1=$2=""; print $0}' | sed 's/^ //') + echo " $cname : $cstatus, ports: $cports" + done } || echo "- $name [STOPPED]" done } install_turn() { - echo "=== Installing global Coturn ===" - printf "TURN domain (eg. turn.example.com): " - read -r TURN_DOMAIN - require_nonempty TURN_DOMAIN "$TURN_DOMAIN" + TURN_DOMAIN="$1" + TURN_SERVER_IP="$2" + TURN_LISTENING_DEVICE="$3" + TURN_SECRET="$4" - printf "TURN IP (eg. 127.0.0.1): " - read -r TURN_SERVER_IP + require_nonempty TURN_DOMAIN "$TURN_DOMAIN" require_nonempty TURN_SERVER_IP "$TURN_SERVER_IP" - - printf "Listening device (eg. eth0): " - read -r TURN_LISTENING_DEVICE require_nonempty TURN_LISTENING_DEVICE "$TURN_LISTENING_DEVICE" - - printf "TURN shared secret: " - read -r TURN_SECRET require_nonempty TURN_SECRET "$TURN_SECRET" + echo "=== Installing global Coturn ($TURN_DOMAIN) ===" + mkdir -p /etc/turn - cat < /etc/turn/turnserver.conf + cat < /etc/turnserver.conf listening-device=$TURN_LISTENING_DEVICE listening-port=3478 tls-listening-port=5349 @@ -95,20 +100,20 @@ EOF } common_prompts() { - printf "Matrix domain (eg. matrix.example.com): " - read -r MATRIX_DOMAIN - require_nonempty MATRIX_DOMAIN "$MATRIX_DOMAIN" + MATRIX_DOMAIN="$1" + EMAIL="$2" + MATRIX_SERVER_IP="$3" - printf "Let's Encrypt certificate email: " - read -r EMAIL + require_nonempty MATRIX_DOMAIN "$MATRIX_DOMAIN" require_nonempty EMAIL "$EMAIL" + require_nonempty MATRIX_SERVER_IP "$MATRIX_SERVER_IP" + DB_PASS=$(tr -dc 'A-Za-z0-9' /dev/null && echo y || echo n) in y) systemctl reload nginx ;; n) nginx -s reload ;; esac case $(command -v certbot >/dev/null && echo y || echo n) in - y) certbot --nginx --non-interactive --agree-tos -m "$EMAIL" -d "$DOMAIN" || { - echo "[WARN] Certbot failed for $DOMAIN" - } ;; - n) echo "[WARN] Certbot not installed, skipping TLS setup" ;; + y) certbot --nginx --non-interactive --agree-tos -m "$EMAIL" -d "$DOMAIN" || { + echo "[WARN] Certbot failed for $DOMAIN" + } ;; + n) echo "[WARN] Certbot not installed, skipping TLS setup" ;; esac } +install_tuwunel() { + echo "=== Installing Tuwunel ===" + common_prompts "$@" + + INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" + mkdir -p "$INSTALL_DIR/config" + cd "$INSTALL_DIR" || exit 1 + cat < .env +TUWUNEL_SERVER_NAME=$MATRIX_DOMAIN +TUWUNEL_DATABASE_PATH=/var/lib/tuwunel +TUWUNEL_PORT=6167 +TUWUNEL_MAX_REQUEST_SIZE=20000000 +TUWUNEL_ALLOW_REGISTRATION=false +TUWUNEL_REGISTRATION_TOKEN=$REG_SECRET +TUWUNEL_ALLOW_ENCRYPTION=true +TUWUNEL_ALLOW_FEDERATION=true +TUWUNEL_TRUSTED_SERVERS='["matrix.org", "matrix.philw.dev"]' + +TUWUNEL_ADDRESS=0.0.0.0 +TUWUNEL_TURN_SECRET="$TURN_SECRET" +TUWUNEL_TURN_URIS=["turn:$TURN_DOMAIN?transport=udp", "turn:$TURN_DOMAIN?transport=tcp"] +TUWUNEL_NEW_USER_DISPLAYNAME_SUFFIX="" +EOF + +cat < compose.yml +services: + homeserver: + image: jevolk/tuwunel:latest + container_name: tuw.philw.dev-tuwunel + restart: unless-stopped + ports: + - "$PORT_HTTP:6167" + volumes: + - db:/var/lib/tuwunel + env_file: + - .env +volumes: + db: +EOF + + $DOCKER_COMPOSE up -d + configure_nginx "$MATRIX_DOMAIN" "$PORT_HTTP" + echo "[OK] Tuwunel server ($MATRIX_DOMAIN) is running." +} install_dendrite() { - echo "=== Installing Matrix Dendrite ===" - common_prompts + echo "=== Installing Dendrite ===" + common_prompts "$@" INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" mkdir -p "$INSTALL_DIR/config" @@ -309,192 +357,197 @@ dns_cache: EOF echo "[INFO] Generating Dendrite 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 - echo "[OK] Keys generated in $PWD" + 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 + echo "[OK] Keys generated in $PWD" $DOCKER_COMPOSE up -d configure_nginx "$MATRIX_DOMAIN" "$PORT_HTTP" 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 - mkdir -p data db - chown -R 991:991 data/ - chown -R 999:999 db/ - DB_USER="synapse" - DB_NAME="synapse" - DB_PASS=$(openssl rand -hex 16) - REG_SECRET=$(openssl rand -hex 32) - - : "${PORT_HTTP:=8008}" - : "${PORT_HTTPS:=8448}" - - cat < .env -# Matrix Synapse environment -MATRIX_DOMAIN=$MATRIX_DOMAIN -PORT_HTTP=$PORT_HTTP -PORT_HTTPS=$PORT_HTTPS - -DB_USER=$DB_USER -DB_NAME=$DB_NAME -DB_PASS=$DB_PASS - -REG_SECRET=$REG_SECRET -TURN_DOMAIN=$TURN_DOMAIN -TURN_SECRET=$TURN_SECRET -EOF - - -cat < compose.yml -services: - db: - image: postgres:15-alpine - restart: always - environment: - POSTGRES_USER: ${DB_USER} - POSTGRES_PASSWORD: ${DB_PASS} - POSTGRES_DB: ${DB_NAME} - LANG: C - LC_ALL: C - command: postgres -c lc_collate=C -c lc_ctype=C - volumes: - - ./db:/var/lib/postgresql/data - networks: - - synapse-net - - synapse: - image: matrixdotorg/synapse:latest - restart: always - depends_on: - - db - environment: - SYNAPSE_SERVER_NAME: ${MATRIX_DOMAIN} - SYNAPSE_REPORT_STATS: "yes" - ports: - - "${PORT_HTTP}:8008" - - "${PORT_HTTPS}:8448" - volumes: - - ./data:/data - networks: - - synapse-net - -networks: - synapse-net: - driver: bridge -EOF - -$DOCKER_COMPOSE --env-file .env up -d -docker run -it --rm -v ./data:/data -e SYNAPSE_SERVER_NAME=$MATRIX_DOMAIN -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:latest generate - -echo "[INFO] Generating homeserver.yaml for Postgres + TURN..." -cat < data/homeserver.yaml -server_name: $MATRIX_DOMAIN -pid_file: /data/homeserver.pid - -listeners: - - port: $PORT_HTTP - tls: false - type: http - x_forwarded: true - resources: - - names: [client, federation] - compress: false - -database: - name: psycopg2 - args: - user: $DB_USER - password: $DB_PASS - host: db - database: $DB_NAME - cp_min: 5 - cp_max: 10 - -enable_registration: false -registration_shared_secret: "$REG_SECRET" - -turn_uris: - - turn:$TURN_DOMAIN?transport=udp - - turn:$TURN_DOMAIN?transport=tcp -turn_shared_secret: "$TURN_SECRET" -turn_user_lifetime: "5m" - -log_config: "/data/$MATRIX_DOMAIN.log.config" -media_store_path: /data/media_store -report_stats: false - -macaroon_secret_key: $REG_SECRET -form_secret: $REG_SECRET -signing_key_path: "/data/$MATRIX_DOMAIN.signing.key" - -trusted_key_servers: - - server_name: "matrix.org" -suppress_key_server_warning: true -EOF - - echo "[INFO] Restarting Synapse with full config..." - $DOCKER_COMPOSE --env-file .env restart synapse - $DOCKER_COMPOSE --env-file .env restart synapse - - configure_nginx "$MATRIX_DOMAIN" "$PORT_HTTP" - echo "[OK] Synapse server ($MATRIX_DOMAIN) is running with Postgres." -} - +# @TODO STILL BROKEN +# install_synapse() { +# echo "=== Installing Synapse ===" +# MATRIX_DOMAIN="$1" +# EMAIL="$2" +# MATRIX_SERVER_IP="$3" +# +# require_nonempty MATRIX_DOMAIN "$MATRIX_DOMAIN" +# require_nonempty EMAIL "$EMAIL" +# require_nonempty MATRIX_SERVER_IP "$MATRIX_SERVER_IP" +# +# INSTALL_DIR="$BASE_DIR/$MATRIX_DOMAIN" +# mkdir -p "$INSTALL_DIR" +# cd "$INSTALL_DIR" || exit 1 +# mkdir -p data db +# +# DB_USER="synapse" +# DB_NAME="synapse" +# DB_PASS=$(openssl rand -hex 16) +# REG_SECRET=$(openssl rand -hex 32) +# if command -v docker-compose >/dev/null 2>&1; then +# DOCKER_COMPOSE_CMD="docker-compose" +# elif command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then +# DOCKER_COMPOSE_CMD="docker compose" +# else +# echo "[ERROR] Neither docker-compose nor docker compose available" >&2 +# exit 1 +# fi +# +# PORT_HTTP=${PORT_HTTP:-8008} +# PORT_HTTPS=${PORT_HTTPS:-8448} +# TURN_DOMAIN=$(cat /etc/turn/domain 2>/dev/null || true) +# TURN_SECRET=$(cat /etc/turn/secret 2>/dev/null || true) +# cat > .env < compose.yml < data/homeserver.yaml < [domain2 ...]" && return 1 - for DOMAIN in "$@"; do SERVER_DIR="$BASE_DIR/$DOMAIN" - case "$DOMAIN" in ""|"/"|".") echo "[ERROR] Refusing to delete dangerous path ($DOMAIN)" continue ;; esac - [ -d "$SERVER_DIR" ] && \ echo "[INFO] Stopping and removing server: $DOMAIN" && \ (cd "$SERVER_DIR" && $DOCKER_COMPOSE down -v) || \ echo "[WARN] Failed to stop container for $DOMAIN" - rm -rf "$SERVER_DIR" rm -f "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/$DOMAIN" done - nginx -t && (systemctl reload nginx 2>/dev/null || nginx -s reload) echo "[OK] Done removing servers: $*" } CMD="${1:-}" - case "$CMD" in - -t) install_turn ;; - -d) install_dendrite ;; - -s) install_synapse ;; - -l) list_servers ;; - -r) - [ -z "$2" ] && { echo "Usage: $0 -r [domain2 ...]"; exit 1; } - shift - remove_server "$@" - ;; - turn) install_turn ;; - dendrite) install_dendrite ;; - synapse) install_synapse ;; + turn) shift; install_turn "$@" ;; + dendrite) shift; install_dendrite "$@" ;; + # synapse) shift; install_synapse "$@" ;; + tuwunel) shift; install_tuwunel "$@" ;; list) list_servers ;; - remove) - [ -z "$2" ] && { echo "Usage: $0 remove [domain2 ...]"; exit 1; } - shift - remove_server "$@" - ;; + remove) shift; remove_server "$@" ;; *) usage ;; esac -- cgit v1.2.3