LINUX.ORG.RU

Ставим Jitsi Meet на VPS одной командой

 ,


0

1

Один bash-скрипт для Debian 12, Debian 13 и Ubuntu 24.04 LTS, с Jitsi Meet, Let’s Encrypt, автообновлением сертификатов, coTURN, учётом сценариев с публичным IP и с сервером за NAT, а также с включением secure-domain авторизации для создателя комнаты. У Jitsi это не отдельная “админка”, а вход по логину/паролю при создании комнаты; сам Jitsi рекомендует для новых инсталляций JWT, потому что secure-domain считается устаревшим, но он всё ещё работает для сценария “только авторизованный пользователь может создать конференцию”.

Jitsi официально поддерживает Debian 11+ и Ubuntu 22.04+, так что Debian 12, Debian 13 и Ubuntu 24.04 попадают в поддерживаемый диапазон. Debian 13 — это trixie, текущий stable Debian; Ubuntu 24.04 LTS имеет стандартную поддержку до мая 2029 года.

Для сервера Jitsi официальные порты — 80/TCP, 443/TCP, 10000/UDP, а для TURN дополнительно 3478/UDP и 5349/TCP. В документации Jitsi TURN по умолчанию использует 3478 для UDP и 5349 для TLS/TCP.

Что именно делает этот скрипт

Он ставит Jitsi из официального репозитория, который Jitsi рекомендует для Debian/Ubuntu, а не пытается собирать компоненты вручную. Официальный quickstart именно так и предлагает устанавливать Jitsi на Debian-based системах.

Чтобы не ловить проблему с поломанным nginx-конфигом при первичной установке, скрипт сначала создаёт временный self-signed сертификат и сообщает пакету Jitsi “использовать свой сертификат”, а уже потом заменяет его на Let’s Encrypt. Это обход типичной проблемы, когда listen 443 ssl уже есть, а ssl_certificate ещё нет.

Для сценария за NAT скрипт настраивает JVB через NAT_HARVESTER_LOCAL_ADDRESS и NAT_HARVESTER_PUBLIC_ADDRESS, а coTURN получает external-ip. Для сценария без NAT эти параметры не форсируются. Документация Jitsi отдельно подчёркивает, что при работе за NAT или в LAN нужно корректно указывать рекламируемый публичный IP для media traffic, иначе конференции могут ломаться, особенно когда участников становится больше двух.

TURN в Jitsi прежде всего нужен тогда, когда peer-to-peer соединение между двумя клиентами не получается напрямую. Документация Jitsi также отмечает, что для корпоративных сетей важен fallback через TCP/TLS, обычно на 5349, а при необходимости можно отдельно строить схему и на 443. В этом скрипте я оставил более простой и безопасный вариант: Jitsi на 443, TURN на 5349.

Что важно понимать

Secure-domain в Jitsi — это не полноценная “админка” с отдельной панелью. Это логин/пароль для пользователя, который создаёт комнату. После создания комнаты остальные участники могут заходить как гости. Именно так этот режим и описан в официальной документации.

Практические параметры сервера

Минимум для небольшого сервера “для себя / маленькой команды”:

  • 2 vCPU
  • 4 GB RAM
  • 20–40 GB SSD
  • 1 публичный домен
  • нормальный канал в интернет

Рекомендовано для одиночного узла:

  • 4 vCPU
  • 8 GB RAM
  • SSD
  • хороший аплинк

Официальная документация Jitsi для machine sizing прямо пишет, что сервер Jitsi Meet обычно нормально чувствует себя на 4 CPU / 8 GB RAM, а для videobridge 4–8 CPU и 8 GB RAM — хорошая конфигурация.

Какие порты открыть на сервере

Если сервер с публичным IP:

  • 80/TCP
  • 443/TCP
  • 10000/UDP
  • 3478/UDP
  • 5349/TCP
  • 22/TCP для SSH

Если сервер за NAT:

  • на роутере пробросить на внутренний IP сервера:

    • 80/TCP
    • 443/TCP
    • 10000/UDP
    • 3478/UDP
    • 5349/TCP

