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:
parent
c11f019aae
commit
32eca6b923
33 changed files with 1906 additions and 0 deletions
229
roles/ess_pro_compose/README.md
Normal file
229
roles/ess_pro_compose/README.md
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# Ansible Role: ess_pro_compose
|
||||
|
||||
Deploys the full **Element Server Suite Pro v26.5.1** stack as a single docker
|
||||
compose project, modelled 1:1 on the official `matrix-stack` Helm chart from
|
||||
Element. Fronted by the existing DMZ Traefik, secrets sourced from OpenBao
|
||||
(plus locally-generated cryptographic material), same conventions as the
|
||||
other `digitalboard.core` roles.
|
||||
|
||||
> **Licensing note:** ESS Pro is distributed as a Helm/Kubernetes product.
|
||||
> Running the Pro images under docker compose requires explicit vendor
|
||||
> agreement, which is in place for this deployment.
|
||||
|
||||
## Architecture
|
||||
|
||||
12 services, mirroring the chart:
|
||||
|
||||
```
|
||||
┌───────────────┐
|
||||
┌──────────────────────HTTP──▶│ element-web │
|
||||
│ └───────────────┘
|
||||
│ ┌───────────────┐
|
||||
│ ┌──────────────────HTTP──▶│ element-admin │
|
||||
│ │ └───────────────┘
|
||||
│ │ ┌───────────────┐
|
||||
│ │ ┌───────────────HTTP──▶│ mas │ ─┐
|
||||
DMZ Traefik ──┤ │ │ └───────────────┘ │ ┌──────────┐
|
||||
│ │ │ ┌───────────────┐ ├─▶│ postgres │
|
||||
│ │ │ ┌────────────HTTP──▶│ haproxy │ │ └──────────┘
|
||||
│ │ │ │ │ (Pro Image) │ │ ┌──────────┐
|
||||
│ │ │ │ └───┬─────────┬─┘ │ │ redis │
|
||||
│ │ │ │ │ │ │ └──────────┘
|
||||
│ │ │ │ ┌─────────────────┘ │ │
|
||||
│ │ │ │ ▼ ▼ │
|
||||
│ │ │ │ ┌──────────────┐ ┌────────────────┴───────┐
|
||||
│ │ │ │ │ synapse-main │◀──▶│ synapse-fed-reader-0..N│
|
||||
│ │ │ │ │ (Python) │ │ (Rust Pro worker) │
|
||||
│ │ │ │ └──────────────┘ └────────────────────────┘
|
||||
│ │ │ │
|
||||
│ │ │ └──HTTP(/.well-known)──▶ haproxy (same instance)
|
||||
│ │ │
|
||||
│ │ └─────HTTP(/sfu/get)──────▶┌──────────────────┐
|
||||
│ │ │ matrix-rtc-auth │ (lk-jwt)
|
||||
│ │ └──────────┬───────┘
|
||||
│ └─HTTP+TCP/30001+UDP/30002───▶┌──────────▼───────┐
|
||||
│ │ matrix-rtc-sfu │ (LiveKit)
|
||||
│ └──────────────────┘
|
||||
│
|
||||
└─ HTTPS termination on Traefik, plain HTTP downstream
|
||||
```
|
||||
|
||||
## Hostnames
|
||||
|
||||
| Component | Hostname |
|
||||
| --------------------- | ------------------------------------ |
|
||||
| Matrix `serverName` | `digitalboard.ch` |
|
||||
| Synapse (via HAProxy) | `matrix.digitalboard.ch` |
|
||||
| MAS | `account.digitalboard.ch` |
|
||||
| Element Web | `chat.digitalboard.ch` |
|
||||
| Element Admin | `admin.digitalboard.ch` |
|
||||
| Matrix RTC / Element Call | `mrtc.digitalboard.ch` |
|
||||
| `.well-known/matrix/` | `digitalboard.ch` (apex) |
|
||||
|
||||
Naming follows Element's official docs (`account.*`, `mrtc.*`). Keycloak on
|
||||
`auth.digitalboard.ch` is untouched.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Collections on the control node:
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install community.docker community.hashi_vault
|
||||
pip install docker hvac
|
||||
```
|
||||
|
||||
2. Target host: Debian bookworm with Docker CE + compose plugin (the shared
|
||||
digitalboard docker role handles this) and `python3-cryptography`.
|
||||
|
||||
3. DMZ Traefik attached to the `proxy` network with a `websecure` entrypoint
|
||||
and a `letsencrypt` certresolver.
|
||||
|
||||
4. DNS A/AAAA records for the apex + five subdomains.
|
||||
|
||||
5. DMZ firewall NAT-forwards TCP/`30001` and UDP/`30002` to the host (Element
|
||||
Call media ports — fixed by the chart, not the wide 50k–60k range).
|
||||
|
||||
6. ESS Pro registry credentials (and Authentik OIDC client secret) bootstrapped
|
||||
in OpenBao at `kv/digitalboard/ess-compose` via
|
||||
`examples/openbao-bootstrap.sh`.
|
||||
|
||||
## How secrets work
|
||||
|
||||
Two layers:
|
||||
|
||||
- **From OpenBao:** Element registry username/token and Authentik OIDC client
|
||||
secret. Pulled at playbook time via `community.hashi_vault.vault_kv2_get`
|
||||
lookups, same pattern as the other digitalboard.core roles.
|
||||
|
||||
- **Generated locally:** The 14 cryptographic secrets the chart's
|
||||
`init-secrets` job normally produces (Synapse signing key, MAS RSA/ECDSA
|
||||
keys, Synapse↔MAS shared secret, replication secret, Postgres passwords,
|
||||
LiveKit secret, admin user password). A Python script bundled with the role
|
||||
generates them on first run into `/opt/ess/secrets/` and never overwrites
|
||||
existing files — runs of the playbook are idempotent. All containers mount
|
||||
this directory read-only as `/secrets/ess-generated/` (matches the chart's
|
||||
mount path).
|
||||
|
||||
The MAS RSA key is generated in DER PKCS8 format, ECDSA in PEM PKCS8, and the
|
||||
Synapse signing key in Synapse's native `ed25519 <keyid> <base64>` format.
|
||||
All formats verified against what the chart's `matrix-tools generate-secrets`
|
||||
produces.
|
||||
|
||||
## Usage
|
||||
|
||||
```yaml
|
||||
# site.yml
|
||||
- hosts: ess_servers
|
||||
become: true
|
||||
roles:
|
||||
- digitalboard.core.ess_pro_compose
|
||||
```
|
||||
|
||||
```yaml
|
||||
# inventory/group_vars/ess_servers.yml -- see examples/
|
||||
ess_server_name: "digitalboard.ch"
|
||||
ess_synapse_fed_reader_replicas: 5
|
||||
ess_oidc_enabled: true
|
||||
ess_oidc_issuer: "https://authentik.digitalboard.ch/application/o/ess/"
|
||||
ess_rtc_external_ip: "203.0.113.42"
|
||||
|
||||
ess_registry_username: "{{ lookup('community.hashi_vault.vault_kv2_get', ...).data.data.registry_username }}"
|
||||
ess_registry_token: "{{ lookup('community.hashi_vault.vault_kv2_get', ...).data.data.registry_token }}"
|
||||
ess_oidc_client_secret: "{{ lookup('community.hashi_vault.vault_kv2_get', ...).data.data.oidc_client_secret }}"
|
||||
```
|
||||
|
||||
Run: `ansible-playbook -i inventories/digitalboard site.yml`
|
||||
|
||||
The role creates `@localadmin:digitalboard.ch` via `mas-cli` and prints the
|
||||
location of the generated password (`/opt/ess/secrets/ADMIN_USER_PASSWORD` on
|
||||
the host).
|
||||
|
||||
## Post-deploy verification
|
||||
|
||||
```bash
|
||||
# All containers healthy
|
||||
docker compose -f /opt/ess/compose.yml ps
|
||||
|
||||
# Synapse + MAS<-->Synapse wiring
|
||||
curl -sS https://matrix.digitalboard.ch/_matrix/client/versions | jq .versions
|
||||
curl -sS https://digitalboard.ch/.well-known/matrix/server | jq
|
||||
curl -sS https://digitalboard.ch/.well-known/matrix/client | jq
|
||||
|
||||
# MAS sanity
|
||||
docker compose -f /opt/ess/compose.yml exec mas \
|
||||
mas-cli --config /conf/mas-config.yaml doctor
|
||||
|
||||
# HAProxy stats (internally)
|
||||
docker compose -f /opt/ess/compose.yml exec haproxy \
|
||||
wget -qO- http://localhost:8405/metrics | head
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
- **Config change:** re-run the playbook. Changed templates trigger
|
||||
per-component `docker compose restart` via handlers.
|
||||
- **Image upgrade:** bump `ess_images.<component>` in defaults or group_vars,
|
||||
re-run.
|
||||
- **Scale federation-reader:** change `ess_synapse_fed_reader_replicas`, re-run
|
||||
(HAProxy backend list is rendered from the same variable).
|
||||
- **Logs:** `docker compose -f /opt/ess/compose.yml logs -f synapse-main`
|
||||
- **Tear down:** `docker compose -f /opt/ess/compose.yml down -v`
|
||||
|
||||
## What's faithful to the chart, what's adapted
|
||||
|
||||
**Faithful to chart v26.5.1:**
|
||||
- All image paths from `registry.element.io` (correct repos: `synapse-onprem`,
|
||||
`synapse-pro-worker`, `matrix-authentication-service`, `element-web-pro`,
|
||||
`element-admin`, `haproxy`, `livekit-server-distroless`, `lk-jwt-service`,
|
||||
`postgres`, `redis-distroless`).
|
||||
- HAProxy config 1:1 from the chart (path-based routing to fed-reader for
|
||||
`/event`, `/state`, `/state_ids`, admin IP allow-list, well-known
|
||||
serving on port 8010, 429.http for queue overflow).
|
||||
- Synapse `homeserver.yaml` merged from the chart's four fragments
|
||||
(underrides + overrides + main listeners + log config) with both Pro
|
||||
modules loaded (`synapse_ess_pro.EssPro`,
|
||||
`synapse_mass_local_room_upgrades.MassLocalRoomUpgradesModule`).
|
||||
- MAS config with all four listeners (web 8080, internal 8081, root 8082,
|
||||
synapse 8083) and `kind: synapse_modern` for delegated auth.
|
||||
- federation-reader (Rust worker) config in its native schema, not
|
||||
Synapse-Python-worker syntax.
|
||||
- LiveKit on TCP 30001 + UDP 30002 muxed, with `node_ip` set for ICE.
|
||||
- Element Web config with Pro features (`use_exclusively`,
|
||||
`element-pro` mobile variant).
|
||||
- Init-secrets bundle generated with matching key types and formats
|
||||
(rand32 url-safe / hex32 / rsa:4096:der / ecdsaprime256v1 PEM /
|
||||
Synapse ed25519 signing key).
|
||||
|
||||
**Adapted for compose:**
|
||||
- K8s DNS-SRV service discovery (`_synapse-http._tcp.X.svc.cluster.local`)
|
||||
replaced with direct compose service names + the embedded DNS resolver
|
||||
(`127.0.0.11:53`). HAProxy backend entries use plain hostnames.
|
||||
- StatefulSet PVCs replaced with named docker volumes.
|
||||
- The chart's `matrix-tools render-config` init-container is replaced by
|
||||
Ansible Jinja2 template rendering on the control node — same merge order,
|
||||
no Python interpreter in init-containers.
|
||||
- The chart's `init-secrets` K8s job is replaced by the local
|
||||
generate-secrets script.
|
||||
- Postgres `postgres-ess-updater` sidecar (which re-runs the init script
|
||||
in case of password changes) is omitted; first-boot init via
|
||||
`/docker-entrypoint-initdb.d/` is sufficient for compose, since the
|
||||
generated passwords don't rotate on re-run (idempotent secrets).
|
||||
- No Synapse Pro autoscaler (K8s HPA only); replica count is static via
|
||||
`ess_synapse_fed_reader_replicas`.
|
||||
|
||||
## Things not yet wired (optional Pro components)
|
||||
|
||||
The chart can also deploy these — not included in this role's first pass,
|
||||
add as needed:
|
||||
|
||||
- Hookshot (Matrix bot framework for GitHub/GitLab/JIRA bridges)
|
||||
- Secure Border Gateway (Federation app-firewall — only relevant if you
|
||||
federate with strict-control orgs / German TI-Messenger)
|
||||
- Advanced Identity Management (LDAP/SCIM provisioning)
|
||||
- AuditBot, AdminBot, supervision
|
||||
- Sygnal (mobile push gateway)
|
||||
- Telemetry service (chart deploys this by default; here it's optional)
|
||||
- Content scanner
|
||||
|
||||
Each maps to its own template directory in `charts/matrix-stack/templates/`
|
||||
and can be added later as additional compose services.
|
||||
149
roles/ess_pro_compose/defaults/main.yml
Normal file
149
roles/ess_pro_compose/defaults/main.yml
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# =============================================================================
|
||||
# ess_pro_compose role — defaults
|
||||
# =============================================================================
|
||||
# Deploys the full ESS Pro stack (matrix-stack chart v26.5.1) as a docker
|
||||
# compose project, including the Pro federation-reader worker. Same conventions
|
||||
# as the other digitalboard.core roles. Secrets are sourced from OpenBao.
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Chart version we're modelling
|
||||
# -----------------------------------------------------------------------------
|
||||
ess_chart_version: "26.5.1"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Project layout on the target host
|
||||
# -----------------------------------------------------------------------------
|
||||
ess_compose_dir: "/opt/ess"
|
||||
ess_compose_project_name: "ess"
|
||||
|
||||
# Where rendered configs and runtime data live (mounted into containers)
|
||||
ess_compose_conf_dir: "{{ ess_compose_dir }}/conf" # rendered configs
|
||||
ess_compose_secrets_dir: "{{ ess_compose_dir }}/secrets" # generated secrets (0600)
|
||||
ess_compose_data_dir: "{{ ess_compose_dir }}/data" # volumes
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Docker networks
|
||||
# -----------------------------------------------------------------------------
|
||||
# Public-facing Traefik network (external, managed by the shared traefik role).
|
||||
ess_compose_traefik_network: "proxy"
|
||||
ess_compose_traefik_entrypoint: "websecure"
|
||||
ess_compose_traefik_certresolver: "letsencrypt"
|
||||
|
||||
# Internal network for service-to-service traffic only.
|
||||
ess_compose_internal_network: "ess_internal"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Matrix identity
|
||||
# -----------------------------------------------------------------------------
|
||||
# Matrix serverName is the domain part of @user:serverName. Immutable.
|
||||
ess_server_name: "digitalboard.ch"
|
||||
|
||||
# Hostnames. Convention follows the official Element docs (account.*, mrtc.*).
|
||||
# Override per environment in group_vars if you want different prefixes.
|
||||
ess_hostnames:
|
||||
synapse: "matrix.{{ ess_server_name }}" # client + federation, fronts HAProxy
|
||||
mas: "account.{{ ess_server_name }}" # Matrix Authentication Service
|
||||
element_web: "chat.{{ ess_server_name }}"
|
||||
element_admin: "admin.{{ ess_server_name }}"
|
||||
matrix_rtc: "mrtc.{{ ess_server_name }}" # Element Call SFU + auth
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Image references (Pro images from registry.element.io, chart 26.5.1)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pin to specific tags for production. The chart bundles digests; we use
|
||||
# version-aligned tags so they're readable. Override individually as needed.
|
||||
ess_images:
|
||||
synapse: "registry.element.io/synapse-onprem:sha-63110a4"
|
||||
synapse_pro_worker: "registry.element.io/synapse-pro-worker:0.4.0"
|
||||
mas: "registry.element.io/matrix-authentication-service:1.17.0"
|
||||
element_web: "registry.element.io/element-web-pro:1.12.18"
|
||||
element_admin: "registry.element.io/element-admin:1.5.0"
|
||||
haproxy: "registry.element.io/haproxy:3.2-alpine"
|
||||
livekit: "registry.element.io/livekit-server-distroless:1.9.1"
|
||||
lk_jwt: "registry.element.io/lk-jwt-service:0.3.0"
|
||||
postgres: "registry.element.io/postgres:16-alpine"
|
||||
postgres_exporter: "registry.element.io/postgres-exporter:0.18.1"
|
||||
redis: "registry.element.io/redis-distroless:7.4"
|
||||
matrix_tools: "registry.element.io/matrix-tools:0.17.8"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Element registry credentials (from customer.element.io)
|
||||
# -----------------------------------------------------------------------------
|
||||
ess_registry_url: "registry.element.io"
|
||||
ess_registry_username: "" # OpenBao lookup in group_vars
|
||||
ess_registry_token: "" # OpenBao lookup in group_vars
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Federation reader worker
|
||||
# -----------------------------------------------------------------------------
|
||||
# The Rust-based Pro worker that handles /state, /state_ids, /event federation
|
||||
# reads. The chart deploys this with 20 replicas; for compose we run it as
|
||||
# scaled instances.
|
||||
ess_synapse_fed_reader_replicas: 1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Delegated authentication via the digitalboard IdP
|
||||
# -----------------------------------------------------------------------------
|
||||
# Authentik in the demo environment, Keycloak in production. Discover the
|
||||
# exact issuer with:
|
||||
# curl -s <issuer>/.well-known/openid-configuration | jq .issuer
|
||||
ess_oidc_enabled: false
|
||||
ess_oidc_issuer: ""
|
||||
ess_oidc_client_id: "ess-mas"
|
||||
ess_oidc_client_secret: "" # OpenBao
|
||||
ess_oidc_provider_name: "Digitalboard"
|
||||
ess_oidc_provider_ulid: "01JBADAUTHENTIKDIGITALBOARD01"
|
||||
ess_oidc_scopes: "openid profile email"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Matrix RTC / Element Call (LiveKit SFU)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Element's Pro chart fixes RTC to TCP 30001 + UDP 30002 (muxed). Forward
|
||||
# those on the DMZ firewall to this host.
|
||||
ess_rtc_tcp_port: 30001
|
||||
ess_rtc_udp_port: 30002
|
||||
|
||||
# Public IP for ICE candidates (the DMZ NAT address). Required.
|
||||
ess_rtc_external_ip: ""
|
||||
# LiveKit non-secret key id (the secret comes from the generated bundle).
|
||||
ess_livekit_key: "matrix-rtc"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Registration / federation policy
|
||||
# -----------------------------------------------------------------------------
|
||||
ess_enable_registration: false
|
||||
ess_enable_federation: true # internet federation; turn off for isolated POCs
|
||||
ess_admin_contact: "mailto:admin@{{ ess_server_name }}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Initial admin user
|
||||
# -----------------------------------------------------------------------------
|
||||
# A localadmin user is created on first deploy via mas-cli. The generated
|
||||
# password lands in {{ ess_compose_secrets_dir }}/ADMIN_USER_PASSWORD.
|
||||
ess_admin_localpart: "localadmin"
|
||||
ess_create_admin_user: true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Element Admin / Synapse Admin allow-list
|
||||
# -----------------------------------------------------------------------------
|
||||
# Source IPs (CIDR) allowed to hit /_synapse/admin/. Default: everyone. Lock
|
||||
# this down for production (e.g. just the office network + bastion).
|
||||
ess_admin_allow_ips:
|
||||
- "0.0.0.0/0"
|
||||
- "::/0"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Resources / sizing (Postgres args)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Chart defaults assume a fairly beefy node. Adjust for your VM.
|
||||
ess_postgres_max_connections: 256
|
||||
ess_postgres_shared_buffers: "1024MB"
|
||||
ess_postgres_effective_cache_size: "3840MB"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Synapse media store
|
||||
# -----------------------------------------------------------------------------
|
||||
ess_synapse_max_upload_size: "100M"
|
||||
ess_synapse_url_previews_enabled: true
|
||||
63
roles/ess_pro_compose/examples/group_vars-ess_servers.yml
Normal file
63
roles/ess_pro_compose/examples/group_vars-ess_servers.yml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# inventory/group_vars/ess_servers.yml
|
||||
# Production config: full Pro stack, secrets from OpenBao.
|
||||
|
||||
# ---- Matrix identity ----------------------------------------------------
|
||||
ess_server_name: "digitalboard.ch"
|
||||
# Default hostnames (matrix./account./chat./admin./mrtc.) inherit from
|
||||
# ess_server_name. Override `ess_hostnames` here if you need different prefixes.
|
||||
|
||||
# ---- Pro worker scaling -------------------------------------------------
|
||||
# Federation-reader workers (Rust). Chart deploys 20 in K8s with HPA.
|
||||
# For a 500-700 user vocational school deployment, 3-5 is plenty.
|
||||
ess_synapse_fed_reader_replicas: 5
|
||||
|
||||
# ---- DMZ Traefik integration --------------------------------------------
|
||||
ess_compose_traefik_network: "proxy"
|
||||
ess_compose_traefik_entrypoint: "websecure"
|
||||
ess_compose_traefik_certresolver: "letsencrypt"
|
||||
|
||||
# ---- Registration / federation policy -----------------------------------
|
||||
ess_enable_registration: false
|
||||
ess_enable_federation: true
|
||||
|
||||
# ---- Delegated auth via Authentik (demo) / Keycloak (prod) --------------
|
||||
ess_oidc_enabled: true
|
||||
# Verify the actual issuer with:
|
||||
# curl -s <issuer>/.well-known/openid-configuration | jq .issuer
|
||||
ess_oidc_issuer: "https://authentik.digitalboard.ch/application/o/ess/"
|
||||
ess_oidc_client_id: "ess-mas"
|
||||
ess_oidc_provider_name: "Digitalboard"
|
||||
|
||||
# ---- Matrix RTC / Element Call ------------------------------------------
|
||||
ess_rtc_external_ip: "203.0.113.42" # DMZ public IP — set for your env
|
||||
|
||||
# ---- Admin allow-list (lock down for prod!) -----------------------------
|
||||
ess_admin_allow_ips:
|
||||
- "10.0.0.0/8" # internal RFC1918
|
||||
- "172.16.0.0/12"
|
||||
- "192.168.0.0/16"
|
||||
- "203.0.113.5/32" # bastion IP
|
||||
|
||||
# =============================================================================
|
||||
# Secrets — from OpenBao (same pattern as bookstack/opnform/homarr)
|
||||
# =============================================================================
|
||||
#
|
||||
# Stored at kv/digitalboard/ess-compose with two keys (registry creds only —
|
||||
# the cryptographic material is generated locally by the role's
|
||||
# generate-secrets script and lives in {{ ess_compose_secrets_dir }} on the
|
||||
# host). The OIDC client secret also lives in OpenBao because it's shared
|
||||
# with the IdP side.
|
||||
|
||||
ess_registry_username: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
||||
'digitalboard/ess-compose',
|
||||
mount_point='kv').data.data.registry_username }}"
|
||||
|
||||
ess_registry_token: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
||||
'digitalboard/ess-compose',
|
||||
mount_point='kv').data.data.registry_token }}"
|
||||
|
||||
ess_oidc_client_secret: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
||||
'digitalboard/ess-compose',
|
||||
mount_point='kv').data.data.oidc_client_secret }}"
|
||||
20
roles/ess_pro_compose/examples/openbao-bootstrap.sh
Executable file
20
roles/ess_pro_compose/examples/openbao-bootstrap.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
# Bootstrap the OpenBao entry for ess_pro_compose.
|
||||
# Only stores the registry credentials and the OIDC client secret —
|
||||
# the rest of the cryptographic material is generated by the role locally
|
||||
# on first deploy (and persists in {{ ess_compose_secrets_dir }} on the host).
|
||||
|
||||
set -euo pipefail
|
||||
MOUNT="${MOUNT:-kv}"
|
||||
PATH_="${PATH_:-digitalboard/ess-compose}"
|
||||
|
||||
read -p "Element registry username (from customer.element.io): " REG_USER
|
||||
read -sp "Element registry token: " REG_TOKEN; echo
|
||||
read -sp "Authentik OIDC client_secret for ess-mas: " OIDC_SECRET; echo
|
||||
|
||||
bao kv put "${MOUNT}/${PATH_}" \
|
||||
registry_username="${REG_USER}" \
|
||||
registry_token="${REG_TOKEN}" \
|
||||
oidc_client_secret="${OIDC_SECRET}"
|
||||
|
||||
echo "Done. Verify: bao kv get ${MOUNT}/${PATH_}"
|
||||
7
roles/ess_pro_compose/examples/site.yml
Normal file
7
roles/ess_pro_compose/examples/site.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- name: Deploy ESS Pro v26.5.1 (full stack with federation-reader worker)
|
||||
hosts: ess_servers
|
||||
become: true
|
||||
roles:
|
||||
- digitalboard.core.ess_pro_compose
|
||||
42
roles/ess_pro_compose/handlers/main.yml
Normal file
42
roles/ess_pro_compose/handlers/main.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- name: Restart haproxy
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
services: [haproxy]
|
||||
state: restarted
|
||||
|
||||
- name: Restart synapse-main
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
services: [synapse-main]
|
||||
state: restarted
|
||||
|
||||
- name: Restart synapse-fed-reader
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
state: restarted
|
||||
|
||||
- name: Restart mas
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
services: [mas]
|
||||
state: restarted
|
||||
|
||||
- name: Restart matrix-rtc-sfu
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
services: [matrix-rtc-sfu, matrix-rtc-authorisation]
|
||||
state: restarted
|
||||
|
||||
- name: Restart element-web
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
services: [element-web]
|
||||
state: restarted
|
||||
|
||||
- name: Restart redis
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
services: [redis]
|
||||
state: restarted
|
||||
17
roles/ess_pro_compose/meta/main.yml
Normal file
17
roles/ess_pro_compose/meta/main.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: ess_pro_compose
|
||||
author: digitalboard
|
||||
description: Full ESS Pro stack (matrix-stack v26.5.1) via docker compose, with federation-reader worker
|
||||
license: MIT
|
||||
min_ansible_version: "2.14"
|
||||
platforms:
|
||||
- name: Debian
|
||||
versions:
|
||||
- bookworm
|
||||
|
||||
dependencies: []
|
||||
|
||||
collections:
|
||||
- community.docker
|
||||
79
roles/ess_pro_compose/tasks/config.yml
Normal file
79
roles/ess_pro_compose/tasks/config.yml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# Render every component's configuration. Each template uses _ess_secrets
|
||||
# facts (loaded in secrets.yml) for password substitution.
|
||||
|
||||
- name: Render HAProxy config
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ ess_compose_conf_dir }}/haproxy/{{ item.dest }}"
|
||||
mode: "0640"
|
||||
loop:
|
||||
- { src: haproxy/haproxy.cfg.j2, dest: haproxy.cfg }
|
||||
- { src: haproxy/429.http.j2, dest: 429.http }
|
||||
- { src: haproxy/path_map_file.j2, dest: path_map_file }
|
||||
- { src: haproxy/path_map_file_get.j2, dest: path_map_file_get }
|
||||
- { src: haproxy/admin-allow-ips.lst.j2, dest: admin-allow-ips.lst }
|
||||
notify: Restart haproxy
|
||||
|
||||
- name: Render well-known files
|
||||
ansible.builtin.template:
|
||||
src: "haproxy/well-known/{{ item }}.j2"
|
||||
dest: "{{ ess_compose_conf_dir }}/haproxy/well-known/{{ item }}"
|
||||
mode: "0644"
|
||||
loop:
|
||||
- server
|
||||
- client
|
||||
- support
|
||||
- element.json
|
||||
notify: Restart haproxy
|
||||
|
||||
- name: Render Synapse configs
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ ess_compose_conf_dir }}/synapse/{{ item.dest }}"
|
||||
mode: "0640"
|
||||
loop:
|
||||
- { src: synapse/homeserver.yaml.j2, dest: homeserver.yaml }
|
||||
- { src: synapse/log_config.yaml.j2, dest: log_config.yaml }
|
||||
- { src: synapse/federation-reader.yaml.j2, dest: federation-reader.yaml }
|
||||
no_log: true
|
||||
notify:
|
||||
- Restart synapse-main
|
||||
- Restart synapse-fed-reader
|
||||
|
||||
- name: Render MAS config
|
||||
ansible.builtin.template:
|
||||
src: mas/config.yaml.j2
|
||||
dest: "{{ ess_compose_conf_dir }}/mas/config.yaml"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
notify: Restart mas
|
||||
|
||||
- name: Render SFU config
|
||||
ansible.builtin.template:
|
||||
src: sfu/config.yaml.j2
|
||||
dest: "{{ ess_compose_conf_dir }}/sfu/config.yaml"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
notify: Restart matrix-rtc-sfu
|
||||
|
||||
- name: Render Element Web config
|
||||
ansible.builtin.template:
|
||||
src: element-web/config.json.j2
|
||||
dest: "{{ ess_compose_conf_dir }}/element-web/config.json"
|
||||
mode: "0644"
|
||||
notify: Restart element-web
|
||||
|
||||
- name: Render Postgres init script
|
||||
ansible.builtin.template:
|
||||
src: postgres/configure-dbs.sh.j2
|
||||
dest: "{{ ess_compose_conf_dir }}/postgres/configure-dbs.sh"
|
||||
mode: "0755"
|
||||
|
||||
- name: Render Redis config
|
||||
ansible.builtin.template:
|
||||
src: redis/redis.conf.j2
|
||||
dest: "{{ ess_compose_conf_dir }}/redis/redis.conf"
|
||||
mode: "0644"
|
||||
notify: Restart redis
|
||||
24
roles/ess_pro_compose/tasks/deploy.yml
Normal file
24
roles/ess_pro_compose/tasks/deploy.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- name: Render compose project file
|
||||
ansible.builtin.template:
|
||||
src: compose.yml.j2
|
||||
dest: "{{ _ess_compose_file }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: Pull all images
|
||||
community.docker.docker_compose_v2_pull:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
register: ess_pull_result
|
||||
|
||||
- name: Bring the stack up
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ ess_compose_dir }}"
|
||||
state: present
|
||||
wait: true
|
||||
wait_timeout: 300
|
||||
register: ess_up_result
|
||||
|
||||
- name: Show running services
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ ess_up_result.services | default([]) | map(attribute='Service') | list }}"
|
||||
39
roles/ess_pro_compose/tasks/main.yml
Normal file
39
roles/ess_pro_compose/tasks/main.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- name: Validate required variables
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ess_server_name | length > 0
|
||||
- ess_registry_username | length > 0
|
||||
- ess_registry_token | length > 0
|
||||
- ess_rtc_external_ip | length > 0
|
||||
fail_msg: >-
|
||||
Required variables are missing. Provide ess_server_name,
|
||||
ess_registry_username, ess_registry_token (OpenBao) and
|
||||
ess_rtc_external_ip in group_vars/ess_servers.yml.
|
||||
quiet: true
|
||||
|
||||
- name: Validate OIDC variables when OIDC is enabled
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ess_oidc_issuer | length > 0
|
||||
- ess_oidc_client_secret | length > 0
|
||||
fail_msg: OIDC enabled but issuer / client_secret missing.
|
||||
quiet: true
|
||||
when: ess_oidc_enabled | bool
|
||||
|
||||
- name: Prerequisites (docker, networks, dirs, registry login)
|
||||
ansible.builtin.import_tasks: prereq.yml
|
||||
|
||||
- name: Generate / verify the ess-generated secret bundle
|
||||
ansible.builtin.import_tasks: secrets.yml
|
||||
|
||||
- name: Render all component configuration files
|
||||
ansible.builtin.import_tasks: config.yml
|
||||
|
||||
- name: Render compose project file and start the stack
|
||||
ansible.builtin.import_tasks: deploy.yml
|
||||
|
||||
- name: Post-install (create admin user)
|
||||
ansible.builtin.import_tasks: postinstall.yml
|
||||
when: ess_create_admin_user | bool
|
||||
48
roles/ess_pro_compose/tasks/postinstall.yml
Normal file
48
roles/ess_pro_compose/tasks/postinstall.yml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# Create @localadmin via mas-cli, using the ADMIN_USER_PASSWORD generated
|
||||
# by secrets.yml. Idempotent: mas-cli rejects duplicates, we ignore that.
|
||||
|
||||
- name: Read generated admin password
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ ess_compose_secrets_dir }}/ADMIN_USER_PASSWORD"
|
||||
register: _ess_admin_pw_slurp
|
||||
no_log: true
|
||||
|
||||
- name: Check whether the admin user already exists
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
docker compose -f {{ _ess_compose_file }}
|
||||
exec -T mas
|
||||
mas-cli --config /conf/mas-config.yaml
|
||||
manage list-users --filter username={{ ess_admin_localpart }}
|
||||
register: _ess_admin_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Register admin user (mas-cli)
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
docker compose -f {{ _ess_compose_file }}
|
||||
exec -T mas
|
||||
mas-cli --config /conf/mas-config.yaml
|
||||
manage register-user --yes
|
||||
--password {{ (_ess_admin_pw_slurp.content | b64decode).strip() | quote }}
|
||||
--admin
|
||||
{{ ess_admin_localpart }}
|
||||
register: _ess_admin_create
|
||||
changed_when: "'created' in (_ess_admin_create.stdout + _ess_admin_create.stderr) | lower"
|
||||
failed_when:
|
||||
- _ess_admin_create.rc != 0
|
||||
- "'already exists' not in (_ess_admin_create.stdout + _ess_admin_create.stderr) | lower"
|
||||
no_log: true
|
||||
when: ess_admin_localpart not in _ess_admin_check.stdout
|
||||
|
||||
- name: Login hint
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Stack is up.
|
||||
Admin user: @{{ ess_admin_localpart }}:{{ ess_server_name }}
|
||||
Password is in {{ ess_compose_secrets_dir }}/ADMIN_USER_PASSWORD on this host.
|
||||
Element Web: https://{{ ess_hostnames.element_web }}
|
||||
Element Admin: https://{{ ess_hostnames.element_admin }}
|
||||
45
roles/ess_pro_compose/tasks/prereq.yml
Normal file
45
roles/ess_pro_compose/tasks/prereq.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
- name: Ensure prerequisite packages on the control target
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- python3-docker
|
||||
- python3-cryptography
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Verify docker compose plugin is available
|
||||
ansible.builtin.command: docker compose version
|
||||
register: ess_compose_check
|
||||
changed_when: false
|
||||
failed_when: ess_compose_check.rc != 0
|
||||
|
||||
- name: Create project directory tree
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
owner: root
|
||||
group: root
|
||||
loop: "{{ _ess_dirs }}"
|
||||
|
||||
- name: Tighten secrets directory permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ ess_compose_secrets_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Ensure the external Traefik proxy network exists
|
||||
community.docker.docker_network:
|
||||
name: "{{ ess_compose_traefik_network }}"
|
||||
state: present
|
||||
|
||||
- name: Authenticate against the Element container registry
|
||||
community.docker.docker_login:
|
||||
registry_url: "{{ ess_registry_url }}"
|
||||
username: "{{ ess_registry_username }}"
|
||||
password: "{{ ess_registry_token }}"
|
||||
no_log: true
|
||||
47
roles/ess_pro_compose/tasks/secrets.yml
Normal file
47
roles/ess_pro_compose/tasks/secrets.yml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# Generate the ess-generated secret bundle. Mirrors the chart's `init-secrets`
|
||||
# job, but runs locally on the host. Idempotent — only writes missing files.
|
||||
|
||||
- name: Render generate-secrets script
|
||||
ansible.builtin.template:
|
||||
src: generate-secrets.py.j2
|
||||
dest: "{{ ess_compose_dir }}/.generate-secrets.py"
|
||||
mode: "0700"
|
||||
|
||||
- name: Run generate-secrets (creates only what's missing)
|
||||
ansible.builtin.command:
|
||||
cmd: "/usr/bin/python3 {{ ess_compose_dir }}/.generate-secrets.py"
|
||||
register: ess_secrets_run
|
||||
changed_when: "'CREATED:' in ess_secrets_run.stdout"
|
||||
|
||||
- name: Verify every required secret exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ ess_compose_secrets_dir }}/{{ item }}"
|
||||
register: ess_secret_stat
|
||||
loop: "{{ _ess_secret_names }}"
|
||||
failed_when: not ess_secret_stat.stat.exists
|
||||
|
||||
- name: Read postgres passwords for config templates (not persisted)
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ ess_compose_secrets_dir }}/{{ item }}"
|
||||
register: ess_password_slurp
|
||||
loop:
|
||||
- POSTGRES_ADMIN_PASSWORD
|
||||
- POSTGRES_SYNAPSE_PASSWORD
|
||||
- POSTGRES_MATRIX_AUTHENTICATION_SERVICE_PASSWORD
|
||||
- SYNAPSE_MACAROON
|
||||
- SYNAPSE_REGISTRATION_SHARED_SECRET
|
||||
- SYNAPSE_WORKERS_REPLICATION_SECRET
|
||||
- MAS_SYNAPSE_SHARED_SECRET
|
||||
- MAS_MATRIX_TOOLS_OIDC_CLIENT_SECRET
|
||||
- ELEMENT_CALL_LIVEKIT_SECRET
|
||||
no_log: true
|
||||
|
||||
- name: Expose passwords as facts for templates
|
||||
ansible.builtin.set_fact:
|
||||
_ess_secrets: "{{ _ess_secrets | default({}) | combine({item.item: (item.content | b64decode).strip()}) }}"
|
||||
loop: "{{ ess_password_slurp.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
no_log: true
|
||||
304
roles/ess_pro_compose/templates/compose.yml.j2
Normal file
304
roles/ess_pro_compose/templates/compose.yml.j2
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
# {{ ansible_managed }}
|
||||
# ESS Pro v{{ ess_chart_version }} on docker compose — rendered by ess_pro_compose.
|
||||
# Topology mirrors the Helm chart: HAProxy fronts all Synapse traffic,
|
||||
# synapse-main is the Python homeserver, synapse-fed-reader is the Rust Pro
|
||||
# worker handling federation reads, MAS handles all auth, LiveKit + lk-jwt
|
||||
# serve Element Call.
|
||||
|
||||
name: {{ ess_compose_project_name }}
|
||||
|
||||
networks:
|
||||
{{ ess_compose_traefik_network }}:
|
||||
external: true
|
||||
{{ ess_compose_internal_network }}:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
synapse_media:
|
||||
|
||||
services:
|
||||
|
||||
# ===========================================================================
|
||||
# Data plane
|
||||
# ===========================================================================
|
||||
|
||||
postgres:
|
||||
image: {{ ess_images.postgres }}
|
||||
container_name: postgres
|
||||
restart: unless-stopped
|
||||
networks: [ {{ ess_compose_internal_network }} ]
|
||||
environment:
|
||||
LC_COLLATE: "C"
|
||||
LC_CTYPE: "C"
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8"
|
||||
POSTGRES_PASSWORD_FILE: /secrets/ess-generated/POSTGRES_ADMIN_PASSWORD
|
||||
command:
|
||||
- postgres
|
||||
- "-c"
|
||||
- "max_connections={{ ess_postgres_max_connections }}"
|
||||
- "-c"
|
||||
- "shared_buffers={{ ess_postgres_shared_buffers }}"
|
||||
- "-c"
|
||||
- "effective_cache_size={{ ess_postgres_effective_cache_size }}"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- {{ ess_compose_secrets_dir }}:/secrets/ess-generated:ro
|
||||
- {{ ess_compose_conf_dir }}/postgres/configure-dbs.sh:/docker-entrypoint-initdb.d/init-ess-dbs.sh:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
redis:
|
||||
image: {{ ess_images.redis }}
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
networks: [ {{ ess_compose_internal_network }} ]
|
||||
command: ["/usr/local/etc/redis/redis.conf"]
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
# ===========================================================================
|
||||
# Synapse (Python main + Rust federation-reader worker)
|
||||
# ===========================================================================
|
||||
|
||||
synapse-main:
|
||||
image: {{ ess_images.synapse }}
|
||||
container_name: synapse-main
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
redis: { condition: service_healthy }
|
||||
networks: [ {{ ess_compose_internal_network }} ]
|
||||
command: ["python3", "-m", "synapse.app.homeserver", "-c", "/conf/homeserver.yaml"]
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/synapse/homeserver.yaml:/conf/homeserver.yaml:ro
|
||||
- {{ ess_compose_conf_dir }}/synapse/log_config.yaml:/conf/log_config.yaml:ro
|
||||
- {{ ess_compose_secrets_dir }}:/secrets/ess-generated:ro
|
||||
- synapse_media:/media
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 60s
|
||||
|
||||
{% for i in range(ess_synapse_fed_reader_replicas | int) %}
|
||||
synapse-fed-reader-{{ i }}:
|
||||
image: {{ ess_images.synapse_pro_worker }}
|
||||
container_name: synapse-fed-reader-{{ i }}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
synapse-main: { condition: service_healthy }
|
||||
networks: [ {{ ess_compose_internal_network }} ]
|
||||
environment:
|
||||
APP_CONFIG_FILEPATH: /conf/federation-reader.yaml
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/synapse/federation-reader.yaml:/conf/federation-reader.yaml:ro
|
||||
- {{ ess_compose_secrets_dir }}:/secrets/ess-generated:ro
|
||||
|
||||
{% endfor %}
|
||||
# ===========================================================================
|
||||
# Matrix Authentication Service (4 listeners)
|
||||
# ===========================================================================
|
||||
|
||||
mas:
|
||||
image: {{ ess_images.mas }}
|
||||
container_name: mas
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
networks:
|
||||
- {{ ess_compose_internal_network }}
|
||||
- {{ ess_compose_traefik_network }}
|
||||
environment:
|
||||
MAS_CONFIG: /conf/mas-config.yaml
|
||||
command: ["server", "--no-migrate"]
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/mas/config.yaml:/conf/mas-config.yaml:ro
|
||||
- {{ ess_compose_secrets_dir }}:/secrets/ess-generated:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://localhost:8081/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 30s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network={{ ess_compose_traefik_network }}"
|
||||
- "traefik.http.routers.ess-mas.rule=Host(`{{ ess_hostnames.mas }}`)"
|
||||
- "traefik.http.routers.ess-mas.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-mas.tls=true"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-mas.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.services.ess-mas.loadbalancer.server.port=8080"
|
||||
|
||||
# MAS root listener (port 8082) is mounted as a separate Traefik router so
|
||||
# /.well-known/openid-configuration on the apex of the mas host is reachable.
|
||||
# We attach a second router on the same service via a path rule.
|
||||
|
||||
# ===========================================================================
|
||||
# HAProxy — fronts all Synapse + well-known traffic
|
||||
# ===========================================================================
|
||||
|
||||
haproxy:
|
||||
image: {{ ess_images.haproxy }}
|
||||
container_name: haproxy
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
synapse-main: { condition: service_healthy }
|
||||
networks:
|
||||
- {{ ess_compose_internal_network }}
|
||||
- {{ ess_compose_traefik_network }}
|
||||
command: ["-f", "/usr/local/etc/haproxy/haproxy.cfg", "-dW"]
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/path_map_file:/synapse/path_map_file:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/path_map_file_get:/synapse/path_map_file_get:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/429.http:/synapse/429.http:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/admin-allow-ips.lst:/synapse/admin-allow-ips.lst:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/well-known/server:/well-known/server:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/well-known/client:/well-known/client:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/well-known/support:/well-known/support:ro
|
||||
- {{ ess_compose_conf_dir }}/haproxy/well-known/element.json:/well-known/element.json:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8406/synapse_ready"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 90s
|
||||
labels:
|
||||
# matrix.<server> -> HAProxy frontend synapse-http-in (port 8008)
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network={{ ess_compose_traefik_network }}"
|
||||
- "traefik.http.routers.ess-synapse.rule=Host(`{{ ess_hostnames.synapse }}`)"
|
||||
- "traefik.http.routers.ess-synapse.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-synapse.tls=true"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-synapse.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.routers.ess-synapse.service=ess-synapse"
|
||||
- "traefik.http.services.ess-synapse.loadbalancer.server.port=8008"
|
||||
# <server>/.well-known/matrix -> HAProxy well-known-in (port 8010)
|
||||
- "traefik.http.routers.ess-wellknown.rule=Host(`{{ ess_server_name }}`) && PathPrefix(`/.well-known/matrix`)"
|
||||
- "traefik.http.routers.ess-wellknown.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-wellknown.tls=true"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-wellknown.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.routers.ess-wellknown.service=ess-wellknown"
|
||||
- "traefik.http.services.ess-wellknown.loadbalancer.server.port=8010"
|
||||
|
||||
# ===========================================================================
|
||||
# Element Web (browser client)
|
||||
# ===========================================================================
|
||||
|
||||
element-web:
|
||||
image: {{ ess_images.element_web }}
|
||||
container_name: element-web
|
||||
restart: unless-stopped
|
||||
networks: [ {{ ess_compose_traefik_network }} ]
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/element-web/config.json:/app/config.json:ro
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network={{ ess_compose_traefik_network }}"
|
||||
- "traefik.http.routers.ess-element-web.rule=Host(`{{ ess_hostnames.element_web }}`)"
|
||||
- "traefik.http.routers.ess-element-web.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-element-web.tls=true"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-element-web.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.services.ess-element-web.loadbalancer.server.port=8080"
|
||||
|
||||
# ===========================================================================
|
||||
# Element Admin (admin panel)
|
||||
# ===========================================================================
|
||||
|
||||
element-admin:
|
||||
image: {{ ess_images.element_admin }}
|
||||
container_name: element-admin
|
||||
restart: unless-stopped
|
||||
networks: [ {{ ess_compose_traefik_network }} ]
|
||||
environment:
|
||||
SERVER_NAME: "{{ ess_server_name }}"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network={{ ess_compose_traefik_network }}"
|
||||
- "traefik.http.routers.ess-element-admin.rule=Host(`{{ ess_hostnames.element_admin }}`)"
|
||||
- "traefik.http.routers.ess-element-admin.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-element-admin.tls=true"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-element-admin.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.services.ess-element-admin.loadbalancer.server.port=8080"
|
||||
|
||||
# ===========================================================================
|
||||
# Matrix RTC / Element Call (LiveKit SFU + lk-jwt)
|
||||
# ===========================================================================
|
||||
|
||||
matrix-rtc-sfu:
|
||||
image: {{ ess_images.livekit }}
|
||||
container_name: matrix-rtc-sfu
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- {{ ess_compose_internal_network }}
|
||||
- {{ ess_compose_traefik_network }}
|
||||
command: ["--config", "/conf/sfu-config.yaml"]
|
||||
volumes:
|
||||
- {{ ess_compose_conf_dir }}/sfu/config.yaml:/conf/sfu-config.yaml:ro
|
||||
# WebRTC media ports — DMZ firewall must NAT-forward these to this host.
|
||||
ports:
|
||||
- "{{ ess_rtc_tcp_port }}:{{ ess_rtc_tcp_port }}/tcp"
|
||||
- "{{ ess_rtc_udp_port }}:{{ ess_rtc_udp_port }}/udp"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network={{ ess_compose_traefik_network }}"
|
||||
- "traefik.http.routers.ess-matrix-rtc.rule=Host(`{{ ess_hostnames.matrix_rtc }}`)"
|
||||
- "traefik.http.routers.ess-matrix-rtc.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-matrix-rtc.tls=true"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-matrix-rtc.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.routers.ess-matrix-rtc.service=ess-matrix-rtc"
|
||||
- "traefik.http.services.ess-matrix-rtc.loadbalancer.server.port=7880"
|
||||
|
||||
matrix-rtc-authorisation:
|
||||
image: {{ ess_images.lk_jwt }}
|
||||
container_name: matrix-rtc-authorisation
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
matrix-rtc-sfu: { condition: service_started }
|
||||
networks:
|
||||
- {{ ess_compose_internal_network }}
|
||||
- {{ ess_compose_traefik_network }}
|
||||
environment:
|
||||
LIVEKIT_URL: "wss://{{ ess_hostnames.matrix_rtc }}"
|
||||
LIVEKIT_KEY: "{{ ess_livekit_key }}"
|
||||
LIVEKIT_SECRET_FROM_FILE: /secrets/ess-generated/ELEMENT_CALL_LIVEKIT_SECRET
|
||||
LIVEKIT_FULL_ACCESS_HOMESERVERS: "{{ ess_server_name }}"
|
||||
volumes:
|
||||
- {{ ess_compose_secrets_dir }}:/secrets/ess-generated:ro
|
||||
labels:
|
||||
# /sfu/get is the JWT token endpoint Element Call hits to join calls.
|
||||
# It lives on the same host as the SFU but on a different backend.
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network={{ ess_compose_traefik_network }}"
|
||||
- "traefik.http.routers.ess-matrix-rtc-auth.rule=Host(`{{ ess_hostnames.matrix_rtc }}`) && PathPrefix(`/sfu/get`)"
|
||||
- "traefik.http.routers.ess-matrix-rtc-auth.entrypoints={{ ess_compose_traefik_entrypoint }}"
|
||||
- "traefik.http.routers.ess-matrix-rtc-auth.tls=true"
|
||||
- "traefik.http.routers.ess-matrix-rtc-auth.priority=200"
|
||||
{% if ess_compose_traefik_certresolver | length > 0 %}
|
||||
- "traefik.http.routers.ess-matrix-rtc-auth.tls.certresolver={{ ess_compose_traefik_certresolver }}"
|
||||
{% endif %}
|
||||
- "traefik.http.routers.ess-matrix-rtc-auth.service=ess-matrix-rtc-auth"
|
||||
- "traefik.http.services.ess-matrix-rtc-auth.loadbalancer.server.port=8080"
|
||||
33
roles/ess_pro_compose/templates/element-web/config.json.j2
Normal file
33
roles/ess_pro_compose/templates/element-web/config.json.j2
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"bug_report_endpoint_url": "local",
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://{{ ess_hostnames.synapse }}",
|
||||
"server_name": "{{ ess_server_name }}"
|
||||
}
|
||||
},
|
||||
"element_call": {
|
||||
"use_exclusively": true
|
||||
},
|
||||
"embedded_pages": {
|
||||
"login_for_welcome": true
|
||||
},
|
||||
"features": {
|
||||
"feature_element_call_video_rooms": true,
|
||||
"feature_group_calls": true,
|
||||
"feature_new_room_decoration_ui": true,
|
||||
"feature_video_rooms": true
|
||||
},
|
||||
"mobile_guide_app_variant": "element-pro",
|
||||
"setting_defaults": {
|
||||
"UIFeature.deactivate": false,
|
||||
"UIFeature.passwordReset": false,
|
||||
"UIFeature.registration": {{ ess_enable_registration | bool | lower }},
|
||||
"feature_group_calls": true,
|
||||
"urlPreviewsEnabled": {{ ess_synapse_url_previews_enabled | bool | lower }},
|
||||
"urlPreviewsEnabled_e2ee": {{ ess_synapse_url_previews_enabled | bool | lower }}
|
||||
},
|
||||
"sso_redirect_options": {
|
||||
"immediate": false
|
||||
}
|
||||
}
|
||||
102
roles/ess_pro_compose/templates/generate-secrets.py.j2
Normal file
102
roles/ess_pro_compose/templates/generate-secrets.py.j2
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
# {{ ansible_managed }}
|
||||
"""
|
||||
Generate the ess-generated secret bundle the way the Helm chart's
|
||||
init-secrets job does. Idempotent: only writes files that don't exist.
|
||||
|
||||
Mirrors `matrix-tools generate-secrets` arguments from chart v{{ ess_chart_version }}.
|
||||
"""
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa
|
||||
|
||||
SECRETS_DIR = Path("{{ ess_compose_secrets_dir }}")
|
||||
SECRETS_DIR.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def write_if_missing(name, content_bytes):
|
||||
p = SECRETS_DIR / name
|
||||
if p.exists():
|
||||
return False
|
||||
# Atomic-ish write
|
||||
tmp = p.with_suffix(p.suffix + ".tmp")
|
||||
tmp.write_bytes(content_bytes)
|
||||
os.chmod(tmp, 0o600)
|
||||
tmp.rename(p)
|
||||
return True
|
||||
|
||||
|
||||
def rand32():
|
||||
# `matrix-tools rand32` produces 32 url-safe characters
|
||||
return secrets.token_urlsafe(24)[:32].encode()
|
||||
|
||||
|
||||
def hex32():
|
||||
return secrets.token_hex(32).encode()
|
||||
|
||||
|
||||
def rsa_der():
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
|
||||
return key.private_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
|
||||
|
||||
def ecdsa_prime256v1():
|
||||
key = ec.generate_private_key(ec.SECP256R1())
|
||||
return key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
|
||||
|
||||
def synapse_signing_key():
|
||||
# Synapse expects: ed25519 <keyid> <unpadded-base64-seed>
|
||||
import base64
|
||||
key = ed25519.Ed25519PrivateKey.generate()
|
||||
seed = key.private_bytes(
|
||||
encoding=serialization.Encoding.Raw,
|
||||
format=serialization.PrivateFormat.Raw,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
# 4-char keyid like Synapse generates
|
||||
keyid = secrets.token_hex(2)
|
||||
b64 = base64.b64encode(seed).rstrip(b"=").decode()
|
||||
return f"ed25519 a_{keyid} {b64}\n".encode()
|
||||
|
||||
|
||||
SPEC = {
|
||||
"POSTGRES_ADMIN_PASSWORD": rand32,
|
||||
"POSTGRES_SYNAPSE_PASSWORD": rand32,
|
||||
"POSTGRES_MATRIX_AUTHENTICATION_SERVICE_PASSWORD": rand32,
|
||||
"SYNAPSE_MACAROON": rand32,
|
||||
"SYNAPSE_REGISTRATION_SHARED_SECRET": rand32,
|
||||
"SYNAPSE_WORKERS_REPLICATION_SECRET": rand32,
|
||||
"SYNAPSE_SIGNING_KEY": synapse_signing_key,
|
||||
"MAS_SYNAPSE_SHARED_SECRET": rand32,
|
||||
"MAS_MATRIX_TOOLS_OIDC_CLIENT_SECRET": rand32,
|
||||
"MAS_ENCRYPTION_SECRET": hex32,
|
||||
"MAS_RSA_PRIVATE_KEY": rsa_der,
|
||||
"MAS_ECDSA_PRIME256V1_PRIVATE_KEY": ecdsa_prime256v1,
|
||||
"ELEMENT_CALL_LIVEKIT_SECRET": rand32,
|
||||
"ADMIN_USER_PASSWORD": rand32,
|
||||
}
|
||||
|
||||
|
||||
created = []
|
||||
for name, fn in SPEC.items():
|
||||
if write_if_missing(name, fn()):
|
||||
created.append(name)
|
||||
|
||||
if created:
|
||||
print("CREATED:", " ".join(created))
|
||||
else:
|
||||
print("NOCHANGE")
|
||||
sys.exit(0)
|
||||
9
roles/ess_pro_compose/templates/haproxy/429.http.j2
Normal file
9
roles/ess_pro_compose/templates/haproxy/429.http.j2
Normal 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"}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# {{ ansible_managed }}
|
||||
{% for cidr in ess_admin_allow_ips %}
|
||||
{{ cidr }}
|
||||
{% endfor %}
|
||||
177
roles/ess_pro_compose/templates/haproxy/haproxy.cfg.j2
Normal file
177
roles/ess_pro_compose/templates/haproxy/haproxy.cfg.j2
Normal 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
|
||||
5
roles/ess_pro_compose/templates/haproxy/path_map_file.j2
Normal file
5
roles/ess_pro_compose/templates/haproxy/path_map_file.j2
Normal 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.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# {{ ansible_managed }}
|
||||
# GET-only worker path map. See path_map_file for context.
|
||||
11
roles/ess_pro_compose/templates/haproxy/well-known/client.j2
Normal file
11
roles/ess_pro_compose/templates/haproxy/well-known/client.j2
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"m.server": "{{ ess_hostnames.synapse }}:443"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
114
roles/ess_pro_compose/templates/mas/config.yaml.j2
Normal file
114
roles/ess_pro_compose/templates/mas/config.yaml.j2
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
## {{ ansible_managed }}
|
||||
## Matrix Authentication Service — merged from chart fragments.
|
||||
## Adapted from ess-helm {{ ess_chart_version }} for docker compose.
|
||||
|
||||
http:
|
||||
public_base: "https://{{ ess_hostnames.mas }}/"
|
||||
issuer: "https://{{ ess_hostnames.mas }}/"
|
||||
listeners:
|
||||
# Public web UI + OAuth + GraphQL + admin API. Fronted by DMZ Traefik.
|
||||
- name: web
|
||||
binds:
|
||||
- host: 0.0.0.0
|
||||
port: 8080
|
||||
resources:
|
||||
- name: human
|
||||
- name: oauth
|
||||
- name: assets
|
||||
- name: graphql
|
||||
undocumented_oauth2_access: true
|
||||
- name: adminapi
|
||||
# Internal — never exposed publicly. Used for healthchecks and metrics.
|
||||
- name: internal
|
||||
binds:
|
||||
- host: 0.0.0.0
|
||||
port: 8081
|
||||
resources:
|
||||
- name: health
|
||||
- name: prometheus
|
||||
- name: connection-info
|
||||
# Root domain — serves .well-known/openid-configuration et al. on
|
||||
# https://{{ ess_hostnames.mas }} root. Mounted as the public listener
|
||||
# since DMZ Traefik strips paths.
|
||||
- name: root
|
||||
binds:
|
||||
- host: 0.0.0.0
|
||||
port: 8082
|
||||
resources:
|
||||
- name: discovery
|
||||
- name: compat
|
||||
# Talks to Synapse on the internal network only.
|
||||
- name: synapse
|
||||
binds:
|
||||
- host: 0.0.0.0
|
||||
port: 8083
|
||||
resources:
|
||||
- name: discovery
|
||||
- name: oauth
|
||||
|
||||
database:
|
||||
uri: "postgresql://matrixauthenticationservice_user:{{ _ess_secrets.POSTGRES_MATRIX_AUTHENTICATION_SERVICE_PASSWORD }}@postgres:5432/matrixauthenticationservice?sslmode=prefer&application_name=matrix-authentication-service"
|
||||
|
||||
telemetry:
|
||||
metrics:
|
||||
exporter: prometheus
|
||||
|
||||
matrix:
|
||||
homeserver: "{{ ess_server_name }}"
|
||||
secret_file: {{ _ess_secret_mount }}/MAS_SYNAPSE_SHARED_SECRET
|
||||
endpoint: "http://synapse-main:8008"
|
||||
kind: synapse_modern
|
||||
|
||||
# ---- OAuth2 clients -------------------------------------------------------
|
||||
clients:
|
||||
# Matrix-tools admin client used by mas-cli operations.
|
||||
- client_id: "000000000000000MATR1XT001S"
|
||||
client_auth_method: client_secret_basic
|
||||
client_secret_file: {{ _ess_secret_mount }}/MAS_MATRIX_TOOLS_OIDC_CLIENT_SECRET
|
||||
|
||||
# ---- Signing keys & encryption (file-mounted) ----------------------------
|
||||
secrets:
|
||||
encryption_file: {{ _ess_secret_mount }}/MAS_ENCRYPTION_SECRET
|
||||
keys:
|
||||
- key_file: {{ _ess_secret_mount }}/MAS_RSA_PRIVATE_KEY
|
||||
- key_file: {{ _ess_secret_mount }}/MAS_ECDSA_PRIME256V1_PRIVATE_KEY
|
||||
|
||||
# ---- Policy ---------------------------------------------------------------
|
||||
policy:
|
||||
data:
|
||||
admin_clients:
|
||||
- "000000000000000MATR1XT001S"
|
||||
admin_users: []
|
||||
client_registration:
|
||||
allow_host_mismatch: false
|
||||
allow_insecure_uris: false
|
||||
|
||||
account:
|
||||
password_registration_enabled: {{ ess_enable_registration | bool | lower }}
|
||||
|
||||
passwords:
|
||||
enabled: true
|
||||
|
||||
{% if ess_oidc_enabled %}
|
||||
# ---- Upstream OIDC (Authentik for demo, Keycloak for prod) ----------------
|
||||
upstream_oauth2:
|
||||
providers:
|
||||
- id: "{{ ess_oidc_provider_ulid }}"
|
||||
human_name: "{{ ess_oidc_provider_name }}"
|
||||
issuer: "{{ ess_oidc_issuer }}"
|
||||
client_id: "{{ ess_oidc_client_id }}"
|
||||
client_secret: "{{ ess_oidc_client_secret }}"
|
||||
token_endpoint_auth_method: client_secret_basic
|
||||
scope: "{{ ess_oidc_scopes }}"
|
||||
claims_imports:
|
||||
localpart:
|
||||
action: require
|
||||
template: "{{ '{{ user.preferred_username }}' }}"
|
||||
displayname:
|
||||
action: suggest
|
||||
template: "{{ '{{ user.name }}' }}"
|
||||
email:
|
||||
action: suggest
|
||||
template: "{{ '{{ user.email }}' }}"
|
||||
set_email_verification: always
|
||||
{% endif %}
|
||||
30
roles/ess_pro_compose/templates/postgres/configure-dbs.sh.j2
Normal file
30
roles/ess_pro_compose/templates/postgres/configure-dbs.sh.j2
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/sh
|
||||
# {{ ansible_managed }}
|
||||
# Postgres init script — chart-equivalent of configure-dbs.sh.
|
||||
# Reads password files from /secrets/ess-generated and creates two DBs.
|
||||
|
||||
set -e
|
||||
|
||||
create_or_ensure_db() {
|
||||
user="$1"
|
||||
db="$2"
|
||||
password="$3"
|
||||
admin_password="$4"
|
||||
|
||||
if echo -n "$admin_password" | psql -W -U postgres -tc "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '$user'" | grep -q 1; then
|
||||
echo -n "$admin_password" | psql -W -U postgres -c "ALTER USER $user PASSWORD '$password'"
|
||||
else
|
||||
echo -n "$admin_password" | psql -W -U postgres -c "CREATE ROLE $user LOGIN PASSWORD '$password'"
|
||||
fi
|
||||
|
||||
if ! echo -n "$admin_password" | psql -W -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '$db'" | grep -q 1; then
|
||||
echo -n "$admin_password" | createdb --encoding=UTF8 --locale=C --template=template0 --owner=$user $db -U postgres
|
||||
fi
|
||||
}
|
||||
|
||||
POSTGRES_PASSWORD="$(cat /secrets/ess-generated/POSTGRES_ADMIN_PASSWORD)"
|
||||
ESS_SYNAPSE_PW="$(cat /secrets/ess-generated/POSTGRES_SYNAPSE_PASSWORD)"
|
||||
ESS_MAS_PW="$(cat /secrets/ess-generated/POSTGRES_MATRIX_AUTHENTICATION_SERVICE_PASSWORD)"
|
||||
|
||||
create_or_ensure_db "matrixauthenticationservice_user" "matrixauthenticationservice" "$ESS_MAS_PW" "$POSTGRES_PASSWORD"
|
||||
create_or_ensure_db "synapse_user" "synapse" "$ESS_SYNAPSE_PW" "$POSTGRES_PASSWORD"
|
||||
27
roles/ess_pro_compose/templates/redis/redis.conf.j2
Normal file
27
roles/ess_pro_compose/templates/redis/redis.conf.j2
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# {{ ansible_managed }}
|
||||
# Redis config — adapted from ess-helm {{ ess_chart_version }}. Used as
|
||||
# pub/sub for Synapse worker replication; no persistence needed.
|
||||
|
||||
protected-mode no
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
tcp-keepalive 300
|
||||
timeout 0
|
||||
daemonize no
|
||||
supervised no
|
||||
loglevel notice
|
||||
logfile ''
|
||||
databases 16
|
||||
always-show-logo no
|
||||
stop-writes-on-bgsave-error yes
|
||||
save ''
|
||||
|
||||
# Disable persistence — Synapse uses Redis only for pub/sub between workers.
|
||||
appendonly no
|
||||
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
hz 1
|
||||
dynamic-hz yes
|
||||
jemalloc-bg-thread yes
|
||||
32
roles/ess_pro_compose/templates/sfu/config.yaml.j2
Normal file
32
roles/ess_pro_compose/templates/sfu/config.yaml.j2
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
## {{ ansible_managed }}
|
||||
## LiveKit SFU — adapted from ess-helm {{ ess_chart_version }}.
|
||||
|
||||
port: 7880
|
||||
|
||||
prometheus:
|
||||
port: 6789
|
||||
|
||||
logging:
|
||||
level: info
|
||||
pion_level: error
|
||||
json: false
|
||||
|
||||
rtc:
|
||||
use_external_ip: false
|
||||
tcp_port: {{ ess_rtc_tcp_port }}
|
||||
udp_port: {{ ess_rtc_udp_port }}
|
||||
# Public IP that LiveKit advertises in ICE candidates. The DMZ NAT forwards
|
||||
# {{ ess_rtc_tcp_port }}/TCP and {{ ess_rtc_udp_port }}/UDP to this host.
|
||||
node_ip: "{{ ess_rtc_external_ip }}"
|
||||
|
||||
# Keys are embedded directly (rendered at compose-up time). The single key
|
||||
# `{{ ess_livekit_key }}` matches what the authorisation service issues
|
||||
# tokens against.
|
||||
keys:
|
||||
{{ ess_livekit_key }}: "{{ _ess_secrets.ELEMENT_CALL_LIVEKIT_SECRET }}"
|
||||
|
||||
room:
|
||||
auto_create: false
|
||||
|
||||
turn:
|
||||
enabled: false
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
## {{ ansible_managed }}
|
||||
## synapse-pro-worker (Rust) federation reader.
|
||||
## This is a different config schema than Python Synapse.
|
||||
|
||||
http:
|
||||
bind_addr: "::"
|
||||
bind_port: 8008
|
||||
|
||||
metrics:
|
||||
bind_addr: "::"
|
||||
bind_port: 9001
|
||||
|
||||
synapse:
|
||||
server_name: "{{ ess_server_name }}"
|
||||
|
||||
database:
|
||||
connection_string: "postgresql://synapse_user:{{ _ess_secrets.POSTGRES_SYNAPSE_PASSWORD }}@postgres:5432/synapse?sslmode=prefer"
|
||||
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
|
||||
logging: basic
|
||||
159
roles/ess_pro_compose/templates/synapse/homeserver.yaml.j2
Normal file
159
roles/ess_pro_compose/templates/synapse/homeserver.yaml.j2
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
## {{ ansible_managed }}
|
||||
## Synapse homeserver config — merged from chart fragments
|
||||
## 01-homeserver-underrides + 04-homeserver-overrides + 05-main.
|
||||
## Adapted from ess-helm {{ ess_chart_version }} for docker compose.
|
||||
|
||||
server_name: "{{ ess_server_name }}"
|
||||
public_baseurl: "https://{{ ess_hostnames.synapse }}/"
|
||||
web_client_location: "https://{{ ess_hostnames.element_web }}/"
|
||||
admin_contact: "{{ ess_admin_contact }}"
|
||||
|
||||
pid_file: /data/homeserver.pid
|
||||
signing_key_path: {{ _ess_secret_mount }}/SYNAPSE_SIGNING_KEY
|
||||
macaroon_secret_key_path: {{ _ess_secret_mount }}/SYNAPSE_MACAROON
|
||||
registration_shared_secret_path: {{ _ess_secret_mount }}/SYNAPSE_REGISTRATION_SHARED_SECRET
|
||||
worker_replication_secret_path: {{ _ess_secret_mount }}/SYNAPSE_WORKERS_REPLICATION_SECRET
|
||||
|
||||
log_config: "/conf/log_config.yaml"
|
||||
enable_metrics: true
|
||||
report_stats: false
|
||||
|
||||
# ---- Listeners (from 05-main.yaml) ----------------------------------------
|
||||
listeners:
|
||||
- port: 8008
|
||||
tls: false
|
||||
type: http
|
||||
bind_addresses: ['0.0.0.0', '::']
|
||||
x_forwarded: true
|
||||
resources:
|
||||
- names: [client, federation]
|
||||
compress: false
|
||||
- port: 9093
|
||||
tls: false
|
||||
type: http
|
||||
bind_addresses: ['0.0.0.0', '::']
|
||||
x_forwarded: false
|
||||
resources:
|
||||
- names: [replication]
|
||||
compress: false
|
||||
- port: 8080
|
||||
tls: false
|
||||
type: http
|
||||
bind_addresses: ['0.0.0.0', '::']
|
||||
x_forwarded: false
|
||||
resources:
|
||||
- names: [health]
|
||||
compress: false
|
||||
- type: metrics
|
||||
port: 9001
|
||||
bind_addresses: ['::']
|
||||
|
||||
enable_media_repo: true
|
||||
media_store_path: "/media/media_store"
|
||||
max_upload_size: "{{ ess_synapse_max_upload_size }}"
|
||||
|
||||
# ---- Pro modules ----------------------------------------------------------
|
||||
modules:
|
||||
- module: "synapse_ess_pro.EssPro"
|
||||
config:
|
||||
version_path: /ess/version
|
||||
- module: "synapse_mass_local_room_upgrades.MassLocalRoomUpgradesModule"
|
||||
config: {}
|
||||
|
||||
# ---- Database -------------------------------------------------------------
|
||||
database:
|
||||
name: psycopg2
|
||||
args:
|
||||
user: synapse_user
|
||||
password: "{{ _ess_secrets.POSTGRES_SYNAPSE_PASSWORD }}"
|
||||
dbname: synapse
|
||||
host: postgres
|
||||
port: 5432
|
||||
sslmode: prefer
|
||||
keepalives: 1
|
||||
keepalives_idle: 10
|
||||
keepalives_interval: 10
|
||||
keepalives_count: 3
|
||||
cp_min: 5
|
||||
cp_max: 10
|
||||
|
||||
# ---- Redis (required for workers) -----------------------------------------
|
||||
redis:
|
||||
enabled: true
|
||||
host: redis
|
||||
port: 6379
|
||||
|
||||
# Replication topology — fed-reader connects back to the main on 9093.
|
||||
instance_map:
|
||||
main:
|
||||
host: synapse-main
|
||||
port: 9093
|
||||
|
||||
# ---- Matrix 2.0 features (MSC4108 QR login, MSC4222 syncv2, MSC4143 RTC) --
|
||||
experimental_features:
|
||||
msc4143_enabled: true
|
||||
msc4222_enabled: true
|
||||
msc4108_enabled: true
|
||||
msc4028_push_encrypted_events: true
|
||||
|
||||
# ---- Delegated auth to MAS (stable since Synapse 1.118) -------------------
|
||||
matrix_authentication_service:
|
||||
enabled: true
|
||||
secret_path: {{ _ess_secret_mount }}/MAS_SYNAPSE_SHARED_SECRET
|
||||
endpoint: "http://mas:8083/"
|
||||
force_http2: true
|
||||
|
||||
password_config:
|
||||
localdb_enabled: false
|
||||
enabled: false
|
||||
|
||||
# ---- Matrix RTC (Element Call discovery) ----------------------------------
|
||||
matrix_rtc:
|
||||
transports:
|
||||
- type: livekit
|
||||
livekit_service_url: "https://{{ ess_hostnames.matrix_rtc }}"
|
||||
|
||||
# ---- URL previews ---------------------------------------------------------
|
||||
url_preview_enabled: {{ ess_synapse_url_previews_enabled | bool | lower }}
|
||||
url_preview_ip_range_whitelist: []
|
||||
url_preview_ip_range_blacklist:
|
||||
- '127.0.0.0/8'
|
||||
- '10.0.0.0/8'
|
||||
- '172.16.0.0/12'
|
||||
- '192.168.0.0/16'
|
||||
- '100.64.0.0/10'
|
||||
- '169.254.0.0/16'
|
||||
- '::1/128'
|
||||
- 'fe80::/10'
|
||||
- 'fc00::/7'
|
||||
|
||||
# ---- Federation -----------------------------------------------------------
|
||||
{% if ess_enable_federation %}
|
||||
send_federation: true
|
||||
federation_client_minimum_tls_version: '1.2'
|
||||
{% else %}
|
||||
send_federation: false
|
||||
federation_domain_whitelist: []
|
||||
{% endif %}
|
||||
|
||||
# ---- Other defaults from chart underrides ---------------------------------
|
||||
require_auth_for_profile_requests: true
|
||||
presence:
|
||||
enabled: false
|
||||
start_pushers: true
|
||||
max_event_delay_duration: 24h
|
||||
|
||||
room_list_publication_rules:
|
||||
- action: allow
|
||||
user_id: "@*:{{ ess_server_name }}"
|
||||
|
||||
rc_message:
|
||||
per_second: 0.5
|
||||
burst_count: 30
|
||||
rc_delayed_event_mgmt:
|
||||
per_second: 1
|
||||
burst_count: 20
|
||||
|
||||
trusted_key_servers:
|
||||
- server_name: "matrix.org"
|
||||
suppress_key_server_warning: true
|
||||
16
roles/ess_pro_compose/templates/synapse/log_config.yaml.j2
Normal file
16
roles/ess_pro_compose/templates/synapse/log_config.yaml.j2
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
## {{ ansible_managed }}
|
||||
version: 1
|
||||
formatters:
|
||||
precise:
|
||||
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
||||
handlers:
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: precise
|
||||
loggers:
|
||||
synapse.storage.SQL:
|
||||
level: INFO
|
||||
root:
|
||||
level: INFO
|
||||
handlers: [console]
|
||||
disable_existing_loggers: false
|
||||
46
roles/ess_pro_compose/vars/main.yml
Normal file
46
roles/ess_pro_compose/vars/main.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# Internal — do not override in inventory.
|
||||
|
||||
# Mount points inside containers (Element Pro convention)
|
||||
_ess_secret_mount: "/secrets/ess-generated"
|
||||
_ess_conf_mount: "/conf"
|
||||
_ess_well_known_mount: "/well-known"
|
||||
|
||||
# Compose file path
|
||||
_ess_compose_file: "{{ ess_compose_dir }}/compose.yml"
|
||||
_ess_env_file: "{{ ess_compose_dir }}/.env"
|
||||
|
||||
# Directory tree to create on the host
|
||||
_ess_dirs:
|
||||
- "{{ ess_compose_dir }}"
|
||||
- "{{ ess_compose_conf_dir }}"
|
||||
- "{{ ess_compose_conf_dir }}/haproxy"
|
||||
- "{{ ess_compose_conf_dir }}/haproxy/well-known"
|
||||
- "{{ ess_compose_conf_dir }}/synapse"
|
||||
- "{{ ess_compose_conf_dir }}/mas"
|
||||
- "{{ ess_compose_conf_dir }}/sfu"
|
||||
- "{{ ess_compose_conf_dir }}/element-web"
|
||||
- "{{ ess_compose_conf_dir }}/postgres"
|
||||
- "{{ ess_compose_conf_dir }}/redis"
|
||||
- "{{ ess_compose_secrets_dir }}"
|
||||
- "{{ ess_compose_data_dir }}"
|
||||
- "{{ ess_compose_data_dir }}/postgres"
|
||||
- "{{ ess_compose_data_dir }}/synapse-media"
|
||||
|
||||
# All Element Pro secret-file names (matches the init-secrets job in the chart)
|
||||
_ess_secret_names:
|
||||
- POSTGRES_ADMIN_PASSWORD
|
||||
- POSTGRES_SYNAPSE_PASSWORD
|
||||
- POSTGRES_MATRIX_AUTHENTICATION_SERVICE_PASSWORD
|
||||
- SYNAPSE_MACAROON
|
||||
- SYNAPSE_REGISTRATION_SHARED_SECRET
|
||||
- SYNAPSE_WORKERS_REPLICATION_SECRET
|
||||
- SYNAPSE_SIGNING_KEY
|
||||
- MAS_SYNAPSE_SHARED_SECRET
|
||||
- MAS_MATRIX_TOOLS_OIDC_CLIENT_SECRET
|
||||
- MAS_ENCRYPTION_SECRET
|
||||
- MAS_RSA_PRIVATE_KEY
|
||||
- MAS_ECDSA_PRIME256V1_PRIVATE_KEY
|
||||
- ELEMENT_CALL_LIVEKIT_SECRET
|
||||
- ADMIN_USER_PASSWORD
|
||||
Loading…
Add table
Add a link
Reference in a new issue