From 20501ab8bcadd00b52e48aa6fd2590a37a98c469 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Mon, 10 Nov 2025 16:16:34 +0100 Subject: [PATCH] =?UTF-8?q?livekit=20a=C3=B1adido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 18 +- .gitignore | 6 +- README.md | 12 +- configs/livekit/livekit.example.yaml | 34 ++++ configs/nginx/matrix-proxy.conf | 54 ++++++ configs/nginx/matrix-rtc-proxy.conf | 31 ++++ configs/well-known/matrix-client.example.json | 12 ++ docker-compose.livekit.yml | 48 ++++++ docker-compose.yml | 26 ++- docs/element-call-livekit.md | 161 ++++++++++++++++++ element-call | 1 + scripts/setup.sh | 61 ++++++- 12 files changed, 446 insertions(+), 18 deletions(-) create mode 100644 configs/livekit/livekit.example.yaml create mode 100644 configs/nginx/matrix-proxy.conf create mode 100644 configs/nginx/matrix-rtc-proxy.conf create mode 100644 configs/well-known/matrix-client.example.json create mode 100644 docker-compose.livekit.yml create mode 100644 docs/element-call-livekit.md create mode 160000 element-call diff --git a/.env.example b/.env.example index bb982d7..ca21a2a 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,22 @@ POSTGRES_PORT=5432 MATRIX_NETWORK_SUBNET=10.10.10.0/24 MATRIX_NETWORK_GATEWAY=10.10.10.1 +# Element Call / LiveKit backend +MATRIX_SITE_BASE_URL=https://matrix.example.com +MATRIX_RTC_BASE_URL=https://matrix-rtc.example.com +LIVEKIT_WS_URL=wss://matrix-rtc.example.com/livekit/sfu +LIVEKIT_JWT_URL=https://matrix-rtc.example.com/livekit/jwt +LIVEKIT_API_KEY=CHANGE_ME +LIVEKIT_API_SECRET=CHANGE_ME_AT_LEAST_32_CHARS +LIVEKIT_HTTP_PORT=7880 +LIVEKIT_TCP_PORT=7881 +LIVEKIT_HEALTH_PORT=7882 +LIVEKIT_UDP_PORT_RANGE_START=50000 +LIVEKIT_UDP_PORT_RANGE_END=50200 +LIVEKIT_JWT_PORT=6080 +LIVEKIT_JWT_BIND=:6080 +LIVEKIT_INSECURE_SKIP_VERIFY_TLS=false + # Element Configuration ELEMENT_BRAND=Element Local ELEMENT_DEFAULT_THEME=light @@ -31,4 +47,4 @@ ENABLE_REGISTRATION_WITHOUT_VERIFICATION=true # Optional: SSL Configuration # SSL_CERT_PATH=./certs/cert.pem -# SSL_KEY_PATH=./certs/key.pem \ No newline at end of file +# SSL_KEY_PATH=./certs/key.pem diff --git a/.gitignore b/.gitignore index d809db6..0be4780 100644 --- a/.gitignore +++ b/.gitignore @@ -32,9 +32,13 @@ certs/ *.key *.crt +# Configuraciones sensibles generadas en tiempo de despliegue +configs/livekit/livekit.yaml +configs/well-known/matrix-client.json + # Archivos de media de Matrix (pueden ser muy grandes) */media_store/ */uploads/ # Archivos de estado de Docker -.dockerignore \ No newline at end of file +.dockerignore diff --git a/README.md b/README.md index fded897..dea7e9c 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,16 @@ element_matrix_chat/ - ✅ Panel de administración web - ✅ Persistencia de datos +## 📞 Element Call + LiveKit + +Si quieres habilitar llamadas con Element Call y su backend LiveKit, revisa la +[guía específica](docs/element-call-livekit.md). Explica cómo: + +- Ajustar Synapse para MatrixRTC +- Generar la configuración de LiveKit +- Ejecutar los contenedores `livekit` y `livekit-jwt` +- Publicar el `.well-known` y el frontend (opcional) + ## 📖 Documentación adicional Consulta la [documentación oficial de Matrix](https://matrix.org/docs/) para configuraciones avanzadas. @@ -83,4 +93,4 @@ docker-compose logs [servicio] Reiniciar servicios: ```bash docker-compose restart -``` \ No newline at end of file +``` diff --git a/configs/livekit/livekit.example.yaml b/configs/livekit/livekit.example.yaml new file mode 100644 index 0000000..ca53e26 --- /dev/null +++ b/configs/livekit/livekit.example.yaml @@ -0,0 +1,34 @@ +# LiveKit configuration example for Element Call. +# Copy this file to configs/livekit/livekit.yaml and replace the placeholder +# values before starting the stack. +port: 7880 +bind_addresses: + - "0.0.0.0" +log_level: info +region: "us-east-1" + +rtc: + tcp_port: 7881 + port_range_start: 50000 + port_range_end: 50200 + use_external_ip: true + force_tcp: false + +# Optional TURN forwarding. Enable only if you already operate a TURN server. +turn: + enabled: false + domain: "" + cert_file: "" + key_file: "" + tls_port: 5349 + udp_port: 443 + external_tls: true + +# The keys map controls which API key/secret pairs are valid. +# Generate your own pair with: openssl rand -hex 32 +# Secrets must be at least 32 characters long. +keys: + LIVEKIT_API_KEY_PLACEHOLDER: LIVEKIT_API_SECRET_PLACEHOLDER + +room: + auto_create: false diff --git a/configs/nginx/matrix-proxy.conf b/configs/nginx/matrix-proxy.conf new file mode 100644 index 0000000..50a85a0 --- /dev/null +++ b/configs/nginx/matrix-proxy.conf @@ -0,0 +1,54 @@ +server { + listen 80; + listen 443 ssl; + server_name localhost; + + ssl_certificate /etc/nginx/certs/localhost.crt; + ssl_certificate_key /etc/nginx/certs/localhost.key; + + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials "true" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization" always; + + # Handle preflight requests generically + if ($request_method = OPTIONS) { + return 204; + } + + # Serve Matrix client well-known metadata + location /.well-known/matrix/client { + default_type application/json; + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials "true" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization" always; + alias /var/www/well-known/matrix-client.json; + } + + # Optional Matrix server discovery stub (useful for federation later) + location /.well-known/matrix/server { + default_type application/json; + return 200 '{"m.server":"localhost:8008"}'; + } + + # Proxy Matrix Client/Server and Synapse admin APIs to the Synapse container + location / { + proxy_pass http://synapse:8008; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 50m; + + # Hide upstream CORS headers and set our own + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + proxy_hide_header Access-Control-Allow-Headers; + proxy_hide_header Access-Control-Expose-Headers; + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials "true" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization" always; + } +} diff --git a/configs/nginx/matrix-rtc-proxy.conf b/configs/nginx/matrix-rtc-proxy.conf new file mode 100644 index 0000000..e1d4053 --- /dev/null +++ b/configs/nginx/matrix-rtc-proxy.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name matrix-rtc.localhost; + + # MatrixRTC Authorization Service (lk-jwt-service) + location ^~ /livekit/jwt/ { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_pass http://livekit-jwt:6080/; + } + + # LiveKit SFU Websocket (signalling) + location ^~ /livekit/sfu/ { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Accept-Encoding gzip; + + proxy_send_timeout 120; + proxy_read_timeout 120; + proxy_buffering off; + + proxy_pass http://livekit:7880/; + } +} diff --git a/configs/well-known/matrix-client.example.json b/configs/well-known/matrix-client.example.json new file mode 100644 index 0000000..b0142c4 --- /dev/null +++ b/configs/well-known/matrix-client.example.json @@ -0,0 +1,12 @@ +{ + "m.homeserver": { + "base_url": "https://matrix.example.com", + "server_name": "matrix.example.com" + }, + "org.matrix.msc4143.rtc_foci": [ + { + "type": "livekit", + "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" + } + ] +} diff --git a/docker-compose.livekit.yml b/docker-compose.livekit.yml new file mode 100644 index 0000000..756d951 --- /dev/null +++ b/docker-compose.livekit.yml @@ -0,0 +1,48 @@ +services: + livekit: + image: livekit/livekit-server:latest + restart: unless-stopped + command: --config /etc/livekit/livekit.yaml + volumes: + - ./configs/livekit/livekit.yaml:/etc/livekit/livekit.yaml:ro + ports: + - "${LIVEKIT_HTTP_PORT:-7880}:7880/tcp" + - "${LIVEKIT_TCP_PORT:-7881}:7881/tcp" + - "${LIVEKIT_HEALTH_PORT:-7882}:7882/tcp" + - "${LIVEKIT_UDP_PORT_RANGE_START:-50000}-${LIVEKIT_UDP_PORT_RANGE_END:-50200}:${LIVEKIT_UDP_PORT_RANGE_START:-50000}-${LIVEKIT_UDP_PORT_RANGE_END:-50200}/udp" + networks: + default: + ipv4_address: 10.10.10.6 + + livekit-jwt: + image: ghcr.io/element-hq/lk-jwt-service:latest-ci + restart: unless-stopped + environment: + LIVEKIT_JWT_BIND: ${LIVEKIT_JWT_BIND:-:6080} + LIVEKIT_URL: ${LIVEKIT_WS_URL} + LIVEKIT_KEY: ${LIVEKIT_API_KEY} + LIVEKIT_SECRET: ${LIVEKIT_API_SECRET} + LIVEKIT_FULL_ACCESS_HOMESERVERS: ${MATRIX_SERVER_NAME} + LIVEKIT_INSECURE_SKIP_VERIFY_TLS: ${LIVEKIT_INSECURE_SKIP_VERIFY_TLS:-false} + networks: + default: + ipv4_address: 10.10.10.7 + + matrix-rtc-proxy: + image: nginx:alpine + restart: unless-stopped + depends_on: + - livekit + - livekit-jwt + volumes: + - ./configs/nginx/matrix-rtc-proxy.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - "${LIVEKIT_JWT_PORT:-6080}:80" + networks: + default: + ipv4_address: 10.10.10.9 + +networks: + default: + name: matrix_net + external: true diff --git a/docker-compose.yml b/docker-compose.yml index 87fbfa3..0c64950 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,10 +32,7 @@ services: default: ipv4_address: 10.10.10.4 volumes: - - matrix_synapse_data:/data - ports: - - "8008:8008" - - "8009:8009" + - ./synapse_data:/data depends_on: - postgres user: "0:0" @@ -54,15 +51,28 @@ services: depends_on: - synapse + matrix-proxy: + image: nginx:alpine + restart: unless-stopped + depends_on: + - synapse + networks: + default: + ipv4_address: 10.10.10.8 + ports: + - "8008:80" + - "443:443" + volumes: + - ./configs/nginx/matrix-proxy.conf:/etc/nginx/conf.d/default.conf:ro + - ./configs/well-known:/var/www/well-known:ro + - ./configs/nginx/certs:/etc/nginx/certs:ro + volumes: matrix_postgres_data: external: true name: matrix_postgres_data - matrix_synapse_data: - external: true - name: matrix_synapse_data networks: default: name: matrix_net - external: true \ No newline at end of file + external: true diff --git a/docs/element-call-livekit.md b/docs/element-call-livekit.md new file mode 100644 index 0000000..cd7072e --- /dev/null +++ b/docs/element-call-livekit.md @@ -0,0 +1,161 @@ +# Element Call + LiveKit en este stack + +Esta guía resume cómo se conecta el repositorio `element-call` incluido en este +proyecto y qué pasos seguir para habilitar el backend MatrixRTC (LiveKit + +lk-jwt-service) dentro del entorno Docker ya existente. + +> ℹ️ Para más contexto técnico revisa también `element-call/docs/self-hosting.md` +> y `element-call/backend/dev_nginx.conf`, donde Element documenta la arquitectura +> oficial de referencia. + +## 1. Requisitos del homeserver + +1. Añade las MSC necesarias en `synapse_data/homeserver.yaml` (o en tu plantilla): + + ```yaml + experimental_features: + msc3266_enabled: true + msc4222_enabled: true + msc4354_enabled: true + + max_event_delay_duration: 24h + + rc_message: + per_second: 0.5 + burst_count: 30 + + rc_delayed_event_mgmt: + per_second: 1 + burst_count: 20 + ``` + +2. Asegúrate de tener un listener `federation` o `openid` expuesto (lo necesita + el `lk-jwt-service` para validar los tokens Matrix). + +3. Configura `.well-known/matrix/client` para publicar el backend MatrixRTC: + + ```json + { + "m.homeserver": { + "base_url": "https://matrix.example.com", + "server_name": "matrix.example.com" + }, + "org.matrix.msc4143.rtc_foci": [ + { + "type": "livekit", + "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" + } + ] + } + ``` + + El archivo `configs/well-known/matrix-client.example.json` sirve como plantilla; + cópialo a `configs/well-known/matrix-client.json` y ajústalo con tu dominio. + Sirve este JSON con TLS (Nginx/Caddy) en `https:///.well-known/matrix/client`. + +## 2. Preparar configuraciones locales + +1. **Variables de entorno** + Edita `.env` (o crea uno nuevo desde `.env.example`) y completa los valores: + - `MATRIX_SITE_BASE_URL`, `MATRIX_RTC_BASE_URL` + - `LIVEKIT_WS_URL`, `LIVEKIT_JWT_URL` + - `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` + - Puertos `LIVEKIT_*` + +2. **LiveKit** + - Copia `configs/livekit/livekit.example.yaml` a + `configs/livekit/livekit.yaml`. + - Cambia región, puertos si es necesario y, sobre todo, define tu par + `keys` con el mismo `LIVEKIT_API_KEY`/`LIVEKIT_API_SECRET` del `.env`. + +3. **Reverse proxy** + Expón dos rutas públicas (con TLS) hacia los nuevos contenedores: + + | Ruta pública | Proxy interno | + | ------------------------------------------- | ----------------------------------- | + | `https://matrix-rtc.example.com/livekit/jwt` | `http://livekit-jwt:${LIVEKIT_JWT_PORT}` | + | `wss://matrix-rtc.example.com/livekit/sfu` | `http://livekit:${LIVEKIT_HTTP_PORT}` | + + Ejemplo básico en Nginx: + + ```nginx + server { + server_name matrix-rtc.example.com; + listen 443 ssl http2; + + location ^~ /livekit/jwt/ { + proxy_pass http://10.10.10.7:${LIVEKIT_JWT_PORT}/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ^~ /livekit/sfu/ { + proxy_pass http://10.10.10.6:${LIVEKIT_HTTP_PORT}/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 120; + proxy_send_timeout 120; + } + } + ``` + +## 3. Desplegar los servicios LiveKit + +1. Levanta la red base y los servicios habituales (`docker-compose up -d`). +2. Arranca los componentes MatrixRTC usando el archivo adicional: + + ```bash + docker compose -f docker-compose.yml -f docker-compose.livekit.yml up -d livekit livekit-jwt + ``` + + El archivo `docker-compose.livekit.yml` añade: + - `livekit`: SFU oficial (`livekit/livekit-server`) usando `configs/livekit/livekit.yaml`. + - `livekit-jwt`: servicio `ghcr.io/element-hq/lk-jwt-service` que firma los + tokens que consumen los clientes MatrixRTC. + +3. Comprueba que los servicios responden: + + ```bash + # JWT service + curl -sf https://matrix-rtc.example.com/livekit/jwt/healthz + + # LiveKit (revisa los logs o escucha el puerto de salud configurado) + docker logs -f element_matrix_chat-livekit-1 + ``` + +## 4. Publicar el frontend Element Call (opcional) + +El backend LiveKit habilita las llamadas MatrixRTC para Element Web / Element X +una vez que el `.well-known` anuncia el foco. Si además quieres exponer la +aplicación Element Call en modo standalone: + +1. Construye el frontend: + + ```bash + cd element-call + corepack enable + yarn install --immutable + cp config/config.sample.json public/config.json # o personaliza uno nuevo + yarn build + ``` + +2. Sirve los archivos de `element-call/dist/` con el servidor web de tu +preferencia (o crea tu propia imagen Docker basada en `element-call/Dockerfile`). +Recuerda actualizar `public/config.json` para apuntar a tu homeserver y, si lo +quieres forzar, al `livekit_service_url`. + +## 5. Verificación final + +1. Abre `https://matrix.example.com/.well-known/matrix/client` y valida que el + JSON devuelve correctamente tu `livekit_service_url`. +2. Desde Element Web (usuario registrado en el mismo homeserver) inicia una + llamada en una sala y revisa en los logs del contenedor `livekit-jwt` + que se emite el token (`docker logs -f `). +3. Usa herramientas como [testmatrix](https://codeberg.org/spaetz/testmatrix) para + validar que tu sitio expone MatrixRTC correctamente. + +Con estos pasos tendrás tanto el backend MatrixRTC (LiveKit + Authorization +Service) como la referencia para desplegar el frontend Element Call dentro de +tu servidor Matrix. diff --git a/element-call b/element-call new file mode 160000 index 0000000..5afc3c2 --- /dev/null +++ b/element-call @@ -0,0 +1 @@ +Subproject commit 5afc3c25987164c795faf6de2b8e6585e94e749d diff --git a/scripts/setup.sh b/scripts/setup.sh index 2ce87d9..a09f874 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,6 +12,10 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +LIVEKIT_COMPOSE_FILE="docker-compose.livekit.yml" +LIVEKIT_CONFIG_FILE="configs/livekit/livekit.yaml" +LIVEKIT_CONFIG_TEMPLATE="configs/livekit/livekit.example.yaml" + # Verificar Docker if ! command -v docker &> /dev/null; then echo -e "${RED}❌ Docker no está instalado${NC}" @@ -50,6 +54,33 @@ fi echo -e "${BLUE}📁 Creando directorios...${NC}" mkdir -p synapse_data/appservices mkdir -p backups +mkdir -p configs/livekit + +# Crear volúmenes externos requeridos por docker-compose si no existen +for volume in matrix_postgres_data; do + if ! docker volume ls --format '{{.Name}}' | grep -qx "$volume"; then + echo -e "${BLUE}🗄️ Creando volumen Docker $volume...${NC}" + docker volume create "$volume" >/dev/null + echo -e "${GREEN}✅ Volumen $volume creado${NC}" + else + echo -e "${YELLOW}ℹ️ Volumen $volume ya existe${NC}" + fi +done + +# Verificar configuración de LiveKit +if [ ! -f "${LIVEKIT_CONFIG_FILE}" ]; then + if [ -f "${LIVEKIT_CONFIG_TEMPLATE}" ]; then + cp "${LIVEKIT_CONFIG_TEMPLATE}" "${LIVEKIT_CONFIG_FILE}" + fi + echo -e "${RED}❌ No se encontró ${LIVEKIT_CONFIG_FILE}${NC}" + echo -e "${YELLOW} Se creó una plantilla base, edítala antes de continuar.${NC}" + exit 1 +fi + +if [ ! -f "${LIVEKIT_COMPOSE_FILE}" ]; then + echo -e "${RED}❌ Falta ${LIVEKIT_COMPOSE_FILE}. No se puede iniciar LiveKit.${NC}" + exit 1 +fi # Generar configuración de Synapse si no existe if [ ! -f synapse_data/homeserver.yaml ]; then @@ -74,6 +105,8 @@ fi echo -e "${BLUE}🐳 Iniciando contenedores...${NC}" docker-compose up -d +echo -e "${BLUE}📡 Iniciando LiveKit + lk-jwt...${NC}" +docker-compose -f "${LIVEKIT_COMPOSE_FILE}" up -d echo -e "${BLUE}⏳ Esperando que los servicios estén listos...${NC}" sleep 20 @@ -81,15 +114,29 @@ sleep 20 # Verificar que todos los servicios estén funcionando echo -e "${BLUE}🔍 Verificando servicios...${NC}" -services=("postgres:5432" "synapse:8008" "element:8081" "synapse-admin:8082") +services=( + "postgres:5432:tcp" + "synapse:8008:http" + "element:8081:http" + "synapse-admin:8082:http" + "livekit:${LIVEKIT_HTTP_PORT:-7880}:http" + "livekit-jwt:${LIVEKIT_JWT_PORT:-6080}:http" +) for service in "${services[@]}"; do - name=${service%:*} - port=${service#*:} + IFS=":" read -r name port proto <<<"$service" - if curl -s http://localhost:$port > /dev/null 2>&1; then - echo -e "${GREEN}✅ $name (puerto $port) - OK${NC}" + if [ "$proto" = "http" ]; then + if curl -s --max-time 5 "http://localhost:$port" > /dev/null 2>&1; then + echo -e "${GREEN}✅ $name (puerto $port) - OK${NC}" + else + echo -e "${RED}❌ $name (puerto $port) - ERROR${NC}" + fi else - echo -e "${RED}❌ $name (puerto $port) - ERROR${NC}" + if timeout 5 bash -c "cat < /dev/null > /dev/tcp/localhost/$port" > /dev/null 2>&1; then + echo -e "${GREEN}✅ $name (puerto $port) - OK${NC}" + else + echo -e "${RED}❌ $name (puerto $port) - ERROR${NC}" + fi fi done @@ -118,4 +165,4 @@ echo -e "${BLUE}🔐 Credenciales de administrador:${NC}" echo -e " • Usuario: ${GREEN}${ADMIN_USERNAME}${NC}" echo -e " • Contraseña: ${GREEN}${ADMIN_PASSWORD}${NC}" echo -echo -e "${YELLOW}💡 Para crear más usuarios usa: ./scripts/create-user.sh${NC}" \ No newline at end of file +echo -e "${YELLOW}💡 Para crear más usuarios usa: ./scripts/create-user.sh${NC}"