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,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

View 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 }}"

View 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

View 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 }}

View 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

View 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