В моём скрипте coTURN использует диапазон relay-портов 49152-49252, поэтому этот диапазон тоже нужно открыть на самом сервере в фаерволе. Это не требование Jitsi-доков как таковых, а следствие конкретной конфигурации coTURN в скрипте.

Какие порты нужны клиенту

Обычно достаточно исходящих:

  • 443/TCP
  • 10000/UDP

Для более жёстких сетей желательно ещё:

  • 3478/UDP
  • 5349/TCP

Официальные Jitsi-доки отдельно указывают 10000/UDP как основной медиа-порт, а 3478/UDP и 5349/TCP — для STUN/TURN и fallback-сценариев. ([jitsi.github.io][2])

Параметры скрипта

Обязательные:

  • -d, --domain — FQDN Jitsi, например meet.example.com
  • -e, --email — email для Let’s Encrypt

Необязательные:

  • --admin-user — логин локального администратора Jitsi, по умолчанию admin
  • --admin-pass — пароль администратора; если не задан, будет сгенерирован
  • --public-ip — публичный IP сервера, если автодетект не подходит
  • --local-ip — локальный IP сервера, если нужно переопределить
  • --turn-domain — домен для TURN; по умолчанию тот же, что и Jitsi-домен
  • --turn-secret — shared secret для TURN; если не задан, будет сгенерирован
  • --no-ufw — не трогать UFW

Пример запуска

Сервер с публичным IP:

sudo bash install-jitsi.sh \
  -d meet.example.com \
  -e admin@example.com

Сервер за NAT:

sudo bash install-jitsi.sh \
  -d meet.example.com \
  -e admin@example.com \
  --public-ip 203.0.113.10 \
  --local-ip 192.168.1.20

Скрипт

#!/usr/bin/env bash
set -Eeuo pipefail

# Jitsi Meet + Let's Encrypt + coTURN + secure-domain auth
# Supported:
#   - Debian 12 (bookworm)
#   - Debian 13 (trixie)
#   - Ubuntu 24.04 LTS (noble)
#
# Usage examples:
#   sudo bash install-jitsi.sh -d meet.example.com -e admin@example.com
#   sudo bash install-jitsi.sh -d meet.example.com -e admin@example.com --public-ip 203.0.113.10 --local-ip 192.168.1.20

DOMAIN=""
EMAIL=""
ADMIN_USER="admin"
ADMIN_PASS=""
TURN_DOMAIN=""
TURN_SECRET=""
PUBLIC_IP=""
LOCAL_IP=""
ENABLE_UFW=1

log()  { printf "\033[1;32m[*]\033[0m %s\n" "$*"; }
warn() { printf "\033[1;33m[!]\033[0m %s\n" "$*"; }
die()  { printf "\033[1;31m[x]\033[0m %s\n" "$*"; exit 1; }

usage() {
  cat <<'EOF'
Usage:
  install-jitsi.sh -d <domain> -e <email> [options]

Required:
  -d, --domain         Jitsi domain, e.g. meet.example.com
  -e, --email          Email for Let's Encrypt

Optional:
      --admin-user     Jitsi secure-domain admin username (default: admin)
      --admin-pass     Jitsi secure-domain admin password (default: autogenerated)
      --public-ip      Public IP of this server/NAT
      --local-ip       Local IP of this server
      --turn-domain    TURN domain (default: same as --domain)
      --turn-secret    TURN shared secret (default: autogenerated)
      --no-ufw         Do not modify UFW
  -h, --help           Show this help

Examples:
  sudo bash install-jitsi.sh -d meet.example.com -e admin@example.com
  sudo bash install-jitsi.sh -d meet.example.com -e admin@example.com --public-ip 203.0.113.10 --local-ip 192.168.1.20
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -d|--domain) DOMAIN="${2:-}"; shift 2 ;;
    -e|--email) EMAIL="${2:-}"; shift 2 ;;
    --admin-user) ADMIN_USER="${2:-}"; shift 2 ;;
    --admin-pass) ADMIN_PASS="${2:-}"; shift 2 ;;
    --public-ip) PUBLIC_IP="${2:-}"; shift 2 ;;
    --local-ip) LOCAL_IP="${2:-}"; shift 2 ;;
    --turn-domain) TURN_DOMAIN="${2:-}"; shift 2 ;;
    --turn-secret) TURN_SECRET="${2:-}"; shift 2 ;;
    --no-ufw) ENABLE_UFW=0; shift ;;
    -h|--help) usage; exit 0 ;;
    *) die "Unknown argument: $1" ;;
  esac
