feat(ess_pro): deploy Element Server Suite Pro via K3s + Helm

Adds k3s and ess_pro roles to replace the planned Nextcloud Talk
stack. Integrates with existing Keycloak (OIDC), Garage (S3 media)
and OpenBao (secrets). Hostnames under digitalboard.ch.
This commit is contained in:
Tobias Wüst 2026-05-27 23:46:37 +02:00
parent c11f019aae
commit 01fd12d75c
18 changed files with 1098 additions and 0 deletions

View file

@ -0,0 +1,41 @@
# SPDX-License-Identifier: MIT-0
---
# Helm needs to authenticate against registry.element.io to pull both the
# matrix-stack chart AND the Pro container images. We do both:
# 1. `helm registry login` so the chart pull works.
# 2. A docker-registry Secret in the namespace so pods can pull images.
- name: Log in to Element Helm/OCI registry
ansible.builtin.command:
cmd: >-
{{ ess_pro_helm_install_dir }}/helm registry login {{ ess_pro_registry_url }}
--username {{ ess_pro_registry_username | quote }}
--password-stdin
stdin: "{{ ess_pro_registry_token }}"
register: helm_login
changed_when: "'Login Succeeded' in (helm_login.stdout + helm_login.stderr)"
no_log: true
- name: Create image pull Secret for the ESS namespace
kubernetes.core.k8s:
kubeconfig: "{{ ess_pro_kubeconfig }}"
state: present
definition:
apiVersion: v1
kind: Secret
type: kubernetes.io/dockerconfigjson
metadata:
name: "{{ ess_pro_image_pull_secret_name }}"
namespace: "{{ ess_pro_namespace }}"
labels:
app.kubernetes.io/managed-by: ansible
data:
.dockerconfigjson: "{{ _dockerconfig | to_json | b64encode }}"
vars:
_dockerconfig:
auths:
"{{ ess_pro_registry_url }}":
username: "{{ ess_pro_registry_username }}"
password: "{{ ess_pro_registry_token }}"
auth: "{{ (ess_pro_registry_username ~ ':' ~ ess_pro_registry_token) | b64encode }}"
no_log: true

View file

@ -0,0 +1,63 @@
---
- name: Render ESS values.yaml
ansible.builtin.template:
src: values.yaml.j2
dest: "{{ ess_pro_values_file }}"
owner: root
group: root
mode: "0640"
- name: Deploy / upgrade ESS Pro Helm release
kubernetes.core.helm:
kubeconfig: "{{ ess_pro_kubeconfig }}"
name: "{{ ess_pro_release_name }}"
chart_ref: "{{ ess_pro_chart_ref }}"
chart_version: "{{ ess_pro_chart_version | default(omit, true) }}"
release_namespace: "{{ ess_pro_namespace }}"
create_namespace: false
values_files:
- "{{ ess_pro_values_file }}"
wait: "{{ ess_pro_helm_wait | bool }}"
wait_timeout: "{{ ess_pro_helm_timeout }}"
atomic: false
state: present
register: helm_release
- name: Show release status
ansible.builtin.debug:
msg: "{{ helm_release.status | default('no status returned') }}"
when: helm_release is defined
- name: Wait for Synapse pod to be Ready
kubernetes.core.k8s_info:
kubeconfig: "{{ ess_pro_kubeconfig }}"
kind: Pod
namespace: "{{ ess_pro_namespace }}"
label_selectors:
- "app.kubernetes.io/name=synapse"
register: synapse_pods
until:
- synapse_pods.resources | length > 0
- synapse_pods.resources[0].status.containerStatuses is defined
- (synapse_pods.resources[0].status.containerStatuses | selectattr('ready', 'equalto', true) | list | length) > 0
retries: 30
delay: 10
- name: Fetch the localadmin bootstrap password (one-shot, only printed in verbose runs)
kubernetes.core.k8s_info:
kubeconfig: "{{ ess_pro_kubeconfig }}"
kind: Secret
namespace: "{{ ess_pro_namespace }}"
name: "{{ ess_pro_release_name }}-generated"
register: ess_generated_secret
when: ess_pro_create_initial_admin | bool
no_log: true
- name: Show how to retrieve the localadmin password
ansible.builtin.debug:
msg: |
ESS Pro is up. To get the localadmin password:
kubectl -n {{ ess_pro_namespace }} get secrets/{{ ess_pro_release_name }}-generated \
-o jsonpath='{.data.ADMIN_USER_PASSWORD}' | base64 -d
Login at https://{{ ess_pro_hostnames.element_admin }} as @localadmin:{{ ess_pro_server_name }}
when: ess_pro_create_initial_admin | bool

