Switch to using an external Etherpad role

This new role also adds native Traefik support and support for other
(non-`amd64`) architectures via self-building.
This commit is contained in:
Slavi Pantaleev 2023-03-02 22:50:13 +02:00
parent ae76db4d77
commit 124fbeda04
19 changed files with 189 additions and 433 deletions

View File

@ -1,3 +1,32 @@
# 2023-03-02
## The matrix-etherpad role lives independently now
**TLDR**: the `matrix-etherpad` role is now included from [another repository](https://gitlab.com/etke.cc/roles/etherpad). Some variables have been renamed. All functionality remains intact.
You need to **update you roles** (`just roles` or `make roles`) regardless of whether you're using Etherpad or not.
If you're making use of Etherpad via this playbook, you will need to update variable references in your `vars.yml` file:
- Rename `matrix_etherpad_public_endpoint` to `etherpad_path_prefix`
- Replace `matrix_etherpad_mode: dimension` with:
- for `matrix-nginx-proxy` users:
- `etherpad_nginx_proxy_dimension_integration_enabled: true`
- `etherpad_hostname: "{{ matrix_server_fqn_dimension }}"`
- for Traefik users:
- define your own `etherpad_hostname` and `etherpad_path_prefix` as you see fit
- Rename all other variables:
- `matrix_etherpad_docker_image_` -> `matrix_etherpad_container_image_`
- `matrix_etherpad_` -> `etherpad_`
Along with this relocation, the new role also:
- supports [self-building](docs/self-building.md), so it should work on `arm32` and `arm64` architectures
- has native Traefik reverse-proxy support (Etherpad requests no longer go through `matrix-nginx-proxy` when using Traefik)
# 2023-02-26 # 2023-02-26
## Traefik is the default reverse-proxy now ## Traefik is the default reverse-proxy now
@ -483,11 +512,11 @@ Various services (like Dimension, etc.) still talk to Synapse via `matrix-nginx-
Until now, [Etherpad](https://etherpad.org/) (which [the playbook could install for you](docs/configuring-playbook-etherpad.md)) required the [Dimension integration manager](docs/configuring-playbook-dimension.md) to also be installed, because Etherpad was hosted on the Dimension domain (at `dimension.DOMAIN/etherpad`). Until now, [Etherpad](https://etherpad.org/) (which [the playbook could install for you](docs/configuring-playbook-etherpad.md)) required the [Dimension integration manager](docs/configuring-playbook-dimension.md) to also be installed, because Etherpad was hosted on the Dimension domain (at `dimension.DOMAIN/etherpad`).
From now on, Etherpad can be installed in `standalone` mode on `etherpad.DOMAIN` and used even without Dimension. This is much more versatile, so the playbook now defaults to this new mode (`matrix_etherpad_mode: standalone`). From now on, Etherpad can be installed in `standalone` mode on `etherpad.DOMAIN` and used even without Dimension. This is much more versatile, so the playbook now defaults to this new mode (`etherpad_mode: standalone`).
If you've already got both Etherpad and Dimension in use you could: If you've already got both Etherpad and Dimension in use you could:
- **either** keep hosting Etherpad under the Dimension domain by adding `matrix_etherpad_mode: dimension` to your `vars.yml` file. All your existing room widgets will continue working at the same URLs and no other changes will be necessary. - **either** keep hosting Etherpad under the Dimension domain by adding `etherpad_mode: dimension` to your `vars.yml` file. All your existing room widgets will continue working at the same URLs and no other changes will be necessary.
- **or**, you could change to hosting Etherpad separately on `etherpad.DOMAIN`. You will need to [configure a DNS record](docs/configuring-dns.md) for this new domain. You will also need to reconfigure Dimension to use the new pad URLs (`https://etherpad.DOMAIN/...`) going forward (refer to our [configuring Etherpad documentation](docs/configuring-playbook-etherpad.md)). All your existing room widgets (which still use `https://dimension.DOMAIN/etherpad/...`) will break as Etherpad is not hosted there anymore. You will need to re-add them or to consider not using `standalone` mode - **or**, you could change to hosting Etherpad separately on `etherpad.DOMAIN`. You will need to [configure a DNS record](docs/configuring-dns.md) for this new domain. You will also need to reconfigure Dimension to use the new pad URLs (`https://etherpad.DOMAIN/...`) going forward (refer to our [configuring Etherpad documentation](docs/configuring-playbook-etherpad.md)). All your existing room widgets (which still use `https://dimension.DOMAIN/etherpad/...`) will break as Etherpad is not hosted there anymore. You will need to re-add them or to consider not using `standalone` mode

View File

@ -1,19 +1,41 @@
# Setting up Etherpad (optional) # Setting up Etherpad (optional)
[Etherpad](https://etherpad.org) is is an open source collaborative text editor that can be embedded in a Matrix chat room using the [Dimension integrations manager](https://dimension.t2bot.io) or used as standalone web app. [Etherpad](https://etherpad.org) is an open source collaborative text editor that can be embedded in a Matrix chat room using the [Dimension integrations manager](https://dimension.t2bot.io) or used as standalone web app.
When enabled together with the Jitsi audio/video conferencing system (see [our docs on Jitsi](configuring-playbook-jitsi.md)), it will be made available as an option during the conferences. When enabled together with the Jitsi audio/video conferencing system (see [our docs on Jitsi](configuring-playbook-jitsi.md)), it will be made available as an option during the conferences.
## Prerequisites ## Decide on a domain and path
Etherpad can be installed in 2 modes: By default, Etherpad is configured to use its own dedicated domain (`etherpad.DOMAIN`) and requires you to [adjust your DNS records](#adjusting-dns-records).
- (default) `standalone` mode (`matrix_etherpad_mode: standalone`) - Etherpad will be hosted on `etherpad.<your-domain>` (`matrix_server_fqn_etherpad`), so the DNS record for this domian must be created. See [Configuring your DNS server](configuring-dns.md) on how to set up the `etherpad` DNS record correctly You can override the domain and path like this:
- `dimension` mode (`matrix_etherpad_mode: dimension`) - Etherpad will be hosted on `dimension.<your-domain>/etherpad` (`matrix_server_fqn_dimension`). This requires that you **first** configure the **Dimension integrations manager** as described in [the playbook documentation](configuring-playbook-dimension.md) ```yaml
# Switch to the domain used for Matrix services (`matrix.DOMAIN`),
# so we won't need to add additional DNS records for Etherpad.
etherpad_hostname: "{{ matrix_server_fqn_matrix }}"
We recomend that you go with the default (`standalone`) mode, which makes Etherpad independent and allows it to be used with or without Dimension. # Expose under the /etherpad subpath
etherpad_path_prefix: /etherpad
```
**NOTE**: When using the old `matrix-nginx-proxy` reverse-proxy instead of Traefik, you have only 2 choices:
- serving Etherpad at its own dedicated domain:
- you need to set the domain using the `matrix_server_fqn_etherpad` variable (not `etherpad_hostname`)
- you must use `etherpad_path_prefix: /`
- serving Etherpad at the [Dimension](configuring-playbook-dimension.md) integration manager's domain (`matrix_server_fqn_dimension`)
- you need to have Dimension enabled
- you need to add `etherpad_path_prefix: /etherpad` or another prefix (different than `/`)
- you need to add `etherpad_nginx_proxy_dimension_integration_enabled: true` to enable this integration
## Adjusting DNS records
Once you've decided on the domain and path, **you may need to adjust your DNS** records to point the Etherpad domain to the Matrix server.
If you've decided to reuse the `matrix.` domain, you won't need to do any extra DNS configuration.
## Installing ## Installing
@ -21,48 +43,51 @@ We recomend that you go with the default (`standalone`) mode, which makes Etherp
[Etherpad](https://etherpad.org) installation is disabled by default. You can enable it in your configuration file (`inventory/host_vars/matrix.<your-domain>/vars.yml`): [Etherpad](https://etherpad.org) installation is disabled by default. You can enable it in your configuration file (`inventory/host_vars/matrix.<your-domain>/vars.yml`):
```yaml ```yaml
matrix_etherpad_enabled: true etherpad_enabled: true
# Uncomment below if you'd like to install Etherpad on the Dimension domain (not recommended)
# matrix_etherpad_mode: dimension
# Uncomment below to enable the admin web UI # Uncomment below to enable the admin web UI
# matrix_etherpad_admin_username: admin # etherpad_admin_username: admin
# matrix_etherpad_admin_password: some-password # etherpad_admin_password: some-password
``` ```
If enabled, the admin web-UI should then be available on `https://etherpad.<your-domain>/admin` (or `https://dimension.<your-domain>/etherpad/admin`, if `matrix_etherpad_mode: dimension`) Then, [run the installation process](installing.md) again (e.g. `just install-all`).
## Managing / Deleting old pads ## Usage
The Etherpad UI should be available at `https://etherpad.<your-domain>`, while the admin UI (if enabled) should then be available at `https://etherpad.<your-domain>/admin`.
If you've [decided on another hostname or path-prefix](#decide-on-a-domain-and-path) (e.g. `https://matrix.DOMAIN/etherpad`), adjust these URLs accordingly before usage.
### Managing / Deleting old pads
If you want to manage and remove old unused pads from Etherpad, you will first need to able Admin access as described above. If you want to manage and remove old unused pads from Etherpad, you will first need to able Admin access as described above.
Then from the plugin manager page (`https://etherpad.<your-domain>/admin/plugins` or `https://dimension.<your-domain>/etherpad/admin/plugins`), install the `adminpads2` plugin. Once installed, you should have a "Manage pads" section in the Admin web-UI. Then from the plugin manager page (`https://etherpad.<your-domain>/admin/plugins`, install the `adminpads2` plugin. Once installed, you should have a "Manage pads" section in the Admin web-UI.
## How to use Etherpad widgets without an Integration Manager (like Dimension) ### How to use Etherpad widgets without an Integration Manager (like Dimension)
This is how it works in Element, it might work quite similar with other clients: This is how it works in Element, it might work quite similar with other clients:
To integrate a standalone etherpad in a room, create your pad by visiting `https://etherpad.DOMAIN`. When the pad opens, copy the URL and send a command like this to the room: `/addwidget URL`. You will then find your integrated Etherpad within the right sidebar in the `Widgets` section. To integrate a standalone etherpad in a room, create your pad by visiting `https://etherpad.DOMAIN`. When the pad opens, copy the URL and send a command like this to the room: `/addwidget URL`. You will then find your integrated Etherpad within the right sidebar in the `Widgets` section.
## Set Dimension default to the self-hosted Etherpad (optional) ### Set Dimension default to the self-hosted Etherpad (optional)
If you decided to install [Dimension integration manager](configuring-playbook-dimension.md) alongside Etherpad, the Dimension administrator users can configure the default URL template. If you decided to install [Dimension integration manager](configuring-playbook-dimension.md) alongside Etherpad, the Dimension administrator users can configure the default URL template.
The Dimension configuration menu can be accessed with the sprocket icon as you begin to add a widget to a room in Element. There you will find the Etherpad Widget Configuration action beneath the _Widgets_ tab. The Dimension configuration menu can be accessed with the sprocket icon as you begin to add a widget to a room in Element. There you will find the Etherpad Widget Configuration action beneath the _Widgets_ tab.
### Removing the integrated Etherpad chat #### Removing the integrated Etherpad chat
If you wish to disable the Etherpad chat button, you can do it by appending `?showChat=false` to the end of the pad URL, or the template. Examples: If you wish to disable the Etherpad chat button, you can do it by appending `?showChat=false` to the end of the pad URL, or the template.
- `https://etherpad.<your-domain>/p/$roomId_$padName?showChat=false` (for the default - `matrix_etherpad_mode: standalone`)
- `https://dimension.<your-domain>/etherpad/p/$roomId_$padName?showChat=false` (for `matrix_etherpad_mode: dimension`) Example: `https://etherpad.<your-domain>/p/$roomId_$padName?showChat=false`
### Known issues ## Known issues
If your Etherpad widget fails to load, this might be due to Dimension generating a Pad name so long, the Etherpad app rejects it. If your Etherpad widget fails to load, this might be due to Dimension generating a Pad name so long, the Etherpad app rejects it.
`$roomId_$padName` can end up being longer than 50 characters. You can avoid having this problem by altering the template so it only contains the three word random identifier `$padName`. `$roomId_$padName` can end up being longer than 50 characters. You can avoid having this problem by altering the template so it only contains the three word random identifier `$padName`.

View File

@ -304,7 +304,7 @@ devture_systemd_service_manager_services_list_auto: |
+ +
([{'name': 'matrix-email2matrix.service', 'priority': 2000, 'groups': ['matrix', 'bridges', 'email2matrix']}] if matrix_email2matrix_enabled else []) ([{'name': 'matrix-email2matrix.service', 'priority': 2000, 'groups': ['matrix', 'bridges', 'email2matrix']}] if matrix_email2matrix_enabled else [])
+ +
([{'name': 'matrix-etherpad.service', 'priority': 4000, 'groups': ['matrix', 'etherpad']}] if matrix_etherpad_enabled else []) ([{'name': (etherpad_identifier + '.service'), 'priority': 4000, 'groups': ['matrix', 'etherpad']}] if etherpad_enabled else [])
+ +
([{'name': (grafana_identifier + '.service'), 'priority': 4000, 'groups': ['matrix', 'monitoring', 'grafana']}] if grafana_enabled else []) ([{'name': (grafana_identifier + '.service'), 'priority': 4000, 'groups': ['matrix', 'monitoring', 'grafana']}] if grafana_enabled else [])
+ +
@ -2208,29 +2208,55 @@ matrix_dimension_database_password: "{{ '%s' | format(matrix_homeserver_generic_
###################################################################### ######################################################################
# #
# matrix-etherpad # etke/etherpad
# #
###################################################################### ######################################################################
matrix_etherpad_enabled: false etherpad_enabled: false
matrix_etherpad_container_http_host_bind_port: "{{ (matrix_playbook_service_host_bind_interface_prefix ~ '9001') if matrix_playbook_service_host_bind_interface_prefix else '' }}" etherpad_identifier: matrix-etherpad
matrix_etherpad_base_url: "{{ 'https://' + matrix_server_fqn_dimension + matrix_etherpad_public_endpoint if matrix_etherpad_mode == 'dimension' else 'https://' + matrix_server_fqn_etherpad + '/' }}" etherpad_scheme: "{{ 'https' if matrix_playbook_ssl_enabled else 'http' }}"
matrix_etherpad_systemd_required_services_list: | etherpad_base_path: "{{ matrix_base_data_path }}/etherpad"
etherpad_framing_enabled: "{{ matrix_dimension_enabled or matrix_jitsi_enabled }}"
etherpad_container_image_self_build: "{{ matrix_architecture not in ['amd64'] }}"
etherpad_container_http_host_bind_port: "{{ (matrix_playbook_service_host_bind_interface_prefix ~ '9001') if matrix_playbook_service_host_bind_interface_prefix else '' }}"
etherpad_container_network: "{{ matrix_nginx_proxy_container_network if matrix_playbook_reverse_proxy_type == 'playbook-managed-nginx' else etherpad_identifier }}"
etherpad_container_additional_networks: |
{{
(
([matrix_playbook_reverse_proxyable_services_additional_network] if matrix_playbook_reverse_proxyable_services_additional_network else [])
+
([devture_postgres_container_network] if devture_postgres_enabled and devture_postgres_container_network != etherpad_container_network else [])
) | unique
}}
etherpad_container_labels_traefik_enabled: "{{ matrix_playbook_reverse_proxy_type in ['playbook-managed-traefik', 'other-traefik-container'] }}"
etherpad_container_labels_traefik_docker_network: "{{ matrix_playbook_reverse_proxyable_services_additional_network }}"
etherpad_container_labels_traefik_entrypoints: "{{ devture_traefik_entrypoint_primary }}"
etherpad_container_labels_traefik_tls_certResolver: "{{ devture_traefik_certResolver_primary }}"
etherpad_systemd_required_services_list: |
{{ {{
['docker.service'] ['docker.service']
+ +
([devture_postgres_identifier ~ '.service'] if devture_postgres_enabled else []) ([devture_postgres_identifier ~ '.service'] if devture_postgres_enabled else [])
}} }}
matrix_etherpad_database_hostname: "{{ devture_postgres_connection_hostname if devture_postgres_enabled else '' }}" etherpad_database_hostname: "{{ devture_postgres_connection_hostname if devture_postgres_enabled else '' }}"
matrix_etherpad_database_password: "{{ '%s' | format(matrix_homeserver_generic_secret_key) | password_hash('sha512', 'etherpad.db', rounds=655555) | to_uuid }}" etherpad_database_name: matrix_etherpad
etherpad_database_username: matrix_etherpad
etherpad_database_password: "{{ '%s' | format(matrix_homeserver_generic_secret_key) | password_hash('sha512', 'etherpad.db', rounds=655555) | to_uuid }}"
###################################################################### ######################################################################
# #
# /matrix-etherpad # /etke/etherpad
# #
###################################################################### ######################################################################
@ -2298,9 +2324,9 @@ matrix_jitsi_web_stun_servers: |
# If the self-hosted Etherpad instance is available, it will also show up in Jitsi conferences, # If the self-hosted Etherpad instance is available, it will also show up in Jitsi conferences,
# unless explicitly disabled by setting `matrix_jitsi_etherpad_enabled` to false. # unless explicitly disabled by setting `matrix_jitsi_etherpad_enabled` to false.
# Falls back to the scalar.vector.im etherpad in case someone sets `matrix_jitsi_etherpad_enabled` to true, # Falls back to the scalar.vector.im etherpad in case someone sets `matrix_jitsi_etherpad_enabled` to true,
# while also setting `matrix_etherpad_enabled` to false. # while also setting `etherpad_enabled` to false.
matrix_jitsi_etherpad_enabled: "{{ matrix_etherpad_enabled }}" matrix_jitsi_etherpad_enabled: "{{ etherpad_enabled }}"
matrix_jitsi_etherpad_base: "{{ matrix_etherpad_base_url if matrix_etherpad_enabled else 'https://scalar.vector.im/etherpad' }}" matrix_jitsi_etherpad_base: "{{ etherpad_base_url if etherpad_enabled else 'https://scalar.vector.im/etherpad' }}"
# Allow verification using JWT and matrix-UVS # Allow verification using JWT and matrix-UVS
matrix_jitsi_prosody_auth_matrix_uvs_auth_token: "{{ matrix_user_verification_service_uvs_auth_token }}" matrix_jitsi_prosody_auth_matrix_uvs_auth_token: "{{ matrix_user_verification_service_uvs_auth_token }}"
@ -2468,7 +2494,7 @@ matrix_nginx_proxy_proxy_cinny_enabled: "{{ matrix_client_cinny_enabled and matr
matrix_nginx_proxy_proxy_buscarron_enabled: "{{ matrix_bot_buscarron_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}" matrix_nginx_proxy_proxy_buscarron_enabled: "{{ matrix_bot_buscarron_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}"
matrix_nginx_proxy_proxy_dimension_enabled: "{{ matrix_dimension_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}" matrix_nginx_proxy_proxy_dimension_enabled: "{{ matrix_dimension_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}"
matrix_nginx_proxy_proxy_rageshake_enabled: "{{ matrix_rageshake_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}" matrix_nginx_proxy_proxy_rageshake_enabled: "{{ matrix_rageshake_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}"
matrix_nginx_proxy_proxy_etherpad_enabled: "{{ matrix_etherpad_enabled and matrix_etherpad_mode == 'standalone' }}" matrix_nginx_proxy_proxy_etherpad_enabled: "{{ etherpad_enabled and not etherpad_nginx_proxy_dimension_integration_enabled and matrix_playbook_reverse_proxy_type in ['playbook-managed-nginx', 'other-nginx-non-container'] }}"
matrix_nginx_proxy_proxy_bot_go_neb_enabled: "{{ matrix_bot_go_neb_enabled }}" matrix_nginx_proxy_proxy_bot_go_neb_enabled: "{{ matrix_bot_go_neb_enabled }}"
matrix_nginx_proxy_proxy_jitsi_enabled: "{{ matrix_jitsi_enabled }}" matrix_nginx_proxy_proxy_jitsi_enabled: "{{ matrix_jitsi_enabled }}"
@ -2484,7 +2510,6 @@ matrix_nginx_proxy_container_labels_traefik_entrypoints: "{{ devture_traefik_ent
matrix_nginx_proxy_container_labels_traefik_tls_certResolver: "{{ devture_traefik_certResolver_primary }}" matrix_nginx_proxy_container_labels_traefik_tls_certResolver: "{{ devture_traefik_certResolver_primary }}"
matrix_nginx_proxy_container_labels_traefik_proxy_matrix_enabled: true matrix_nginx_proxy_container_labels_traefik_proxy_matrix_enabled: true
matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_enabled: "{{ matrix_etherpad_enabled and matrix_etherpad_mode == 'standalone' }}"
matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_enabled: "{{ matrix_bot_go_neb_enabled }}" matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_enabled: "{{ matrix_bot_go_neb_enabled }}"
matrix_nginx_proxy_container_labels_traefik_proxy_jitsi_enabled: "{{ matrix_jitsi_enabled }}" matrix_nginx_proxy_container_labels_traefik_proxy_jitsi_enabled: "{{ matrix_jitsi_enabled }}"
@ -2576,7 +2601,7 @@ matrix_nginx_proxy_systemd_wanted_services_list: |
+ +
(['matrix-bot-go-neb.service'] if matrix_bot_go_neb_enabled else []) (['matrix-bot-go-neb.service'] if matrix_bot_go_neb_enabled else [])
+ +
(['matrix-etherpad.service'] if matrix_etherpad_enabled else []) ([etherpad_identifier + '.service'] if etherpad_enabled else [])
+ +
(['matrix-hookshot.service'] if matrix_hookshot_enabled else []) (['matrix-hookshot.service'] if matrix_hookshot_enabled else [])
}} }}
@ -2597,7 +2622,7 @@ matrix_ssl_domains_to_obtain_certificates_for: |
+ +
([matrix_server_fqn_dimension] if matrix_dimension_enabled else []) ([matrix_server_fqn_dimension] if matrix_dimension_enabled else [])
+ +
([matrix_server_fqn_etherpad] if (matrix_etherpad_enabled and matrix_etherpad_mode == 'standalone') else []) ([matrix_server_fqn_etherpad] if (etherpad_enabled and not etherpad_nginx_proxy_dimension_integration_enabled) else [])
+ +
([matrix_server_fqn_bot_go_neb] if matrix_bot_go_neb_enabled else []) ([matrix_server_fqn_bot_go_neb] if matrix_bot_go_neb_enabled else [])
+ +
@ -2888,10 +2913,10 @@ devture_postgres_managed_databases_auto: |
}] if (matrix_dimension_enabled and matrix_dimension_database_engine == 'postgres' and matrix_dimension_database_hostname == devture_postgres_connection_hostname) else []) }] if (matrix_dimension_enabled and matrix_dimension_database_engine == 'postgres' and matrix_dimension_database_hostname == devture_postgres_connection_hostname) else [])
+ +
([{ ([{
'name': matrix_etherpad_database_name, 'name': etherpad_database_name,
'username': matrix_etherpad_database_username, 'username': etherpad_database_username,
'password': matrix_etherpad_database_password, 'password': etherpad_database_password,
}] if (matrix_etherpad_enabled and matrix_etherpad_database_engine == 'postgres' and matrix_etherpad_database_hostname == devture_postgres_connection_hostname) else []) }] if (etherpad_enabled and etherpad_database_engine == 'postgres' and etherpad_database_hostname == devture_postgres_connection_hostname) else [])
+ +
([{ ([{
'name': prometheus_postgres_exporter_database_name, 'name': prometheus_postgres_exporter_database_name,

View File

@ -103,7 +103,8 @@
- custom/matrix-ldap-registration-proxy - custom/matrix-ldap-registration-proxy
- custom/matrix-ma1sd - custom/matrix-ma1sd
- custom/matrix-dimension - custom/matrix-dimension
- custom/matrix-etherpad - galaxy/etherpad
- custom/etherpad-proxy-connect
- custom/matrix-email2matrix - custom/matrix-email2matrix
- custom/matrix-sygnal - custom/matrix-sygnal
- galaxy/ntfy - galaxy/ntfy

View File

@ -48,6 +48,9 @@
- src: git+https://gitlab.com/etke.cc/roles/redis.git - src: git+https://gitlab.com/etke.cc/roles/redis.git
version: v7.0.9-0 version: v7.0.9-0
- src: git+https://gitlab.com/etke.cc/roles/etherpad.git
version: v1.8.18-0
- src: git+https://github.com/devture/com.devture.ansible.role.traefik.git - src: git+https://github.com/devture/com.devture.ansible.role.traefik.git
version: 4ec9187017cb7832f521fc273fabd0a873ca2736 version: 4ec9187017cb7832f521fc273fabd0a873ca2736

View File

@ -0,0 +1,11 @@
---
# etherpad-proxy-connect is a compatibility role connecting the new Etherpad role with matrix-nginx-proxy.
# It adds back support for serving Etherpad under the Dimension domain (`matrix_server_fqn_dimension`).
# Controls whether Etherpad will be hosted under the Dimension domain when matrix-nginx-proxy is used (depending on matrix_playbook_reverse_proxy_type).
# If you're not using matrix-nginx-proxy, then this value has no effect.
etherpad_nginx_proxy_dimension_integration_enabled: false
# Controls the path at which Etherpad will be exposed on the Dimension domain.
etherpad_nginx_proxy_dimension_integration_path_prefix: "{{ etherpad_path_prefix }}"

View File

@ -11,14 +11,14 @@
- name: Generate Etherpad proxying configuration for matrix-nginx-proxy - name: Generate Etherpad proxying configuration for matrix-nginx-proxy
ansible.builtin.set_fact: ansible.builtin.set_fact:
matrix_etherpad_matrix_nginx_proxy_configuration: | etherpad_matrix_nginx_proxy_configuration: |
rewrite ^{{ matrix_etherpad_public_endpoint }}$ {{ matrix_nginx_proxy_x_forwarded_proto_value }}://$server_name{{ matrix_etherpad_public_endpoint }}/ permanent; rewrite ^{{ etherpad_nginx_proxy_dimension_integration_path_prefix }}$ {{ matrix_nginx_proxy_x_forwarded_proto_value }}://$server_name{{ etherpad_nginx_proxy_dimension_integration_path_prefix }}/ permanent;
location {{ matrix_etherpad_public_endpoint }}/ { location {{ etherpad_nginx_proxy_dimension_integration_path_prefix }}/ {
{% if matrix_nginx_proxy_enabled | default(False) %} {% if matrix_nginx_proxy_enabled | default(False) %}
{# Use the embedded DNS resolver in Docker containers to discover the service #} {# Use the embedded DNS resolver in Docker containers to discover the service #}
resolver 127.0.0.11 valid=5s; resolver 127.0.0.11 valid=5s;
proxy_pass http://matrix-etherpad:9001/; proxy_pass http://{{ etherpad_identifier }}:9001/;
{# These are proxy directives needed specifically by Etherpad #} {# These are proxy directives needed specifically by Etherpad #}
proxy_buffering off; proxy_buffering off;
proxy_http_version 1.1; # recommended with keepalive connections proxy_http_version 1.1; # recommended with keepalive connections
@ -42,5 +42,5 @@
{{ {{
matrix_nginx_proxy_proxy_dimension_additional_server_configuration_blocks | default([]) matrix_nginx_proxy_proxy_dimension_additional_server_configuration_blocks | default([])
+ +
[matrix_etherpad_matrix_nginx_proxy_configuration] [etherpad_matrix_nginx_proxy_configuration]
}} }}

View File

@ -0,0 +1,12 @@
---
- when: etherpad_enabled | bool and etherpad_nginx_proxy_dimension_integration_enabled | bool
block:
- ansible.builtin.include_tasks: "{{ role_path }}/tasks/validate_config.yml"
- ansible.builtin.include_tasks: "{{ role_path }}/tasks/inject_into_nginx_proxy.yml"
tags:
- install-all
- setup-all
- install-nginx-proxy
- setup-nginx-proxy

View File

@ -0,0 +1,32 @@
---
- when: matrix_playbook_reverse_proxy_type not in ['playbook-managed-nginx', 'other-nginx-non-container']
name: Fail if reverse-proxy is not nginx
ansible.builtin.fail:
msg: >
Etherpad's integration into matrix-nginx-proxy's Dimension server only makes sense if you're using matrix-nginx-proxy.
`matrix_playbook_reverse_proxy_type` ({{ matrix_playbook_reverse_proxy_type }}) indicates that you're using another reverse-proxy.
If you're using Traefik, you should configure `etherpad_hostname` and `etherpad_path_prefix` instead.
- when: not matrix_dimension_enabled
name: Fail if Dimension not enabled
ansible.builtin.fail:
msg: >
Etherpad's integration into matrix-nginx-proxy's Dimension server only makes sense if you're using Dimension.
Looks like Dimension is not enabled in your configuration (judging by `matrix_dimension_enabled`).
Consider configuring `etherpad_hostname` and `etherpad_path_prefix` instead.
- when: etherpad_hostname != matrix_server_fqn_dimension
name: Fail if Etherpad hostname does not match Dimension hostname
ansible.builtin.fail:
msg: >
Etherpad's integration into matrix-nginx-proxy's Dimension server requires that you set `etherpad_hostname` to `matrix_server_fqn_dimension`.
Consider adding this to your configuration: `{% raw %}etherpad_hostname: "{{ matrix_server_fqn_dimension }}"{% endraw %}`
- when: etherpad_nginx_proxy_dimension_integration_path_prefix == '/'
name: Fail if / path prefix used for Etherpad
ansible.builtin.fail:
msg: >
Etherpad's integration into matrix-nginx-proxy's Dimension server only makes sense if you're using a non-`/` path for Etherpad.
You've chosen a path prefix of `/` in `etherpad_nginx_proxy_dimension_integration_path_prefix`.
The `/` path must go to Dimension itself, so you need to pick a different prefix (e.g. `/etherpad`).

View File

@ -72,7 +72,7 @@ matrix_server_fqn_buscarron: "buscarron.{{ matrix_domain }}"
# This is where you access the Dimension. # This is where you access the Dimension.
matrix_server_fqn_dimension: "dimension.{{ matrix_domain }}" matrix_server_fqn_dimension: "dimension.{{ matrix_domain }}"
# This is where you access the etherpad (if enabled via matrix_etherpad_enabled; disabled by default). # This is where you access the etherpad (if enabled via etherpad_enabled; disabled by default).
matrix_server_fqn_etherpad: "etherpad.{{ matrix_domain }}" matrix_server_fqn_etherpad: "etherpad.{{ matrix_domain }}"
# For use with Go-NEB! (github callback url for example) # For use with Go-NEB! (github callback url for example)

View File

@ -1,102 +0,0 @@
---
# Project source code URL: https://github.com/ether/etherpad-lite
matrix_etherpad_enabled: false
# standalone = etherpad installed on subdomain (etherpad.DOMAIN) and can be used as-is
# dimension = etherpad installed in subdir of dimension (dimension.DOMAIN/etherpad) and can be used with dimension
matrix_etherpad_mode: standalone
matrix_etherpad_base_path: "{{ matrix_base_data_path }}/etherpad"
matrix_etherpad_version: 1.8.18
matrix_etherpad_docker_image: "{{ matrix_container_global_registry_prefix }}etherpad/etherpad:{{ matrix_etherpad_version }}"
matrix_etherpad_docker_image_force_pull: "{{ matrix_etherpad_docker_image.endswith(':latest') }}"
# List of systemd services that matrix-etherpad.service depends on.
matrix_etherpad_systemd_required_services_list: ['docker.service']
# List of systemd services that matrix-etherpad.service wants
matrix_etherpad_systemd_wanted_services_list: []
# Container user has to be able to write to the source file directories until this bug is fixed:
# https://github.com/ether/etherpad-lite/issues/2683
matrix_etherpad_user_uid: '5001'
matrix_etherpad_user_gid: '5001'
# Controls whether the matrix-etherpad container exposes its HTTP port (tcp/9001 in the container).
#
# Takes an "<ip>:<port>" or "<port>" value (e.g. "127.0.0.1:9001"), or empty string to not expose.
matrix_etherpad_container_http_host_bind_port: ''
# A list of extra arguments to pass to the container
matrix_etherpad_container_extra_arguments: []
# Used only when `matrix_etherpad_mode: dimension`
matrix_etherpad_public_endpoint: '/etherpad'
# By default, the Etherpad app can be accessed on etherpad subdomain
matrix_etherpad_base_url: "https://{{ matrix_server_fqn_etherpad }}/"
# Database-related configuration fields.
#
# Etherpad requires a dedicated database
matrix_etherpad_database_engine: 'postgres'
matrix_etherpad_database_username: 'matrix_etherpad'
matrix_etherpad_database_password: 'some-password'
matrix_etherpad_database_hostname: ''
matrix_etherpad_database_port: 5432
matrix_etherpad_database_name: 'matrix_etherpad'
# If a admin username and password is set, the /admin web page will be
# available.
matrix_etherpad_admin_username: ''
matrix_etherpad_admin_password: ''
matrix_etherpad_database_connection_string: 'postgres://{{ matrix_etherpad_database_username }}:{{ matrix_etherpad_database_password }}@{{ matrix_etherpad_database_hostname }}:{{ matrix_etherpad_database_port }}/{{ matrix_etherpad_database_name }}'
# Variables configuring the etherpad
matrix_etherpad_title: 'Etherpad'
matrix_etherpad_abiword: null
matrix_etherpad_soffice: null
matrix_etherpad_default_pad_text: |
Welcome to Etherpad!
This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!
Get involved with Etherpad at https://etherpad.org
# Default Etherpad configuration template which covers the generic use case.
# You can customize it by controlling the various variables inside it.
#
# For a more advanced customization, you can extend the default (see `matrix_etherpad_configuration_extension_json`)
# or completely replace this variable with your own template.
matrix_etherpad_configuration_default: "{{ lookup('template', 'templates/settings.json.j2') }}"
# Your custom JSON configuration for Etherpad goes here.
# This configuration extends the default starting configuration (`matrix_etherpad_configuration_json`).
#
# You can override individual variables from the default configuration, or introduce new ones.
#
# If you need something more special, you can take full control by
# completely redefining `matrix_etherpad_configuration_json`.
#
# Example configuration extension follows:
#
# matrix_etherpad_configuration_extension_json: |
# {
# "loadTest": true,
# "commitRateLimiting": {
# "duration": 1,
# "points": 10
# }
# }
#
matrix_etherpad_configuration_extension_json: '{}'
matrix_etherpad_configuration_extension: "{{ matrix_etherpad_configuration_extension_json | from_json if matrix_etherpad_configuration_extension_json | from_json is mapping else {} }}"
# Holds the final Etherpad configuration (a combination of the default and its extension).
# You most likely don't need to touch this variable. Instead, see `matrix_etherpad_configuration_json`.
matrix_etherpad_configuration: "{{ matrix_etherpad_configuration_default | combine(matrix_etherpad_configuration_extension, recursive=True) }}"

View File

@ -1,29 +0,0 @@
---
- block:
- when: matrix_etherpad_enabled | bool and matrix_etherpad_mode == 'dimension'
ansible.builtin.include_tasks: "{{ role_path }}/tasks/inject_into_nginx_proxy.yml"
tags:
- setup-all
- setup-nginx-proxy
- install-all
- install-nginx-proxy
- block:
- when: matrix_etherpad_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/validate_config.yml"
- when: matrix_etherpad_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_install.yml"
tags:
- setup-all
- setup-etherpad
- install-all
- install-etherpad
- block:
- when: not matrix_etherpad_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_uninstall.yml"
tags:
- setup-all
- setup-etherpad

View File

@ -1,34 +0,0 @@
---
- name: Ensure Etherpad base path exists
ansible.builtin.file:
path: "{{ matrix_etherpad_base_path }}"
state: directory
mode: 0770
owner: "{{ matrix_etherpad_user_uid }}"
group: "{{ matrix_etherpad_user_gid }}"
- name: Ensure Etherpad config installed
ansible.builtin.copy:
content: "{{ matrix_etherpad_configuration | to_nice_json }}"
dest: "{{ matrix_etherpad_base_path }}/settings.json"
mode: 0640
owner: "{{ matrix_etherpad_user_uid }}"
group: "{{ matrix_etherpad_user_gid }}"
- name: Ensure Etherpad image is pulled
community.docker.docker_image:
name: "{{ matrix_etherpad_docker_image }}"
source: "{{ 'pull' if ansible_version.major > 2 or ansible_version.minor > 7 else omit }}"
force_source: "{{ matrix_etherpad_docker_image_force_pull if ansible_version.major > 2 or ansible_version.minor >= 8 else omit }}"
force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_etherpad_docker_image_force_pull }}"
register: result
retries: "{{ devture_playbook_help_container_retries_count }}"
delay: "{{ devture_playbook_help_container_retries_delay }}"
until: result is not failed
- name: Ensure matrix-etherpad.service installed
ansible.builtin.template:
src: "{{ role_path }}/templates/systemd/matrix-etherpad.service.j2"
dest: "{{ devture_systemd_docker_base_systemd_path }}/matrix-etherpad.service"
mode: 0644

View File

@ -1,25 +0,0 @@
---
- name: Check existence of matrix-etherpad service
ansible.builtin.stat:
path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-etherpad.service"
register: matrix_etherpad_service_stat
- when: matrix_etherpad_service_stat.stat.exists | bool
block:
- name: Ensure matrix-etherpad is stopped
ansible.builtin.service:
name: matrix-etherpad
state: stopped
enabled: false
daemon_reload: true
- name: Ensure matrix-etherpad.service doesn't exist
ansible.builtin.file:
path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-etherpad.service"
state: absent
- name: Ensure Etherpad base directory doesn't exist
ansible.builtin.file:
path: "{{ matrix_etherpad_base_path }}"
state: absent

View File

@ -1,15 +0,0 @@
---
- name: Fail if required Etherpad settings not defined
ansible.builtin.fail:
msg: >
You need to define a required configuration setting (`{{ item.name }}`).
when: "item.when | bool and vars[item.name] == ''"
with_items:
- {'name': 'matrix_etherpad_database_hostname', when: true}
- name: Fail if wrong mode selected
ansible.builtin.fail:
msg: >-
You're using Etherpad in 'dimension' mode (`matrix_etherpad_serving_mode: dimension`), which tries to host Etherpad at the Dimension subdomain - `{{ matrix_server_fqn_dimension }}`. However, this isn't possible because Dimension is not enabled. To resolve the problem, either enable Dimension (`matrix_dimension_enabled: true`) or switch Etherpad to standalone mode (`matrix_etherpad_mode: standalone`) and have it served on its own domain (`{{ matrix_server_fqn_etherpad }}`).
when: matrix_etherpad_enabled | bool and matrix_etherpad_mode == 'dimension' and not matrix_dimension_enabled | default(False) | bool

View File

@ -1,116 +0,0 @@
{
"title": {{ matrix_etherpad_title|to_json }},
"favicon": "favicon.ico",
"skinName": "colibris",
"skinVariants": "super-light-toolbar super-light-editor light-background",
"ip": "::",
"port": 9001,
"showSettingsInAdminPage": true,
"dbType": {{ matrix_etherpad_database_engine|to_json }},
"dbSettings": {
"database": {{ matrix_etherpad_database_name|to_json }},
"host": {{ matrix_etherpad_database_hostname|to_json }},
"password": {{ matrix_etherpad_database_password|to_json }},
"port": {{ matrix_etherpad_database_port|to_json }},
"user": {{ matrix_etherpad_database_username|to_json }}
},
"defaultPadText" : {{ matrix_etherpad_default_pad_text|to_json }},
"suppressErrorsInPadText": false,
"requireSession": false,
"editOnly": false,
"minify": true,
"maxAge": 21600,
"abiword": {{ matrix_etherpad_abiword|to_json }},
"soffice": {{ matrix_etherpad_soffice|to_json }},
"tidyHtml": null,
"allowUnknownFileEnds": true,
"requireAuthentication": false,
"requireAuthorization": false,
"trustProxy": true,
"cookie": {
"sameSite": "Lax"
},
"disableIPlogging": true,
"automaticReconnectionTimeout": 0,
"scrollWhenFocusLineIsOutOfViewport": {
"percentage": {
"editionAboveViewport": 0,
"editionBelowViewport": 0
},
"duration": 0,
"scrollWhenCaretIsInTheLastLineOfViewport": false,
"percentageToScrollWhenUserPressesArrowUp": 0
},
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
"socketIo": {
"maxHttpBufferSize": 10000
},
"loadTest": false,
"dumpOnUncleanExit": false,
"indentationOnNewLine": false,
"importExportRateLimiting": {
"windowMs": 90000,
"max": 10
},
"importMaxFileSize": 52428800,
"commitRateLimiting": {
"duration": 1,
"points": 10
},
"exposeVersion": false,
"padOptions": {
"noColors": false,
"showControls": true,
"showChat": false,
"showLineNumbers": true,
"useMonospaceFont": false,
"userName": false,
"userColor": false,
"rtl": false,
"alwaysShowChat": false,
"chatAndUsers": false,
"lang": "en-gb"
},
{% if matrix_etherpad_admin_username != "" and matrix_etherpad_admin_password != "" %}
"users": {
{{ matrix_etherpad_admin_username|to_json }}: {
"password": {{ matrix_etherpad_admin_password|to_json }},
"is_admin": true
}
},
{% endif %}
"padShortcutEnabled" : {
"altF9": true,
"altC": true,
"cmdShift2": true,
"delete": true,
"return": true,
"esc": true,
"cmdS": true,
"tab": true,
"cmdZ": true,
"cmdY": true,
"cmdI": true,
"cmdB": true,
"cmdU": true,
"cmd5": true,
"cmdShiftL": true,
"cmdShiftN": true,
"cmdShift1": true,
"cmdShiftC": true,
"cmdH": true,
"ctrlHome": true,
"pageUp": true,
"pageDown": true
},
"loglevel": "WARN",
"logconfig" :
{ "appenders": [
{ "type": "console",
"layout": {"type": "messagePassThrough"}
}
]
},
"customLocaleStrings": {},
"enableAdminUITests": false
}

View File

@ -1,44 +0,0 @@
#jinja2: lstrip_blocks: "True"
[Unit]
Description=Matrix Etherpad
{% for service in matrix_etherpad_systemd_required_services_list %}
Requires={{ service }}
After={{ service }}
{% endfor %}
{% for service in matrix_etherpad_systemd_wanted_services_list %}
Wants={{ service }}
{% endfor %}
DefaultDependencies=no
[Service]
Type=simple
Environment="HOME={{ devture_systemd_docker_base_systemd_unit_home_path }}"
ExecStartPre=-{{ devture_systemd_docker_base_host_command_docker }} kill matrix-etherpad
ExecStartPre=-{{ devture_systemd_docker_base_host_command_docker }} rm matrix-etherpad
ExecStart={{ devture_systemd_docker_base_host_command_docker }} run --rm --name matrix-etherpad \
--log-driver=none \
--user={{ matrix_etherpad_user_uid }}:{{ matrix_etherpad_user_gid }} \
--cap-drop=ALL \
--network={{ matrix_docker_network }} \
{% if matrix_etherpad_container_http_host_bind_port %}
-p {{ matrix_etherpad_container_http_host_bind_port }}:9001 \
{% endif %}
--mount type=bind,src={{ matrix_etherpad_base_path }},dst=/data \
{% for arg in matrix_etherpad_container_extra_arguments %}
{{ arg }} \
{% endfor %}
{{ matrix_etherpad_docker_image }} \
node --experimental-worker src/node/server.js \
--settings /data/settings.json --credentials /data/credentials.json \
--sessionkey /data/sessionkey.json --apikey /data/apijey.json
ExecStop=-{{ devture_systemd_docker_base_host_command_docker }} kill matrix-etherpad
ExecStop=-{{ devture_systemd_docker_base_host_command_docker }} rm matrix-etherpad
Restart=always
RestartSec=30
SyslogIdentifier=matrix-etherpad
[Install]
WantedBy=multi-user.target

View File

@ -63,11 +63,6 @@ matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_rule: "Host(
matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_entrypoint: "{{ matrix_federation_traefik_entrypoint }}" matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_entrypoint: "{{ matrix_federation_traefik_entrypoint }}"
matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_entrypoints: "{{ matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_entrypoint }}" matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_entrypoints: "{{ matrix_nginx_proxy_container_labels_traefik_proxy_matrix_federation_entrypoint }}"
matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_enabled: false
matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_hostname: "{{ matrix_server_fqn_etherpad }}"
matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_tls: "{{ matrix_nginx_proxy_container_labels_traefik_entrypoints != 'web' }}"
matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_rule: "Host(`{{ matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_hostname }}`)"
matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_enabled: false matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_enabled: false
matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_hostname: "{{ matrix_server_fqn_bot_go_neb }}" matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_hostname: "{{ matrix_server_fqn_bot_go_neb }}"
matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_tls: "{{ matrix_nginx_proxy_container_labels_traefik_entrypoints != 'web' }}" matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_tls: "{{ matrix_nginx_proxy_container_labels_traefik_entrypoints != 'web' }}"

View File

@ -39,18 +39,6 @@ traefik.http.routers.matrix-nginx-proxy-matrix-federation.entrypoints={{ matrix_
{% endif %} {% endif %}
{% if matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_enabled %}
# Etherpad
traefik.http.routers.matrix-nginx-proxy-etherpad.rule={{ matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_rule }}
traefik.http.routers.matrix-nginx-proxy-etherpad.service=matrix-nginx-proxy-web
traefik.http.routers.matrix-nginx-proxy-etherpad.tls={{ matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_tls | to_json }}
{% if matrix_nginx_proxy_container_labels_traefik_proxy_etherpad_tls %}
traefik.http.routers.matrix-nginx-proxy-etherpad.tls.certResolver={{ matrix_nginx_proxy_container_labels_traefik_tls_certResolver }}
{% endif %}
traefik.http.routers.matrix-nginx-proxy-etherpad.entrypoints={{ matrix_nginx_proxy_container_labels_traefik_entrypoints }}
{% endif %}
{% if matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_enabled %} {% if matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_enabled %}
# Go NEB bot # Go NEB bot
traefik.http.routers.matrix-nginx-proxy-bot_go_neb.rule={{ matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_rule }} traefik.http.routers.matrix-nginx-proxy-bot_go_neb.rule={{ matrix_nginx_proxy_container_labels_traefik_proxy_bot_go_neb_rule }}