Один 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.