done

[[ -n "$DOMAIN" ]] || die "Domain is required."
[[ -n "$EMAIL"  ]] || die "Email is required."
[[ $EUID -eq 0 ]] || die "Run as root."

if [[ -z "$TURN_DOMAIN" ]]; then
  TURN_DOMAIN="$DOMAIN"
fi

randstr() {
  tr -dc 'A-Za-z0-9' </dev/urandom | head -c "${1:-32}"
}

if [[ -z "$ADMIN_PASS" ]]; then
  ADMIN_PASS="$(randstr 20)"
fi

if [[ -z "$TURN_SECRET" ]]; then
  TURN_SECRET="$(randstr 48)"
fi

backup_file() {
  local f="$1"
  if [[ -f "$f" && ! -f "${f}.bak.pre-jitsi-installer" ]]; then
    cp -a "$f" "${f}.bak.pre-jitsi-installer"
  fi
}

is_private_ipv4() {
  local ip="${1:-}"
  [[ "$ip" =~ ^10\. ]] && return 0
  [[ "$ip" =~ ^192\.168\. ]] && return 0
  [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] && return 0
  [[ "$ip" =~ ^127\. ]] && return 0
  return 1
}

detect_local_ip() {
  ip -4 route get 1.1.1.1 2>/dev/null | awk '{
    for (i=1; i<=NF; i++) if ($i=="src") { print $(i+1); exit }
  }'
}

detect_public_ip() {
  curl -4fsS https://api.ipify.org 2>/dev/null \
    || curl -4fsS https://ifconfig.me 2>/dev/null \
    || true
}

replace_or_append_line() {
  local file="$1"
  local key="$2"
  local value="$3"
  mkdir -p "$(dirname "$file")"
  touch "$file"
  if grep -qE "^[#[:space:]]*${key}=" "$file"; then
    sed -i "s|^[#[:space:]]*${key}=.*|${key}=${value}|g" "$file"
  else
    echo "${key}=${value}" >> "$file"
  fi
}

ensure_line_present() {
  local file="$1"
  local line="$2"
  grep -Fqx "$line" "$file" 2>/dev/null || echo "$line" >> "$file"
}

insert_after_pattern_once() {
  local file="$1"
  local pattern="$2"
  local insert="$3"
  local marker="$4"

  grep -Fq "$marker" "$file" && return 0

  awk -v pat="$pattern" -v ins="$insert" '
    $0 ~ pat && !done {
      print $0
      print ins
      done=1
      next
    }
    { print $0 }
    END {
      if (!done) {
        print ""
        print ins
      }
    }
  ' "$file" > "${file}.tmp"
  mv "${file}.tmp" "$file"
}

set_locale() {
  if ! locale -a 2>/dev/null | grep -q '^en_US\.utf8$\|^en_US\.UTF-8$'; then
    log "Installing locales"
    apt-get update -y
    apt-get install -y locales
    sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
    sed -i 's/^# *ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/' /etc/locale.gen
    locale-gen
    update-locale LANG=en_US.UTF-8 LC_CTYPE=ru_RU.UTF-8 LANGUAGE="en_US:en"
  fi
  export LANG=en_US.UTF-8
  export LC_CTYPE=ru_RU.UTF-8
  export LANGUAGE=en_US:en
}

