feat(ess-pro/compose): deploy Element Server Suite Pro via Compose

initial commit of the converted role from helm charts for qubernetis to compose ansible role
This commit is contained in:
Tobias Wüst 2026-06-04 10:52:05 +02:00
parent c11f019aae
commit 32eca6b923
33 changed files with 1906 additions and 0 deletions

View file

@ -0,0 +1,9 @@
HTTP/1.0 429 Too Many Requests
Cache-Control: no-cache
Connection: close
Content-Type: application/json
access-control-allow-origin: *
access-control-allow-methods: GET, POST, PUT, DELETE, OPTIONS
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, Authorization
{"errcode":"M_UNKNOWN","error":"Server is unavailable"}

View file

@ -0,0 +1,4 @@
# {{ ansible_managed }}
{% for cidr in ess_admin_allow_ips %}
{{ cidr }}
{% endfor %}

View file

@ -0,0 +1,177 @@
# {{ ansible_managed }}
# Adapted from ess-helm chart {{ ess_chart_version }} (ess-haproxy ConfigMap).
# K8s DNS-SRV-based service discovery replaced with direct compose hostnames.
global
maxconn 20000
log stdout format raw local0 info
tune.maxrewrite 4096
stats socket ipv4@127.0.0.1:1999 level admin
dns-accept-family ipv4
defaults
mode http
fullconn 10000
maxconn 10000
log global
option forwardfor if-none
option forwarded
timeout connect 5s
timeout queue 60s
timeout client 900s
timeout http-keep-alive 900s
timeout http-request 10s
timeout server 180s
http-reuse aggressive
default-server maxconn 500
option redispatch
compression algo gzip
compression type text/plain text/html text/xml application/json text/css
hash-type consistent sdbm
# Compose resolves service names via the embedded DNS (127.0.0.11). We point
# HAProxy at it so backend health-checks pick up restarts properly.
resolvers compose-dns
nameserver dns1 127.0.0.11:53
accepted_payload_size 8192
hold timeout 600s
hold refused 600s
frontend prometheus
bind *:8405
http-request use-service prometheus-exporter if { path /metrics }
monitor-uri /haproxy_test
no log
frontend http-blackhole
bind *:8009
http-request deny content-type application/json string '{"errcode": "M_FORBIDDEN", "error": "Blocked"}'
frontend startup
bind *:8406
acl synapse_dead nbsrv(synapse-main) lt 1
monitor-uri /synapse_ready
monitor fail if synapse_dead
# ----------------------------------------------------------------------------
# Synapse traffic — main entrypoint that the DMZ Traefik points at for matrix.*
# ----------------------------------------------------------------------------
frontend synapse-http-in
bind *:8008
errorfile 503 /synapse/429.http
http-request capture hdr(host) len 32
http-request capture req.fhdr(x-forwarded-for) len 64
http-request capture req.fhdr(user-agent) len 200
http-request set-header X-Forwarded-Proto https if !{ hdr(X-Forwarded-Proto) -m found }
http-request set-var(txn.x_forwarded_proto) hdr(x-forwarded-proto)
http-response add-header Strict-Transport-Security max-age=31536000 if { var(txn.x_forwarded_proto) -m str -i "https" }
# Access token extraction (used by upstream rate-limit decisions)
http-request set-var(req.access_token) urlp("access_token") if { urlp("access_token") -m found }
http-request set-var(req.access_token) req.fhdr(Authorization),word(2," ") if { hdr_beg("Authorization") -i "Bearer " }
http-request set-header X-Access-Token %[var(req.access_token)]
http-response set-header Permissions-Policy "interest-cohort=()"
# Admin endpoint IP allow-list
acl is_admin path_reg ^/_synapse/admin/.*
http-request set-var(txn.user_ip) req.fhdr(x-forwarded-for) if { hdr(x-forwarded-for) -m found }
http-request set-var(txn.user_ip) src if !{ hdr(x-forwarded-for) -m found }
acl allow_ip_admin var(txn.user_ip) -m ip -f /synapse/admin-allow-ips.lst
http-request deny if !allow_ip_admin is_admin
# FOSS-worker path maps (empty by default; reserved for advanced worker splits)
acl has_get_map path -m reg -M -f /synapse/path_map_file_get
http-request set-var(req.backend) path,map_reg(/synapse/path_map_file_get,main) if has_get_map METH_GET
http-request set-var(req.backend) path,map_reg(/synapse/path_map_file,main) unless { var(req.backend) -m found }
# Pro federation-reader worker: takes /event, /state, /state_ids reads
acl has_available_pro_fed nbsrv('synapse-pro-federation-api-requests') ge 1
http-request set-var(req.backend) str('pro-federation-api-requests') if has_available_pro_fed { path -m reg ^/_matrix/federation/v1/event/ }
http-request set-var(req.backend) str('pro-federation-api-requests') if has_available_pro_fed { path -m reg ^/_matrix/federation/v1/state/ }
http-request set-var(req.backend) str('pro-federation-api-requests') if has_available_pro_fed { path -m reg ^/_matrix/federation/v1/state_ids/ }
# CORS preflight short-circuits
acl rendezvous path_beg /_matrix/client/unstable/org.matrix.msc4108/rendezvous
acl rendezvous path_beg /_synapse/client/rendezvous
use_backend return_204_rendezvous if { method OPTIONS } rendezvous
use_backend return_204_synapse if { method OPTIONS }
# Failover from pro-fed-reader to main if the worker is unavailable
acl has_failover var(req.backend) -m str "pro-federation-api-requests"
acl backend_unavailable str(),concat('synapse-',req.backend),nbsrv lt 1
use_backend synapse-main-failover if has_failover backend_unavailable
use_backend synapse-%[var(req.backend)]
backend synapse-main
default-server maxconn 250
option httpchk
http-check connect port 8080
http-check send meth GET uri /health
server main synapse-main:8008 check port 8080 resolvers compose-dns
backend synapse-main-failover
default-server maxconn 250
option httpchk
http-check connect port 8080
http-check send meth GET uri /health
server main synapse-main:8008 check port 8080 resolvers compose-dns
backend synapse-pro-federation-api-requests
option httpchk
http-check connect port 8008
http-check send meth GET uri /health/alive
balance uri whole
# The federation-reader worker is a Rust service speaking h2c.
{% for i in range(ess_synapse_fed_reader_replicas | int) %}
server fed-reader-{{ i }} synapse-fed-reader-{{ i }}:8008 check resolvers compose-dns proto h2
{% endfor %}
backend return_204_synapse
http-request return status 204 hdr "Access-Control-Allow-Origin" "*" hdr "Access-Control-Allow-Methods" "GET, HEAD, POST, PUT, DELETE, OPTIONS" hdr "Access-Control-Allow-Headers" "Origin, X-Requested-With, Content-Type, Accept, Authorization, Date" hdr "Access-Control-Expose-Headers" "Synapse-Trace-Id, Server"
backend return_204_rendezvous
http-request return status 204 hdr "Access-Control-Allow-Origin" "*" hdr "Access-Control-Allow-Methods" "GET, HEAD, POST, PUT, DELETE, OPTIONS" hdr "Access-Control-Allow-Headers" "Origin, Content-Type, Accept, Content-Type, If-Match, If-None-Match" hdr "Access-Control-Expose-Headers" "Synapse-Trace-Id, Server, ETag"
# ----------------------------------------------------------------------------
# Well-known — served at the apex domain via the same HAProxy.
# DMZ Traefik routes Host=`{{ ess_server_name }}` && PathPrefix(/.well-known) here.
# ----------------------------------------------------------------------------
frontend well-known-in
bind *:8010
acl is_delete_put_post_method method DELETE POST PUT
http-request deny status 405 if is_delete_put_post_method
acl well-known path /.well-known/matrix/server
acl well-known path /.well-known/matrix/client
acl well-known path /.well-known/matrix/support
acl well-known path /.well-known/element/element.json
http-request redirect code 301 location https://{{ ess_hostnames.element_web }} unless well-known
use_backend well-known-static if well-known
default_backend well-known-no-match
backend well-known-static
mode http
http-after-response set-header X-Frame-Options SAMEORIGIN
http-after-response set-header X-Content-Type-Options nosniff
http-after-response set-header X-XSS-Protection "1; mode=block"
http-after-response set-header Content-Security-Policy "frame-ancestors 'self'"
http-after-response set-header X-Robots-Tag "noindex, nofollow, noarchive, noimageindex"
http-after-response set-header Access-Control-Allow-Origin *
http-after-response set-header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
http-after-response set-header Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization"
http-request return status 200 content-type "application/json" file "/well-known/server" if { path /.well-known/matrix/server }
http-request return status 200 content-type "application/json" file "/well-known/client" if { path /.well-known/matrix/client }
http-request return status 200 content-type "application/json" file "/well-known/support" if { path /.well-known/matrix/support }
http-request return status 200 content-type "application/json" file "/well-known/element.json" if { path /.well-known/element/element.json }
backend well-known-no-match
mode http
http-request deny status 404
backend return_500
http-request deny deny_status 500

View file

@ -0,0 +1,5 @@
# {{ ansible_managed }}
# Map matrix paths to worker backends. Format: path_regexp backend_name
# Chart default: empty (no FOSS-worker splits). Reserved for advanced
# worker topologies; the Pro federation-reader routing is hard-coded in
# haproxy.cfg via the synapse-pro-federation-api-requests backend.

View file

@ -0,0 +1,2 @@
# {{ ansible_managed }}
# GET-only worker path map. See path_map_file for context.

View file

@ -0,0 +1,11 @@
{
"m.homeserver": {
"base_url": "https://{{ ess_hostnames.synapse }}"
},
"org.matrix.msc4143.rtc_foci": [
{
"livekit_service_url": "https://{{ ess_hostnames.matrix_rtc }}",
"type": "livekit"
}
]
}

View file

@ -0,0 +1 @@
{"m.server": "{{ ess_hostnames.synapse }}:443"}

View file

@ -0,0 +1 @@
{}