Files
ess-docker-compose/caddy/Caddyfile
T
wmair ac40364f65 Add self-hosted Element Call frontend container
Previously Element Call used call.element.io for the frontend JS while
routing media through a local LiveKit SFU. This adds the self-hosted
Element Call frontend so no requests go to Element's CDN at all.

Changes:
- docker-compose.yml: add element-call service (ghcr.io/element-hq/element-call)
  under the element-call profile, port 8083:8080. No config required — the app
  reads the Matrix homeserver from URL params passed by Element Web, and gets
  the livekit_service_url from the homeserver's .well-known/matrix/client.
- caddy/Caddyfile: add call.example.test:443 virtual host for local dev.
- deploy.sh: add CALL_DOMAIN variable (call.example.test / call.<domain>),
  prompt for call subdomain in production mode, write CALL_DOMAIN to .env,
  update element_call.url from call.element.io to the self-hosted CALL_DOMAIN
  in all three places (element/config.json generator and both Caddyfile
  inline JSON strings), add Caddy blocks for CALL_DOMAIN in local and
  production Caddyfile generation, add CALL_DOMAIN to /etc/hosts hint,
  update summary output.
- README.md: rewrite to reflect actual project state — deploy.sh workflow,
  all optional components, correct architecture diagram, bridge setup, ports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:54:40 +01:00

290 lines
9.2 KiB
Caddyfile

