mirror of
https://github.com/casjaysdevdocker/prosody
synced 2026-06-23 20:01:02 -04:00
🦈🏠🐜❗ Initial Commit ❗🐜🦈🏠
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# .dockerignore created on 05/19/26 at 09:30
|
||||
|
||||
# version control
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# local and secret config
|
||||
.env
|
||||
app.env
|
||||
default.env
|
||||
.claude/
|
||||
|
||||
# build artifacts
|
||||
binaries/
|
||||
releases/
|
||||
|
||||
# runtime volume data (never in image)
|
||||
volumes/
|
||||
docker/volumes/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# docs and meta (not needed in image)
|
||||
*.md
|
||||
LICENSE*
|
||||
@@ -0,0 +1,98 @@
|
||||
# Template generated on Sun May 17 10:58:44 PM EDT 2026 from https://github.com/alexkaratarakis/gitattributes"
|
||||
# Common settings that generally should always be used with your language specific settings
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
# The above will handle all files NOT found below
|
||||
# Documents
|
||||
*.bibtex text diff=bibtex
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.md text diff=markdown
|
||||
*.mdx text diff=markdown
|
||||
*.tex text diff=tex
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text eol=crlf
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
*.sql text
|
||||
*.epub diff=astextplain
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as text by default.
|
||||
*.svg text
|
||||
# If you want to treat it as binary,
|
||||
# use the following line instead.
|
||||
# *.svg binary
|
||||
*.eps binary
|
||||
# Scripts
|
||||
*.bash text eol=lf
|
||||
*.fish text eol=lf
|
||||
*.ksh text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.zsh text eol=lf
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
# Serialisation
|
||||
*.json text
|
||||
*.toml text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
# Archives
|
||||
*.7z binary
|
||||
*.bz binary
|
||||
*.bz2 binary
|
||||
*.bzip2 binary
|
||||
*.gz binary
|
||||
*.lz binary
|
||||
*.lzma binary
|
||||
*.rar binary
|
||||
*.tar binary
|
||||
*.taz binary
|
||||
*.tbz binary
|
||||
*.tbz2 binary
|
||||
*.tgz binary
|
||||
*.tlz binary
|
||||
*.txz binary
|
||||
*.xz binary
|
||||
*.Z binary
|
||||
*.zip binary
|
||||
*.zst binary
|
||||
# Text files where line endings should be preserved
|
||||
*.patch -text
|
||||
# Exclude files from exporting
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitkeep export-ignore
|
||||
|
||||
# Template generated on Sun May 17 10:58:44 PM EDT 2026
|
||||
# Files for git large file system
|
||||
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.rpm filter=lfs diff=lfs merge=lfs -text
|
||||
*.7zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.bzip2 filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Global owners — review required for everything not covered below
|
||||
* @casjaysdevdocker
|
||||
|
||||
# Workflow changes require maintainer sign-off
|
||||
.github/ @casjaysdevdocker
|
||||
|
||||
# Docker assets
|
||||
docker/ @casjaysdevdocker
|
||||
@@ -0,0 +1,58 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: docker/setup-buildx-action@b5730e1a0a27db01e64c4ab5f4f7c8a63c1de14a # v3.10.0
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/metadata-action@902fa8ec7d6ecbea8a986b37db18de1c4e72470b # v5.7.0
|
||||
id: meta
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
tags: |
|
||||
type=edge,branch=main
|
||||
type=sha,prefix=sha-
|
||||
annotations: |
|
||||
maintainer=${{ github.repository_owner }} <${{ github.repository_owner }}@casjay.pro>
|
||||
org.opencontainers.image.vendor=${{ github.repository_owner }}
|
||||
org.opencontainers.image.authors=${{ github.repository_owner }}
|
||||
org.opencontainers.image.title=${{ github.event.repository.name }}
|
||||
org.opencontainers.image.description=Containerized version of ${{ github.event.repository.name }}
|
||||
org.opencontainers.image.url=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.source=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.documentation=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.vcs-type=Git
|
||||
com.github.containers.toolbox=false
|
||||
|
||||
- uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
labels: ""
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and publish release image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: docker/setup-buildx-action@b5730e1a0a27db01e64c4ab5f4f7c8a63c1de14a # v3.10.0
|
||||
|
||||
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: docker/metadata-action@902fa8ec7d6ecbea8a986b37db18de1c4e72470b # v5.7.0
|
||||
id: meta
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest
|
||||
type=raw,value=${{ steps.version.outputs.version }}
|
||||
annotations: |
|
||||
maintainer=${{ github.repository_owner }} <${{ github.repository_owner }}@casjay.pro>
|
||||
org.opencontainers.image.vendor=${{ github.repository_owner }}
|
||||
org.opencontainers.image.authors=${{ github.repository_owner }}
|
||||
org.opencontainers.image.title=${{ github.event.repository.name }}
|
||||
org.opencontainers.image.description=Containerized version of ${{ github.event.repository.name }}
|
||||
org.opencontainers.image.url=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.source=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.documentation=${{ github.event.repository.html_url }}
|
||||
org.opencontainers.image.vcs-type=Git
|
||||
com.github.containers.toolbox=false
|
||||
|
||||
- uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
labels: ""
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
BUILD_DATE=${{ github.event.head_commit.timestamp }}
|
||||
VCS_REF=${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039edc1654bcb6e02e54e87 # v2.2.2
|
||||
with:
|
||||
generate_release_notes: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
# gitignore created on 05/19/26 at 09:30
|
||||
ignoredirmessage
|
||||
|
||||
# ignore commit message
|
||||
**/.gitcommit
|
||||
|
||||
# ignore build failure markers
|
||||
**/.build_failed*
|
||||
|
||||
# ignore backup files
|
||||
**/*.bak
|
||||
|
||||
# ignore push/git control files
|
||||
**/.no_push
|
||||
**/.no_git
|
||||
|
||||
# ignore install markers
|
||||
**/.installed
|
||||
|
||||
# ignore work in progress scripts
|
||||
**/*.rewrite.sh
|
||||
**/*.refactor.sh
|
||||
|
||||
# ignore dotenv files
|
||||
.env
|
||||
app.env
|
||||
default.env
|
||||
|
||||
# ignore log and temp files
|
||||
*.log
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# OS generated files
|
||||
### Linux ###
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
.DS_Store?
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
|
||||
# runtime volume data — never committed
|
||||
volumes/
|
||||
docker/volumes/
|
||||
|
||||
# Claude Code runtime files
|
||||
.claude/settings.local.json
|
||||
.claude/backups/
|
||||
.claude/cache/
|
||||
.claude/file-history/
|
||||
.claude/history.jsonl
|
||||
.claude/projects/
|
||||
.claude/statsFile
|
||||
.claude/*.lock
|
||||
@@ -0,0 +1,7 @@
|
||||
# Claude loader
|
||||
|
||||
Project spec: [IDEA.md](IDEA.md)
|
||||
|
||||
This is a Docker image project. Implementation conventions come from
|
||||
`~/.claude/memory/dockerfile_conventions.md`. All Docker assets live
|
||||
under `docker/`.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Project description
|
||||
|
||||
A production-ready Docker image for [Prosody XMPP](https://prosody.im/) configured specifically for Jitsi Meet deployments. Replaces the upstream `jitsi/docker-jitsi-meet` prosody image with a standards-compliant CasjaysDev image built on Alpine Linux with full BOSH and WebSocket support (mod_websocket).
|
||||
|
||||
## Project variables
|
||||
|
||||
```
|
||||
project_name: prosody
|
||||
project_org: casjaysdevdocker
|
||||
internal_name: prosody
|
||||
internal_org: casjaysdevdocker
|
||||
image_registry: ghcr.io
|
||||
upstream_image: prosody (Alpine package)
|
||||
jitsi_repo: https://github.com/jitsi/docker-jitsi-meet
|
||||
```
|
||||
|
||||
## Business logic
|
||||
|
||||
- Must be a drop-in replacement for `jitsi/docker-jitsi-meet` prosody container
|
||||
- Must support all standard Jitsi Docker env vars: `XMPP_DOMAIN`, `XMPP_AUTH_DOMAIN`, `XMPP_MUC_DOMAIN`, `XMPP_INTERNAL_MUC_DOMAIN`, `XMPP_GUEST_DOMAIN`, `JICOFO_AUTH_PASSWORD`, `JVB_AUTH_PASSWORD`, `ENABLE_AUTH`, `ENABLE_GUESTS`, `ENABLE_XMPP_WEBSOCKET`, `PUBLIC_URL`, `LOG_LEVEL`
|
||||
- Must have BOSH working on `/http-bind` at port 5280
|
||||
- Must have WebSocket working on `/xmpp-websocket` at port 5280 (this is the primary fix over upstream)
|
||||
- Must register jicofo and jvb users automatically at startup
|
||||
- Must generate self-signed TLS certificates for Jitsi domains at startup if not present
|
||||
- Must expose ports 5222 (c2s), 5280 (HTTP/BOSH/WS), 5347 (component)
|
||||
- Must include all Jitsi prosody plugins from `jitsi/docker-jitsi-meet` prosody image
|
||||
- Must run prosody as the `prosody` system user (non-root), not as root
|
||||
- Must use `tini` as PID 1
|
||||
- Must work with zero config — sane defaults for all env vars
|
||||
- Must NOT require a separate nginx reverse proxy in the container
|
||||
- Must NOT include s6 or any other process supervisor — single process per container
|
||||
- `consider_websocket_secure = true` and `consider_bosh_secure = true` must always be set so trusted-proxy HTTPS termination works
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2026 casjay <git-admin@casjaysdev.pro>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
1. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
@@ -0,0 +1,8 @@
|
||||
## 👋 Welcome to prosody 🚀
|
||||
|
||||
prosody README
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
🤖 casjay: [Github](https://github.com/casjay) 🤖
|
||||
@@ -0,0 +1,69 @@
|
||||
# =============================================================================
|
||||
# Jitsi Source Stage — extract plugins and pure-Lua libs from the official
|
||||
# jitsi/prosody image at build time so we never fetch at runtime
|
||||
# =============================================================================
|
||||
FROM jitsi/prosody:stable AS jitsi-source
|
||||
|
||||
# =============================================================================
|
||||
# Runtime Stage
|
||||
# =============================================================================
|
||||
FROM alpine:latest
|
||||
|
||||
ARG VERSION=dev
|
||||
ARG BUILD_DATE
|
||||
ARG VCS_REF
|
||||
ARG LICENSE=MIT
|
||||
|
||||
# Install runtime dependencies
|
||||
# prosody from Alpine 13.x matches jitsi/prosody:stable (also 13.x)
|
||||
# su-exec drops from root to prosody user before exec
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
curl \
|
||||
openssl \
|
||||
prosody \
|
||||
su-exec \
|
||||
tini
|
||||
|
||||
# Apply prosody 13.x compatibility patch:
|
||||
# idna_to_ascii was removed in libicu 72+ but the Lua util still references it;
|
||||
# the jitsi base image applies this same patch (sed -i '/idna_to_ascii/d')
|
||||
RUN sed -i '/idna_to_ascii/d' /usr/lib/prosody/util/jid.lua 2>/dev/null || true
|
||||
|
||||
# Create runtime directories
|
||||
RUN mkdir -p \
|
||||
/config/certs \
|
||||
/config/conf.d \
|
||||
/config/data \
|
||||
/prosody-plugins \
|
||||
/prosody-plugins-contrib \
|
||||
/prosody-plugins-custom
|
||||
|
||||
# Copy Jitsi-specific prosody plugins (pure Lua — architecture-independent)
|
||||
COPY --from=jitsi-source /prosody-plugins /prosody-plugins
|
||||
COPY --from=jitsi-source /prosody-plugins-contrib /prosody-plugins-contrib
|
||||
|
||||
# Copy pure-Lua libraries that the Jitsi plugins depend on (basexx, net-url, etc.)
|
||||
# We do NOT copy /usr/local/lib/lua (compiled .so for glibc/Debian — incompatible with musl/Alpine)
|
||||
# lua5.4-cjson is provided by Alpine's prosody dependency and takes precedence
|
||||
COPY --from=jitsi-source /usr/local/share/lua/5.4 /usr/local/share/lua/5.4
|
||||
|
||||
# Copy rootfs overlay (mirrors Linux FHS)
|
||||
COPY docker/rootfs/ /
|
||||
|
||||
# Copy Dockerfile into image for reference
|
||||
COPY docker/Dockerfile /root/Dockerfile
|
||||
|
||||
# Set ownership and permissions
|
||||
RUN chown -R prosody:prosody /config /prosody-plugins-custom /prosody-plugins-contrib && \
|
||||
chmod 755 /usr/local/bin/*
|
||||
|
||||
# BOSH + WebSocket (5280), c2s client connections (5222), component connections (5347)
|
||||
EXPOSE 5222 5280 5347
|
||||
|
||||
STOPSIGNAL SIGRTMIN+3
|
||||
|
||||
HEALTHCHECK --start-period=2m --interval=1m --timeout=10s --retries=3 \
|
||||
CMD /usr/local/bin/healthcheck.sh || exit 1
|
||||
|
||||
ENTRYPOINT [ "tini", "-p", "SIGTERM", "--", "/usr/local/bin/entrypoint.sh" ]
|
||||
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
prosody:
|
||||
image: ghcr.io/casjaysdevdocker/prosody:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- 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
|
||||
- JICOFO_AUTH_PASSWORD=changeme_jicofo
|
||||
- JVB_AUTH_PASSWORD=changeme_jvb
|
||||
- ENABLE_AUTH=0
|
||||
- ENABLE_GUESTS=0
|
||||
- ENABLE_XMPP_WEBSOCKET=1
|
||||
- LOG_LEVEL=info
|
||||
volumes:
|
||||
- ./volumes/config:/config
|
||||
ports:
|
||||
- "5222:5222"
|
||||
- "5280:5280"
|
||||
- "5347:5347"
|
||||
networks:
|
||||
- prosody-net
|
||||
|
||||
networks:
|
||||
prosody-net:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,489 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# healthcheck.sh — container health probe
|
||||
# Returns 0 (healthy) if prosody's HTTP health endpoint responds 200
|
||||
|
||||
: "${PROSODY_HTTP_PORT:=5280}"
|
||||
|
||||
curl -q -LSs --max-time 5 "http://127.0.0.1:${PROSODY_HTTP_PORT}/health" -o /dev/null
|
||||
Reference in New Issue
Block a user