View file

@ -0,0 +1,51 @@
# SPDX-License-Identifier: MIT-0
---
- name: Validate required variables
ansible.builtin.assert:
that:
- ess_pro_server_name | length > 0
- ess_pro_registry_username | length > 0
- ess_pro_registry_token | length > 0
fail_msg: >-
ess_pro_server_name, ess_pro_registry_username and ess_pro_registry_token
must be set. Provide them in group_vars/ess_servers.yml (typically as
OpenBao lookups, following the digitalboard.core convention).
quiet: true
- name: Validate OIDC variables when OIDC is enabled
ansible.builtin.assert:
that:
- ess_pro_oidc_issuer | length > 0
- ess_pro_oidc_client_secret | length > 0
fail_msg: ess_pro_oidc_issuer and ess_pro_oidc_client_secret must be set when OIDC is enabled.
quiet: true
when: ess_pro_oidc_enabled | bool
- name: Validate S3 variables when S3 media is enabled
ansible.builtin.assert:
that:
- ess_pro_s3_endpoint | length > 0
- ess_pro_s3_access_key | length > 0
- ess_pro_s3_secret_key | length > 0
fail_msg: S3 endpoint, access key and secret key must be set when S3 media is enabled.
quiet: true
when: ess_pro_s3_media_enabled | bool
- name: Validate external Postgres variables
ansible.builtin.assert:
that:
- ess_pro_postgres_host | length > 0
- ess_pro_postgres_synapse_password | length > 0
- ess_pro_postgres_mas_password | length > 0
fail_msg: External Postgres host and per-component passwords must be set when ess_pro_postgres_external is true.
quiet: true
when: ess_pro_postgres_external | bool
- name: Run prerequisite tasks (Helm CLI, namespace)
ansible.builtin.import_tasks: prerequisites.yml
- name: Authenticate against Element image registry and create pull secret
ansible.builtin.import_tasks: credentials.yml
- name: Render values.yaml and deploy the Helm release
ansible.builtin.import_tasks: deploy.yml

View file

@ -0,0 +1,66 @@
---
- name: Ensure required OS packages are present
ansible.builtin.apt:
name:
- python3-kubernetes
- python3-yaml
- ca-certificates
- curl
state: present
update_cache: true
- name: Check whether Helm is already installed
ansible.builtin.stat:
path: "{{ ess_pro_helm_install_dir }}/helm"
register: helm_binary
- name: Check installed Helm version
ansible.builtin.command: "{{ ess_pro_helm_install_dir }}/helm version --short"
register: helm_version_check
changed_when: false
failed_when: false
when: helm_binary.stat.exists
- name: Download Helm tarball
ansible.builtin.get_url:
url: "https://get.helm.sh/helm-{{ ess_pro_helm_version }}-linux-amd64.tar.gz"
dest: "/tmp/helm-{{ ess_pro_helm_version }}.tar.gz"
mode: "0644"
when: not helm_binary.stat.exists or (ess_pro_helm_version not in (helm_version_check.stdout | default('')))
- name: Unpack Helm
ansible.builtin.unarchive:
src: "/tmp/helm-{{ ess_pro_helm_version }}.tar.gz"
dest: /tmp/
remote_src: true
creates: "/tmp/linux-amd64/helm"
when: not helm_binary.stat.exists or (ess_pro_helm_version not in (helm_version_check.stdout | default('')))
- name: Install Helm binary
ansible.builtin.copy:
src: /tmp/linux-amd64/helm
dest: "{{ ess_pro_helm_install_dir }}/helm"
remote_src: true
mode: "0755"
when: not helm_binary.stat.exists or (ess_pro_helm_version not in (helm_version_check.stdout | default('')))
- name: Ensure ESS config directory exists
ansible.builtin.file:
path: "{{ ess_pro_config_dir }}"
state: directory
mode: "0750"
owner: root
group: root
- name: Ensure ESS namespace exists
kubernetes.core.k8s:
kubeconfig: "{{ ess_pro_kubeconfig }}"
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ ess_pro_namespace }}"
labels:
app.kubernetes.io/managed-by: ansible
app.kubernetes.io/part-of: digitalboard