# Local Development Caddyfile for Matrix Stack
# Uses self-signed certificates for local HTTPS testing
# Auto-generated by deploy.sh — do not edit manually
{
# Use local CA for self-signed certificates
local_certs
# Enable admin API
admin 0.0.0.0:2019
}
# =========================
# Matrix Homeserver (Synapse)
# =========================
matrix.example.test:443 {
# TLS with self-signed cert
tls internal
# Well-known client endpoint
# IMPORTANT: The respond body must stay on a single line. If you edit this file manually
# and your editor wraps the JSON, Caddy will refuse to start with "invalid control character".
@wk path /.well-known/matrix/client
handle @wk {
header Content-Type application/json
header Access-Control-Allow-Origin "*"
respond `{"m.homeserver":{"base_url":"https://matrix.example.test"},"m.authentication":{"issuer":"https://auth.example.test/"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://rtc.example.test/livekit/jwt"}]}` 200
}
# Well-known server endpoint (federation)
@wk_server path /.well-known/matrix/server
handle @wk_server {
header Content-Type application/json
respond `{"m.server":"matrix.example.test:443"}` 200
}
# Rendezvous endpoints for QR code login (MSC4108)
@rendezvous path_regexp rendezvous ^/_matrix/client/(unstable|v1)/org\.matrix\.(msc3886|msc4108)/rendezvous.*$
handle @rendezvous {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, Accept, If-Match, If-None-Match"
header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
encode {
}
reverse_proxy synapse:8008 {
header_down -Access-Control-Allow-Origin
header_down -Access-Control-Allow-Methods
header_down -Access-Control-Allow-Headers
header_down -Vary
}
}
# Client versions endpoint with CORS
@versions path /_matrix/client/versions
handle @versions {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, Accept"
header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
reverse_proxy synapse:8008 {
header_down -Access-Control-Allow-Origin
header_down -Access-Control-Allow-Methods
header_down -Access-Control-Allow-Headers
header_down -Vary
}
}
# CORS preflight for auth metadata
@auth_preflight {
method OPTIONS
path /_matrix/client/unstable/org.matrix.msc2965/auth_metadata
}
handle @auth_preflight {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, Accept"
header Access-Control-Max-Age "86400"
respond 204
}
# CORS preflight for all Matrix API
@preflight {
method OPTIONS
path_regexp matrix ^/_matrix/.*$
}
handle @preflight {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, Accept"
header Access-Control-Max-Age "86400"
respond 204
}
# MAS compat endpoints (login/logout/refresh) with CORS
@compat path \
/_matrix/client/v3/login* \
/_matrix/client/v3/logout* \
/_matrix/client/v3/refresh* \
/_matrix/client/r0/login* \
/_matrix/client/r0/logout* \
/_matrix/client/r0/refresh*
handle @compat {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, Accept"
header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
reverse_proxy mas:8080 {
header_down -Access-Control-Allow-Origin
header_down -Access-Control-Allow-Methods
header_down -Access-Control-Allow-Headers
header_down -Vary
}
}
# Everything else under /_matrix → Synapse with CORS
@matrix_rest path_regexp matrix ^/_matrix/.*$
handle @matrix_rest {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, Accept"
header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers"
reverse_proxy synapse:8008 {
header_down -Access-Control-Allow-Origin
header_down -Access-Control-Allow-Methods
header_down -Access-Control-Allow-Headers
header_down -Vary
}
}
# Default: everything else → Synapse
handle {
reverse_proxy synapse:8008
}
}
# =========================
# Matrix Authentication Service (MAS)
# =========================
auth.example.test:443 {
tls internal
# OIDC Discovery
@disco path /.well-known/openid-configuration
handle @disco {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Methods "GET, OPTIONS"
header ?Access-Control-Allow-Headers "*"
reverse_proxy mas:8080
}
# Dynamic Client Registration: CORS preflight
@reg_opts {
method OPTIONS
path /oauth2/registration
}
handle @reg_opts {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Methods "POST, OPTIONS"
header ?Access-Control-Allow-Headers "*"
respond 204
}
# Dynamic Client Registration (POST)
@reg path /oauth2/registration
route @reg {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Methods "POST, OPTIONS"
header ?Access-Control-Allow-Headers "*"
reverse_proxy mas:8080
}
# JWKS preflight
@jwks_opts {
method OPTIONS
path /oauth2/keys.json
}
handle @jwks_opts {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Methods "GET, OPTIONS"
header ?Access-Control-Allow-Headers "*"
respond 204
}
# Map keys.json → /oauth2/jwks (MAS naming)
@jwksjson path /oauth2/keys.json
route @jwksjson {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Methods "GET, OPTIONS"
header ?Access-Control-Allow-Headers "*"
uri replace /oauth2/keys.json /oauth2/jwks
reverse_proxy mas:8080
}
# Generic OAuth2 endpoints
@oauth path /oauth2/*
route @oauth {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Methods "GET, OPTIONS, POST"
header ?Access-Control-Allow-Headers "*"
reverse_proxy mas:8080
}
# Account portal
handle_path /account/* {
reverse_proxy mas:8080
}
# Authelia endpoints (proxy to authelia)
handle_path /authelia/* {
reverse_proxy authelia:9091
}
# Fallback: everything else to MAS
handle {
reverse_proxy mas:8080
}
# Add CORS on error responses
handle_errors {
header ?Access-Control-Allow-Origin "*"
header ?Access-Control-Allow-Headers "*"
header ?Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
}
}
# =========================
# Authelia SSO
# =========================
authelia.example.test:443 {
tls internal
reverse_proxy authelia:9091
}
# =========================
# Element Web Client
# =========================
element.example.test:443 {
tls internal
# Serve config.json with proper settings
# IMPORTANT: respond body must stay on a single line — see well-known note above.
@cfg path /config.json
handle @cfg {
header Content-Type application/json
header Cache-Control no-store
respond `{"default_server_config":{"m.homeserver":{"base_url":"https://matrix.example.test","server_name":"matrix.example.test"}},"default_server_name":"matrix.example.test","disable_custom_urls":false,"disable_guests":true,"features":{"feature_oidc_aware_navigation":true,"feature_element_call_video_rooms":true},"element_call":{"url":"https://call.element.io","participant_limit":8,"brand":"Element Call"}}` 200
}
# Everything else to Element container
handle {
reverse_proxy element:80
}
}
# =========================
# Element Admin
# =========================
admin.example.test:443 {
tls internal
handle {
reverse_proxy element-admin:8080
}
}
# =========================
# Element Call (LiveKit)
# =========================
rtc.example.test:443 {
tls internal
handle_path /livekit/jwt* {
reverse_proxy lk-jwt-service:8080
}
handle_path /livekit/sfu* {
reverse_proxy livekit:7880
}
}
# =========================
# Element Call Frontend
# =========================
call.example.test:443 {
tls internal
reverse_proxy element-call:8080
}