detect_distro() {
  . /etc/os-release
  DISTRO_ID="${ID}"
  DISTRO_VERSION="${VERSION_ID}"
  DISTRO_CODENAME="${VERSION_CODENAME:-}"

  case "${DISTRO_ID}:${DISTRO_VERSION}" in
    debian:12|debian:13|ubuntu:24.04) ;;
    *)
      die "Unsupported OS: ${PRETTY_NAME}. Supported: Debian 12, Debian 13, Ubuntu 24.04 LTS"
      ;;
  esac

  case "${DISTRO_ID}:${DISTRO_VERSION}" in
    debian:12) DISTRO_NAME="Debian 12 (bookworm)" ;;
    debian:13) DISTRO_NAME="Debian 13 (trixie)" ;;
    ubuntu:24.04) DISTRO_NAME="Ubuntu 24.04 LTS (noble)" ;;
  esac
}

prepare_hostname() {
  log "Setting hostname to ${DOMAIN}"
  hostnamectl set-hostname "$DOMAIN"
  if ! grep -qE "^\s*127\.0\.1\.1\s+${DOMAIN}\b" /etc/hosts; then
    echo "127.0.1.1 ${DOMAIN} ${DOMAIN%%.*}" >> /etc/hosts
  fi
}

install_base_packages() {
  log "Installing prerequisites"
  apt-get update -y
  DEBIAN_FRONTEND=noninteractive apt-get install -y \
    curl gnupg2 ca-certificates debconf-utils lsb-release \
    apt-transport-https nginx-full openssl certbot python3-certbot-nginx \
    coturn prosody-modules jq
}

add_jitsi_repo() {
  log "Adding Jitsi repository"
  install -d -m 0755 /usr/share/keyrings
  if [[ ! -f /usr/share/keyrings/jitsi-keyring.gpg ]]; then
    curl -fsSL https://download.jitsi.org/jitsi-key.gpg.key | gpg --dearmor -o /usr/share/keyrings/jitsi-keyring.gpg
  fi
  cat > /etc/apt/sources.list.d/jitsi-stable.list <<EOF
deb [signed-by=/usr/share/keyrings/jitsi-keyring.gpg] https://download.jitsi.org stable/
EOF
  apt-get update -y
}

create_temp_cert() {
  log "Creating temporary certificate for preseed"
  install -d -m 0755 /etc/jitsi/meet
  if [[ ! -s "/etc/jitsi/meet/${DOMAIN}.crt" || ! -s "/etc/jitsi/meet/${DOMAIN}.key" ]]; then
    openssl req -x509 -nodes -newkey rsa:2048 -days 5 \
      -keyout "/etc/jitsi/meet/${DOMAIN}.key" \
      -out "/etc/jitsi/meet/${DOMAIN}.crt" \
      -subj "/CN=${DOMAIN}" >/dev/null 2>&1
    chmod 600 "/etc/jitsi/meet/${DOMAIN}.key"
    chmod 644 "/etc/jitsi/meet/${DOMAIN}.crt"
  fi
}

preseed_jitsi() {
  log "Preseeding package configuration"
  cat <<EOF | debconf-set-selections
jitsi-videobridge2 jitsi-videobridge/jvb-hostname string ${DOMAIN}
jitsi-meet-web-config jitsi-meet/cert-choice select I want to use my own certificate
jitsi-meet-web-config jitsi-meet/cert-path-crt string /etc/jitsi/meet/${DOMAIN}.crt
jitsi-meet-web-config jitsi-meet/cert-path-key string /etc/jitsi/meet/${DOMAIN}.key
jitsi-meet-web-config jitsi-meet/jaas-choice boolean false
EOF
}

install_jitsi() {
  log "Installing Jitsi Meet"
  DEBIAN_FRONTEND=noninteractive apt-get install -y jitsi-meet
}

