mirror of
https://github.com/casjaysdevdocker/prosody
synced 2026-06-24 08:01:07 -04:00
490 lines
13 KiB
Bash
490 lines
13 KiB
Bash
#!/usr/bin/env bash
|
|
# shellcheck shell=bash
|
|
# entrypoint.sh — container startup script
|
|
# Called by: tini → entrypoint.sh → prosody
|
|
|
|
set -euo pipefail
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Defaults
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
: "${XMPP_DOMAIN:=meet.jitsi}"
|
|
: "${XMPP_AUTH_DOMAIN:=auth.meet.jitsi}"
|
|
: "${XMPP_GUEST_DOMAIN:=guest.meet.jitsi}"
|
|
: "${XMPP_MUC_DOMAIN:=muc.meet.jitsi}"
|
|
: "${XMPP_INTERNAL_MUC_DOMAIN:=internal-muc.meet.jitsi}"
|
|
: "${XMPP_HIDDEN_DOMAIN:=hidden.meet.jitsi}"
|
|
: "${XMPP_PORT:=5222}"
|
|
: "${PROSODY_HTTP_PORT:=5280}"
|
|
: "${ENABLE_AUTH:=0}"
|
|
: "${ENABLE_GUESTS:=0}"
|
|
: "${ENABLE_XMPP_WEBSOCKET:=1}"
|
|
: "${ENABLE_LOBBY:=1}"
|
|
: "${ENABLE_BREAKOUT_ROOMS:=1}"
|
|
: "${ENABLE_AV_MODERATION:=1}"
|
|
: "${ENABLE_END_CONFERENCE:=1}"
|
|
: "${ENABLE_RECORDING:=0}"
|
|
: "${ENABLE_TRANSCRIPTIONS:=0}"
|
|
: "${JVB_AUTH_USER:=jvb}"
|
|
: "${JIBRI_RECORDER_USER:=recorder}"
|
|
: "${JIBRI_XMPP_USER:=jibri}"
|
|
: "${JIGASI_XMPP_USER:=jigasi}"
|
|
: "${LOG_LEVEL:=info}"
|
|
: "${PROSODY_MODE:=client}"
|
|
: "${PROSODY_C2S_REQUIRE_ENCRYPTION:=true}"
|
|
: "${PROSODY_ADMINS:=}"
|
|
: "${AUTH_TYPE:=internal}"
|
|
: "${DISABLE_POLLS:=0}"
|
|
|
|
# Derived
|
|
XMPP_MUC_DOMAIN_PREFIX="${XMPP_MUC_DOMAIN%%.*}"
|
|
PROSODY_CFG="/config/prosody.cfg.lua"
|
|
PROSODY_SITE_CFG="/config/conf.d/jitsi-meet.cfg.lua"
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Guards
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
[[ -n "${JICOFO_AUTH_PASSWORD:-}" ]] || { echo "FATAL: JICOFO_AUTH_PASSWORD must be set" >&2; exit 1; }
|
|
[[ -n "${JVB_AUTH_PASSWORD:-}" ]] || { echo "FATAL: JVB_AUTH_PASSWORD must be set" >&2; exit 1; }
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Directory setup
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
mkdir -p /config/certs /config/conf.d /config/data \
|
|
/prosody-plugins-custom /prosody-plugins-contrib
|
|
|
|
chown -R prosody:prosody /config /prosody-plugins-custom /prosody-plugins-contrib 2>/dev/null || true
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Generate main prosody.cfg.lua
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
__generate_main_cfg() {
|
|
# Build admins list
|
|
local admins_list=""
|
|
if [[ -n "${PROSODY_ADMINS}" ]]; then
|
|
while IFS=',' read -ra parts; do
|
|
for admin in "${parts[@]}"; do
|
|
admin="${admin// /}"
|
|
[[ -n "$admin" ]] && admins_list+=" \"${admin}\";\n"
|
|
done
|
|
done <<< "${PROSODY_ADMINS}"
|
|
fi
|
|
|
|
cat > "${PROSODY_CFG}" << MAIN_CFG
|
|
-- Generated by entrypoint.sh — do not edit manually
|
|
|
|
---------- Server-wide settings ----------
|
|
|
|
admins = {
|
|
$(printf '%b' "${admins_list}")}
|
|
|
|
component_admins_as_room_owners = true
|
|
|
|
modules_enabled = {
|
|
-- Core
|
|
"roster";
|
|
"saslauth";
|
|
"tls";
|
|
"disco";
|
|
"ping";
|
|
"version";
|
|
"posix";
|
|
"limits";
|
|
"private";
|
|
|
|
-- HTTP (BOSH + WebSocket — required for Jitsi)
|
|
"bosh";
|
|
"websocket";
|
|
"http";
|
|
"http_health";
|
|
|
|
-- Status
|
|
"uptime";
|
|
};
|
|
|
|
modules_disabled = {
|
|
"offline";
|
|
"register";
|
|
"s2s";
|
|
};
|
|
|
|
-- Never allow unauthenticated registration globally
|
|
allow_registration = false;
|
|
|
|
-- Rate limits for incoming connections
|
|
limits = {
|
|
c2s = {
|
|
rate = "10kb/s";
|
|
};
|
|
};
|
|
|
|
-- Garbage collector
|
|
gc = {
|
|
mode = "incremental";
|
|
threshold = 400;
|
|
speed = 250;
|
|
step_size = 13;
|
|
};
|
|
|
|
pidfile = "/config/data/prosody.pid";
|
|
|
|
c2s_require_encryption = ${PROSODY_C2S_REQUIRE_ENCRYPTION};
|
|
c2s_ports = { ${XMPP_PORT} }
|
|
c2s_interfaces = { "*" }
|
|
|
|
s2s_secure_auth = false
|
|
|
|
authentication = "internal_hashed"
|
|
|
|
-- Logging
|
|
log = {
|
|
{ levels = { min = "${LOG_LEVEL}" }, timestamps = "%Y-%m-%d %X", to = "console" };
|
|
};
|
|
|
|
-- HTTP configuration
|
|
-- consider_bosh_secure and consider_websocket_secure allow HTTPS-terminated
|
|
-- connections from a reverse proxy to be treated as secure.
|
|
consider_bosh_secure = true;
|
|
consider_websocket_secure = true;
|
|
|
|
-- Allow WebSocket + BOSH on the same port (5280) that the reverse proxy reaches
|
|
http_ports = { ${PROSODY_HTTP_PORT} }
|
|
http_interfaces = { "*" }
|
|
|
|
-- Stream management (required for smacks / WebSocket reconnect)
|
|
smacks_max_unacked_stanzas = 5;
|
|
smacks_hibernation_time = 60;
|
|
smacks_max_old_sessions = 1;
|
|
|
|
-- epoll backend for better performance
|
|
network_backend = "epoll";
|
|
network_settings = {
|
|
tcp_backlog = 511;
|
|
};
|
|
|
|
-- Data storage
|
|
data_path = "/config/data";
|
|
|
|
-- Plugin paths
|
|
plugin_paths = { "/prosody-plugins-custom", "/prosody-plugins/", "/prosody-plugins-contrib" };
|
|
|
|
Include "conf.d/*.cfg.lua"
|
|
MAIN_CFG
|
|
}
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Generate conf.d/jitsi-meet.cfg.lua
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
__generate_site_cfg() {
|
|
# Determine main VirtualHost authentication
|
|
local main_auth="jitsi-anonymous"
|
|
if [[ "${ENABLE_AUTH}" == "1" ]] || [[ "${ENABLE_AUTH}" == "true" ]]; then
|
|
main_auth="internal_hashed"
|
|
fi
|
|
|
|
# Determine guest VirtualHost authentication
|
|
local guest_auth="jitsi-anonymous"
|
|
|
|
# Build global admins block for conf.d
|
|
cat > "${PROSODY_SITE_CFG}" << SITE_START
|
|
-- Generated by entrypoint.sh — do not edit manually
|
|
|
|
admins = {
|
|
"focus@${XMPP_AUTH_DOMAIN}",
|
|
"${JVB_AUTH_USER}@${XMPP_AUTH_DOMAIN}"
|
|
}
|
|
|
|
unlimited_jids = {
|
|
"focus@${XMPP_AUTH_DOMAIN}",
|
|
"${JVB_AUTH_USER}@${XMPP_AUTH_DOMAIN}"
|
|
}
|
|
|
|
muc_mapper_domain_base = "${XMPP_DOMAIN}";
|
|
muc_mapper_domain_prefix = "${XMPP_MUC_DOMAIN_PREFIX}";
|
|
|
|
recorder_prefixes = { "${JIBRI_RECORDER_USER}@${XMPP_HIDDEN_DOMAIN}" };
|
|
transcriber_prefixes = { "${JIGASI_TRANSCRIBER_USER:-transcriber}@${XMPP_HIDDEN_DOMAIN}" };
|
|
|
|
http_default_host = "${XMPP_DOMAIN}"
|
|
|
|
-- Main virtual host
|
|
VirtualHost "${XMPP_DOMAIN}"
|
|
authentication = "${main_auth}"
|
|
ssl = {
|
|
key = "/config/certs/${XMPP_DOMAIN}.key";
|
|
certificate = "/config/certs/${XMPP_DOMAIN}.crt";
|
|
}
|
|
modules_enabled = {
|
|
"bosh";
|
|
"websocket";
|
|
"smacks";
|
|
"conference_duration";
|
|
"muc_lobby_rooms";
|
|
"muc_breakout_rooms";
|
|
"features_identity";
|
|
SITE_START
|
|
|
|
# Optional XMPP_MODULES
|
|
if [[ -n "${XMPP_MODULES:-}" ]]; then
|
|
while IFS=',' read -ra mods; do
|
|
for mod in "${mods[@]}"; do
|
|
mod="${mod// /}"
|
|
[[ -n "$mod" ]] && printf ' "%s";\n' "${mod}" >> "${PROSODY_SITE_CFG}"
|
|
done
|
|
done <<< "${XMPP_MODULES}"
|
|
fi
|
|
|
|
cat >> "${PROSODY_SITE_CFG}" << SITE_MAIN_HOST
|
|
}
|
|
main_muc = "${XMPP_MUC_DOMAIN}"
|
|
lobby_muc = "lobby.${XMPP_DOMAIN}"
|
|
breakout_rooms_muc = "breakout.${XMPP_DOMAIN}"
|
|
c2s_require_encryption = ${PROSODY_C2S_REQUIRE_ENCRYPTION}
|
|
|
|
SITE_MAIN_HOST
|
|
|
|
# Guest domain (only when auth is enabled and guests are enabled)
|
|
if { [[ "${ENABLE_AUTH}" == "1" ]] || [[ "${ENABLE_AUTH}" == "true" ]]; } && \
|
|
{ [[ "${ENABLE_GUESTS}" == "1" ]] || [[ "${ENABLE_GUESTS}" == "true" ]]; }; then
|
|
cat >> "${PROSODY_SITE_CFG}" << SITE_GUEST
|
|
VirtualHost "${XMPP_GUEST_DOMAIN}"
|
|
authentication = "${guest_auth}"
|
|
modules_enabled = {
|
|
"smacks";
|
|
}
|
|
main_muc = "${XMPP_MUC_DOMAIN}"
|
|
lobby_muc = "lobby.${XMPP_DOMAIN}"
|
|
breakout_rooms_muc = "breakout.${XMPP_DOMAIN}"
|
|
c2s_require_encryption = ${PROSODY_C2S_REQUIRE_ENCRYPTION}
|
|
|
|
SITE_GUEST
|
|
fi
|
|
|
|
# Auth domain
|
|
cat >> "${PROSODY_SITE_CFG}" << SITE_AUTH
|
|
VirtualHost "${XMPP_AUTH_DOMAIN}"
|
|
ssl = {
|
|
key = "/config/certs/${XMPP_AUTH_DOMAIN}.key";
|
|
certificate = "/config/certs/${XMPP_AUTH_DOMAIN}.crt";
|
|
}
|
|
modules_enabled = {
|
|
"limits_exception";
|
|
"smacks";
|
|
}
|
|
authentication = "internal_hashed"
|
|
smacks_hibernation_time = 15;
|
|
|
|
SITE_AUTH
|
|
|
|
# Hidden domain for recording/transcription
|
|
if [[ "${ENABLE_RECORDING}" == "1" ]] || [[ "${ENABLE_RECORDING}" == "true" ]] || \
|
|
[[ "${ENABLE_TRANSCRIPTIONS}" == "1" ]] || [[ "${ENABLE_TRANSCRIPTIONS}" == "true" ]]; then
|
|
cat >> "${PROSODY_SITE_CFG}" << SITE_HIDDEN
|
|
VirtualHost "${XMPP_HIDDEN_DOMAIN}"
|
|
modules_enabled = {
|
|
"smacks";
|
|
}
|
|
authentication = "internal_hashed"
|
|
|
|
SITE_HIDDEN
|
|
fi
|
|
|
|
# Components
|
|
cat >> "${PROSODY_SITE_CFG}" << SITE_COMPONENTS
|
|
-- Internal MUC (JVB/jicofo signaling — never exposed to clients)
|
|
Component "${XMPP_INTERNAL_MUC_DOMAIN}" "muc"
|
|
storage = "memory"
|
|
modules_enabled = {
|
|
"muc_hide_all";
|
|
"muc_filter_access";
|
|
}
|
|
restrict_room_creation = true
|
|
muc_filter_whitelist = "${XMPP_AUTH_DOMAIN}"
|
|
muc_room_locking = false
|
|
muc_room_default_public_jids = true
|
|
muc_room_cache_size = 1000
|
|
muc_tombstones = false
|
|
muc_room_allow_persistent = false
|
|
|
|
-- Conference MUC (participants join rooms here)
|
|
Component "${XMPP_MUC_DOMAIN}" "muc"
|
|
restrict_room_creation = true
|
|
storage = "memory"
|
|
modules_enabled = {
|
|
"muc_hide_all";
|
|
"muc_meeting_id";
|
|
"muc_domain_mapper";
|
|
"muc_password_whitelist";
|
|
}
|
|
muc_room_cache_size = 10000
|
|
muc_room_locking = false
|
|
muc_room_default_public_jids = true
|
|
muc_tombstones = false
|
|
muc_room_allow_persistent = false
|
|
muc_password_whitelist = {
|
|
"focus@${XMPP_AUTH_DOMAIN}";
|
|
}
|
|
|
|
-- Focus component proxy
|
|
Component "focus.${XMPP_DOMAIN}" "client_proxy"
|
|
target_address = "focus@${XMPP_AUTH_DOMAIN}"
|
|
|
|
-- Speakerstats component
|
|
Component "speakerstats.${XMPP_DOMAIN}" "speakerstats_component"
|
|
muc_component = "${XMPP_MUC_DOMAIN}"
|
|
|
|
-- End conference component
|
|
Component "endconference.${XMPP_DOMAIN}" "end_conference"
|
|
muc_component = "${XMPP_MUC_DOMAIN}"
|
|
|
|
-- AV moderation component
|
|
Component "avmoderation.${XMPP_DOMAIN}" "av_moderation_component"
|
|
muc_component = "${XMPP_MUC_DOMAIN}"
|
|
|
|
-- Lobby MUC
|
|
Component "lobby.${XMPP_DOMAIN}" "muc"
|
|
storage = "memory"
|
|
restrict_room_creation = true
|
|
muc_tombstones = false
|
|
muc_room_allow_persistent = false
|
|
muc_room_cache_size = 10000
|
|
muc_room_locking = false
|
|
muc_room_default_public_jids = true
|
|
modules_enabled = {
|
|
"muc_hide_all";
|
|
}
|
|
|
|
-- Breakout rooms MUC
|
|
Component "breakout.${XMPP_DOMAIN}" "muc"
|
|
storage = "memory"
|
|
restrict_room_creation = true
|
|
muc_room_cache_size = 10000
|
|
muc_room_locking = false
|
|
muc_room_default_public_jids = true
|
|
muc_tombstones = false
|
|
muc_room_allow_persistent = false
|
|
modules_enabled = {
|
|
"muc_hide_all";
|
|
"muc_meeting_id";
|
|
}
|
|
|
|
-- Room metadata component
|
|
Component "metadata.${XMPP_DOMAIN}" "room_metadata_component"
|
|
muc_component = "${XMPP_MUC_DOMAIN}"
|
|
breakout_rooms_component = "breakout.${XMPP_DOMAIN}"
|
|
|
|
SITE_COMPONENTS
|
|
|
|
# Polls component (optional disable)
|
|
if [[ "${DISABLE_POLLS}" != "1" ]] && [[ "${DISABLE_POLLS}" != "true" ]]; then
|
|
cat >> "${PROSODY_SITE_CFG}" << SITE_POLLS
|
|
Component "polls.${XMPP_DOMAIN}" "polls_component"
|
|
|
|
SITE_POLLS
|
|
fi
|
|
}
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Certificate generation
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
__generate_cert() {
|
|
local domain="${1}"
|
|
local cert_dir="/config/certs"
|
|
local key="${cert_dir}/${domain}.key"
|
|
local crt="${cert_dir}/${domain}.crt"
|
|
|
|
[[ -f "${key}" && -f "${crt}" ]] && return 0
|
|
|
|
echo "INFO: Generating self-signed certificate for ${domain}"
|
|
openssl req -x509 -newkey rsa:4096 \
|
|
-keyout "${key}" -out "${crt}" \
|
|
-days 3650 -nodes \
|
|
-subj "/CN=${domain}" 2>/dev/null
|
|
chmod 600 "${key}"
|
|
chown prosody:prosody "${key}" "${crt}" 2>/dev/null || true
|
|
}
|
|
|
|
__generate_certs() {
|
|
__generate_cert "${XMPP_DOMAIN}"
|
|
__generate_cert "${XMPP_AUTH_DOMAIN}"
|
|
}
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# User registration
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
__register_user() {
|
|
local user="${1}" domain="${2}" pass="${3}"
|
|
# prosodyctl register is idempotent — re-register just updates the password
|
|
su-exec prosody prosodyctl --config "${PROSODY_CFG}" \
|
|
register "${user}" "${domain}" "${pass}" 2>/dev/null || true
|
|
}
|
|
|
|
__register_jitsi_users() {
|
|
# Start prosody temporarily in background so prosodyctl can connect
|
|
su-exec prosody prosody --config "${PROSODY_CFG}" -F &
|
|
local pid=$!
|
|
|
|
# Wait until prosody is accepting connections (up to 30s)
|
|
local i=0
|
|
while ! su-exec prosody prosodyctl --config "${PROSODY_CFG}" status >/dev/null 2>&1; do
|
|
sleep 1
|
|
i=$(( i + 1 ))
|
|
if (( i >= 30 )); then
|
|
echo "FATAL: prosody did not start within 30s" >&2
|
|
kill "${pid}" 2>/dev/null || true
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "INFO: Registering jicofo user"
|
|
__register_user "focus" "${XMPP_AUTH_DOMAIN}" "${JICOFO_AUTH_PASSWORD}"
|
|
|
|
echo "INFO: Registering jvb user"
|
|
__register_user "${JVB_AUTH_USER}" "${XMPP_AUTH_DOMAIN}" "${JVB_AUTH_PASSWORD}"
|
|
|
|
# Subscribe focus to the focus component so roster is correct
|
|
if [[ "${PROSODY_MODE}" == "client" ]]; then
|
|
su-exec prosody prosodyctl --config "${PROSODY_CFG}" \
|
|
mod_roster_command subscribe "focus.${XMPP_DOMAIN}" "focus@${XMPP_AUTH_DOMAIN}" 2>/dev/null || true
|
|
fi
|
|
|
|
# Optional: jibri user
|
|
if [[ -n "${JIBRI_XMPP_PASSWORD:-}" ]]; then
|
|
echo "INFO: Registering jibri user"
|
|
__register_user "${JIBRI_XMPP_USER}" "${XMPP_AUTH_DOMAIN}" "${JIBRI_XMPP_PASSWORD}"
|
|
fi
|
|
|
|
# Optional: jigasi user
|
|
if [[ -n "${JIGASI_XMPP_PASSWORD:-}" ]]; then
|
|
echo "INFO: Registering jigasi user"
|
|
__register_user "${JIGASI_XMPP_USER}" "${XMPP_AUTH_DOMAIN}" "${JIGASI_XMPP_PASSWORD}"
|
|
fi
|
|
|
|
# Shut down the temporary instance
|
|
kill "${pid}" 2>/dev/null || true
|
|
wait "${pid}" 2>/dev/null || true
|
|
# Clean up pid file so the main instance can start
|
|
rm -f /config/data/prosody.pid
|
|
sleep 1
|
|
}
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
# Main
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
echo "INFO: Generating prosody configuration"
|
|
__generate_main_cfg
|
|
__generate_site_cfg
|
|
__generate_certs
|
|
__register_jitsi_users
|
|
|
|
echo "INFO: Starting prosody"
|
|
exec su-exec prosody prosody --config "${PROSODY_CFG}" -F
|