Files
jason dfd79d5774 🦈🏠🐜 Initial Commit 🐜🦈🏠
2026-05-19 12:54:07 -04:00

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