remove_duplicate_wasm() {
  if compgen -G "/etc/nginx/sites-enabled/*.conf" >/dev/null; then
    sed -i '/application\/wasm[[:space:]]\+wasm;$/d' /etc/nginx/sites-enabled/*.conf || true
  fi
}

ensure_nginx_ok() {
  remove_duplicate_wasm
  nginx -t
  systemctl enable --now nginx
  systemctl reload nginx || true
}

install_le_material() {
  local dom="$1"
  [[ -s "/etc/letsencrypt/live/${dom}/fullchain.pem" ]] || return 1
  [[ -s "/etc/letsencrypt/live/${dom}/privkey.pem" ]] || return 1

  install -m 0644 "/etc/letsencrypt/live/${dom}/fullchain.pem" "/etc/jitsi/meet/${dom}.crt"
  install -m 0600 "/etc/letsencrypt/live/${dom}/privkey.pem" "/etc/jitsi/meet/${dom}.key"
}

obtain_certificate() {
  log "Trying Let's Encrypt via nginx plugin"
  if certbot --nginx -d "$DOMAIN" -m "$EMAIL" --agree-tos -n --redirect; then
    install_le_material "$DOMAIN"
    return 0
  fi

  warn "certbot --nginx failed, falling back to standalone"
  systemctl stop nginx || true
  if certbot certonly --standalone -d "$DOMAIN" -m "$EMAIL" --agree-tos -n; then
    install_le_material "$DOMAIN"
    systemctl start nginx
    nginx -t && systemctl reload nginx
    return 0
  fi

  die "Could not obtain Let's Encrypt certificate."
}

configure_nat_detection() {
  if [[ -z "$LOCAL_IP" ]]; then
    LOCAL_IP="$(detect_local_ip)"
  fi
  [[ -n "$LOCAL_IP" ]] || die "Could not detect local IP. Use --local-ip."

  if [[ -z "$PUBLIC_IP" ]]; then
    if is_private_ipv4 "$LOCAL_IP"; then
      PUBLIC_IP="$(detect_public_ip)"
    else
      PUBLIC_IP="$LOCAL_IP"
    fi
  fi

  if [[ -z "$PUBLIC_IP" ]]; then
    warn "Could not detect public IP automatically. NAT/media auto-tuning will be partial."
  fi

  log "Local IP: ${LOCAL_IP}"
  log "Public IP: ${PUBLIC_IP:-unknown}"
}

configure_jvb_nat() {
  local jvb_props="/etc/jitsi/videobridge/sip-communicator.properties"
  touch "$jvb_props"
  backup_file "$jvb_props"

  replace_or_append_line "$jvb_props" "org.ice4j.ice.harvest.DISABLE_AWS_HARVESTER" "true"

  if [[ -n "$PUBLIC_IP" && "$PUBLIC_IP" != "$LOCAL_IP" ]]; then
    log "Configuring JVB for NAT"
    replace_or_append_line "$jvb_props" "org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS" "$LOCAL_IP"
    replace_or_append_line "$jvb_props" "org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS" "$PUBLIC_IP"
  else
    sed -i '/^org\.ice4j\.ice\.harvest\.NAT_HARVESTER_LOCAL_ADDRESS=/d' "$jvb_props" || true
    sed -i '/^org\.ice4j\.ice\.harvest\.NAT_HARVESTER_PUBLIC_ADDRESS=/d' "$jvb_props" || true
  fi
}

configure_coturn() {
  log "Configuring coTURN"

  backup_file /etc/default/coturn
  if [[ -f /etc/default/coturn ]]; then
    sed -i 's/^#\?TURNSERVER_ENABLED=.*/TURNSERVER_ENABLED=1/' /etc/default/coturn || true
    grep -q '^TURNSERVER_ENABLED=1$' /etc/default/coturn || echo 'TURNSERVER_ENABLED=1' >> /etc/default/coturn
  else
    echo 'TURNSERVER_ENABLED=1' > /etc/default/coturn
  fi

  local extip_line=""
  if [[ -n "$PUBLIC_IP" && "$PUBLIC_IP" != "$LOCAL_IP" ]]; then
    extip_line="external-ip=${PUBLIC_IP}/${LOCAL_IP}"
  elif [[ -n "$PUBLIC_IP" ]]; then
    extip_line="external-ip=${PUBLIC_IP}"
  fi

  backup_file /etc/turnserver.conf
  cat > /etc/turnserver.conf <<EOF
use-auth-secret
static-auth-secret=${TURN_SECRET}
realm=${DOMAIN}
server-name=${TURN_DOMAIN}

fingerprint
listening-port=3478
tls-listening-port=5349

listening-ip=${LOCAL_IP}
relay-ip=${LOCAL_IP}
${extip_line}

cert=/etc/jitsi/meet/${DOMAIN}.crt
pkey=/etc/jitsi/meet/${DOMAIN}.key

no-cli
no-loopback-peers
no-multicast-peers

min-port=49152
max-port=49252

# Practical defaults
proc-user=turnserver
proc-group=turnserver
EOF

  systemctl enable coturn
  systemctl restart coturn
}

