| @@ -1,5 +1,11 @@ | |||||
| # 2026-07-15 | # 2026-07-15 | ||||
| ## Google Voice bridging | |||||
| The playbook can now bridge [Google Voice](https://voice.google.com/) via the [mautrix-gvoice](https://github.com/mautrix/gvoice) bridge. Text and media flow both ways, and portal rooms build themselves for your recent conversations. | |||||
| Login is by cookie, not a paired phone: you copy the cookies from a browser signed in to voice.google.com and hand them to the bot. Google expires them on its own schedule, so expect to log in again every so often. See [Setting up Mautrix Google Voice bridging](./docs/configuring-playbook-bridge-mautrix-gvoice.md) to get started. | |||||
| ## matrix-appservice-kakaotalk has been removed from the playbook | ## matrix-appservice-kakaotalk has been removed from the playbook | ||||
| The [matrix-appservice-kakaotalk](./docs/configuring-playbook-bridge-appservice-kakaotalk.md) bridge has been removed from the playbook. This component could only be installed by self-building its source code, and its upstream repository has become unreachable, which makes installation impossible. The bridge was also based on the unmaintained node-kakao library, and there have been reports that using it may get KakaoTalk accounts banned. | The [matrix-appservice-kakaotalk](./docs/configuring-playbook-bridge-appservice-kakaotalk.md) bridge has been removed from the playbook. This component could only be installed by self-building its source code, and its upstream repository has become unreachable, which makes installation impossible. The bridge was also based on the unmaintained node-kakao library, and there have been reports that using it may get KakaoTalk accounts banned. | ||||
| @@ -116,6 +116,7 @@ Bridges can be used to connect your Matrix installation with third-party communi | |||||
| | [mautrix-slack](https://github.com/mautrix/slack) | ❌ | Bridge to [Slack](https://slack.com/) | [Link](docs/configuring-playbook-bridge-mautrix-slack.md) | | | [mautrix-slack](https://github.com/mautrix/slack) | ❌ | Bridge to [Slack](https://slack.com/) | [Link](docs/configuring-playbook-bridge-mautrix-slack.md) | | ||||
| | [mautrix-telegram](https://github.com/mautrix/telegram) | ❌ | Bridge to [Telegram](https://telegram.org/) | [Link](docs/configuring-playbook-bridge-mautrix-telegram.md) | | | [mautrix-telegram](https://github.com/mautrix/telegram) | ❌ | Bridge to [Telegram](https://telegram.org/) | [Link](docs/configuring-playbook-bridge-mautrix-telegram.md) | | ||||
| | [mautrix-gmessages](https://github.com/mautrix/gmessages) | ❌ | Bridge to [Google Messages](https://messages.google.com/) | [Link](docs/configuring-playbook-bridge-mautrix-gmessages.md) | | | [mautrix-gmessages](https://github.com/mautrix/gmessages) | ❌ | Bridge to [Google Messages](https://messages.google.com/) | [Link](docs/configuring-playbook-bridge-mautrix-gmessages.md) | | ||||
| | [mautrix-gvoice](https://github.com/mautrix/gvoice) | ❌ | Bridge to [Google Voice](https://voice.google.com/) | [Link](docs/configuring-playbook-bridge-mautrix-gvoice.md) | | |||||
| | [mautrix-whatsapp](https://github.com/mautrix/whatsapp) | ❌ | Bridge to [WhatsApp](https://www.whatsapp.com/) | [Link](docs/configuring-playbook-bridge-mautrix-whatsapp.md) | | | [mautrix-whatsapp](https://github.com/mautrix/whatsapp) | ❌ | Bridge to [WhatsApp](https://www.whatsapp.com/) | [Link](docs/configuring-playbook-bridge-mautrix-whatsapp.md) | | ||||
| | [mautrix-wsproxy](https://github.com/mautrix/wsproxy) | ❌ | Bridge to Android SMS or Apple iMessage | [Link](docs/configuring-playbook-bridge-mautrix-wsproxy.md) | | | [mautrix-wsproxy](https://github.com/mautrix/wsproxy) | ❌ | Bridge to Android SMS or Apple iMessage | [Link](docs/configuring-playbook-bridge-mautrix-wsproxy.md) | | ||||
| | [matrix-rustpush-bridge](https://github.com/jasonlaguidice/imessage) | ❌ | Bridge to [iMessage](https://support.apple.com/messages) via Apple Push Notification service | [Link](docs/configuring-playbook-bridge-rustpush.md) | | | [matrix-rustpush-bridge](https://github.com/jasonlaguidice/imessage) | ❌ | Bridge to [iMessage](https://support.apple.com/messages) via Apple Push Notification service | [Link](docs/configuring-playbook-bridge-rustpush.md) | | ||||
| @@ -0,0 +1,73 @@ | |||||
| <!-- | |||||
| SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| --> | |||||
| # Setting up Mautrix Google Voice bridging (optional) | |||||
| <sup>Refer the common guide for configuring mautrix bridges: [Setting up a Generic Mautrix Bridge](configuring-playbook-bridge-mautrix-bridges.md)</sup> | |||||
| The playbook can install and configure [mautrix-gvoice](https://github.com/mautrix/gvoice) for you, for bridging to [Google Voice](https://voice.google.com/). | |||||
| See the project's [documentation](https://docs.mau.fi/bridges/go/gvoice/index.html) to learn what it does and why it might be useful to you. | |||||
| ## Prerequisite (optional) | |||||
| ### Enable Appservice Double Puppet | |||||
| If you want to set up [Double Puppeting](https://docs.mau.fi/bridges/general/double-puppeting.html) (hint: you most likely do) for this bridge automatically, you need to have enabled [Appservice Double Puppet](configuring-playbook-appservice-double-puppet.md) for this playbook. | |||||
| See [this section](configuring-playbook-bridge-mautrix-bridges.md#set-up-double-puppeting-optional) on the [common guide for configuring mautrix bridges](configuring-playbook-bridge-mautrix-bridges.md) for details about setting up Double Puppeting. | |||||
| ## Adjusting the playbook configuration | |||||
| To enable the bridge, add the following configuration to your `inventory/host_vars/matrix.example.com/vars.yml` file: | |||||
| ```yaml | |||||
| matrix_mautrix_gvoice_enabled: true | |||||
| ``` | |||||
| ### Extending the configuration | |||||
| There are some additional things you may wish to configure about the bridge. | |||||
| <!-- NOTE: relay mode is not supported for this bridge --> | |||||
| See [this section](configuring-playbook-bridge-mautrix-bridges.md#extending-the-configuration) on the [common guide for configuring mautrix bridges](configuring-playbook-bridge-mautrix-bridges.md) for details about variables that you can customize and the bridge's default configuration, including [bridge permissions](configuring-playbook-bridge-mautrix-bridges.md#configure-bridge-permissions-optional), [encryption support](configuring-playbook-bridge-mautrix-bridges.md#enable-encryption-optional), [bot's username](configuring-playbook-bridge-mautrix-bridges.md#set-the-bots-username-optional), etc. | |||||
| ## Installing | |||||
| After configuring the playbook, run it with [playbook tags](playbook-tags.md) as below: | |||||
| <!-- NOTE: let this conservative command run (instead of install-all) to make it clear that failure of the command means something is clearly broken. --> | |||||
| ```sh | |||||
| ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start | |||||
| ``` | |||||
| The shortcut commands with the [`just` program](just.md) are also available: `just install-all` or `just setup-all` | |||||
| `just install-all` is useful for maintaining your setup quickly ([2x-5x faster](../CHANGELOG.md#2x-5x-performance-improvements-in-playbook-runtime) than `just setup-all`) when its components remain unchanged. If you adjust your `vars.yml` to remove other components, you'd need to run `just setup-all`, or these components will still remain installed. Note these shortcuts run the `ensure-matrix-users-created` tag too. | |||||
| ## Usage | |||||
| To use the bridge, start a chat with `@gvoicebot:example.com` (where `example.com` is your base domain, not the `matrix.` domain). | |||||
| Google Voice has no phone to pair and no QR code to scan. It logs in with cookies, which you copy from a browser already signed in to [voice.google.com](https://voice.google.com/) and hand to the bot. It is fiddlier than scanning a code and feels more suspicious than it is, but Google leaves no cleaner door open. The bridge's [official Authentication guide](https://docs.mau.fi/bridges/go/gvoice/authentication.html) has the exact cookies to grab and the steps for grabbing them. | |||||
| Those cookies are a login session, and Google expires them on its own schedule. When they lapse the bridge goes quiet and you log in again. Nothing is broken, that is just how cookie auth ages. | |||||
| Once you log in, the bridge builds portal rooms for your recent conversations and carries text and media both ways. Don't reach for it to start a brand-new chat or to place a call, though. That ground still belongs to Google Voice, so keep the app around for those. | |||||
| ## Troubleshooting | |||||
| As with all other services, you can find the logs in [systemd-journald](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html) by logging in to the server with SSH and running `journalctl -fu matrix-mautrix-gvoice`. | |||||
| ### Increase logging verbosity | |||||
| The default logging level for this component is `warn`. If you want to increase the verbosity, add the following configuration to your `vars.yml` file and re-run the playbook: | |||||
| ```yaml | |||||
| # Valid values: fatal, error, warn, info, debug, trace | |||||
| matrix_mautrix_gvoice_logging_level: 'debug' | |||||
| ``` | |||||
| @@ -146,6 +146,8 @@ Bridges can be used to connect your Matrix installation with third-party communi | |||||
| - [Setting up Mautrix Google Messages bridging](configuring-playbook-bridge-mautrix-gmessages.md) | - [Setting up Mautrix Google Messages bridging](configuring-playbook-bridge-mautrix-gmessages.md) | ||||
| - [Setting up Mautrix Google Voice bridging](configuring-playbook-bridge-mautrix-gvoice.md) | |||||
| - [Setting up Mautrix Whatsapp bridging](configuring-playbook-bridge-mautrix-whatsapp.md) | - [Setting up Mautrix Whatsapp bridging](configuring-playbook-bridge-mautrix-whatsapp.md) | ||||
| - [Setting up Instagram bridging via Mautrix Meta](configuring-playbook-bridge-mautrix-meta-instagram.md) | - [Setting up Instagram bridging via Mautrix Meta](configuring-playbook-bridge-mautrix-meta-instagram.md) | ||||
| @@ -91,6 +91,7 @@ Bridges can be used to connect your Matrix installation with third-party communi | |||||
| | [mautrix-slack](configuring-playbook-bridge-mautrix-slack.md) | [mautrix/slack](https://mau.dev/mautrix/slack/container_registry) | ❌ | Bridge to [Slack](https://slack.com/) | | | [mautrix-slack](configuring-playbook-bridge-mautrix-slack.md) | [mautrix/slack](https://mau.dev/mautrix/slack/container_registry) | ❌ | Bridge to [Slack](https://slack.com/) | | ||||
| | [mautrix-telegram](configuring-playbook-bridge-mautrix-telegram.md) | [mautrix/telegram](https://mau.dev/mautrix/telegram/container_registry) | ❌ | Bridge to [Telegram](https://telegram.org/) | | | [mautrix-telegram](configuring-playbook-bridge-mautrix-telegram.md) | [mautrix/telegram](https://mau.dev/mautrix/telegram/container_registry) | ❌ | Bridge to [Telegram](https://telegram.org/) | | ||||
| | [mautrix-gmessages](configuring-playbook-bridge-mautrix-gmessages.md) | [mautrix/gmessages](https://mau.dev/mautrix/gmessages/container_registry) | ❌ | Bridge to [Google Messages](https://messages.google.com/) | | | [mautrix-gmessages](configuring-playbook-bridge-mautrix-gmessages.md) | [mautrix/gmessages](https://mau.dev/mautrix/gmessages/container_registry) | ❌ | Bridge to [Google Messages](https://messages.google.com/) | | ||||
| | [mautrix-gvoice](configuring-playbook-bridge-mautrix-gvoice.md) | [mautrix/gvoice](https://mau.dev/mautrix/gvoice/container_registry) | ❌ | Bridge to [Google Voice](https://voice.google.com/) | | |||||
| | [mautrix-whatsapp](configuring-playbook-bridge-mautrix-whatsapp.md) | [mautrix/whatsapp](https://mau.dev/mautrix/whatsapp/container_registry) | ❌ | Bridge to [WhatsApp](https://www.whatsapp.com/) | | | [mautrix-whatsapp](configuring-playbook-bridge-mautrix-whatsapp.md) | [mautrix/whatsapp](https://mau.dev/mautrix/whatsapp/container_registry) | ❌ | Bridge to [WhatsApp](https://www.whatsapp.com/) | | ||||
| | [mautrix-wsproxy](configuring-playbook-bridge-mautrix-wsproxy.md) | [mautrix/wsproxy](https://mau.dev/mautrix/wsproxy/container_registry) | ❌ | Bridge to Android SMS or Apple iMessage | | | [mautrix-wsproxy](configuring-playbook-bridge-mautrix-wsproxy.md) | [mautrix/wsproxy](https://mau.dev/mautrix/wsproxy/container_registry) | ❌ | Bridge to Android SMS or Apple iMessage | | ||||
| | [mautrix-twitter](configuring-playbook-bridge-mautrix-twitter.md) | [mautrix/twitter](https://mau.dev/mautrix/twitter/container_registry) | ❌ | Bridge to [Twitter](https://twitter.com/) | | | [mautrix-twitter](configuring-playbook-bridge-mautrix-twitter.md) | [mautrix/twitter](https://mau.dev/mautrix/twitter/container_registry) | ❌ | Bridge to [Twitter](https://twitter.com/) | | ||||
| @@ -41,6 +41,7 @@ Possibly outdated list of roles where self-building the Docker image is currentl | |||||
| - `matrix-bridge-mautrix-telegram` | - `matrix-bridge-mautrix-telegram` | ||||
| - `matrix-bridge-mautrix-signal` | - `matrix-bridge-mautrix-signal` | ||||
| - `matrix-bridge-mautrix-gmessages` | - `matrix-bridge-mautrix-gmessages` | ||||
| - `matrix-bridge-mautrix-gvoice` | |||||
| - `matrix-bridge-mautrix-whatsapp` | - `matrix-bridge-mautrix-whatsapp` | ||||
| - `matrix-bridge-mx-puppet-steam` | - `matrix-bridge-mx-puppet-steam` | ||||
| - `matrix-bot-mjolnir` | - `matrix-bot-mjolnir` | ||||
| @@ -132,6 +132,8 @@ matrix_homeserver_container_extra_arguments_auto: | | |||||
| + | + | ||||
| (['--mount type=bind,src=' + matrix_mautrix_gmessages_config_path + '/registration.yaml,dst=/matrix-mautrix-gmessages-registration.yaml,ro'] if matrix_mautrix_gmessages_enabled else []) | (['--mount type=bind,src=' + matrix_mautrix_gmessages_config_path + '/registration.yaml,dst=/matrix-mautrix-gmessages-registration.yaml,ro'] if matrix_mautrix_gmessages_enabled else []) | ||||
| + | + | ||||
| (['--mount type=bind,src=' + matrix_mautrix_gvoice_config_path + '/registration.yaml,dst=/matrix-mautrix-gvoice-registration.yaml,ro'] if matrix_mautrix_gvoice_enabled else []) | |||||
| + | |||||
| (['--mount type=bind,src=' + matrix_mautrix_whatsapp_config_path + '/registration.yaml,dst=/matrix-mautrix-whatsapp-registration.yaml,ro'] if matrix_mautrix_whatsapp_enabled else []) | (['--mount type=bind,src=' + matrix_mautrix_whatsapp_config_path + '/registration.yaml,dst=/matrix-mautrix-whatsapp-registration.yaml,ro'] if matrix_mautrix_whatsapp_enabled else []) | ||||
| + | + | ||||
| (['--mount type=bind,src=' + matrix_mautrix_wsproxy_config_path + '/androidsms-registration.yaml,dst=/matrix-mautrix-androidsms-registration.yaml,ro'] if matrix_mautrix_wsproxy_enabled else []) | (['--mount type=bind,src=' + matrix_mautrix_wsproxy_config_path + '/androidsms-registration.yaml,dst=/matrix-mautrix-androidsms-registration.yaml,ro'] if matrix_mautrix_wsproxy_enabled else []) | ||||
| @@ -189,6 +191,8 @@ matrix_homeserver_app_service_config_files_auto: | | |||||
| + | + | ||||
| (['/matrix-mautrix-gmessages-registration.yaml'] if matrix_mautrix_gmessages_enabled else []) | (['/matrix-mautrix-gmessages-registration.yaml'] if matrix_mautrix_gmessages_enabled else []) | ||||
| + | + | ||||
| (['/matrix-mautrix-gvoice-registration.yaml'] if matrix_mautrix_gvoice_enabled else []) | |||||
| + | |||||
| (['/matrix-mautrix-whatsapp-registration.yaml'] if matrix_mautrix_whatsapp_enabled else []) | (['/matrix-mautrix-whatsapp-registration.yaml'] if matrix_mautrix_whatsapp_enabled else []) | ||||
| + | + | ||||
| (['/matrix-mautrix-androidsms-registration.yaml'] if matrix_mautrix_wsproxy_enabled else []) | (['/matrix-mautrix-androidsms-registration.yaml'] if matrix_mautrix_wsproxy_enabled else []) | ||||
| @@ -485,6 +489,13 @@ devture_systemd_service_manager_services_list_auto: | | |||||
| 'groups': ['matrix', 'bridges', 'mautrix-gmessages'], | 'groups': ['matrix', 'bridges', 'mautrix-gmessages'], | ||||
| }] if matrix_mautrix_gmessages_enabled else []) | }] if matrix_mautrix_gmessages_enabled else []) | ||||
| + | + | ||||
| ([{ | |||||
| 'name': 'matrix-mautrix-gvoice.service', | |||||
| 'priority': 2000, | |||||
| 'restart_necessary': (matrix_mautrix_gvoice_restart_necessary | bool), | |||||
| 'groups': ['matrix', 'bridges', 'mautrix-gvoice'], | |||||
| }] if matrix_mautrix_gvoice_enabled else []) | |||||
| + | |||||
| ([{ | ([{ | ||||
| 'name': 'matrix-mautrix-whatsapp.service', | 'name': 'matrix-mautrix-whatsapp.service', | ||||
| 'priority': 2000, | 'priority': 2000, | ||||
| @@ -2199,6 +2210,87 @@ matrix_mautrix_gmessages_database_password: "{{ (matrix_homeserver_generic_secre | |||||
| # | # | ||||
| ###################################################################### | ###################################################################### | ||||
| ###################################################################### | |||||
| # | |||||
| # matrix-bridge-mautrix-gvoice | |||||
| # | |||||
| ###################################################################### | |||||
| # We don't enable bridges by default. | |||||
| matrix_mautrix_gvoice_enabled: false | |||||
| matrix_mautrix_gvoice_systemd_required_services_list_auto: | | |||||
| {{ | |||||
| matrix_addons_homeserver_systemd_services_list | |||||
| + | |||||
| ([postgres_identifier ~ '.service'] if (postgres_enabled and matrix_mautrix_gvoice_database_hostname == postgres_connection_hostname) else []) | |||||
| }} | |||||
| matrix_mautrix_gvoice_container_image_registry_prefix_upstream: "{{ matrix_container_global_registry_prefix_override if matrix_container_global_registry_prefix_override else matrix_mautrix_gvoice_container_image_registry_prefix_upstream_default }}" | |||||
| matrix_mautrix_gvoice_container_image_self_build: "{{ matrix_architecture not in ['arm64', 'amd64'] }}" | |||||
| matrix_mautrix_gvoice_container_network: "{{ matrix_addons_container_network }}" | |||||
| matrix_mautrix_gvoice_container_additional_networks_auto: |- | |||||
| {{ | |||||
| ( | |||||
| ([] if matrix_addons_homeserver_container_network == '' else [matrix_addons_homeserver_container_network]) | |||||
| + | |||||
| ([postgres_container_network] if (postgres_enabled and matrix_mautrix_gvoice_database_hostname == postgres_connection_hostname and matrix_mautrix_gvoice_container_network != postgres_container_network) else []) | |||||
| + | |||||
| ([matrix_playbook_reverse_proxyable_services_additional_network] if (matrix_playbook_reverse_proxyable_services_additional_network and matrix_mautrix_gvoice_container_labels_traefik_enabled) else []) | |||||
| ) | unique | |||||
| }} | |||||
| matrix_mautrix_gvoice_container_labels_traefik_enabled: "{{ matrix_playbook_reverse_proxy_type in ['playbook-managed-traefik', 'other-traefik-container'] }}" | |||||
| matrix_mautrix_gvoice_container_labels_traefik_docker_network: "{{ matrix_playbook_reverse_proxyable_services_additional_network }}" | |||||
| matrix_mautrix_gvoice_container_labels_traefik_entrypoints: "{{ traefik_entrypoint_primary }}" | |||||
| matrix_mautrix_gvoice_container_labels_traefik_tls_certResolver: "{{ traefik_certResolver_primary }}" | |||||
| matrix_mautrix_gvoice_container_labels_metrics_middleware_basic_auth_enabled: "{{ matrix_metrics_exposure_http_basic_auth_enabled }}" | |||||
| matrix_mautrix_gvoice_container_labels_metrics_middleware_basic_auth_users: "{{ matrix_metrics_exposure_http_basic_auth_users }}" | |||||
| matrix_mautrix_gvoice_appservice_token: "{{ (matrix_homeserver_generic_secret_key + ':gvoice.as.token') | hash('sha512') | to_uuid }}" | |||||
| matrix_mautrix_gvoice_homeserver_address: "{{ matrix_addons_homeserver_client_api_url }}" | |||||
| matrix_mautrix_gvoice_homeserver_token: "{{ (matrix_homeserver_generic_secret_key + ':gvoice.hs.token') | hash('sha512') | to_uuid }}" | |||||
| matrix_mautrix_gvoice_provisioning_shared_secret: "{{ (matrix_homeserver_generic_secret_key + ':gvoice.prov') | hash('sha512') | to_uuid }}" | |||||
| matrix_mautrix_gvoice_homeserver_async_media: "{{ matrix_homeserver_implementation in ['synapse'] }}" | |||||
| matrix_mautrix_gvoice_double_puppet_secrets_auto: |- | |||||
| {{ | |||||
| { | |||||
| matrix_mautrix_gvoice_homeserver_domain: ("as_token:" + matrix_appservice_double_puppet_registration_as_token) | |||||
| } | |||||
| if matrix_appservice_double_puppet_enabled | |||||
| else {} | |||||
| }} | |||||
| matrix_mautrix_gvoice_metrics_enabled: "{{ prometheus_enabled or matrix_metrics_exposure_enabled }}" | |||||
| matrix_mautrix_gvoice_metrics_proxying_enabled: "{{ matrix_mautrix_gvoice_metrics_enabled and matrix_metrics_exposure_enabled }}" | |||||
| matrix_mautrix_gvoice_metrics_proxying_hostname: "{{ matrix_metrics_exposure_hostname }}" | |||||
| matrix_mautrix_gvoice_metrics_proxying_path_prefix: "{{ matrix_metrics_exposure_path_prefix }}/mautrix-gvoice" | |||||
| matrix_mautrix_gvoice_scheme: "{{ 'https' if matrix_playbook_ssl_enabled else 'http' }}" | |||||
| matrix_mautrix_gvoice_exposure_enabled: "{{ matrix_bridges_exposure_enabled }}" | |||||
| matrix_mautrix_gvoice_exposure_hostname: "{{ matrix_bridges_exposure_hostname }}" | |||||
| matrix_mautrix_gvoice_exposure_path_prefix: "{{ matrix_bridges_exposure_path_prefix }}/gvoice" | |||||
| # Postgres is the default, except if not using internal Postgres server | |||||
| matrix_mautrix_gvoice_database_engine: "{{ 'postgres' if postgres_enabled else 'sqlite' }}" | |||||
| matrix_mautrix_gvoice_database_hostname: "{{ postgres_connection_hostname if postgres_enabled else '' }}" | |||||
| matrix_mautrix_gvoice_database_password: "{{ (matrix_homeserver_generic_secret_key + ':maugvoice.db') | hash('sha512') | to_uuid }}" | |||||
| ###################################################################### | |||||
| # | |||||
| # /matrix-bridge-mautrix-gvoice | |||||
| # | |||||
| ###################################################################### | |||||
| ###################################################################### | ###################################################################### | ||||
| # | # | ||||
| # matrix-bridge-mautrix-wsproxy | # matrix-bridge-mautrix-wsproxy | ||||
| @@ -4132,6 +4224,12 @@ postgres_managed_databases_auto: | | |||||
| 'password': matrix_mautrix_gmessages_database_password, | 'password': matrix_mautrix_gmessages_database_password, | ||||
| }] if (matrix_mautrix_gmessages_enabled and matrix_mautrix_gmessages_database_engine == 'postgres' and matrix_mautrix_gmessages_database_hostname == postgres_connection_hostname) else []) | }] if (matrix_mautrix_gmessages_enabled and matrix_mautrix_gmessages_database_engine == 'postgres' and matrix_mautrix_gmessages_database_hostname == postgres_connection_hostname) else []) | ||||
| + | + | ||||
| ([{ | |||||
| 'name': matrix_mautrix_gvoice_database_name, | |||||
| 'username': matrix_mautrix_gvoice_database_username, | |||||
| 'password': matrix_mautrix_gvoice_database_password, | |||||
| }] if (matrix_mautrix_gvoice_enabled and matrix_mautrix_gvoice_database_engine == 'postgres' and matrix_mautrix_gvoice_database_hostname == postgres_connection_hostname) else []) | |||||
| + | |||||
| ([{ | ([{ | ||||
| 'name': matrix_mautrix_whatsapp_database_name, | 'name': matrix_mautrix_whatsapp_database_name, | ||||
| 'username': matrix_mautrix_whatsapp_database_username, | 'username': matrix_mautrix_whatsapp_database_username, | ||||
| @@ -5036,6 +5134,11 @@ matrix_ketesa_config_asManagedUsers_auto: | | |||||
| '^@gmessages_[a-zA-Z0-9]+:'+(matrix_domain | regex_escape)+'$', | '^@gmessages_[a-zA-Z0-9]+:'+(matrix_domain | regex_escape)+'$', | ||||
| ] if matrix_mautrix_gmessages_enabled else []) | ] if matrix_mautrix_gmessages_enabled else []) | ||||
| + | + | ||||
| ([ | |||||
| '^@'+(matrix_mautrix_gvoice_appservice_bot_username | default('') | regex_escape)+':'+(matrix_domain | regex_escape)+'$', | |||||
| '^@gvoice_[a-zA-Z0-9]+:'+(matrix_domain | regex_escape)+'$', | |||||
| ] if matrix_mautrix_gvoice_enabled else []) | |||||
| + | |||||
| ([ | ([ | ||||
| '^@'+(matrix_mautrix_googlechat_appservice_bot_username | default('') | regex_escape)+':'+(matrix_domain | regex_escape)+'$', | '^@'+(matrix_mautrix_googlechat_appservice_bot_username | default('') | regex_escape)+':'+(matrix_domain | regex_escape)+'$', | ||||
| '^@googlechat_[a-zA-Z0-9]+:'+(matrix_domain | regex_escape)+'$', | '^@googlechat_[a-zA-Z0-9]+:'+(matrix_domain | regex_escape)+'$', | ||||
| @@ -5905,6 +6008,7 @@ matrix_static_files_file_matrix_mautrix_property_fi_mau_bridges_auto: |- | |||||
| [ | [ | ||||
| matrix_mautrix_bluesky_appservice_public_address if (matrix_mautrix_bluesky_enabled and matrix_mautrix_bluesky_container_labels_traefik_enabled and matrix_mautrix_bluesky_container_labels_exposure_enabled) else '', | matrix_mautrix_bluesky_appservice_public_address if (matrix_mautrix_bluesky_enabled and matrix_mautrix_bluesky_container_labels_traefik_enabled and matrix_mautrix_bluesky_container_labels_exposure_enabled) else '', | ||||
| matrix_mautrix_gmessages_bridge_public_address if (matrix_mautrix_gmessages_enabled and matrix_mautrix_gmessages_container_labels_traefik_enabled and matrix_mautrix_gmessages_container_labels_exposure_enabled) else '', | matrix_mautrix_gmessages_bridge_public_address if (matrix_mautrix_gmessages_enabled and matrix_mautrix_gmessages_container_labels_traefik_enabled and matrix_mautrix_gmessages_container_labels_exposure_enabled) else '', | ||||
| matrix_mautrix_gvoice_bridge_public_address if (matrix_mautrix_gvoice_enabled and matrix_mautrix_gvoice_container_labels_traefik_enabled and matrix_mautrix_gvoice_container_labels_exposure_enabled) else '', | |||||
| matrix_mautrix_meta_instagram_bridge_public_address if (matrix_mautrix_meta_instagram_enabled and matrix_mautrix_meta_instagram_container_labels_traefik_enabled and matrix_mautrix_meta_instagram_container_labels_exposure_enabled) else '', | matrix_mautrix_meta_instagram_bridge_public_address if (matrix_mautrix_meta_instagram_enabled and matrix_mautrix_meta_instagram_container_labels_traefik_enabled and matrix_mautrix_meta_instagram_container_labels_exposure_enabled) else '', | ||||
| matrix_mautrix_meta_messenger_bridge_public_address if (matrix_mautrix_meta_messenger_enabled and matrix_mautrix_meta_messenger_container_labels_traefik_enabled and matrix_mautrix_meta_messenger_container_labels_exposure_enabled) else '', | matrix_mautrix_meta_messenger_bridge_public_address if (matrix_mautrix_meta_messenger_enabled and matrix_mautrix_meta_messenger_container_labels_traefik_enabled and matrix_mautrix_meta_messenger_container_labels_exposure_enabled) else '', | ||||
| matrix_mautrix_signal_bridge_public_address if (matrix_mautrix_signal_enabled and matrix_mautrix_signal_container_labels_traefik_enabled and matrix_mautrix_signal_container_labels_exposure_enabled) else '', | matrix_mautrix_signal_bridge_public_address if (matrix_mautrix_signal_enabled and matrix_mautrix_signal_container_labels_traefik_enabled and matrix_mautrix_signal_container_labels_exposure_enabled) else '', | ||||
| @@ -0,0 +1,249 @@ | |||||
| # SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| # SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| # | |||||
| # SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| --- | |||||
| # mautrix-gvoice is a Matrix <-> Google Voice bridge | |||||
| # Project source code URL: https://github.com/mautrix/gvoice | |||||
| matrix_mautrix_gvoice_enabled: true | |||||
| matrix_mautrix_gvoice_container_image_self_build: false | |||||
| matrix_mautrix_gvoice_container_image_self_build_repo: "https://github.com/mautrix/gvoice.git" | |||||
| matrix_mautrix_gvoice_container_image_self_build_branch: "{{ 'main' if matrix_mautrix_gvoice_version == 'latest' else matrix_mautrix_gvoice_version }}" | |||||
| # renovate: datasource=docker depName=dock.mau.dev/mautrix/gvoice | |||||
| matrix_mautrix_gvoice_version: v0.2605.0 | |||||
| # See: https://mau.dev/mautrix/gvoice/container_registry | |||||
| matrix_mautrix_gvoice_container_image: "{{ matrix_mautrix_gvoice_container_image_registry_prefix }}mautrix/gvoice:{{ matrix_mautrix_gvoice_version }}" | |||||
| matrix_mautrix_gvoice_container_image_registry_prefix: "{{ 'localhost/' if matrix_mautrix_gvoice_container_image_self_build else matrix_mautrix_gvoice_container_image_registry_prefix_upstream }}" | |||||
| matrix_mautrix_gvoice_container_image_registry_prefix_upstream: "{{ matrix_mautrix_gvoice_container_image_registry_prefix_upstream_default }}" | |||||
| matrix_mautrix_gvoice_container_image_registry_prefix_upstream_default: "dock.mau.dev/" | |||||
| matrix_mautrix_gvoice_base_path: "{{ matrix_base_data_path }}/mautrix-gvoice" | |||||
| matrix_mautrix_gvoice_config_path: "{{ matrix_mautrix_gvoice_base_path }}/config" | |||||
| matrix_mautrix_gvoice_data_path: "{{ matrix_mautrix_gvoice_base_path }}/data" | |||||
| matrix_mautrix_gvoice_container_src_files_path: "{{ matrix_mautrix_gvoice_base_path }}/docker-src" | |||||
| matrix_mautrix_gvoice_homeserver_address: "" | |||||
| # Whether asynchronous uploads via MSC2246 should be enabled for media. | |||||
| # Requires a homeserver that supports MSC2246 (https://github.com/matrix-org/matrix-spec-proposals/pull/2246). | |||||
| matrix_mautrix_gvoice_homeserver_async_media: false | |||||
| matrix_mautrix_gvoice_homeserver_domain: "{{ matrix_domain }}" | |||||
| matrix_mautrix_gvoice_appservice_address: "http://matrix-mautrix-gvoice:8080" | |||||
| # Scheme of the bridge's public address (see `matrix_mautrix_gvoice_bridge_public_address`). | |||||
| matrix_mautrix_gvoice_scheme: https | |||||
| # The public base URL at which this bridge's HTTP API is reachable from outside (when exposed). | |||||
| # Used for the provisioning API's external-server (OpenID) flow and for public media links. | |||||
| matrix_mautrix_gvoice_bridge_public_address: "{{ (matrix_mautrix_gvoice_scheme + '://' + matrix_mautrix_gvoice_exposure_hostname + matrix_mautrix_gvoice_exposure_path_prefix) if matrix_mautrix_gvoice_exposure_enabled else '' }}" | |||||
| matrix_mautrix_gvoice_msc4190_enabled: "{{ matrix_bridges_msc4190_enabled }}" | |||||
| matrix_mautrix_gvoice_self_sign_enabled: "{{ matrix_bridges_self_sign_enabled }}" | |||||
| matrix_mautrix_gvoice_backfill_enabled: true | |||||
| matrix_mautrix_gvoice_backfill_max_initial_messages: 50 | |||||
| matrix_mautrix_gvoice_backfill_max_catchup_messages: 500 | |||||
| matrix_mautrix_gvoice_backfill_unread_hours_threshold: 720 | |||||
| matrix_mautrix_gvoice_backfill_threads_max_initial_messages: 50 | |||||
| matrix_mautrix_gvoice_command_prefix: "!gv" | |||||
| matrix_mautrix_gvoice_container_network: "" | |||||
| matrix_mautrix_gvoice_container_additional_networks: "{{ matrix_mautrix_gvoice_container_additional_networks_auto + matrix_mautrix_gvoice_container_additional_networks_custom }}" | |||||
| matrix_mautrix_gvoice_container_additional_networks_auto: [] | |||||
| matrix_mautrix_gvoice_container_additional_networks_custom: [] | |||||
| # matrix_mautrix_gvoice_container_labels_traefik_enabled controls whether labels to assist a Traefik reverse-proxy will be attached to the container. | |||||
| # See `../templates/labels.j2` for details. | |||||
| # | |||||
| # To inject your own other container labels, see `matrix_mautrix_gvoice_container_labels_additional_labels`. | |||||
| matrix_mautrix_gvoice_container_labels_traefik_enabled: true | |||||
| matrix_mautrix_gvoice_container_labels_traefik_docker_network: "{{ matrix_mautrix_gvoice_container_network }}" | |||||
| matrix_mautrix_gvoice_container_labels_traefik_entrypoints: web-secure | |||||
| matrix_mautrix_gvoice_container_labels_traefik_tls_certResolver: default # noqa var-naming | |||||
| # Controls whether labels will be added that expose mautrix-gvoice' metrics | |||||
| matrix_mautrix_gvoice_container_labels_metrics_enabled: "{{ matrix_mautrix_gvoice_metrics_enabled and matrix_mautrix_gvoice_metrics_proxying_enabled }}" | |||||
| matrix_mautrix_gvoice_container_labels_metrics_traefik_rule: "Host(`{{ matrix_mautrix_gvoice_metrics_proxying_hostname }}`) && PathPrefix(`{{ matrix_mautrix_gvoice_metrics_proxying_path_prefix }}`)" | |||||
| matrix_mautrix_gvoice_container_labels_metrics_traefik_priority: 0 | |||||
| matrix_mautrix_gvoice_container_labels_metrics_traefik_entrypoints: "{{ matrix_mautrix_gvoice_container_labels_traefik_entrypoints }}" | |||||
| matrix_mautrix_gvoice_container_labels_metrics_traefik_tls: "{{ matrix_mautrix_gvoice_container_labels_metrics_traefik_entrypoints != 'web' }}" | |||||
| matrix_mautrix_gvoice_container_labels_metrics_traefik_tls_certResolver: "{{ matrix_mautrix_gvoice_container_labels_traefik_tls_certResolver }}" # noqa var-naming | |||||
| matrix_mautrix_gvoice_container_labels_metrics_middleware_basic_auth_enabled: false | |||||
| # See: https://doc.traefik.io/traefik/middlewares/http/basicauth/#users | |||||
| matrix_mautrix_gvoice_container_labels_metrics_middleware_basic_auth_users: '' | |||||
| # Controls whether labels will be added that expose mautrix-gvoice' HTTP API | |||||
| # (used by tools like mautrix-manager for bridge login) at `https://<hostname><path_prefix>`. | |||||
| matrix_mautrix_gvoice_container_labels_exposure_enabled: "{{ matrix_mautrix_gvoice_exposure_enabled }}" | |||||
| matrix_mautrix_gvoice_container_labels_exposure_traefik_rule: "Host(`{{ matrix_mautrix_gvoice_exposure_hostname }}`) && PathPrefix(`{{ matrix_mautrix_gvoice_exposure_path_prefix }}`)" | |||||
| matrix_mautrix_gvoice_container_labels_exposure_traefik_priority: 0 | |||||
| matrix_mautrix_gvoice_container_labels_exposure_traefik_entrypoints: "{{ matrix_mautrix_gvoice_container_labels_traefik_entrypoints }}" | |||||
| matrix_mautrix_gvoice_container_labels_exposure_traefik_tls: "{{ matrix_mautrix_gvoice_container_labels_exposure_traefik_entrypoints != 'web' }}" | |||||
| matrix_mautrix_gvoice_container_labels_exposure_traefik_tls_certResolver: "{{ matrix_mautrix_gvoice_container_labels_traefik_tls_certResolver }}" # noqa var-naming | |||||
| # matrix_mautrix_gvoice_container_labels_additional_labels contains a multiline string with additional labels to add to the container label file. | |||||
| # See `../templates/labels.j2` for details. | |||||
| # | |||||
| # Example: | |||||
| # matrix_mautrix_gvoice_container_labels_additional_labels: | | |||||
| # my.label=1 | |||||
| # another.label="here" | |||||
| matrix_mautrix_gvoice_container_labels_additional_labels: '' | |||||
| # A list of extra arguments to pass to the container | |||||
| matrix_mautrix_gvoice_container_extra_arguments: [] | |||||
| # List of systemd services that matrix-mautrix-gvoice.service depends on. | |||||
| matrix_mautrix_gvoice_systemd_required_services_list: "{{ matrix_mautrix_gvoice_systemd_required_services_list_default + matrix_mautrix_gvoice_systemd_required_services_list_auto + matrix_mautrix_gvoice_systemd_required_services_list_custom }}" | |||||
| matrix_mautrix_gvoice_systemd_required_services_list_default: "{{ [devture_systemd_docker_base_docker_service_name] if devture_systemd_docker_base_docker_service_name else [] }}" | |||||
| matrix_mautrix_gvoice_systemd_required_services_list_auto: [] | |||||
| matrix_mautrix_gvoice_systemd_required_services_list_custom: [] | |||||
| # List of systemd services that matrix-mautrix-gvoice.service wants | |||||
| matrix_mautrix_gvoice_systemd_wanted_services_list: [] | |||||
| matrix_mautrix_gvoice_appservice_token: '' | |||||
| matrix_mautrix_gvoice_homeserver_token: '' | |||||
| matrix_mautrix_gvoice_appservice_bot_username: gvoicebot | |||||
| # Minimum severity of journal log messages. | |||||
| # Valid values: fatal, error, warn, info, debug, trace | |||||
| matrix_mautrix_gvoice_logging_level: 'warn' | |||||
| # Whether or not created rooms should have federation enabled. | |||||
| # If false, created portal rooms will never be federated. | |||||
| matrix_mautrix_gvoice_federate_rooms: true | |||||
| # Whether or not metrics endpoint should be enabled. | |||||
| # Enabling them is usually enough for a local (in-container) Prometheus to consume them. | |||||
| # If metrics need to be consumed by another (external) Prometheus server, consider exposing them via `matrix_mautrix_gvoice_metrics_proxying_enabled`. | |||||
| matrix_mautrix_gvoice_metrics_enabled: false | |||||
| # Controls whether metrics should be proxied (exposed) on a public URL. | |||||
| matrix_mautrix_gvoice_metrics_proxying_enabled: false | |||||
| matrix_mautrix_gvoice_metrics_proxying_hostname: '' | |||||
| matrix_mautrix_gvoice_metrics_proxying_path_prefix: '' | |||||
| # Controls whether mautrix-gvoice' HTTP API is exposed publicly (used by tools like mautrix-manager for bridge login). | |||||
| matrix_mautrix_gvoice_exposure_enabled: false | |||||
| matrix_mautrix_gvoice_exposure_hostname: '' | |||||
| matrix_mautrix_gvoice_exposure_path_prefix: '' | |||||
| # Database-related configuration fields. | |||||
| # | |||||
| # To use SQLite, stick to these defaults. | |||||
| # | |||||
| # To use Postgres: | |||||
| # - change the engine (`matrix_mautrix_gvoice_database_engine: 'postgres'`) | |||||
| # - adjust your database credentials via the `matrix_mautrix_gvoice_database_*` variables | |||||
| matrix_mautrix_gvoice_database_engine: 'sqlite' | |||||
| matrix_mautrix_gvoice_sqlite_database_path_local: "{{ matrix_mautrix_gvoice_data_path }}/mautrix-gvoice.db" | |||||
| matrix_mautrix_gvoice_sqlite_database_path_in_container: "/data/mautrix-gvoice.db" | |||||
| matrix_mautrix_gvoice_database_username: 'matrix_mautrix_gvoice' | |||||
| matrix_mautrix_gvoice_database_password: 'some-password' | |||||
| matrix_mautrix_gvoice_database_hostname: '' | |||||
| matrix_mautrix_gvoice_database_port: 5432 | |||||
| matrix_mautrix_gvoice_database_name: 'matrix_mautrix_gvoice' | |||||
| matrix_mautrix_gvoice_database_sslmode: disable | |||||
| matrix_mautrix_gvoice_database_connection_string: 'postgresql://{{ matrix_mautrix_gvoice_database_username }}:{{ matrix_mautrix_gvoice_database_password }}@{{ matrix_mautrix_gvoice_database_hostname }}:{{ matrix_mautrix_gvoice_database_port }}/{{ matrix_mautrix_gvoice_database_name }}?sslmode={{ matrix_mautrix_gvoice_database_sslmode }}' | |||||
| matrix_mautrix_gvoice_appservice_database_type: "{{ { | |||||
| 'sqlite': 'sqlite3', | |||||
| 'postgres': 'postgres', | |||||
| }[matrix_mautrix_gvoice_database_engine] }}" | |||||
| matrix_mautrix_gvoice_appservice_database_uri: "{{ { | |||||
| 'sqlite': matrix_mautrix_gvoice_sqlite_database_path_in_container, | |||||
| 'postgres': matrix_mautrix_gvoice_database_connection_string, | |||||
| }[matrix_mautrix_gvoice_database_engine] }}" | |||||
| matrix_mautrix_gvoice_double_puppet_secrets: "{{ matrix_mautrix_gvoice_double_puppet_secrets_auto | combine(matrix_mautrix_gvoice_double_puppet_secrets_custom) }}" | |||||
| matrix_mautrix_gvoice_double_puppet_secrets_auto: {} | |||||
| matrix_mautrix_gvoice_double_puppet_secrets_custom: {} | |||||
| # Enable End-to-bridge encryption | |||||
| matrix_mautrix_gvoice_bridge_encryption_allow: "{{ matrix_bridges_encryption_enabled }}" | |||||
| matrix_mautrix_gvoice_bridge_encryption_default: "{{ matrix_bridges_encryption_default }}" | |||||
| matrix_mautrix_gvoice_bridge_encryption_require: false | |||||
| matrix_mautrix_gvoice_bridge_encryption_appservice: false | |||||
| matrix_mautrix_gvoice_bridge_encryption_key_sharing_allow: "{{ matrix_mautrix_gvoice_bridge_encryption_allow }}" | |||||
| matrix_mautrix_gvoice_bridge_encryption_pickle_key: mautrix.bridge.e2ee | |||||
| matrix_mautrix_gvoice_network_displayname_template: "{% raw %}{{ or .Contact.Name .Name }}{% endraw %}" | |||||
| matrix_mautrix_gvoice_appservice_username_template: "{% raw %}gvoice_{{.}}{% endraw %}" | |||||
| matrix_mautrix_gvoice_public_media_signing_key: '' | |||||
| # Shared secret for authentication of provisioning API requests. | |||||
| # If set to "disable", the provisioning API will be disabled. | |||||
| matrix_mautrix_gvoice_provisioning_shared_secret: disable | |||||
| matrix_mautrix_gvoice_bridge_personal_filtering_spaces: true | |||||
| matrix_mautrix_gvoice_bridge_permissions: | | |||||
| {{ | |||||
| {'*': 'relay', matrix_mautrix_gvoice_homeserver_domain: 'user'} | |||||
| | combine({matrix_admin: 'admin'} if matrix_admin else {}) | |||||
| }} | |||||
| # Default mautrix-gvoice 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_mautrix_gvoice_configuration_extension_yaml`) | |||||
| # or completely replace this variable with your own template. | |||||
| matrix_mautrix_gvoice_configuration_yaml: "{{ lookup('template', 'templates/config.yaml.j2') }}" | |||||
| matrix_mautrix_gvoice_configuration_extension_yaml: | | |||||
| # Your custom YAML configuration goes here. | |||||
| # This configuration extends the default starting configuration (`matrix_mautrix_gvoice_configuration_yaml`). | |||||
| # | |||||
| # 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_mautrix_gvoice_configuration_yaml`. | |||||
| matrix_mautrix_gvoice_configuration_extension: "{{ matrix_mautrix_gvoice_configuration_extension_yaml | from_yaml if matrix_mautrix_gvoice_configuration_extension_yaml | from_yaml is mapping else {} }}" | |||||
| # Holds the final configuration (a combination of the default and its extension). | |||||
| # You most likely don't need to touch this variable. Instead, see `matrix_mautrix_gvoice_configuration_yaml`. | |||||
| matrix_mautrix_gvoice_configuration: "{{ matrix_mautrix_gvoice_configuration_yaml | from_yaml | combine(matrix_mautrix_gvoice_configuration_extension, recursive=True) }}" | |||||
| matrix_mautrix_gvoice_registration_yaml: | | |||||
| id: gvoice | |||||
| url: {{ matrix_mautrix_gvoice_appservice_address }} | |||||
| as_token: "{{ matrix_mautrix_gvoice_appservice_token }}" | |||||
| hs_token: "{{ matrix_mautrix_gvoice_homeserver_token }}" | |||||
| # See https://github.com/mautrix/signal/issues/43 | |||||
| sender_localpart: _bot_{{ matrix_mautrix_gvoice_appservice_bot_username }} | |||||
| rate_limited: false | |||||
| namespaces: | |||||
| users: | |||||
| - regex: '^@gvoice_.+:{{ matrix_mautrix_gvoice_homeserver_domain | regex_escape }}$' | |||||
| exclusive: true | |||||
| - exclusive: true | |||||
| regex: '^@{{ matrix_mautrix_gvoice_appservice_bot_username | regex_escape }}:{{ matrix_mautrix_gvoice_homeserver_domain | regex_escape }}$' | |||||
| de.sorunome.msc2409.push_ephemeral: true | |||||
| receive_ephemeral: true | |||||
| io.element.msc4190: {{ matrix_mautrix_gvoice_msc4190_enabled | to_json }} | |||||
| matrix_mautrix_gvoice_registration: "{{ matrix_mautrix_gvoice_registration_yaml | from_yaml }}" | |||||
| # matrix_mautrix_gvoice_restart_necessary controls whether the service | |||||
| # will be restarted (when true) or merely started (when false) by the | |||||
| # systemd service manager role (when conditional restart is enabled). | |||||
| # | |||||
| # This value is automatically computed during installation based on whether | |||||
| # any configuration files, the systemd service file, or the container image changed. | |||||
| # The default of `false` means "no restart needed", appropriate when the role's | |||||
| # installation tasks haven't run (e.g., due to --tags skipping them). | |||||
| matrix_mautrix_gvoice_restart_necessary: false | |||||
| @@ -0,0 +1,25 @@ | |||||
| # SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| # SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| # | |||||
| # SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| --- | |||||
| - tags: | |||||
| - setup-all | |||||
| - setup-mautrix-gvoice | |||||
| - install-all | |||||
| - install-mautrix-gvoice | |||||
| block: | |||||
| - when: matrix_mautrix_gvoice_enabled | bool | |||||
| ansible.builtin.include_tasks: "{{ role_path }}/tasks/validate_config.yml" | |||||
| - when: matrix_mautrix_gvoice_enabled | bool | |||||
| ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_install.yml" | |||||
| - tags: | |||||
| - setup-all | |||||
| - setup-mautrix-gvoice | |||||
| block: | |||||
| - when: not matrix_mautrix_gvoice_enabled | bool | |||||
| ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_uninstall.yml" | |||||
| @@ -0,0 +1,176 @@ | |||||
| # SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| # SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| # | |||||
| # SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| --- | |||||
| - ansible.builtin.set_fact: | |||||
| matrix_mautrix_gvoice_migration_requires_restart: false | |||||
| - when: "matrix_mautrix_gvoice_database_engine == 'postgres'" | |||||
| block: | |||||
| - name: Check if an SQLite database already exists | |||||
| ansible.builtin.stat: | |||||
| path: "{{ matrix_mautrix_gvoice_sqlite_database_path_local }}" | |||||
| register: matrix_mautrix_gvoice_sqlite_database_path_local_stat_result | |||||
| - when: "matrix_mautrix_gvoice_sqlite_database_path_local_stat_result.stat.exists | bool" | |||||
| block: | |||||
| - ansible.builtin.include_role: | |||||
| name: galaxy/postgres | |||||
| tasks_from: migrate_db_to_postgres | |||||
| vars: | |||||
| postgres_db_migration_request: | |||||
| src: "{{ matrix_mautrix_gvoice_sqlite_database_path_local }}" | |||||
| dst: "{{ matrix_mautrix_gvoice_database_connection_string }}" | |||||
| caller: "{{ role_path | basename }}" | |||||
| engine_variable_name: 'matrix_mautrix_gvoice_database_engine' | |||||
| engine_old: 'sqlite' | |||||
| systemd_services_to_stop: ['matrix-mautrix-gvoice.service'] | |||||
| pgloader_options: ['--with "quote identifiers"'] | |||||
| - ansible.builtin.set_fact: | |||||
| matrix_mautrix_gvoice_migration_requires_restart: true | |||||
| - name: Ensure Mautrix gvoice paths exists | |||||
| ansible.builtin.file: | |||||
| path: "{{ item.path }}" | |||||
| state: directory | |||||
| mode: '0750' | |||||
| owner: "{{ matrix_user_name }}" | |||||
| group: "{{ matrix_group_name }}" | |||||
| with_items: | |||||
| - {path: "{{ matrix_mautrix_gvoice_base_path }}", when: true} | |||||
| - {path: "{{ matrix_mautrix_gvoice_config_path }}", when: true} | |||||
| - {path: "{{ matrix_mautrix_gvoice_data_path }}", when: true} | |||||
| - {path: "{{ matrix_mautrix_gvoice_container_src_files_path }}", when: "{{ matrix_mautrix_gvoice_container_image_self_build }}"} | |||||
| when: item.when | bool | |||||
| - name: Ensure Mautrix gvoice image is pulled | |||||
| community.docker.docker_image_pull: | |||||
| name: "{{ matrix_mautrix_gvoice_container_image }}" | |||||
| pull: always | |||||
| when: not matrix_mautrix_gvoice_container_image_self_build | |||||
| register: matrix_mautrix_gvoice_container_image_pull_result | |||||
| retries: "{{ devture_playbook_help_container_retries_count }}" | |||||
| delay: "{{ devture_playbook_help_container_retries_delay }}" | |||||
| until: matrix_mautrix_gvoice_container_image_pull_result is not failed | |||||
| - name: Ensure Mautrix gvoice repository is present on self-build | |||||
| ansible.builtin.git: | |||||
| repo: "{{ matrix_mautrix_gvoice_container_image_self_build_repo }}" | |||||
| dest: "{{ matrix_mautrix_gvoice_container_src_files_path }}" | |||||
| version: "{{ matrix_mautrix_gvoice_container_image_self_build_branch }}" | |||||
| force: "yes" | |||||
| become: true | |||||
| become_user: "{{ matrix_user_name }}" | |||||
| register: matrix_mautrix_gvoice_git_pull_results | |||||
| when: "matrix_mautrix_gvoice_container_image_self_build | bool" | |||||
| - name: Ensure Mautrix gvoice Docker image is built | |||||
| community.docker.docker_image_build: | |||||
| name: "{{ matrix_mautrix_gvoice_container_image }}" | |||||
| dockerfile: Dockerfile | |||||
| path: "{{ matrix_mautrix_gvoice_container_src_files_path }}" | |||||
| pull: true | |||||
| rebuild: "{{ 'always' if matrix_mautrix_gvoice_git_pull_results.changed | bool else 'never' }}" | |||||
| when: "matrix_mautrix_gvoice_container_image_self_build | bool" | |||||
| register: matrix_mautrix_gvoice_container_image_build_result | |||||
| - name: Check if an old database file exists | |||||
| ansible.builtin.stat: | |||||
| path: "{{ matrix_mautrix_gvoice_base_path }}/mautrix-gvoice.db" | |||||
| register: matrix_mautrix_gvoice_stat_database | |||||
| - name: Check if an old Matrix state file exists | |||||
| ansible.builtin.stat: | |||||
| path: "{{ matrix_mautrix_gvoice_base_path }}/mx-state.json" | |||||
| register: matrix_mautrix_gvoice_stat_mx_state | |||||
| - name: (Data relocation) Ensure matrix-mautrix-gvoice.service is stopped | |||||
| ansible.builtin.service: | |||||
| name: matrix-mautrix-gvoice | |||||
| state: stopped | |||||
| enabled: false | |||||
| daemon_reload: true | |||||
| failed_when: false | |||||
| when: "matrix_mautrix_gvoice_stat_database.stat.exists" | |||||
| - name: (Data relocation) Move mautrix-gvoice database file to ./data directory | |||||
| ansible.builtin.command: | |||||
| cmd: "mv {{ matrix_mautrix_gvoice_base_path }}/mautrix-gvoice.db {{ matrix_mautrix_gvoice_data_path }}/mautrix-gvoice.db" | |||||
| creates: "{{ matrix_mautrix_gvoice_data_path }}/mautrix-gvoice.db" | |||||
| removes: "{{ matrix_mautrix_gvoice_base_path }}/mautrix-gvoice.db" | |||||
| when: "matrix_mautrix_gvoice_stat_database.stat.exists" | |||||
| - name: (Data relocation) Move mautrix-gvoice mx-state file to ./data directory | |||||
| ansible.builtin.command: | |||||
| cmd: "mv {{ matrix_mautrix_gvoice_base_path }}/mx-state.json {{ matrix_mautrix_gvoice_data_path }}/mx-state.json" | |||||
| creates: "{{ matrix_mautrix_gvoice_data_path }}/mx-state.json" | |||||
| removes: "{{ matrix_mautrix_gvoice_base_path }}/mx-state.json" | |||||
| when: "matrix_mautrix_gvoice_stat_mx_state.stat.exists" | |||||
| - name: Ensure mautrix-gvoice config.yaml installed | |||||
| ansible.builtin.copy: | |||||
| content: "{{ matrix_mautrix_gvoice_configuration | to_nice_yaml(indent=2, width=999999) }}" | |||||
| dest: "{{ matrix_mautrix_gvoice_config_path }}/config.yaml" | |||||
| mode: '0644' | |||||
| owner: "{{ matrix_user_name }}" | |||||
| group: "{{ matrix_group_name }}" | |||||
| register: matrix_mautrix_gvoice_config_result | |||||
| - name: Ensure mautrix-gvoice registration.yaml installed | |||||
| ansible.builtin.copy: | |||||
| content: "{{ matrix_mautrix_gvoice_registration | to_nice_yaml(indent=2, width=999999) }}" | |||||
| dest: "{{ matrix_mautrix_gvoice_config_path }}/registration.yaml" | |||||
| mode: '0644' | |||||
| owner: "{{ matrix_user_name }}" | |||||
| group: "{{ matrix_group_name }}" | |||||
| register: matrix_mautrix_gvoice_registration_result | |||||
| - name: Ensure matrix-mautrix-gvoice.service installed | |||||
| ansible.builtin.template: | |||||
| src: "{{ role_path }}/templates/systemd/matrix-mautrix-gvoice.service.j2" | |||||
| dest: "{{ devture_systemd_docker_base_systemd_path }}/matrix-mautrix-gvoice.service" | |||||
| mode: '0644' | |||||
| register: matrix_mautrix_gvoice_systemd_service_result | |||||
| - name: Ensure mautrix-gvoice support files installed | |||||
| ansible.builtin.template: | |||||
| src: "{{ role_path }}/templates/{{ item }}.j2" | |||||
| dest: "{{ matrix_mautrix_gvoice_base_path }}/{{ item }}" | |||||
| mode: '0640' | |||||
| owner: "{{ matrix_user_name }}" | |||||
| group: "{{ matrix_group_name }}" | |||||
| with_items: | |||||
| - labels | |||||
| register: matrix_mautrix_gvoice_support_files_result | |||||
| - name: Ensure matrix-mautrix-gvoice container network is created | |||||
| when: matrix_mautrix_gvoice_container_network != 'host' | |||||
| community.general.docker_network: | |||||
| enable_ipv6: "{{ devture_systemd_docker_base_ipv6_enabled }}" | |||||
| name: "{{ matrix_mautrix_gvoice_container_network }}" | |||||
| driver: bridge | |||||
| driver_options: "{{ devture_systemd_docker_base_container_networks_driver_options }}" | |||||
| - name: Determine whether matrix-mautrix-gvoice needs a restart | |||||
| ansible.builtin.set_fact: | |||||
| matrix_mautrix_gvoice_restart_necessary: >- | |||||
| {{ | |||||
| matrix_mautrix_gvoice_migration_requires_restart | default(false) | |||||
| or matrix_mautrix_gvoice_config_result.changed | default(false) | |||||
| or matrix_mautrix_gvoice_registration_result.changed | default(false) | |||||
| or matrix_mautrix_gvoice_support_files_result.changed | default(false) | |||||
| or matrix_mautrix_gvoice_systemd_service_result.changed | default(false) | |||||
| or matrix_mautrix_gvoice_container_image_pull_result.changed | default(false) | |||||
| or matrix_mautrix_gvoice_container_image_build_result.changed | default(false) | |||||
| }} | |||||
| - name: Ensure matrix-mautrix-gvoice.service restarted, if necessary | |||||
| ansible.builtin.service: | |||||
| name: "matrix-mautrix-gvoice.service" | |||||
| state: restarted | |||||
| daemon_reload: true | |||||
| when: "matrix_mautrix_gvoice_migration_requires_restart | bool" | |||||
| @@ -0,0 +1,25 @@ | |||||
| # SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| # SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| # | |||||
| # SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| --- | |||||
| - name: Check existence of matrix-mautrix-gvoice service | |||||
| ansible.builtin.stat: | |||||
| path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-mautrix-gvoice.service" | |||||
| register: matrix_mautrix_gvoice_service_stat | |||||
| - when: matrix_mautrix_gvoice_service_stat.stat.exists | bool | |||||
| block: | |||||
| - name: Ensure matrix-mautrix-gvoice is stopped | |||||
| ansible.builtin.service: | |||||
| name: matrix-mautrix-gvoice | |||||
| state: stopped | |||||
| enabled: false | |||||
| daemon_reload: true | |||||
| - name: Ensure matrix-mautrix-gvoice.service doesn't exist | |||||
| ansible.builtin.file: | |||||
| path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-mautrix-gvoice.service" | |||||
| state: absent | |||||
| @@ -0,0 +1,28 @@ | |||||
| # SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| # SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| # | |||||
| # SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| --- | |||||
| - name: (Deprecation) Catch and report renamed settings | |||||
| ansible.builtin.fail: | |||||
| msg: >- | |||||
| Your configuration contains a variable, which now has a different name. | |||||
| Please rename the variable (`{{ item.old }}` -> `{{ item.new }}`) on your configuration file (vars.yml). | |||||
| when: "lookup('ansible.builtin.varnames', ('^' + item.old + '$'), wantlist=True) | length > 0" | |||||
| with_items: [] | |||||
| - name: Fail if required mautrix-gvoice settings not defined | |||||
| ansible.builtin.fail: | |||||
| msg: >- | |||||
| You need to define a required configuration setting (`{{ item.name }}`). | |||||
| when: "item.when | bool and lookup('vars', item.name, default='') | string | length == 0" | |||||
| with_items: | |||||
| - {'name': 'matrix_mautrix_gvoice_appservice_token', when: true} | |||||
| - {'name': 'matrix_mautrix_gvoice_homeserver_address', when: true} | |||||
| - {'name': 'matrix_mautrix_gvoice_homeserver_token', when: true} | |||||
| - {'name': 'matrix_mautrix_gvoice_database_hostname', when: "{{ matrix_mautrix_gvoice_database_engine == 'postgres' }}"} | |||||
| - {'name': 'matrix_mautrix_gvoice_metrics_proxying_hostname', when: "{{ matrix_mautrix_gvoice_metrics_proxying_enabled }}"} | |||||
| - {'name': 'matrix_mautrix_gvoice_metrics_proxying_path_prefix', when: "{{ matrix_mautrix_gvoice_metrics_proxying_enabled }}"} | |||||
| - {'name': 'matrix_mautrix_gvoice_container_network', when: true} | |||||
| @@ -0,0 +1,413 @@ | |||||
| #jinja2: lstrip_blocks: True | |||||
| # Network-specific config options | |||||
| network: | |||||
| # Displayname template for Google Voice contacts. | |||||
| displayname_template: {{ matrix_mautrix_gvoice_network_displayname_template | to_json }} | |||||
| # Config options that affect the central bridge module. | |||||
| bridge: | |||||
| # The prefix for commands. Only required in non-management rooms. | |||||
| command_prefix: '!gv' | |||||
| # Should the bridge create a space for each login containing the rooms that account is in? | |||||
| personal_filtering_spaces: {{ matrix_mautrix_gvoice_bridge_personal_filtering_spaces | to_json }} | |||||
| # Whether the bridge should set names and avatars explicitly for DM portals. | |||||
| # This is only necessary when using clients that don't support MSC4171. | |||||
| private_chat_portal_meta: false | |||||
| # Should events be handled asynchronously within portal rooms? | |||||
| # If true, events may end up being out of order, but slow events won't block other ones. | |||||
| async_events: false | |||||
| # Should every user have their own portals rather than sharing them? | |||||
| # By default, users who are in the same group on the remote network will be | |||||
| # in the same Matrix room bridged to that group. If this is set to true, | |||||
| # every user will get their own Matrix room instead. | |||||
| split_portals: false | |||||
| # Should the bridge resend `m.bridge` events to all portals on startup? | |||||
| resend_bridge_info: false | |||||
| # Should leaving Matrix rooms be bridged as leaving groups on the remote network? | |||||
| bridge_matrix_leave: false | |||||
| # Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority. | |||||
| # Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone. | |||||
| tag_only_on_create: true | |||||
| # Should room mute status only be synced when creating the portal? | |||||
| # Like tags, mutes can't currently be synced back to the remote network. | |||||
| mute_only_on_create: true | |||||
| # What should be done to portal rooms when a user logs out or is logged out? | |||||
| # Permitted values: | |||||
| # nothing - Do nothing, let the user stay in the portals | |||||
| # kick - Remove the user from the portal rooms, but don't delete them | |||||
| # unbridge - Remove all ghosts in the room and disassociate it from the remote chat | |||||
| # delete - Remove all ghosts and users from the room (i.e. delete it) | |||||
| cleanup_on_logout: | |||||
| # Should cleanup on logout be enabled at all? | |||||
| enabled: false | |||||
| # Settings for manual logouts (explicitly initiated by the Matrix user) | |||||
| manual: | |||||
| # Action for private portals which will never be shared with other Matrix users. | |||||
| private: nothing | |||||
| # Action for portals with a relay user configured. | |||||
| relayed: nothing | |||||
| # Action for portals which may be shared, but don't currently have any other Matrix users. | |||||
| shared_no_users: nothing | |||||
| # Action for portals which have other logged-in Matrix users. | |||||
| shared_has_users: nothing | |||||
| # Settings for credentials being invalidated (initiated by the remote network, possibly through user action). | |||||
| # Keys have the same meanings as in the manual section. | |||||
| bad_credentials: | |||||
| private: nothing | |||||
| relayed: nothing | |||||
| shared_no_users: nothing | |||||
| shared_has_users: nothing | |||||
| # Settings for relay mode | |||||
| relay: | |||||
| # Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any | |||||
| # authenticated user into a relaybot for that chat. | |||||
| enabled: false | |||||
| # Should only admins be allowed to set themselves as relay users? | |||||
| # If true, non-admins can only set users listed in default_relays as relays in a room. | |||||
| admin_only: true | |||||
| # List of user login IDs which anyone can set as a relay, as long as the relay user is in the room. | |||||
| default_relays: [] | |||||
| # The formats to use when sending messages via the relaybot. | |||||
| # Available variables: | |||||
| # .Sender.UserID - The Matrix user ID of the sender. | |||||
| # .Sender.Displayname - The display name of the sender (if set). | |||||
| # .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room. | |||||
| # .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set, | |||||
| # plus the user ID in parentheses if the displayname is not unique. | |||||
| # If the displayname is not set, this is just the user ID. | |||||
| # .Message - The `formatted_body` field of the message. | |||||
| # .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string. | |||||
| # .FileName - The name of the file being sent. | |||||
| message_formats: | |||||
| m.text: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}{% endraw %}" | |||||
| m.notice: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}{% endraw %}" | |||||
| m.emote: "{% raw %}* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}{% endraw %}" | |||||
| m.file: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}" | |||||
| m.image: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}" | |||||
| m.audio: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}" | |||||
| m.video: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}" | |||||
| m.location: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}" | |||||
| # For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names. | |||||
| # This has all the Sender variables available under message_formats (but without the .Sender prefix). | |||||
| # Note that you need to manually remove the displayname from message_formats above. | |||||
| displayname_format: "{% raw %}{{ .DisambiguatedName }}{% endraw %}" | |||||
| # Permissions for using the bridge. | |||||
| # Permitted values: | |||||
| # relay - Talk through the relaybot (if enabled), no access otherwise | |||||
| # commands - Access to use commands in the bridge, but not login. | |||||
| # user - Access to use the bridge with puppeting. | |||||
| # admin - Full access, user level with some additional administration tools. | |||||
| # Permitted keys: | |||||
| # * - All Matrix users | |||||
| # domain - All users on that homeserver | |||||
| # mxid - Specific user | |||||
| permissions: {{ matrix_mautrix_gvoice_bridge_permissions|to_json }} | |||||
| # Config for the bridge's database. | |||||
| database: | |||||
| # The database type. "sqlite3-fk-wal" and "postgres" are supported. | |||||
| type: postgres | |||||
| # The database URI. | |||||
| # SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended. | |||||
| # https://github.com/mattn/go-sqlite3#connection-string | |||||
| # Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable | |||||
| # To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql | |||||
| uri: {{ matrix_mautrix_gvoice_appservice_database_uri|to_json }} | |||||
| # Maximum number of connections. | |||||
| max_open_conns: 5 | |||||
| max_idle_conns: 1 | |||||
| # Maximum connection idle time and lifetime before they're closed. Disabled if null. | |||||
| # Parsed with https://pkg.go.dev/time#ParseDuration | |||||
| max_conn_idle_time: null | |||||
| max_conn_lifetime: null | |||||
| # Homeserver details. | |||||
| homeserver: | |||||
| # The address that this appservice can use to connect to the homeserver. | |||||
| # Local addresses without HTTPS are generally recommended when the bridge is running on the same machine, | |||||
| # but https also works if they run on different machines. | |||||
| address: {{ matrix_mautrix_gvoice_homeserver_address | to_json }} | |||||
| # The domain of the homeserver (also known as server_name, used for MXIDs, etc). | |||||
| domain: {{ matrix_mautrix_gvoice_homeserver_domain | to_json }} | |||||
| # What software is the homeserver running? | |||||
| # Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here. | |||||
| software: standard | |||||
| # The URL to push real-time bridge status to. | |||||
| # If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes. | |||||
| # The bridge will use the appservice as_token to authorize requests. | |||||
| status_endpoint: | |||||
| # Endpoint for reporting per-message status. | |||||
| # If set, the bridge will make POST requests to this URL when processing a message from Matrix. | |||||
| # It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable | |||||
| # (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported. | |||||
| # The bridge will use the appservice as_token to authorize requests. | |||||
| message_send_checkpoint_endpoint: | |||||
| # Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246? | |||||
| async_media: {{ matrix_mautrix_gvoice_homeserver_async_media | to_json }} | |||||
| # Should the bridge use a websocket for connecting to the homeserver? | |||||
| # The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy, | |||||
| # mautrix-asmux (deprecated), and hungryserv (proprietary). | |||||
| websocket: false | |||||
| # How often should the websocket be pinged? Pinging will be disabled if this is zero. | |||||
| ping_interval_seconds: 0 | |||||
| # Application service host/registration related details. | |||||
| # Changing these values requires regeneration of the registration (except when noted otherwise) | |||||
| appservice: | |||||
| # The address that the homeserver can use to connect to this appservice. | |||||
| address: {{ matrix_mautrix_gvoice_appservice_address }} | |||||
| # A public address that external services can use to reach this appservice. | |||||
| # This value doesn't affect the registration file. | |||||
| public_address: {{ matrix_mautrix_gvoice_bridge_public_address | to_json }} | |||||
| # The hostname and port where this appservice should listen. | |||||
| # For Docker, you generally have to change the hostname to 0.0.0.0. | |||||
| hostname: 0.0.0.0 | |||||
| port: 8080 | |||||
| # The unique ID of this appservice. | |||||
| id: gvoice | |||||
| # Appservice bot details. | |||||
| bot: | |||||
| # Username of the appservice bot. | |||||
| username: {{ matrix_mautrix_gvoice_appservice_bot_username|to_json }} | |||||
| # Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty | |||||
| # to leave display name/avatar as-is. | |||||
| displayname: Google Voice bridge bot | |||||
| avatar: mxc://maunium.net/VOPtYGBzHLRfPTEzGgNMpeKo | |||||
| # Whether to receive ephemeral events via appservice transactions. | |||||
| ephemeral_events: true | |||||
| # Should incoming events be handled asynchronously? | |||||
| # This may be necessary for large public instances with lots of messages going through. | |||||
| # However, messages will not be guaranteed to be bridged in the same order they were sent in. | |||||
| # This value doesn't affect the registration file. | |||||
| async_transactions: false | |||||
| # Authentication tokens for AS <-> HS communication. Autogenerated; do not modify. | |||||
| as_token: {{ matrix_mautrix_gvoice_appservice_token | to_json }} | |||||
| hs_token: {{ matrix_mautrix_gvoice_homeserver_token | to_json }} | |||||
| # Localpart template of MXIDs for remote users. | |||||
| # {% raw %}{{.}}{% endraw %} is replaced with the internal ID of the user. | |||||
| username_template: {{ matrix_mautrix_gvoice_appservice_username_template | to_json }} | |||||
| # Config options that affect the Matrix connector of the bridge. | |||||
| matrix: | |||||
| # Whether the bridge should send the message status as a custom com.beeper.message_send_status event. | |||||
| message_status_events: false | |||||
| # Whether the bridge should send a read receipt after successfully bridging a message. | |||||
| delivery_receipts: false | |||||
| # Whether the bridge should send error notices via m.notice events when a message fails to bridge. | |||||
| message_error_notices: true | |||||
| # Whether the bridge should update the m.direct account data event when double puppeting is enabled. | |||||
| sync_direct_chat_list: false | |||||
| # Whether created rooms should have federation enabled. If false, created portal rooms | |||||
| # will never be federated. Changing this option requires recreating rooms. | |||||
| federate_rooms: {{ matrix_mautrix_gvoice_federate_rooms | to_json }} | |||||
| # The threshold as bytes after which the bridge should roundtrip uploads via the disk | |||||
| # rather than keeping the whole file in memory. | |||||
| upload_file_threshold: 5242880 | |||||
| # Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors. | |||||
| analytics: | |||||
| # API key to send with tracking requests. Tracking is disabled if this is null. | |||||
| token: null | |||||
| # Address to send tracking requests to. | |||||
| url: https://api.segment.io/v1/track | |||||
| # Optional user ID for tracking events. If null, defaults to using Matrix user ID. | |||||
| user_id: null | |||||
| # Settings for provisioning API | |||||
| provisioning: | |||||
| # Prefix for the provisioning API paths. | |||||
| prefix: /_matrix/provision | |||||
| # Shared secret for authentication. If set to "generate" or null, a random secret will be generated, | |||||
| # or if set to "disable", the provisioning API will be disabled. | |||||
| shared_secret: {{ matrix_mautrix_gvoice_provisioning_shared_secret | to_json }} | |||||
| # Whether to allow provisioning API requests to be authed using Matrix access tokens. | |||||
| # This follows the same rules as double puppeting to determine which server to contact to check the token, | |||||
| # which means that by default, it only works for users on the same server as the bridge. | |||||
| allow_matrix_auth: true | |||||
| # Enable debug API at /debug with provisioning authentication. | |||||
| debug_endpoints: false | |||||
| # Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks). | |||||
| # These settings control whether the bridge will provide such public media access. | |||||
| public_media: | |||||
| # Should public media be enabled at all? | |||||
| # The public_address field under the appservice section MUST be set when enabling public media. | |||||
| enabled: false | |||||
| # A key for signing public media URLs. | |||||
| # If set to "generate", a random key will be generated. | |||||
| signing_key: {{ matrix_mautrix_gvoice_public_media_signing_key | to_json }} | |||||
| # Number of seconds that public media URLs are valid for. | |||||
| # If set to 0, URLs will never expire. | |||||
| expiry: 0 | |||||
| # Length of hash to use for public media URLs. Must be between 0 and 32. | |||||
| hash_length: 32 | |||||
| # Settings for converting remote media to custom mxc:// URIs instead of reuploading. | |||||
| # More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html | |||||
| direct_media: | |||||
| # Should custom mxc:// URIs be used instead of reuploading media? | |||||
| enabled: false | |||||
| # The server name to use for the custom mxc:// URIs. | |||||
| # This server name will effectively be a real Matrix server, it just won't implement anything other than media. | |||||
| # You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge. | |||||
| server_name: media.example.com | |||||
| # Optionally a custom .well-known response. This defaults to `server_name:443` | |||||
| well_known_response: | |||||
| # Optionally specify a custom prefix for the media ID part of the MXC URI. | |||||
| media_id_prefix: | |||||
| # If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916 | |||||
| # media download redirects if the requester supports it. Optionally, you can force redirects | |||||
| # and not allow proxying at all by setting this to false. | |||||
| # This option does nothing if the remote network does not support media downloads over HTTP. | |||||
| allow_proxy: true | |||||
| # Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file. | |||||
| # This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them. | |||||
| server_key: "" | |||||
| # Settings for backfilling messages. | |||||
| # Note that the exact way settings are applied depends on the network connector. | |||||
| # See https://docs.mau.fi/bridges/general/backfill.html for more details. | |||||
| backfill: | |||||
| # Whether to do backfilling at all. | |||||
| enabled: {{ matrix_mautrix_gvoice_backfill_enabled | to_json }} | |||||
| # Maximum number of messages to backfill in empty rooms. | |||||
| max_initial_messages: {{ matrix_mautrix_gvoice_backfill_max_initial_messages | to_json }} | |||||
| # Maximum number of missed messages to backfill after bridge restarts. | |||||
| max_catchup_messages: {{ matrix_mautrix_gvoice_backfill_max_catchup_messages | to_json }} | |||||
| # If a backfilled chat is older than this number of hours, | |||||
| # mark it as read even if it's unread on the remote network. | |||||
| unread_hours_threshold: {{ matrix_mautrix_gvoice_backfill_unread_hours_threshold | to_json }} | |||||
| # Settings for backfilling threads within other backfills. | |||||
| threads: | |||||
| # Maximum number of messages to backfill in a new thread. | |||||
| max_initial_messages: {{ matrix_mautrix_gvoice_backfill_threads_max_initial_messages | to_json }} | |||||
| # Settings for the backwards backfill queue. This only applies when connecting to | |||||
| # Beeper as standard Matrix servers don't support inserting messages into history. | |||||
| queue: | |||||
| # Should the backfill queue be enabled? | |||||
| enabled: false | |||||
| # Number of messages to backfill in one batch. | |||||
| batch_size: 100 | |||||
| # Delay between batches in seconds. | |||||
| batch_delay: 20 | |||||
| # Maximum number of batches to backfill per portal. | |||||
| # If set to -1, all available messages will be backfilled. | |||||
| max_batches: -1 | |||||
| # Optional network-specific overrides for max batches. | |||||
| # Interpretation of this field depends on the network connector. | |||||
| max_batches_override: {} | |||||
| # Settings for enabling double puppeting | |||||
| double_puppet: | |||||
| # Servers to always allow double puppeting from. | |||||
| # This is only for other servers and should NOT contain the server the bridge is on. | |||||
| servers: {} | |||||
| # Whether to allow client API URL discovery for other servers. When using this option, | |||||
| # users on other servers can use double puppeting even if their server URLs aren't | |||||
| # explicitly added to the servers map above. | |||||
| allow_discovery: false | |||||
| # Shared secrets for automatic double puppeting. | |||||
| # See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions. | |||||
| secrets: {{ matrix_mautrix_gvoice_double_puppet_secrets | to_json }} | |||||
| # End-to-bridge encryption support options. | |||||
| # | |||||
| # See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info. | |||||
| encryption: | |||||
| # Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms. | |||||
| allow: {{ matrix_mautrix_gvoice_bridge_encryption_allow | to_json }} | |||||
| # Whether to force-enable encryption in all bridged rooms. | |||||
| default: {{ matrix_mautrix_gvoice_bridge_encryption_default | to_json }} | |||||
| # Whether to require all messages to be encrypted and drop any unencrypted messages. | |||||
| require: {{ matrix_mautrix_gvoice_bridge_encryption_require | to_json }} | |||||
| # Whether to use MSC2409/MSC3202 instead of /sync long polling for receiving encryption-related data. | |||||
| # This option is not yet compatible with standard Matrix servers like Synapse and should not be used. | |||||
| appservice: {{ matrix_mautrix_gvoice_bridge_encryption_appservice | to_json }} | |||||
| # Whether to use MSC4190 instead of appservice login to create the bridge bot device. | |||||
| # Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202. | |||||
| # Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861). | |||||
| # Changing this option requires updating the appservice registration file. | |||||
| msc4190: {{ matrix_mautrix_gvoice_msc4190_enabled | to_json }} | |||||
| # Whether to enable self-signing for bridges (Only the bridge bot uses this for now) | |||||
| # Requires msc4190 to replace keys on reset | |||||
| self_sign: {{ matrix_mautrix_gvoice_self_sign_enabled | to_json }} | |||||
| # Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled. | |||||
| # You must use a client that supports requesting keys from other users to use this feature. | |||||
| allow_key_sharing: {{ matrix_mautrix_gvoice_bridge_encryption_key_sharing_allow | to_json }} | |||||
| # Pickle key for encrypting encryption keys in the bridge database. | |||||
| # If set to generate, a random key will be generated. | |||||
| pickle_key: {{ matrix_mautrix_gvoice_bridge_encryption_pickle_key | to_json }} | |||||
| # Options for deleting megolm sessions from the bridge. | |||||
| delete_keys: | |||||
| # Beeper-specific: delete outbound sessions when hungryserv confirms | |||||
| # that the user has uploaded the key to key backup. | |||||
| delete_outbound_on_ack: false | |||||
| # Don't store outbound sessions in the inbound table. | |||||
| dont_store_outbound: false | |||||
| # Ratchet megolm sessions forward after decrypting messages. | |||||
| ratchet_on_decrypt: false | |||||
| # Delete fully used keys (index >= max_messages) after decrypting messages. | |||||
| delete_fully_used_on_decrypt: false | |||||
| # Delete previous megolm sessions from same device when receiving a new one. | |||||
| delete_prev_on_new_session: false | |||||
| # Delete megolm sessions received from a device when the device is deleted. | |||||
| delete_on_device_delete: false | |||||
| # Periodically delete megolm sessions when 2x max_age has passed since receiving the session. | |||||
| periodically_delete_expired: false | |||||
| # Delete inbound megolm sessions that don't have the received_at field used for | |||||
| # automatic ratcheting and expired session deletion. This is meant as a migration | |||||
| # to delete old keys prior to the bridge update. | |||||
| delete_outdated_inbound: false | |||||
| # What level of device verification should be required from users? | |||||
| # | |||||
| # Valid levels: | |||||
| # unverified - Send keys to all device in the room. | |||||
| # cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys. | |||||
| # cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes). | |||||
| # cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot. | |||||
| # Note that creating user signatures from the bridge bot is not currently possible. | |||||
| # verified - Require manual per-device verification | |||||
| # (currently only possible by modifying the `trust` column in the `crypto_device` database table). | |||||
| verification_levels: | |||||
| # Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix. | |||||
| receive: unverified | |||||
| # Minimum level that the bridge should accept for incoming Matrix messages. | |||||
| send: unverified | |||||
| # Minimum level that the bridge should require for accepting key requests. | |||||
| share: cross-signed-tofu | |||||
| # Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content. | |||||
| # See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event. | |||||
| rotation: | |||||
| # Enable custom Megolm room key rotation settings. Note that these | |||||
| # settings will only apply to rooms created after this option is set. | |||||
| enable_custom: false | |||||
| # The maximum number of milliseconds a session should be used | |||||
| # before changing it. The Matrix spec recommends 604800000 (a week) | |||||
| # as the default. | |||||
| milliseconds: 604800000 | |||||
| # The maximum number of messages that should be sent with a given a | |||||
| # session before changing it. The Matrix spec recommends 100 as the | |||||
| # default. | |||||
| messages: 100 | |||||
| # Disable rotating keys when a user's devices change? | |||||
| # You should not enable this option unless you understand all the implications. | |||||
| disable_device_change_key_rotation: false | |||||
| # Logging config. See https://github.com/tulir/zeroconfig for details. | |||||
| logging: | |||||
| min_level: {{ matrix_mautrix_gvoice_logging_level }} | |||||
| writers: | |||||
| - type: stdout | |||||
| format: pretty | |||||
| @@ -0,0 +1,4 @@ | |||||
| SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| @@ -0,0 +1,86 @@ | |||||
| {# | |||||
| SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| #} | |||||
| {% if matrix_mautrix_gvoice_container_labels_traefik_enabled %} | |||||
| traefik.enable=true | |||||
| {% if matrix_mautrix_gvoice_container_labels_traefik_docker_network %} | |||||
| traefik.docker.network={{ matrix_mautrix_gvoice_container_labels_traefik_docker_network }} | |||||
| {% endif %} | |||||
| traefik.http.services.matrix-mautrix-gvoice-metrics.loadbalancer.server.port=8001 | |||||
| {% if matrix_mautrix_gvoice_container_labels_metrics_enabled %} | |||||
| ############################################################ | |||||
| # # | |||||
| # Metrics # | |||||
| # # | |||||
| ############################################################ | |||||
| {% if matrix_mautrix_gvoice_container_labels_metrics_middleware_basic_auth_enabled %} | |||||
| traefik.http.middlewares.matrix-mautrix-gvoice-metrics-basic-auth.basicauth.users={{ matrix_mautrix_gvoice_container_labels_metrics_middleware_basic_auth_users }} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.middlewares=matrix-mautrix-gvoice-metrics-basic-auth | |||||
| {% endif %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.rule={{ matrix_mautrix_gvoice_container_labels_metrics_traefik_rule }} | |||||
| {% if matrix_mautrix_gvoice_container_labels_metrics_traefik_priority | int > 0 %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.priority={{ matrix_mautrix_gvoice_container_labels_metrics_traefik_priority }} | |||||
| {% endif %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.service=matrix-mautrix-gvoice-metrics | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.entrypoints={{ matrix_mautrix_gvoice_container_labels_metrics_traefik_entrypoints }} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.tls={{ matrix_mautrix_gvoice_container_labels_metrics_traefik_tls | to_json }} | |||||
| {% if matrix_mautrix_gvoice_container_labels_metrics_traefik_tls %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-metrics.tls.certResolver={{ matrix_mautrix_gvoice_container_labels_metrics_traefik_tls_certResolver }} | |||||
| {% endif %} | |||||
| ############################################################ | |||||
| # # | |||||
| # /Metrics # | |||||
| # # | |||||
| ############################################################ | |||||
| {% endif %} | |||||
| {% if matrix_mautrix_gvoice_container_labels_exposure_enabled %} | |||||
| ############################################################ | |||||
| # # | |||||
| # Bridge API exposure # | |||||
| # # | |||||
| ############################################################ | |||||
| traefik.http.services.matrix-mautrix-gvoice-exposure.loadbalancer.server.port=8080 | |||||
| traefik.http.middlewares.matrix-mautrix-gvoice-exposure-strip-prefix.stripprefix.prefixes={{ matrix_mautrix_gvoice_exposure_path_prefix }} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.middlewares=matrix-mautrix-gvoice-exposure-strip-prefix | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.rule={{ matrix_mautrix_gvoice_container_labels_exposure_traefik_rule }} | |||||
| {% if matrix_mautrix_gvoice_container_labels_exposure_traefik_priority | int > 0 %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.priority={{ matrix_mautrix_gvoice_container_labels_exposure_traefik_priority }} | |||||
| {% endif %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.service=matrix-mautrix-gvoice-exposure | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.entrypoints={{ matrix_mautrix_gvoice_container_labels_exposure_traefik_entrypoints }} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.tls={{ matrix_mautrix_gvoice_container_labels_exposure_traefik_tls | to_json }} | |||||
| {% if matrix_mautrix_gvoice_container_labels_exposure_traefik_tls %} | |||||
| traefik.http.routers.matrix-mautrix-gvoice-exposure.tls.certResolver={{ matrix_mautrix_gvoice_container_labels_exposure_traefik_tls_certResolver }} | |||||
| {% endif %} | |||||
| ############################################################ | |||||
| # # | |||||
| # /Bridge API exposure # | |||||
| # # | |||||
| ############################################################ | |||||
| {% endif %} | |||||
| {% endif %} | |||||
| {{ matrix_mautrix_gvoice_container_labels_additional_labels }} | |||||
| @@ -0,0 +1,51 @@ | |||||
| #jinja2: lstrip_blocks: True | |||||
| [Unit] | |||||
| Description=Matrix Mautrix gvoice bridge | |||||
| {% for service in matrix_mautrix_gvoice_systemd_required_services_list %} | |||||
| Requires={{ service }} | |||||
| After={{ service }} | |||||
| {% endfor %} | |||||
| {% for service in matrix_mautrix_gvoice_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_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-mautrix-gvoice 2>/dev/null || true' | |||||
| ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-mautrix-gvoice 2>/dev/null || true' | |||||
| ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} create \ | |||||
| --rm \ | |||||
| --name=matrix-mautrix-gvoice \ | |||||
| --log-driver=none \ | |||||
| --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ | |||||
| --cap-drop=ALL \ | |||||
| --network={{ matrix_mautrix_gvoice_container_network }} \ | |||||
| --mount type=bind,src={{ matrix_mautrix_gvoice_config_path }},dst=/config \ | |||||
| --mount type=bind,src={{ matrix_mautrix_gvoice_data_path }},dst=/data \ | |||||
| --label-file={{ matrix_mautrix_gvoice_base_path }}/labels \ | |||||
| --workdir=/data \ | |||||
| {% for arg in matrix_mautrix_gvoice_container_extra_arguments %} | |||||
| {{ arg }} \ | |||||
| {% endfor %} | |||||
| {{ matrix_mautrix_gvoice_container_image }} \ | |||||
| /usr/bin/mautrix-gvoice -c /config/config.yaml -r /config/registration.yaml --no-update | |||||
| {% if matrix_mautrix_gvoice_container_network != 'host' %} | |||||
| {% for network in matrix_mautrix_gvoice_container_additional_networks %} | |||||
| ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} network connect {{ network }} matrix-mautrix-gvoice | |||||
| {% endfor %} | |||||
| {% endif %} | |||||
| ExecStart={{ devture_systemd_docker_base_host_command_docker }} start --attach matrix-mautrix-gvoice | |||||
| ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-mautrix-gvoice 2>/dev/null || true' | |||||
| ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-mautrix-gvoice 2>/dev/null || true' | |||||
| Restart=always | |||||
| RestartSec=30 | |||||
| SyslogIdentifier=matrix-mautrix-gvoice | |||||
| [Install] | |||||
| WantedBy=multi-user.target | |||||
| @@ -0,0 +1,4 @@ | |||||
| SPDX-FileCopyrightText: 2026 MDAD project contributors | |||||
| SPDX-FileCopyrightText: 2026 Nikita Chernyi | |||||
| SPDX-License-Identifier: AGPL-3.0-or-later | |||||
| @@ -65,6 +65,7 @@ | |||||
| - custom/matrix-bridge-mautrix-telegram | - custom/matrix-bridge-mautrix-telegram | ||||
| - custom/matrix-bridge-mautrix-signal | - custom/matrix-bridge-mautrix-signal | ||||
| - custom/matrix-bridge-mautrix-gmessages | - custom/matrix-bridge-mautrix-gmessages | ||||
| - custom/matrix-bridge-mautrix-gvoice | |||||
| - custom/matrix-bridge-mautrix-whatsapp | - custom/matrix-bridge-mautrix-whatsapp | ||||
| - custom/matrix-bridge-mautrix-wsproxy | - custom/matrix-bridge-mautrix-wsproxy | ||||
| - custom/matrix-bridge-mautrix-discord | - custom/matrix-bridge-mautrix-discord | ||||