configure_prosody_turn() {
  local pfile="/etc/prosody/conf.avail/${DOMAIN}.cfg.lua"
  [[ -f "$pfile" ]] || die "Prosody config not found: $pfile"
  backup_file "$pfile"

  if ! grep -q 'turncredentials_secret' "$pfile"; then
    cat >> "$pfile" <<EOF

turncredentials_secret = "${TURN_SECRET}";
turncredentials = {
  { type = "stun",  host = "${TURN_DOMAIN}", port = "3478" },
  { type = "turn",  host = "${TURN_DOMAIN}", port = "3478", transport = "udp" },
  { type = "turns", host = "${TURN_DOMAIN}", port = "5349", transport = "tcp" }
};
EOF
  fi

  if ! grep -q '"turncredentials"' "$pfile"; then
    perl -0777 -i -pe 's/(modules_enabled\s*=\s*\{\s*\n)/$1        "turncredentials";\n/s' "$pfile"
  fi
}

configure_secure_domain() {
  log "Configuring secure-domain auth"

  local pfile="/etc/prosody/conf.avail/${DOMAIN}.cfg.lua"
  local cfile="/etc/jitsi/meet/${DOMAIN}-config.js"
  local jicofo="/etc/jitsi/jicofo/jicofo.conf"

  [[ -f "$pfile" ]] || die "Prosody config not found: $pfile"
  [[ -f "$cfile" ]] || die "Jitsi config not found: $cfile"
  [[ -f "$jicofo" ]] || die "Jicofo config not found: $jicofo"

  backup_file "$pfile"
  backup_file "$cfile"
  backup_file "$jicofo"

  perl -0777 -i -pe '
    s/(VirtualHost\s+"'"$DOMAIN"'".*?\n\s*authentication\s*=\s*)"anonymous"/$1"internal_hashed"/s
  ' "$pfile"

  if ! grep -q "VirtualHost \"guest.${DOMAIN}\"" "$pfile"; then
    cat >> "$pfile" <<EOF

VirtualHost "guest.${DOMAIN}"
    authentication = "jitsi-anonymous"
    c2s_require_encryption = false
EOF
  fi

  if ! grep -q "anonymousdomain: 'guest.${DOMAIN}'" "$cfile"; then
    perl -0777 -i -pe '
      s/(hosts:\s*\{\s*.*?domain:\s*'\'''"$DOMAIN"''\'',\s*\n)/$1        anonymousdomain: '\''guest.'"$DOMAIN"''\'',\n/s
    ' "$cfile"
  fi

  if ! grep -q 'useTurnUdp:' "$cfile"; then
    perl -0777 -i -pe 's/(var config = \{\n)/$1    useTurnUdp: true,\n/s' "$cfile"
  fi

  if ! grep -q 'useStunTurn:' "$cfile"; then
    perl -0777 -i -pe 's/(p2p:\s*\{\n)/$1        useStunTurn: true,\n/s' "$cfile"
  fi

  if ! grep -q 'login-url:' "$jicofo"; then
    perl -0777 -i -pe '
      s/(jicofo\s*\{)/$1\n  authentication: {\n    enabled: true\n    type: XMPP\n    login-url: "'"$DOMAIN"'"\n  }\n/s
    ' "$jicofo"
  fi

  prosodyctl unregister "$ADMIN_USER" "$DOMAIN" >/dev/null 2>&1 || true
  prosodyctl register "$ADMIN_USER" "$DOMAIN" "$ADMIN_PASS"
}

install_renewal_hook() {
  log "Installing renewal hook"
  install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy

  cat > /etc/letsencrypt/renewal-hooks/deploy/99-jitsi-refresh <<EOF
#!/usr/bin/env bash
set -e
install -m 0644 /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /etc/jitsi/meet/${DOMAIN}.crt
install -m 0600 /etc/letsencrypt/live/${DOMAIN}/privkey.pem /etc/jitsi/meet/${DOMAIN}.key
systemctl reload nginx || true
systemctl restart prosody || true
systemctl restart jicofo || true
systemctl restart jitsi-videobridge2 || true
systemctl restart coturn || true
EOF
  chmod +x /etc/letsencrypt/renewal-hooks/deploy/99-jitsi-refresh

  if systemctl list-unit-files | grep -q '^certbot.timer'; then
    systemctl enable --now certbot.timer
  fi
}

configure_firewall() {
  [[ $ENABLE_UFW -eq 1 ]] || return 0
  command -v ufw >/dev/null 2>&1 || return 0

  log "Configuring UFW"
  ufw allow 22/tcp || true
  ufw allow 80/tcp || true
  ufw allow 443/tcp || true
  ufw allow 10000/udp || true
  ufw allow 3478/udp || true
  ufw allow 5349/tcp || true
  ufw allow 49152:49252/udp || true
}

restart_services() {
  log "Restarting services"
  systemctl restart prosody
  systemctl restart jicofo
  systemctl restart jitsi-videobridge2
  systemctl restart coturn
  systemctl restart nginx
}

show_summary() {
  cat <<EOF

============================================================
Jitsi Meet installation complete

URL:
  https://${DOMAIN}

Secure-domain admin:
  login:    ${ADMIN_USER}
  password: ${ADMIN_PASS}

TURN:
  domain:   ${TURN_DOMAIN}
  secret:   ${TURN_SECRET}
  tls port: 5349
  udp port: 3478

Network:
  local IP:  ${LOCAL_IP}
  public IP: ${PUBLIC_IP:-unknown}

Notes:
  - secure-domain means only authenticated users can create rooms
  - guests can join existing rooms anonymously
  - certbot auto-renew is enabled
  - after renewal, nginx/prosody/jicofo/jvb/coturn are restarted automatically

Check:
  certbot renew --dry-run
  nginx -t
  systemctl status nginx prosody jicofo jitsi-videobridge2 coturn
============================================================

EOF
}

main() {
  detect_distro
  log "Detected OS: ${DISTRO_NAME}"

  set_locale
  prepare_hostname
  install_base_packages
  add_jitsi_repo
  create_temp_cert
  preseed_jitsi
  install_jitsi
  ensure_nginx_ok
  obtain_certificate
  configure_nat_detection
  configure_jvb_nat
  configure_coturn
  configure_prosody_turn
  configure_secure_domain
  install_renewal_hook
  configure_firewall
  restart_services
  show_summary
}

main "$@"

PS. Скрипт протестирован на Debian 12.



Проверено: dataman ()
Последнее исправление: unclestephen (всего исправлений: 1)

Хорошая штука Jitsi Meet! Дядя Штефан тоже молодец!

maccabeus
()
Ответ на: комментарий от gagarin0

Ну ты зря, конечно. Пусть индексируется в поисковиках, люди приходят, читают, устанавливают свободные программы. Больше русскоязычной информации – лучше для всех.

XMPP ★★
()
Ответ на: комментарий от XMPP

Я не против «original content», я даже сквозь пальцы могу смотреть на использование perl рядом с sed для подстановки переменных.

С тем же успехом можно было использовать https://github.com/jitsi/docker-jitsi-meet, поднять jitsi через docker-compose без изобретения велосипедов, и написать два абзаца инструкций на русском языке для индексации поисковиками.

gagarin0
()
Ответ на: комментарий от gagarin0

Очевидно, тем, что тут нет докера, что однозначно преимущество.

Однако, блоатварь всё равно присутствует, например certbot, да и вообще скрипт какой-то слишком сложный.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от dataman

А, одной командой. Опечатка, а ты уже и рад придраться. Суть не меняется - команд в этой простыне куча.

firkax ★★★★★
()
Ответ на: комментарий от dataman

отличная идея! как я раньше не подумал модератор🤦‍♂️

ripgrep
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.